@serve.zone/interfaces 27.3.1 → 27.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/.smartconfig.json +11 -4
  2. package/changelog.md +30 -0
  3. package/dist_ts/00_commitinfo_data.js +1 -1
  4. package/dist_ts/data/index.d.ts +1 -0
  5. package/dist_ts/data/index.js +2 -1
  6. package/dist_ts/data/runtimeassignment.d.ts +120 -0
  7. package/dist_ts/data/runtimeassignment.js +267 -0
  8. package/dist_ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.d.ts +291 -0
  9. package/dist_ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.golden.d.ts +30 -0
  10. package/dist_ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.golden.js +408 -0
  11. package/dist_ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.js +1126 -0
  12. package/dist_ts/runtime.corestore.databaseexportpackage.d.ts +118 -0
  13. package/dist_ts/runtime.corestore.databaseexportpackage.golden.d.ts +22 -0
  14. package/dist_ts/runtime.corestore.databaseexportpackage.golden.js +242 -0
  15. package/dist_ts/runtime.corestore.databaseexportpackage.js +428 -0
  16. package/dist_ts/runtime.d.ts +2 -0
  17. package/dist_ts/runtime.js +3 -1
  18. package/package.json +4 -4
  19. package/readme.md +67 -1
  20. package/ts/00_commitinfo_data.ts +1 -1
  21. package/ts/data/index.ts +1 -0
  22. package/ts/data/runtimeassignment.ts +402 -0
  23. package/ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.golden.ts +510 -0
  24. package/ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.ts +2470 -0
  25. package/ts/runtime.corestore.databaseexportpackage.golden.ts +323 -0
  26. package/ts/runtime.corestore.databaseexportpackage.ts +862 -0
  27. package/ts/runtime.ts +2 -0
  28. /package/{license → license.md} +0 -0
