@ankorcs/protocol 0.0.0-stage → 0.6.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.
@@ -0,0 +1,90 @@
1
+ /**
2
+ * The agent's upload of stored events to the controller, at {@link DEFAULT_EVENTS_PATH} unless
3
+ * `agent.yml` sets `controller.events_path`.
4
+ */
5
+ import { z } from 'zod';
6
+ import { PluginToControllerMessage } from './game-events.js';
7
+ import { AllocationId } from './values.js';
8
+ /** An event's position among its server's events, from 0. */
9
+ export const EventSeq = z.number().int().nonnegative();
10
+ /**
11
+ * One frame of an event batch: an event and its position among the events of the server that the
12
+ * batch names. A node runs each allocation once and refuses to launch one it has run before, so the
13
+ * batch's `allocation_id` and the frame's `seq` identify the event. Parse each frame of a batch on
14
+ * its own with this schema.
15
+ */
16
+ export const EventBatchFrame = z.object({
17
+ /** The event's position among the events of its server. */
18
+ seq: EventSeq,
19
+ // Retain original payload fields, including additional nested measurements.
20
+ /** The gameplay event, one of {@link PluginToControllerMessage}. */
21
+ event: PluginToControllerMessage.and(z.record(z.string(), z.unknown())),
22
+ });
23
+ /**
24
+ * The most frames one event upload carries. The agent sends a server's events in batches of up to
25
+ * this many.
26
+ */
27
+ export const EVENT_BATCH_MAX_FRAMES = 128;
28
+ /**
29
+ * The most bytes the frames of one event upload come to together. A receiver accepts request bodies
30
+ * of this size plus the `frames` wrapper, because refusing a batch for its size stalls the upload
31
+ * of that server's events.
32
+ */
33
+ export const EVENT_BATCH_MAX_BYTES = 2 * 1024 * 1024;
34
+ /**
35
+ * What the agent reads of a batch frame: its position. The controller validates the event itself.
36
+ */
37
+ export const EventFrame = z.looseObject({ seq: EventSeq });
38
+ /**
39
+ * The body of an event upload: 1 to {@link EVENT_BATCH_MAX_FRAMES} frames of the server that
40
+ * `allocation_id` names, with consecutive `seq`. Store frames idempotently by `allocation_id` and
41
+ * `seq`, because a lost receipt means the same frames arrive again.
42
+ *
43
+ * This schema checks only each frame's position, so one bad event cannot fail the batch. Parse each
44
+ * frame on its own with {@link EventBatchFrame}, and quarantine a frame that fails by listing it in
45
+ * `rejected` of the {@link EventBatchReceipt}.
46
+ *
47
+ * A server plays the one match of its launch, so the match of a batch is the match that the controller
48
+ * launched `allocation_id` with. The frames come to at most {@link EVENT_BATCH_MAX_BYTES}
49
+ * together.
50
+ *
51
+ * Rejected unless each frame's `seq` is one above the `seq` of the frame before it.
52
+ */
53
+ export const EventBatch = z.object({
54
+ /** The server that sent every event of the batch. */
55
+ allocation_id: AllocationId,
56
+ /** The frames, in `seq` order. */
57
+ frames: z.array(EventFrame).min(1).max(EVENT_BATCH_MAX_FRAMES),
58
+ }).superRefine(({ frames }, ctx) => {
59
+ const first = frames[0];
60
+ for (const [index, frame] of frames.entries()) {
61
+ if (frame.seq !== first.seq + index) {
62
+ ctx.addIssue({ code: 'custom', path: ['frames', index], message: 'A batch must contain one contiguous sequence' });
63
+ }
64
+ }
65
+ });
66
+ /**
67
+ * The controller's answer to a batch it holds. Every frame of the batch is either canonical
68
+ * history or quarantined under one of the `rejected` entries, and the agent may delete them all.
69
+ * Resending a quarantined frame would fail the same way.
70
+ */
71
+ export const EventBatchReceipt = z.object({
72
+ /** Frames quarantined instead of stored. */
73
+ rejected: z.array(z.object({
74
+ /** The quarantined frame. */
75
+ seq: EventSeq,
76
+ /** Why it was quarantined. */
77
+ reason: z.string().min(1),
78
+ })),
79
+ });
80
+ /**
81
+ * The body of a `409` answer to an event upload. The controller could not read the identities of
82
+ * the batch, so it refuses the whole batch. The agent retries that server with a backoff of its
83
+ * own, and the node's other servers upload meanwhile.
84
+ */
85
+ export const EventBatchRejection = z.object({
86
+ /** Always `event_integrity_error`. */
87
+ code: z.literal('event_integrity_error'),
88
+ /** What made the batch untrustworthy. */
89
+ detail: z.string(),
90
+ });