@@ -0,0 +1,402 @@
1
+ import type { TSha256Digest, TImmutableContainerPlatform } from './immutableimage.js';
2
+ import { buildDigestPinnedImageReference, isSha256Digest } from './immutableimage.js';
3
+ import { canonicalizeStrictJson, createCanonicalJsonSha256Hex } from '../private/canonicaljson.js';
4
+
5
+ export interface IRuntimeControllerV1 {
6
+ kind: 'cloudly' | 'onebox';
7
+ id: string;
8
+ /** Persisted authority incarnation; changing it requires explicit re-admission. */
9
+ epoch: string;
10
+ }
11
+
12
+ /** Content-bound reference, not a credential, mount path or permission grant. */
13
+ export interface IRuntimeAuthorityReferenceV1 {
14
+ id: string;
15
+ generation: number;
16
+ digest: TSha256Digest;
17
+ }
18
+
19
+ export interface IRuntimeAssignmentReferenceV1 {
20
+ assignmentId: string;
21
+ generation: number;
22
+ digest: TSha256Digest;
23
+ }
24
+
25
+ /** Derived from authenticated transport and locally configured ownership. */
26
+ export interface IRuntimeAssignmentScopeV1 {
27
+ controller: IRuntimeControllerV1;
28
+ organizationId: string;
29
+ nodeId: string;
30
+ runtimeNamespace: string;
31
+ }
32
+
33
+ export type TRuntimeAssignmentDispositionV1 =
34
+ | 'run'
35
+ | 'stop-preserve'
36
+ | 'remove-runtime-preserve-storage';
37
+
38
+ export interface IRuntimeAssignmentV1 extends IRuntimeAssignmentScopeV1 {
39
+ schemaVersion: 1;
40
+ assignmentId: string;
41
+ serviceId: string;
42
+ /** Stable logical replica; independent of container IDs and execution attempts. */
43
+ replicaId: string;
44
+ /** One execution attempt, immutable for this assignment. */
45
+ attemptId: string;
46
+ /** Logical topology provenance; placement authority remains nodeId. */
47
+ workerGroupId: string | null;
48
+ siteId: string | null;
49
+ generation: number;
50
+ /** Exact CAS predecessor; null only at generation 1. */
51
+ previous: IRuntimeAssignmentReferenceV1 | null;
52
+ disposition: TRuntimeAssignmentDispositionV1;
53
+ /** No automatic relocation or expiry-based claim of a stopped writer. */
54
+ offlinePolicy: 'continue-node-bound';
55
+ workload: {
56
+ registryHost: string;
57
+ repository: string;
58
+ imageDigest: TSha256Digest;
59
+ platform: TImmutableContainerPlatform;
60
+ /** Immutable complete invocation/configuration owned by the controller. */
61
+ config: IRuntimeAuthorityReferenceV1;
62
+ network: IRuntimeAuthorityReferenceV1 | null;
63
+ secrets: IRuntimeAuthorityReferenceV1 | null;
64
+ /** Sorted unique reference IDs. Empty means no persistent storage claims. */
65
+ storage: IRuntimeAuthorityReferenceV1[];
66
+ };
67
+ digest: TSha256Digest;
68
+ }
69
+
70
+ export type TRuntimeAssignmentDecisionV1 =
71
+ | { status: 'accept' | 'replay'; assignment: IRuntimeAssignmentV1 }
72
+ | { status: 'reject'; reason: 'invalid' | 'scope-mismatch' | 'conflict' | 'stale'
73
+ | 'predecessor-mismatch' | 'attempt-mismatch' | 'disposition-regression' };
74
+
75
+ /** Value-free execution evidence. Receipt/acceptance alone never means ready. */
76
+ export interface IRuntimeAssignmentObservationV1 {
77
+ schemaVersion: 1;
78
+ assignment: IRuntimeAssignmentReferenceV1;
79
+ /** Authenticated reporter session; its authority is verified by the receiver. */
80
+ reporterSessionId: string;
81
+ /** Durable per-assignment counter; never resets when a reporter reconnects. */
82
+ sequence: number;
83
+ observedAt: number;
84
+ phase: 'pending' | 'running' | 'stopped' | 'removed' | 'failed' | 'unknown';
85
+ ready: boolean;
86
+ runtime: {
87
+ sandboxId: string;
88
+ containerId: string;
89
+ imageDigest: TSha256Digest;
90
+ } | null;
91
+ digest: TSha256Digest;
92
+ }
93
+
94
+ export const runtimeAssignmentV1Contract = Object.freeze({
95
+ assignmentDigestDomain: 'serve.zone/runtime-assignment-v1',
96
+ observationDigestDomain: 'serve.zone/runtime-assignment-observation-v1',
97
+ maximumStorageReferences: 64,
98
+ omission: 'no-effect',
99
+ removal: 'retain-assignment-tombstone-and-storage',
100
+ } as const);
101
+
102
+ const fail: (messageArg: string) => never = (messageArg) => { throw new Error(messageArg); };
103
+ const isIdentifier = (valueArg: unknown): valueArg is string =>
104
+ typeof valueArg === 'string' && /^[A-Za-z0-9][A-Za-z0-9:._-]{0,199}$/.test(valueArg);
105
+ const isPositiveInteger = (valueArg: unknown): valueArg is number =>
106
+ typeof valueArg === 'number' && Number.isSafeInteger(valueArg) && valueArg > 0;
107
+ const isDigest = (valueArg: unknown): valueArg is TSha256Digest =>
108
+ typeof valueArg === 'string' && isSha256Digest(valueArg);
109
+
110
+ /** Reject accessors before reading any fields; do not run input-owned code. */
111
+ const isExactRecord = (
112
+ valueArg: unknown,
113
+ keysArg: readonly string[],
114
+ ): valueArg is Record<string, unknown> => {
115
+ if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg)) return false;
116
+ const prototype = Object.getPrototypeOf(valueArg);
117
+ if (prototype !== Object.prototype && prototype !== null) return false;
118
+ const keys = Reflect.ownKeys(valueArg);
119
+ return keys.length === keysArg.length && keys.every((keyArg) => {
120
+ const descriptor = Object.getOwnPropertyDescriptor(valueArg, keyArg);
121
+ return typeof keyArg === 'string' && keysArg.includes(keyArg)
122
+ && descriptor?.enumerable === true && Object.hasOwn(descriptor, 'value');
123
+ });
124
+ };
125
+
126
+ const isController = (valueArg: unknown): valueArg is IRuntimeControllerV1 =>
127
+ isExactRecord(valueArg, ['kind', 'id', 'epoch'])
128
+ && (valueArg.kind === 'cloudly' || valueArg.kind === 'onebox')
129
+ && isIdentifier(valueArg.id) && isIdentifier(valueArg.epoch);
130
+ const isAuthorityReference = (valueArg: unknown): valueArg is IRuntimeAuthorityReferenceV1 =>
131
+ isExactRecord(valueArg, ['id', 'generation', 'digest'])
132
+ && isIdentifier(valueArg.id) && isPositiveInteger(valueArg.generation) && isDigest(valueArg.digest);
133
+ const isAssignmentReference = (valueArg: unknown): valueArg is IRuntimeAssignmentReferenceV1 =>
134
+ isExactRecord(valueArg, ['assignmentId', 'generation', 'digest'])
135
+ && isIdentifier(valueArg.assignmentId) && isPositiveInteger(valueArg.generation)
136
+ && isDigest(valueArg.digest);
137
+
138
+ const assignmentKeys = [
139
+ 'schemaVersion', 'controller', 'organizationId', 'nodeId', 'runtimeNamespace',
140
+ 'assignmentId', 'serviceId', 'replicaId', 'attemptId', 'workerGroupId', 'siteId',
141
+ 'generation', 'previous', 'disposition', 'offlinePolicy', 'workload', 'digest',
142
+ ] as const;
143
+
144
+ const assertAssignment = (valueArg: unknown): IRuntimeAssignmentV1 => {
145
+ if (!isExactRecord(valueArg, assignmentKeys)
146
+ || valueArg.schemaVersion !== 1 || !isController(valueArg.controller)
147
+ || !isPositiveInteger(valueArg.generation) || !isDigest(valueArg.digest)
148
+ || valueArg.offlinePolicy !== 'continue-node-bound'
149
+ || !['run', 'stop-preserve', 'remove-runtime-preserve-storage'].includes(
150
+ valueArg.disposition as string,
151
+ )) fail('runtime assignment must use its exact v1 schema');
152
+ for (const key of ['organizationId', 'nodeId', 'runtimeNamespace', 'assignmentId',
153
+ 'serviceId', 'replicaId', 'attemptId']) {
154
+ if (!isIdentifier(valueArg[key])) fail(`runtime assignment ${key} must be a canonical identifier`);
155
+ }
156
+ for (const key of ['workerGroupId', 'siteId']) {
157
+ if (valueArg[key] !== null && !isIdentifier(valueArg[key])) fail('invalid runtime topology ID');
158
+ }
159
+ if (valueArg.generation === 1 ? valueArg.previous !== null
160
+ : !isAssignmentReference(valueArg.previous)
161
+ || valueArg.previous.assignmentId !== valueArg.assignmentId
162
+ || valueArg.previous.generation !== (valueArg.generation as number) - 1) {
163
+ fail('runtime assignment must bind its exact preceding generation');
164
+ }
165
+ const workload = valueArg.workload;
166
+ if (!isExactRecord(workload, ['registryHost', 'repository', 'imageDigest', 'platform',
167
+ 'config', 'network', 'secrets', 'storage'])
168
+ || typeof workload.registryHost !== 'string' || workload.registryHost.length > 253
169
+ || typeof workload.repository !== 'string' || workload.repository.length > 255
170
+ || !isDigest(workload.imageDigest)
171
+ || !['linux/amd64', 'linux/arm64'].includes(workload.platform as string)
172
+ || !isAuthorityReference(workload.config)
173
+ || (workload.network !== null && !isAuthorityReference(workload.network))
174
+ || (workload.secrets !== null && !isAuthorityReference(workload.secrets))
175
+ || !Array.isArray(workload.storage)
176
+ || workload.storage.length > runtimeAssignmentV1Contract.maximumStorageReferences) {
177
+ fail('runtime assignment workload must use exact immutable references');
178
+ }
179
+ buildDigestPinnedImageReference(workload.registryHost as string,
180
+ workload.repository as string, workload.imageDigest as TSha256Digest);
181
+ let previousId = '';
182
+ for (let index = 0; index < (workload.storage as unknown[]).length; index++) {
183
+ const descriptor = Object.getOwnPropertyDescriptor(workload.storage, String(index));
184
+ if (!descriptor || !Object.hasOwn(descriptor, 'value')
185
+ || !isAuthorityReference(descriptor.value) || descriptor.value.id <= previousId) {
186
+ fail('runtime assignment storage references must be dense, sorted and unique');
187
+ }
188
+ previousId = descriptor.value.id;
189
+ }
190
+ canonicalizeStrictJson(valueArg, fail);
191
+ return valueArg as unknown as IRuntimeAssignmentV1;
192
+ };
193
+
194
+ const digestPayload = async (domainArg: string, valueArg: { digest: TSha256Digest }): Promise<TSha256Digest> => {
195
+ const { digest: _digest, ...payload } = valueArg;
196
+ return `sha256:${await createCanonicalJsonSha256Hex(
197
+ canonicalizeStrictJson({ domain: domainArg, payload }, fail), fail,
198
+ )}`;
199
+ };
200
+
201
+ /** Digest helpers validate structure and exclude only the object's own digest. */
202
+ export const computeRuntimeAssignmentV1Digest = async (
203
+ assignmentArg: IRuntimeAssignmentV1,
204
+ ): Promise<TSha256Digest> => digestPayload(
205
+ runtimeAssignmentV1Contract.assignmentDigestDomain, assertAssignment(assignmentArg),
206
+ );
207
+
208
+ export const validateRuntimeAssignmentV1 = async (valueArg: unknown): Promise<string[]> => {
209
+ try {
210
+ const assignment = snapshotAssignment(valueArg);
211
+ return assignment.digest === await computeRuntimeAssignmentV1Digest(assignment)
212
+ ? [] : ['runtime assignment digest does not match'];
213
+ } catch {
214
+ return ['runtime assignment must use its exact canonical v1 schema'];
215
+ }
216
+ };
217
+
218
+ const snapshotAssignment = (valueArg: unknown): IRuntimeAssignmentV1 =>
219
+ JSON.parse(canonicalizeStrictJson(assertAssignment(valueArg), fail)) as IRuntimeAssignmentV1;
220
+
221
+ export const getRuntimeAssignmentV1Reference = (
222
+ assignmentArg: IRuntimeAssignmentV1,
223
+ ): IRuntimeAssignmentReferenceV1 => ({
224
+ assignmentId: assignmentArg.assignmentId,
225
+ generation: assignmentArg.generation,
226
+ digest: assignmentArg.digest,
227
+ });
228
+
229
+ const canonical = (valueArg: unknown): string => canonicalizeStrictJson(valueArg, fail);
230
+ const immutableAttempt = (assignmentArg: IRuntimeAssignmentV1): string => {
231
+ const { generation, previous, disposition, digest, ...attempt } = assignmentArg;
232
+ return canonical(attempt);
233
+ };
234
+
235
+ /**
236
+ * Pure CAS decision, not authentication or execution. The caller must persist
237
+ * acceptance atomically before effects, serialize replica ownership, and retain
238
+ * removed assignments as tombstones. New attempts require separate stop/fencing
239
+ * admission for every older writer. An omitted assignment causes no transition.
240
+ * Persist only the returned detached snapshot, never the mutable input object.
241
+ */
242
+ export const evaluateRuntimeAssignmentV1 = async (
243
+ incomingArg: IRuntimeAssignmentV1,
244
+ acceptedArg: IRuntimeAssignmentV1 | null,
245
+ trustedScopeArg: IRuntimeAssignmentScopeV1,
246
+ ): Promise<TRuntimeAssignmentDecisionV1> => {
247
+ try {
248
+ incomingArg = snapshotAssignment(incomingArg);
249
+ acceptedArg = acceptedArg === null ? null : snapshotAssignment(acceptedArg);
250
+ if (!isExactRecord(trustedScopeArg, ['controller', 'organizationId', 'nodeId', 'runtimeNamespace'])
251
+ || !isController(trustedScopeArg.controller)
252
+ || !isIdentifier(trustedScopeArg.organizationId) || !isIdentifier(trustedScopeArg.nodeId)
253
+ || !isIdentifier(trustedScopeArg.runtimeNamespace)) return { status: 'reject', reason: 'invalid' };
254
+ trustedScopeArg = JSON.parse(canonical(trustedScopeArg)) as IRuntimeAssignmentScopeV1;
255
+ } catch {
256
+ return { status: 'reject', reason: 'invalid' };
257
+ }
258
+ if ((await validateRuntimeAssignmentV1(incomingArg)).length
259
+ || (acceptedArg !== null && (await validateRuntimeAssignmentV1(acceptedArg)).length)) {
260
+ return { status: 'reject', reason: 'invalid' };
261
+ }
262
+ const matchesScope = (assignmentArg: IRuntimeAssignmentV1): boolean =>
263
+ assignmentArg.controller.kind === trustedScopeArg.controller.kind
264
+ && assignmentArg.controller.id === trustedScopeArg.controller.id
265
+ && assignmentArg.controller.epoch === trustedScopeArg.controller.epoch
266
+ && assignmentArg.organizationId === trustedScopeArg.organizationId
267
+ && assignmentArg.nodeId === trustedScopeArg.nodeId
268
+ && assignmentArg.runtimeNamespace === trustedScopeArg.runtimeNamespace;
269
+ if (!matchesScope(incomingArg) || (acceptedArg !== null && !matchesScope(acceptedArg))) {
270
+ return { status: 'reject', reason: 'scope-mismatch' };
271
+ }
272
+ if (acceptedArg === null) {
273
+ return incomingArg.generation === 1
274
+ && incomingArg.disposition === 'run'
275
+ ? { status: 'accept', assignment: incomingArg }
276
+ : { status: 'reject', reason: 'predecessor-mismatch' };
277
+ }
278
+ if (incomingArg.assignmentId !== acceptedArg.assignmentId) {
279
+ return { status: 'reject', reason: 'attempt-mismatch' };
280
+ }
281
+ if (incomingArg.generation < acceptedArg.generation) return { status: 'reject', reason: 'stale' };
282
+ if (incomingArg.generation === acceptedArg.generation) {
283
+ return incomingArg.digest === acceptedArg.digest
284
+ ? { status: 'replay', assignment: acceptedArg } : { status: 'reject', reason: 'conflict' };
285
+ }
286
+ if (canonical(incomingArg.previous) !== canonical(getRuntimeAssignmentV1Reference(acceptedArg))) {
287
+ return { status: 'reject', reason: 'predecessor-mismatch' };
288
+ }
289
+ if (immutableAttempt(incomingArg) !== immutableAttempt(acceptedArg)) {
290
+ return { status: 'reject', reason: 'attempt-mismatch' };
291
+ }
292
+ const order: Record<TRuntimeAssignmentDispositionV1, number> = {
293
+ run: 0, 'stop-preserve': 1, 'remove-runtime-preserve-storage': 2,
294
+ };
295
+ if (order[incomingArg.disposition] !== order[acceptedArg.disposition] + 1) {
296
+ return { status: 'reject', reason: 'disposition-regression' };
297
+ }
298
+ return { status: 'accept', assignment: incomingArg };
299
+ };
300
+
301
+ const assertObservation = (valueArg: unknown): IRuntimeAssignmentObservationV1 => {
302
+ if (!isExactRecord(valueArg, ['schemaVersion', 'assignment', 'reporterSessionId',
303
+ 'sequence', 'observedAt', 'phase', 'ready', 'runtime', 'digest'])
304
+ || valueArg.schemaVersion !== 1 || !isAssignmentReference(valueArg.assignment)
305
+ || !isIdentifier(valueArg.reporterSessionId) || !isPositiveInteger(valueArg.sequence)
306
+ || !isPositiveInteger(valueArg.observedAt) || !isDigest(valueArg.digest)
307
+ || typeof valueArg.ready !== 'boolean'
308
+ || !['pending', 'running', 'stopped', 'removed', 'failed', 'unknown'].includes(valueArg.phase as string)) {
309
+ fail('runtime observation must use its exact v1 schema');
310
+ }
311
+ if (valueArg.runtime !== null && (!isExactRecord(valueArg.runtime, [
312
+ 'sandboxId', 'containerId', 'imageDigest',
313
+ ]) || !isIdentifier(valueArg.runtime.sandboxId) || !isIdentifier(valueArg.runtime.containerId)
314
+ || !isDigest(valueArg.runtime.imageDigest))) fail('runtime identity must be canonical');
315
+ if ((valueArg.ready && valueArg.phase !== 'running')
316
+ || (valueArg.phase === 'running' && valueArg.runtime === null)
317
+ || (valueArg.phase === 'removed' && valueArg.runtime !== null)) {
318
+ fail('runtime observation readiness and identity contradict its phase');
319
+ }
320
+ canonical(valueArg);
321
+ return valueArg as unknown as IRuntimeAssignmentObservationV1;
322
+ };
323
+
324
+ export const computeRuntimeAssignmentObservationV1Digest = async (
325
+ observationArg: IRuntimeAssignmentObservationV1,
326
+ ): Promise<TSha256Digest> => digestPayload(
327
+ runtimeAssignmentV1Contract.observationDigestDomain, assertObservation(observationArg),
328
+ );
329
+
330
+ /** Checks exact assignment evidence; transport must separately bind reporter/session. */
331
+ export const validateRuntimeAssignmentObservationV1 = async (
332
+ observationArg: unknown,
333
+ assignmentArg: IRuntimeAssignmentV1,
334
+ ): Promise<string[]> => {
335
+ try {
336
+ const observation = JSON.parse(canonical(assertObservation(observationArg))) as IRuntimeAssignmentObservationV1;
337
+ assignmentArg = snapshotAssignment(assignmentArg);
338
+ if ((await validateRuntimeAssignmentV1(assignmentArg)).length) return ['invalid assignment'];
339
+ if (canonical(observation.assignment) !== canonical(getRuntimeAssignmentV1Reference(assignmentArg))) {
340
+ return ['runtime observation does not match the exact assignment'];
341
+ }
342
+ if (observation.digest !== await computeRuntimeAssignmentObservationV1Digest(observation)) {
343
+ return ['runtime observation digest does not match'];
344
+ }
345
+ if (observation.ready && (assignmentArg.disposition !== 'run'
346
+ || observation.runtime?.imageDigest !== assignmentArg.workload.imageDigest)) {
347
+ return ['runtime readiness does not match authorized execution'];
348
+ }
349
+ return [];
350
+ } catch {
351
+ return ['runtime observation must use its exact canonical v1 schema'];
352
+ }
353
+ };
354
+
355
+ export type TRuntimeObservationDecisionV1 =
356
+ | { status: 'accept' | 'replay'; observation: IRuntimeAssignmentObservationV1 }
357
+ | { status: 'reject'; reason: 'invalid' | 'session-mismatch' | 'stale' | 'conflict' | 'sequence-gap' };
358
+
359
+ /**
360
+ * Receiver must authorize the assignment's node before calling this helper.
361
+ * Compare/persist the returned snapshot atomically with the old receipt. A new
362
+ * session continues the durable counter; it does not reset observation history.
363
+ * Across dispositions, the old receipt must reference the exact predecessor;
364
+ * receivers must process each revision's observations in order.
365
+ * These observations are never sufficient authority to relocate a storage writer.
366
+ */
367
+ export const evaluateRuntimeAssignmentObservationV1 = async (
368
+ incomingArg: IRuntimeAssignmentObservationV1,
369
+ acceptedArg: IRuntimeAssignmentObservationV1 | null,
370
+ assignmentArg: IRuntimeAssignmentV1,
371
+ trustedReporterSessionIdArg: string,
372
+ ): Promise<TRuntimeObservationDecisionV1> => {
373
+ try {
374
+ incomingArg = JSON.parse(canonical(assertObservation(incomingArg))) as IRuntimeAssignmentObservationV1;
375
+ acceptedArg = acceptedArg === null ? null
376
+ : JSON.parse(canonical(assertObservation(acceptedArg))) as IRuntimeAssignmentObservationV1;
377
+ assignmentArg = snapshotAssignment(assignmentArg);
378
+ if (!isIdentifier(trustedReporterSessionIdArg)
379
+ || (await validateRuntimeAssignmentObservationV1(incomingArg, assignmentArg)).length
380
+ || (acceptedArg !== null && (
381
+ acceptedArg.digest !== await computeRuntimeAssignmentObservationV1Digest(acceptedArg)
382
+ || acceptedArg.assignment.assignmentId !== assignmentArg.assignmentId
383
+ || (canonical(acceptedArg.assignment) !== canonical(getRuntimeAssignmentV1Reference(assignmentArg))
384
+ && canonical(acceptedArg.assignment) !== canonical(assignmentArg.previous))
385
+ ))) return { status: 'reject', reason: 'invalid' };
386
+ if (incomingArg.reporterSessionId !== trustedReporterSessionIdArg) {
387
+ return { status: 'reject', reason: 'session-mismatch' };
388
+ }
389
+ if (acceptedArg !== null && incomingArg.sequence <= acceptedArg.sequence) {
390
+ if (incomingArg.sequence < acceptedArg.sequence) return { status: 'reject', reason: 'stale' };
391
+ return incomingArg.digest === acceptedArg.digest
392
+ ? { status: 'replay', observation: acceptedArg }
393
+ : { status: 'reject', reason: 'conflict' };
394
+ }
395
+ if (incomingArg.sequence !== (acceptedArg?.sequence ?? 0) + 1) {
396
+ return { status: 'reject', reason: 'sequence-gap' };
397
+ }
398
+ return { status: 'accept', observation: incomingArg };
399
+ } catch {
400
+ return { status: 'reject', reason: 'invalid' };
401
+ }
402
+ };