@worker-protocol/schemas 0.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/dist/index.js ADDED
@@ -0,0 +1,1075 @@
1
+ import * as z from "zod";
2
+ /**
3
+ * The Zod objects that generate `schemas/`.
4
+ *
5
+ * Nothing here carries behaviour of its own. Every constraint below encodes a rule that
6
+ * `spec/` already states, and each one cites the rule id it encodes so that a conformance
7
+ * report can name the rule rather than the file. Where `spec/` has not decided something, the
8
+ * object here is deliberately permissive and says so — a schema that overstates is worse than
9
+ * one that admits a hole, because the schema is the normative artifact and prose defers to it.
10
+ */
11
+ /**
12
+ * The base every schema's `$id` is built on — and NO HOME HAS BEEN CHOSEN.
13
+ *
14
+ * `null` means each `$id` is the bare file name, which identifies a schema within this set and
15
+ * commits the project to no domain. Edition 0.1 is published and these packages are at 0.1.0, so
16
+ * the old justification — that nothing was published — has expired; the choice stands on what is
17
+ * left of it, which is that no home has been chosen and nothing consumes these by URL.
18
+ *
19
+ * A `$id` identifies; it does not have to resolve. Validation works whether or not anything is
20
+ * ever served at it, and relative `$ref`s between these schemas resolve against the document's
21
+ * own location exactly as they would against an absolute base.
22
+ *
23
+ * When a home is chosen, set this to something of the shape
24
+ * `https://<host>/worker-protocol/schemas` — namespaced under a path rather than hung off a
25
+ * domain root, so that a later move changes one segment and not the shape of every identifier.
26
+ * That is the only edit required: the host would then appear in each generated file on its own
27
+ * `$id` line and nowhere else, because every cross-schema `$ref` stays relative.
28
+ */
29
+ export const SCHEMA_ID_BASE = null;
30
+ /**
31
+ * DESC-23 — the edition of worker-protocol these schemas encode.
32
+ *
33
+ * It is not this package's version and cannot be read off it. A package version is SemVer and
34
+ * describes the package; an edition is `MAJOR.MINOR` and describes the protocol, and the two move
35
+ * for different reasons — a Zod major, a type made more precise, a build fixed, none of which is a
36
+ * change to anything a Worker sends. `packages/README.md` carries that argument, including why the
37
+ * two numbers agreeing today is a coincidence rather than a rule.
38
+ */
39
+ export const EDITION = "0.1";
40
+ /** The `$id` of one generated schema. A registry id is also its file name, plus `.json`. */
41
+ export const schemaId = (name) => SCHEMA_ID_BASE === null ? `${name}.json` : `${SCHEMA_ID_BASE}/${name}.json`;
42
+ /**
43
+ * The registry the generator walks. A registry id becomes three things: the generated file name,
44
+ * the `$ref` other schemas point at — relative, so `capability-entry.json` and not a URL — and,
45
+ * once rewritten through `schemaId`, that file's own absolute `$id`.
46
+ */
47
+ export const registry = z.registry();
48
+ /**
49
+ * DESC-8 — the closed enumeration of Capability names this edition defines. Normative, and the
50
+ * list a verifier checks an undotted name against. `spec/README.md`'s table is a reading aid.
51
+ */
52
+ export const capabilityName = z
53
+ .enum(["health", "metrics", "actions", "alerts", "activity", "nudges", "tasks", "events"])
54
+ .meta({
55
+ title: "Capability name",
56
+ description: "DESC-8. The Capability names the current edition of worker-protocol defines.",
57
+ });
58
+ /**
59
+ * DESC-14 — a name containing a `.` is the Worker's own and is never defined by this
60
+ * specification. The pattern asserts only what DESC-14 asserts: at least one dot, and no dot at
61
+ * either end. naming.md answers the rest of the syntax by adding nothing to it: nobody compares one
62
+ * Worker's vendor Capability against another's, so there is no collision for a longer name to
63
+ * prevent.
64
+ */
65
+ export const vendorCapabilityName = z
66
+ .string()
67
+ .regex(/^[^.\s]+(?:\.[^.\s]+)+$/)
68
+ .meta({
69
+ title: "Vendor Capability name",
70
+ description: "DESC-14. A Capability a Worker defines itself. Contains a dot, which is what makes it " +
71
+ "disjoint from the reserved names of DESC-8. The dot is the whole of the syntax: naming.md " +
72
+ "requires nothing further, because no reader ever compares one Worker's vendor Capability " +
73
+ "against another's. NAME-1 fixes how any two names are compared.",
74
+ });
75
+ /**
76
+ * NAME-7 — a name this protocol expects one party to match against a name that came from somewhere
77
+ * else: a Task type, a Skill, an event type.
78
+ *
79
+ * The pattern is at least three dot-separated labels — two or more for the DNS name in reverse
80
+ * label order, one or more for the local part — each a DNS label of lowercase letters, digits and
81
+ * hyphens, never starting or ending with a hyphen.
82
+ *
83
+ * Lowercase is asserted rather than left to taste, and it is the one part of this that is load-
84
+ * bearing rather than conventional. DNS is case-insensitive, so `Example.com` and `example.com`
85
+ * are one domain; NAME-1 compares names byte for byte, so `com.Example.x` and `com.example.x`
86
+ * would be two names for one thing. One spelling closes a trap the two rules open between them.
87
+ *
88
+ * NAME-8 — that the domain is one the minting team controls — has no schema witness and cannot
89
+ * have one. Nothing verifies domain ownership, which is why it recommends rather than binds.
90
+ */
91
+ export const qualifiedName = z
92
+ .string()
93
+ .regex(/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?){2,}$/)
94
+ .meta({
95
+ title: "Qualified name",
96
+ description: "NAME-7. A name matched across Workers: a DNS name the minting team controls, in reverse " +
97
+ "label order, followed by a local part — `tech.rowing.fleet.verify-vehicle`. At least three " +
98
+ "labels, lowercase. NAME-8 asks that the domain be one you control and no schema can check " +
99
+ "it. This does NOT apply to a name read only inside the Descriptor that declared it: a " +
100
+ "vendor Capability (DESC-14), an Action, a metric.",
101
+ });
102
+ /**
103
+ * DESC-12 — an address is an absolute `https` URL, or a relative reference resolved against the
104
+ * URL the Descriptor was read from.
105
+ *
106
+ * The pattern is the load-bearing part: `format` is an annotation in Draft 2020-12 unless a
107
+ * validator opts into format-assertion, so a schema that relied on `format: "uri-reference"`
108
+ * alone would assert nothing. This admits a string that either begins `https://` or carries no
109
+ * scheme at all, which is exactly the two cases DESC-12 names.
110
+ */
111
+ export const address = z
112
+ .string()
113
+ .regex(/^(?:https:\/\/|(?![A-Za-z][A-Za-z0-9+.-]*:))\S*$/)
114
+ .meta({
115
+ title: "Address",
116
+ format: "uri-reference",
117
+ description: "DESC-12. An absolute https URL, or a relative reference resolved against the Descriptor's " +
118
+ "own URL. DESC-13 governs whether a credential may be sent to one that is off-origin.",
119
+ });
120
+ /**
121
+ * DESC-22 — what a Worker declares about one Capability it implements.
122
+ *
123
+ * Loose on purpose, and the reason has changed since it was written. Every Capability file now
124
+ * defines its extension — `healthEntry`, `metricsEntry`, `actionsEntry`, `alertsEntry`,
125
+ * `activityEntry`, `nudgesEntry`, `tasksEntry`, `eventsEntry` — so this is no longer holding a door
126
+ * open for something unwritten.
127
+ * What keeps it loose is that `descriptor.json` still holds its entries as a record of THIS shape
128
+ * rather than binding each reserved name to its own, so closing it here would refuse every
129
+ * conformant Descriptor. Tightening that is a breaking change to the normative artifact and is
130
+ * listed in descriptor.md as the next edition's.
131
+ */
132
+ export const capabilityEntry = z
133
+ .looseObject({
134
+ version: z
135
+ .number()
136
+ .int()
137
+ .min(1)
138
+ .meta({
139
+ description: "DESC-9. A single integer; there is no minor version. DESC-29 makes it count breaking " +
140
+ "changes to this Capability's own surface alone.",
141
+ }),
142
+ address: address.optional(),
143
+ })
144
+ .meta({
145
+ title: "Capability entry",
146
+ description: "DESC-22. What a Worker declares about one Capability: its version, and the address it " +
147
+ "answers at where it answers over HTTP. The Capability's name is the key this entry is held " +
148
+ "under, which is what makes it declarable at most once. The address is optional HERE ONLY: " +
149
+ "each Capability's own file requires one, and every Capability answered over HTTP does — " +
150
+ "`events`, answered over a broker, is why the shared entry cannot require it. Open to the " +
151
+ "extensions each Capability's own file defines, including the conditional declarations of " +
152
+ "DESC-11.",
153
+ });
154
+ /**
155
+ * HLTH-2 — the three values a health status takes, and the only three.
156
+ *
157
+ * `degraded` is the one that earns its place: `healthy` and `unhealthy` alone would force a Worker
158
+ * that works with one dependency down to lie in one direction or the other.
159
+ */
160
+ export const healthStatus = z.enum(["healthy", "degraded", "unhealthy"]).meta({
161
+ title: "Health status",
162
+ description: "HLTH-2. The three values, for the whole Worker and for one named check alike.",
163
+ });
164
+ /**
165
+ * HLTH-2 — one named check.
166
+ *
167
+ * Loose on purpose: health.md records as open what a check carries beyond its status and detail —
168
+ * an observed value, a unit, a threshold — and closing this would answer that by accident.
169
+ */
170
+ export const healthCheck = z
171
+ .looseObject({
172
+ status: healthStatus,
173
+ detail: z
174
+ .string()
175
+ .optional()
176
+ .meta({
177
+ description: "HLTH-2. Short, human-readable, addressed to whoever is looking. Not addressed to a " +
178
+ "program: nothing in this protocol parses it.",
179
+ }),
180
+ })
181
+ .meta({
182
+ title: "Health check",
183
+ description: "HLTH-2. One dependency or invariant a Worker reports on, under its own name.",
184
+ });
185
+ /**
186
+ * HLTH-2 — the whole answer.
187
+ *
188
+ * Closed, unlike a check: HLTH-2 enumerates the envelope exhaustively and no open question asks
189
+ * for a third member. The check object inside is where the open question lives.
190
+ *
191
+ * `checks` is required and may be empty. A Worker with no dependency worth reporting answers `{}`
192
+ * rather than omitting the member, so that every reader parses one shape.
193
+ */
194
+ export const health = z
195
+ .strictObject({
196
+ status: healthStatus.meta({
197
+ description: "HLTH-2. The Worker's own summary. HLTH-3 forbids `healthy` while any check it reports " +
198
+ "is not passing.",
199
+ }),
200
+ checks: z.record(z.string(), healthCheck).meta({
201
+ description: "HLTH-2. Keyed by check name. Whether check names are shared across Workers is open, " +
202
+ "which is why the key carries no pattern; if they come to be shared they become a name " +
203
+ "that crosses between Workers and NAME-7 reaches them.",
204
+ }),
205
+ })
206
+ .meta({
207
+ title: "Health",
208
+ description: "HLTH-2. What a Worker answers at the address its `health` entry declares.",
209
+ });
210
+ /**
211
+ * HLTH-1 — the `health` Capability entry, which requires the address the shared entry leaves
212
+ * optional. This is the extension DESC-22 promises each Capability's own file will define, and it
213
+ * is the first one.
214
+ */
215
+ export const healthEntry = capabilityEntry.extend({ address }).meta({
216
+ title: "Health capability entry",
217
+ description: "HLTH-1. The shared Capability entry with the address required, because `health` is " +
218
+ "answered over HTTP. DESC-22 makes the address optional in the shared entry only so that " +
219
+ "`events`, answered over a broker, can be declared at all.",
220
+ });
221
+ /**
222
+ * DESC-1, DESC-2, DESC-6, DESC-22, DESC-23 — the document every Worker serves.
223
+ *
224
+ * Closed on purpose: the rules above enumerate what a Descriptor carries, and a new top-level
225
+ * member is what an edition is for (DESC-23). A Worker extends its entries, not its Descriptor.
226
+ *
227
+ * `strictObject`, not `object`, so that the Zod objects other packages consume and the JSON
228
+ * Schema generated from them agree about the same document. Zod's default object STRIPS an
229
+ * unknown key while the generated `additionalProperties: false` REJECTS it — a TypeScript
230
+ * consumer and a Python one would otherwise reach opposite verdicts on one Descriptor.
231
+ */
232
+ /**
233
+ * TASK-31 — what a Worker declares about one Skill, which today is nothing.
234
+ *
235
+ * Empty and strict on purpose. A Skill carries no declaration yet, and the shape for *nothing yet*
236
+ * is the one an optional member can join without invalidating a document already written — which
237
+ * NAME-5 calls compatible. A list of names could only have grown by becoming this, and becoming
238
+ * this later would have cost every Worker that declared a Skill a rewrite.
239
+ */
240
+ export const skillDeclaration = z
241
+ .strictObject({
242
+ payload: z
243
+ .looseObject({})
244
+ .optional()
245
+ .meta({
246
+ description: "TASK-31. The JSON Schema of the payload this Worker REQUIRES in order to answer a Task " +
247
+ "of this type — its own requirement, and not a copy of what any owner sends. NAME-6 " +
248
+ "judges the two in the direction the document travels: a Tower validates the Tasks an " +
249
+ "owner actually raises against this, and knows before any work is handed over whether " +
250
+ "this Worker can read it. May ask for less than an owner sends; a Tower that finds it " +
251
+ "asking for more has its answer. OPTIONAL: a Skill that states no requirement claims " +
252
+ "the capability and nothing about what it needs, which is conformant and is where this " +
253
+ "protocol stood before the field existed. What it costs is the check.",
254
+ }),
255
+ produces: z
256
+ .looseObject({})
257
+ .optional()
258
+ .meta({
259
+ description: "TASK-31. The JSON Schema of what this Worker PRODUCES in answer to a Task of this type " +
260
+ "— its own capability, and not a copy of any owner's Action. NAME-6 judges it against " +
261
+ "the input of the Action that answers the type at each owner: a Tower knows before any " +
262
+ "work is handed over whether this Worker can produce what that owner takes, and two " +
263
+ "owners asking for the same fact under different names are, correctly, two different " +
264
+ "answers. OPTIONAL, on the same terms as `payload`.",
265
+ }),
266
+ })
267
+ .meta({
268
+ title: "Skill declaration",
269
+ description: "TASK-31. What this Worker declares about one Task type it answers. The owner's `raises` " +
270
+ "says what is sent and its `actions` what is taken back; this says what the answerer " +
271
+ "requires and what it produces, where it says anything at all, and each pair is what a " +
272
+ "Tower compares.",
273
+ });
274
+ export const descriptor = z
275
+ .strictObject({
276
+ id: z
277
+ .string()
278
+ .min(1)
279
+ .meta({
280
+ description: "DESC-6. The Worker's own id, which is not the URL it is served from. DESC-27 adds " +
281
+ "that it is not derived from that URL and survives a move, and DESC-28 that it is " +
282
+ "opaque, stable and unambiguous without ambient context. NAME-9 requires only that no " +
283
+ "two Workers share one, which a namespaced name and a random identifier satisfy " +
284
+ "equally — so no pattern is asserted here on purpose. A schema can assert that it is a " +
285
+ "non-empty string and no more; DESC-27, DESC-28 and NAME-9 have no schema witness.",
286
+ }),
287
+ edition: z
288
+ .string()
289
+ .regex(/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/)
290
+ .meta({
291
+ description: "DESC-23. The edition of worker-protocol this Worker speaks, written MAJOR.MINOR. " +
292
+ "Editions are ordered by comparing MAJOR and then MINOR as numbers, which is the " +
293
+ "ordering DESC-25 spends when a verifier reports itself older than a Worker. DESC-24 " +
294
+ "gives the two components their meaning: MAJOR is what a reader cannot survive not " +
295
+ "knowing, MINOR is what an older reader may ignore and still be correct. Leading " +
296
+ "zeros are refused so that one edition has one spelling and string equality agrees " +
297
+ "with numeric comparison.",
298
+ }),
299
+ skills: z
300
+ .record(qualifiedName, skillDeclaration)
301
+ .optional()
302
+ .meta({
303
+ description: "TASK-31. The Task types this Worker answers, which IS its Skill — the unit of " +
304
+ "discovery the Tower catalogs by. It is at the root rather than in the `tasks` entry " +
305
+ "because a Skill is served at no address and answered by no surface: it is what a " +
306
+ "Worker IS, like its id, and a Capability is what a Worker SERVES. Omitted by a Worker " +
307
+ "with no Skill, which is the ordinary case for one that only raises Tasks of its own.",
308
+ }),
309
+ capabilities: z.record(z.union([capabilityName, vendorCapabilityName]), capabilityEntry).meta({
310
+ description: "DESC-22. Keyed by Capability name, which is what makes a Capability declared at most " +
311
+ "once: a list could not express that, because JSON Schema compares whole items for " +
312
+ "uniqueness and two entries named `health` validate cleanly as distinct items. DESC-2 " +
313
+ "admits any combination, including none. A key is a reserved name (DESC-8) or a vendor " +
314
+ "one (DESC-14), and the dot is what tells the two apart.",
315
+ }),
316
+ })
317
+ .meta({
318
+ title: "Descriptor",
319
+ description: "DESC-1. The document a Worker serves at the route DESC-3 fixes, and the whole of what " +
320
+ "every Worker owes.",
321
+ });
322
+ /**
323
+ * ENDP-25 — the closed code enumeration, split by the class each code carries.
324
+ *
325
+ * Every code names a condition some rule in `spec/` already states; none was invented to fill a
326
+ * gap. endpoints.md holds the other half of ENDP-26 — which status each code is answered with —
327
+ * because a status code is not in the body and no schema can assert it.
328
+ */
329
+ export const rejectCodes = [
330
+ "malformed_request",
331
+ "invalid_parameter",
332
+ "unknown_filter",
333
+ "unsupported_version",
334
+ "idempotency_key_required",
335
+ "unauthenticated",
336
+ "forbidden",
337
+ "not_found",
338
+ "conflict",
339
+ "idempotency_key_reused",
340
+ "unprocessable_content",
341
+ "schema_mismatch",
342
+ ];
343
+ export const retryCodes = [
344
+ "request_timeout",
345
+ "rate_limited",
346
+ "internal_error",
347
+ "unavailable",
348
+ "upstream_error",
349
+ "upstream_timeout",
350
+ ];
351
+ const message = z.string().meta({
352
+ description: "ENDP-25. A human-readable message. Not addressed to a program.",
353
+ });
354
+ /**
355
+ * ENDP-25, ENDP-26 — the envelope every response that is not a success carries.
356
+ *
357
+ * A union of two branches rather than one object with two independent fields, because ENDP-26
358
+ * says a code carries its class. Written as one object, `not_found` with a class of `retry` would
359
+ * validate cleanly and defeat the thing the class exists for. Written this way, the constraint
360
+ * holds in the Zod object and in the generated JSON alike, with no rule stated in only one of
361
+ * them.
362
+ *
363
+ * Each branch is loose: endpoints.md records as open whether the envelope carries structured
364
+ * detail beyond these three, and closing it here would answer that question by accident, in the
365
+ * artifact prose defers to.
366
+ */
367
+ export const error = z
368
+ .union([
369
+ z.looseObject({
370
+ code: z.enum(rejectCodes).meta({
371
+ description: "ENDP-25. A code naming a condition this request will meet again (ENDP-28). " +
372
+ "endpoints.md gives the status each is answered with.",
373
+ }),
374
+ message,
375
+ class: z.literal("reject").meta({
376
+ description: "ENDP-26. Carried by the code, not chosen beside it.",
377
+ }),
378
+ }),
379
+ z.looseObject({
380
+ code: z.enum(retryCodes).meta({
381
+ description: "ENDP-25. A code naming a condition that may have passed by the time the request is " +
382
+ "sent again (ENDP-30). endpoints.md gives the status each is answered with.",
383
+ }),
384
+ message,
385
+ class: z.literal("retry").meta({
386
+ description: "ENDP-26. Carried by the code, not chosen beside it.",
387
+ }),
388
+ }),
389
+ ])
390
+ .meta({
391
+ title: "Error",
392
+ description: "ENDP-25. The error envelope shared by every surface. The code is drawn from a closed " +
393
+ "enumeration, so a new code requires a new edition — the price of a vocabulary a " +
394
+ "conformance check can actually verify. ENDP-27 decides whether the code or the status " +
395
+ "wins when they disagree.",
396
+ });
397
+ /**
398
+ * ENDP-20, ENDP-21 — the envelope every surface that answers a list answers it in.
399
+ *
400
+ * Closed, unlike the error envelope, because ENDP-20 enumerates the envelope exhaustively and
401
+ * no open question asks for more. A surface that needs a third member is asking for a change to
402
+ * ENDP-20, which is a thing the register can see.
403
+ */
404
+ export const page = z
405
+ .strictObject({
406
+ items: z.array(z.unknown()).meta({
407
+ description: "ENDP-20. The page. The shared envelope cannot type its items — each surface's own " +
408
+ "schema narrows them.",
409
+ }),
410
+ nextCursor: z
411
+ .string()
412
+ .min(1)
413
+ .optional()
414
+ .meta({
415
+ description: "ENDP-21. Opaque to the caller, produced only by the Worker, never constructed. A " +
416
+ "string because a caller sends it back as a request parameter. ENDP-20: absent at the " +
417
+ "end of the collection — absent, not null.",
418
+ }),
419
+ })
420
+ .meta({
421
+ title: "Page",
422
+ description: "ENDP-20. The page envelope shared by every collection surface.",
423
+ });
424
+ /**
425
+ * MET-3 — the five periods a metric may accumulate over, and the only five.
426
+ *
427
+ * Closed because a console renders a period selector from what a metric declares, and an arbitrary
428
+ * duration would make that a free-text box. It is also where accumulation stops being a time
429
+ * series, which is the distinction this Capability rests on.
430
+ */
431
+ export const metricGranularity = z.enum(["hour", "day", "week", "month", "year"]).meta({
432
+ title: "Metric granularity",
433
+ description: "MET-3. The period one bucket covers. MET-20 cuts every boundary in the time zone the entry " +
434
+ "declares, and MET-7 makes a week the ISO 8601 one, beginning Monday.",
435
+ });
436
+ /**
437
+ * MET-4 — one dimension a metric is broken down by, held under its name.
438
+ *
439
+ * `values` absent is the free case and is not the same as an empty list, which is why the minimum
440
+ * is 1: a dimension that declared no possible value could never be filtered to anything, and
441
+ * MET-17 would answer `400` for every value a caller sent.
442
+ *
443
+ * Declaring the set buys two different things, which is why metrics.md spends two rules on it:
444
+ * MET-17 refuses a value outside it, and MET-19 grants the dimension the right to be broken down
445
+ * by — a free dimension is filtered and never grouped, because nothing would bound the answer.
446
+ */
447
+ /**
448
+ * MET-5, MET-16 — the characters a dimension name may use: what a query parameter needs, and no
449
+ * more. Shared between the declaration, the bucket and the `by` parameter so the three agree.
450
+ */
451
+ export const DIMENSION_NAME = /^[A-Za-z0-9_-]+$/;
452
+ export const metricDimension = z
453
+ .strictObject({
454
+ values: z
455
+ .array(z.string().min(1))
456
+ .min(1)
457
+ .optional()
458
+ .meta({
459
+ description: "MET-4. The closed set of values this dimension takes. Absent means any string, and " +
460
+ "MET-17 then accepts one that matches nothing rather than refusing it. Only a " +
461
+ "dimension that declares its set may be broken down by, under MET-19.",
462
+ }),
463
+ })
464
+ .meta({
465
+ title: "Metric dimension",
466
+ description: "MET-4. One dimension of a metric, declared under its name so that a reader knows every " +
467
+ "dimension before it calls. MET-16 fixes it with a query parameter of that same name.",
468
+ });
469
+ /**
470
+ * MET-21, MET-3, MET-4 — what a Worker declares about one metric.
471
+ *
472
+ * Closed: MET-3 and MET-4 enumerate the declaration, and no open question in metrics.md asks for
473
+ * another member of it. A member added later is what an edition is for, which is DESC-23.
474
+ *
475
+ * `dimensions` is required and may be empty, for the reason `checks` is in `health`: one shape for
476
+ * every reader, rather than a member whose absence and whose emptiness say the same thing.
477
+ */
478
+ export const metricDeclaration = z
479
+ .strictObject({
480
+ unit: z
481
+ .string()
482
+ .min(1)
483
+ .meta({
484
+ description: "MET-3. Declared by the Worker and parsed by nothing here. This protocol has no " +
485
+ "dimensional analysis: the unit exists so a console can put something beside a number.",
486
+ }),
487
+ additive: z.boolean().meta({
488
+ description: "MET-3. Whether buckets of this metric may be summed. Declared because a reader will " +
489
+ "otherwise assume it and produce a number that is wrong and plausible: tokens over two " +
490
+ "days is the sum of the two, vehicles that reported over two days is not.",
491
+ }),
492
+ granularities: z
493
+ .array(metricGranularity)
494
+ .min(1)
495
+ .meta({
496
+ description: "MET-3. At least one, and only what the Worker actually keeps. MET-10 answers `400` " +
497
+ "for anything not listed here, and MET-8 lets a read omit the granularity where this " +
498
+ "carries exactly one.",
499
+ }),
500
+ dimensions: z.record(z.string().regex(DIMENSION_NAME), metricDimension).meta({
501
+ description: "MET-4. Keyed by dimension name. The pattern asserts only what the transport needs, " +
502
+ "because MET-16 spells the name into a query parameter; NAME-3 imposes no convention on " +
503
+ "a name a Worker mints. MET-5 also forbids the names this protocol defines on a read, " +
504
+ "which no pattern here asserts: excluding a word list needs a negative lookahead, and " +
505
+ "RE2-backed validators refuse one. May be empty.",
506
+ }),
507
+ })
508
+ .meta({
509
+ title: "Metric declaration",
510
+ description: "MET-21. One metric, held under its name in the `metrics` entry. The Descriptor is the " +
511
+ "catalog: the surface itself never lists what exists.",
512
+ });
513
+ /**
514
+ * MET-6 — an IANA Time Zone Database name.
515
+ *
516
+ * The separator is written `[/]` rather than `\/` so the generated pattern carries no JavaScript
517
+ * escape. A regular expression literal cannot hold a bare `/` outside a character class, and `\/`
518
+ * is an escape ECMA-262 accepts and Java refuses outright — a runtime's fingerprint smuggled into
519
+ * the normative artifact, which is the same thing the generator strips a safe-integer bound for.
520
+ *
521
+ * The pattern refuses the common wrong answers — an offset like `-03:00`, an abbreviation like
522
+ * `ART` — and asserts nothing about whether the zone exists. No schema can check a name against
523
+ * a database that ships with the reader.
524
+ */
525
+ export const timeZone = z
526
+ .string()
527
+ .regex(/^(?:UTC|[A-Za-z_]+[/][A-Za-z0-9_+/-]+)$/)
528
+ .meta({
529
+ title: "Time zone",
530
+ description: "MET-6. An IANA Time Zone Database name — `America/Argentina/Buenos_Aires`, `UTC`. A fixed " +
531
+ "offset is not one: an offset cannot say when a day begins across a daylight-saving " +
532
+ "transition, which is the whole reason the zone is declared. This is the calendar a caller " +
533
+ "gets when the Worker has agreed no other with it.",
534
+ });
535
+ /**
536
+ * MET-1, MET-21, MET-6 — the `metrics` Capability entry.
537
+ *
538
+ * The address is required, as HLTH-1 requires it, because this Capability is answered over HTTP.
539
+ */
540
+ export const metricsEntry = capabilityEntry
541
+ .extend({
542
+ address,
543
+ timeZone,
544
+ publishes: z.record(z.string().min(1), metricDeclaration).meta({
545
+ description: "MET-21. Every metric the Worker publishes, keyed by name. A name not here is `404` " +
546
+ "under MET-9. The key carries no pattern: it is spelled into the VALUE of a query " +
547
+ "parameter, which is percent-encoded, and naming.md leaves a Worker's own names alone.",
548
+ }),
549
+ })
550
+ .meta({
551
+ title: "Metrics capability entry",
552
+ description: "MET-1. The shared Capability entry with the address required, the time zone every bucket " +
553
+ "boundary is cut in, and the metrics this Worker publishes.",
554
+ });
555
+ /**
556
+ * An RFC 3339 instant carrying an offset — MET-13, TASK-28 and ALRT-3 all take one.
557
+ *
558
+ * `format` is an annotation in Draft 2020-12 unless a validator opts into format-assertion, so the
559
+ * pattern is what binds. It admits a wrong date — the 31st of February — because a regular
560
+ * expression that ruled those out would be unreadable, and a Worker that emits one has a bug no
561
+ * schema was going to find.
562
+ */
563
+ export const INSTANT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
564
+ const instant = (description) => z.string().regex(INSTANT).meta({ format: "date-time", description });
565
+ /**
566
+ * MET-12, MET-13, MET-15, MET-19 — one bucket.
567
+ *
568
+ * Closed, and it carries no name, unit or granularity: MET-8 has the caller name the metric and,
569
+ * where there is a choice, the granularity, and MET-21 has it read the unit from the Descriptor
570
+ * before it calls. Repeating any of them here would be a second place for them to disagree.
571
+ *
572
+ * It carries no status and no judgment of any kind. metrics.md answers `how much` and stops: a
573
+ * Worker that decides one of its own numbers is wrong raises an Alert, and what is inside a
574
+ * Worker's settings is not a thing this surface has a view of.
575
+ */
576
+ export const metricBucket = z
577
+ .strictObject({
578
+ start: instant("MET-13. Inclusive. MET-20 cuts it in the zone the entry declares."),
579
+ end: instant("MET-13. Exclusive, and carried rather than derived: a day across a daylight-saving " +
580
+ "transition is 23 or 25 hours, and a reader comparing this against its own clock knows " +
581
+ "whether the bucket is still accumulating without holding a calendar."),
582
+ value: z
583
+ .number()
584
+ .nullable()
585
+ .meta({
586
+ description: "MET-13, MET-15. What the Worker accumulated over the period. Null means the Worker no " +
587
+ "longer holds this bucket and never means zero; a bucket it accumulated nothing in is " +
588
+ "absent from the answer instead.",
589
+ }),
590
+ dimensions: z
591
+ .record(z.string().regex(DIMENSION_NAME), z.string())
592
+ .optional()
593
+ .meta({
594
+ description: "MET-19. The values this bucket is broken down by, present only on a read that asked " +
595
+ "for a breakdown with `by`. One entry per dimension named there, each a value from " +
596
+ "that dimension's declared set — MET-19 admits no other kind.",
597
+ }),
598
+ })
599
+ .meta({
600
+ title: "Metric bucket",
601
+ description: "MET-13. One period of one metric, whole under MET-12.",
602
+ });
603
+ /**
604
+ * MET-14 — what a read answers: the shared page envelope with its items narrowed, which is the
605
+ * narrowing ENDP-20 says each surface's own schema performs.
606
+ */
607
+ export const metricPage = page
608
+ .extend({
609
+ items: z.array(metricBucket).meta({
610
+ description: "MET-14. Ascending by start. MET-12: whole buckets only.",
611
+ }),
612
+ })
613
+ .meta({
614
+ title: "Metric page",
615
+ description: "MET-14. One page of buckets, in the envelope ENDP-20 fixes for every collection in this " +
616
+ "protocol.",
617
+ });
618
+ /**
619
+ * ACT-12 — the declaration ENDP-15 requires of an Action that takes an idempotency key.
620
+ *
621
+ * A union rather than one object with an optional member, on the same reasoning that makes `error`
622
+ * a union: written flat, `from: "input"` with no member named would validate cleanly and leave the
623
+ * Worker with nowhere to read the key from. The two branches make the half-set state unspellable.
624
+ */
625
+ export const idempotencyDeclaration = z
626
+ .union([
627
+ z.strictObject({
628
+ required: z.boolean().meta({
629
+ description: "ENDP-18. Whether a key is required. A required key that is absent is `400`; an Action " +
630
+ "that declares none is at-least-once under retry.",
631
+ }),
632
+ from: z.literal("header").meta({
633
+ description: "ENDP-15. The key arrives in `Idempotency-Key`. It is opaque, and the Worker records " +
634
+ "it without parsing it.",
635
+ }),
636
+ windowSeconds: z
637
+ .number()
638
+ .int()
639
+ .min(1)
640
+ .meta({
641
+ description: "ENDP-15, ENDP-16. How long the Worker answers the recorded outcome for a repeat. " +
642
+ "Declared because the guarantee is worthless without it: no Worker remembers forever, " +
643
+ "and one that has forgotten performs the Action again while the caller still believes " +
644
+ "it is protected.",
645
+ }),
646
+ }),
647
+ z.strictObject({
648
+ required: z.boolean(),
649
+ from: z.literal("input").meta({
650
+ description: "ACT-12. The key is a named member of the input. A payload that already carries its " +
651
+ "own identity needs no second key beside it, and the member is the Worker's own data.",
652
+ }),
653
+ member: z.string().min(1).meta({
654
+ description: "ACT-12. Which member of the input the Worker reads the key from.",
655
+ }),
656
+ windowSeconds: z.number().int().min(1),
657
+ }),
658
+ ])
659
+ .meta({
660
+ title: "Idempotency declaration",
661
+ description: "ACT-12. Carried in an Action's entry, because a caller decides whether it can retry " +
662
+ "safely BEFORE it sends anything. This is DESC-11's first case.",
663
+ });
664
+ /**
665
+ * ACT-2, ACT-3, ACT-4 — what a Worker declares about one Action.
666
+ *
667
+ * Closed: these rules enumerate the declaration, and a member added later is what an edition is
668
+ * for. The schemas INSIDE it are the Worker's own and are not constrained here — this protocol has
669
+ * no data model, and an Action's input is exactly where that matters most.
670
+ */
671
+ export const actionDeclaration = z
672
+ .strictObject({
673
+ input: z.looseObject({}).meta({
674
+ description: "ACT-2. The JSON Schema of this Action's input, and the whole of what a caller sends. A " +
675
+ "console renders a form from it without having been told anything else about the Worker.",
676
+ }),
677
+ result: z
678
+ .looseObject({})
679
+ .optional()
680
+ .meta({
681
+ description: "ACT-3. The JSON Schema of what a performance answers, absent where it answers nothing. " +
682
+ "ACT-10 draws `200` and `204` at exactly this.",
683
+ }),
684
+ completesWithinCall: z.boolean().meta({
685
+ description: "ACT-4. Declared rather than discovered, because a caller decides whether it can wait " +
686
+ "before it sends. ACT-11: one that does not answers `202` with no body.",
687
+ }),
688
+ idempotency: idempotencyDeclaration.optional().meta({
689
+ description: "ACT-12, ENDP-15. Absent where the Action takes no key.",
690
+ }),
691
+ readAddress: address.optional().meta({
692
+ description: "ACT-15. Where a GET answers a document this Action would accept. Required of " +
693
+ "`configure` and optional for every other Action — which is a condition on the KEY an " +
694
+ "entry is held under, and so a rule rather than a shape.",
695
+ }),
696
+ })
697
+ .meta({
698
+ title: "Action declaration",
699
+ description: "ACT-2. One Action, held under its name in the `actions` entry. The name is the Worker's " +
700
+ "own: NAME-7 does not reach it, because it is resolved inside the Descriptor that " +
701
+ "declared it. `configure` is the one name ACT-13 reserves.",
702
+ });
703
+ /**
704
+ * ACT-16 — the `actions` Capability entry.
705
+ *
706
+ * The address is required, as HLTH-1 and MET-1 require it, because this Capability is answered
707
+ * over HTTP. One address for the Capability and a parameter naming the Action, which is the shape
708
+ * `metrics` already uses — actions.md argues the two alternatives down at length.
709
+ */
710
+ export const actionsEntry = capabilityEntry
711
+ .extend({
712
+ address,
713
+ accepts: z.record(z.string().min(1), actionDeclaration).meta({
714
+ description: "ACT-16. Every Action the Worker accepts, keyed by name. An Action not here is `404` " +
715
+ "under ACT-6. The key carries no pattern: it travels as the VALUE of the `action` " +
716
+ "parameter and is percent-encoded like any other.",
717
+ }),
718
+ })
719
+ .meta({
720
+ title: "Actions capability entry",
721
+ description: "ACT-16. The shared Capability entry with the address required, and the Actions this Worker " +
722
+ "accepts. The Descriptor is the catalog: the surface performs and never lists what exists.",
723
+ });
724
+ /**
725
+ * TASK-32 — one Task type a Worker raises.
726
+ *
727
+ * The Actions named here are the OWNER's own, declared in its `actions` entry: a Response is an
728
+ * Action posted into the owner, so the closed list is a list of names that entry holds.
729
+ */
730
+ export const taskTypeDeclaration = z
731
+ .strictObject({
732
+ payload: z.looseObject({}).meta({
733
+ description: "TASK-32. The JSON Schema of this Task type's payload. The Worker's own — this protocol " +
734
+ "has no data model — and what a consumer renders or validates against.",
735
+ }),
736
+ answeredBy: z
737
+ .string()
738
+ .min(1)
739
+ .meta({
740
+ description: "TASK-32. The one Action of this Worker's own that answers a Task of this type, by the " +
741
+ "name its `actions` entry holds it under. One and not a list: where a Task can end " +
742
+ "several ways, the endings are variants of that Action's input, told apart by a " +
743
+ "discriminator. A name and not an instruction: the owner says what would answer, never " +
744
+ "who.",
745
+ }),
746
+ })
747
+ .meta({
748
+ title: "Task type declaration",
749
+ description: "TASK-32. One Task type, held under a qualified name (TASK-4, NAME-7) because it is matched " +
750
+ "by a party that did not mint it.",
751
+ });
752
+ /**
753
+ * TASK-27, TASK-32 — the `tasks` Capability entry.
754
+ *
755
+ * One address, and a read. The entry carried a second one a claim was posted to until the Claim
756
+ * lifecycle was withdrawn; `spec/tasks.md` holds the argument, and the short of it is that a lease
757
+ * over a unit of work is orchestration, which this specification names a non-goal.
758
+ *
759
+ * It carried a third thing until TASK-31 moved it: what the Worker ANSWERS, which is served at no
760
+ * address and is now `skills` on the Descriptor's root. What is left here is what the declared
761
+ * address actually answers instances of.
762
+ */
763
+ export const tasksEntry = capabilityEntry
764
+ .extend({
765
+ address,
766
+ raises: z.record(qualifiedName, taskTypeDeclaration).meta({
767
+ description: "TASK-32. Every Task type this Worker raises. A Worker that raises none declares an empty " +
768
+ "map rather than omitting it, so that every reader parses one shape.",
769
+ }),
770
+ })
771
+ .meta({
772
+ title: "Tasks capability entry",
773
+ description: "TASK-27. The shared Capability entry with the reading address required, and the Task " +
774
+ "types this Worker raises. What it ANSWERS is TASK-31's `skills`, on the Descriptor root.",
775
+ });
776
+ /**
777
+ * TASK-28 — one Task on the wire.
778
+ *
779
+ * It carries no status and nothing anybody declared about it. A Task exists while its condition
780
+ * holds (TASK-15) and disappears when it stops, so there is no state for a reader to interpret;
781
+ * what a reader needs is what it is, what would answer it, and how long it has been true.
782
+ */
783
+ export const task = z
784
+ .strictObject({
785
+ id: z
786
+ .string()
787
+ .min(1)
788
+ .meta({ description: "TASK-28. The owner's own id for this Task. Opaque to everyone else." }),
789
+ type: qualifiedName.meta({
790
+ description: "TASK-28, TASK-4. One of the types the entry declares under `raises`.",
791
+ }),
792
+ payload: z.unknown().meta({
793
+ description: "TASK-28. Against the schema that type declared. The Worker's own shape.",
794
+ }),
795
+ since: instant("TASK-28. When this Task's condition began. It is what a stuck Task is read from: one open " +
796
+ "since Tuesday is one nobody has answered, and it is the field ALRT-3 puts on an Alert, " +
797
+ "read the same way. It replaced counts of Claims that had failed and lapsed, and says " +
798
+ "less: how long a condition has held, and nothing about what anybody did about it."),
799
+ })
800
+ .meta({
801
+ title: "Task",
802
+ description: "TASK-28. One Task whose condition holds. TASK-5 answers these in the page envelope of " +
803
+ "ENDP-20.",
804
+ });
805
+ /** TASK-5 — what a read answers: the shared page envelope with its items narrowed to Tasks. */
806
+ export const taskPage = page
807
+ .extend({
808
+ items: z.array(task).meta({ description: "TASK-5. The Tasks whose conditions hold." }),
809
+ })
810
+ .meta({
811
+ title: "Task page",
812
+ description: "TASK-5. One page of Tasks, in the envelope ENDP-20 fixes for every collection.",
813
+ });
814
+ /**
815
+ * ALRT-4 — the two severities, and the only two.
816
+ *
817
+ * The contrast with `healthStatus` is the argument rather than an inconsistency. `degraded` earns
818
+ * a third value there because a Worker working with one dependency down has a real state with no
819
+ * honest spelling in two. Here the only decision an operator takes is whether to look now, and a
820
+ * third value would be a place to hedge rather than a state anybody needed to express.
821
+ */
822
+ export const alertSeverity = z.enum(["warning", "critical"]).meta({
823
+ title: "Alert severity",
824
+ description: "ALRT-4. Whether an operator should look now or look later.",
825
+ });
826
+ /**
827
+ * ALRT-3 — one Alert.
828
+ *
829
+ * It carries no status and nothing anybody declared about it, for the reason a Task does not: an
830
+ * Alert exists while its condition holds and ends when it stops (ALRT-5), so there is no state for
831
+ * a reader to interpret and no dismissal for anyone to record.
832
+ */
833
+ export const alert = z
834
+ .strictObject({
835
+ id: z.string().min(1).meta({
836
+ description: "ALRT-3. The Worker's own id for this Alert. Opaque to everyone else.",
837
+ }),
838
+ severity: alertSeverity,
839
+ since: instant("ALRT-3. When the condition began, as an RFC 3339 instant carrying an offset. It is " +
840
+ "what lets a console tell `this is new` from `this is the same thing as yesterday`, " +
841
+ "which is most of what dismissal was being asked to do."),
842
+ summary: z
843
+ .string()
844
+ .min(1)
845
+ .meta({
846
+ description: "ALRT-3. Human-readable, and parsed by nothing. The reader this surface exists for is a " +
847
+ "person looking at a console; what a program acts on is the severity and the Actions.",
848
+ }),
849
+ actions: z.array(z.string().min(1)).meta({
850
+ description: "ALRT-3, ALRT-7. The Actions this Alert offers, by the names the Worker's own `actions` " +
851
+ "entry holds them under. May be empty. Names and not schemas, because the schema is " +
852
+ "already in that entry and a second copy is a second thing to keep in step.",
853
+ }),
854
+ })
855
+ .meta({
856
+ title: "Alert",
857
+ description: "ALRT-3. One condition an operator should see, while it holds.",
858
+ });
859
+ /** ALRT-2 — what a read answers: the page envelope with its items narrowed to Alerts. */
860
+ export const alertPage = page
861
+ .extend({
862
+ items: z.array(alert).meta({ description: "ALRT-2. The Alerts whose conditions hold." }),
863
+ })
864
+ .meta({
865
+ title: "Alert page",
866
+ description: "ALRT-2. One page of Alerts, in the envelope ENDP-20 fixes for every collection.",
867
+ });
868
+ /** NDG-2 — one nudge on the wire: a Task type, and nothing else. */
869
+ export const nudge = z
870
+ .strictObject({
871
+ type: qualifiedName.meta({
872
+ description: "NDG-2. The Task type there is work of. Not the Task: TASK-15 makes the owner " +
873
+ "authoritative over whether the condition still holds, so a Task in flight is a claim that " +
874
+ "may be false by the time it lands. The receiver reads, and what it reads is true when it " +
875
+ "reads it.",
876
+ }),
877
+ })
878
+ .meta({
879
+ title: "Nudge",
880
+ description: "NDG-2. What a POST to the `nudges` address carries. Fixed here and not by the Worker, which " +
881
+ "is why this is an address of its own rather than an Action: ACT-2 has an Action's input be " +
882
+ "the Worker's own shape, and this one never was.",
883
+ });
884
+ /** NDG-1 — the `nudges` Capability entry. The address is required: this is answered over HTTP. */
885
+ export const nudgesEntry = capabilityEntry.extend({ address }).meta({
886
+ title: "Nudges capability entry",
887
+ description: "NDG-1. The shared Capability entry with the address required. Nothing else: what arrives is " +
888
+ "fixed by `nudge.json` rather than declared, and which Task types this Worker will take one " +
889
+ "for is already its `skills` (NDG-3).",
890
+ });
891
+ /** ALRT-1 — the `alerts` Capability entry. The address is required: this is answered over HTTP. */
892
+ export const alertsEntry = capabilityEntry.extend({ address }).meta({
893
+ title: "Alerts capability entry",
894
+ description: "ALRT-1. The shared Capability entry with the address required. Nothing else: what a Worker " +
895
+ "raises an Alert about is its own business, so there is no catalog to declare.",
896
+ });
897
+ /**
898
+ * ACTV-4 — the three states an activity may be in, and the only three.
899
+ *
900
+ * Three rather than Alerts' two, on health's argument rather than alerts': a Worker that will run
901
+ * something at midnight and a Worker with four hundred items queued are both `not running`, and one
902
+ * word for both would make an operator unable to tell `backing up` from `waiting for its time`.
903
+ */
904
+ export const activityState = z.enum(["scheduled", "pending", "running"]).meta({
905
+ title: "Activity state",
906
+ description: "ACTV-4. `scheduled` is undertaken for a later moment the Worker knows, and nothing is wrong. " +
907
+ "`pending` is undertaken and waiting to start, and the length of that list is what an " +
908
+ "operator watches. `running` is under way. No fourth value.",
909
+ });
910
+ /**
911
+ * ACTV-3 — one activity.
912
+ *
913
+ * No type and no payload, deliberately: a payload with no declared schema is JSON nobody outside
914
+ * the Worker can validate or render, which is the blob alerts.md argues a protocol must not offer.
915
+ * The summary is for a person; the state is what a program acts on.
916
+ */
917
+ export const activity = z
918
+ .strictObject({
919
+ id: z.string().min(1).meta({
920
+ description: "ACTV-3. The Worker's own id for this activity. Opaque to everyone else.",
921
+ }),
922
+ state: activityState,
923
+ since: instant("ACTV-3. When the activity entered its current state, as an RFC 3339 instant carrying an " +
924
+ "offset. For `running`, when work began; for `pending`, when it joined the queue, which " +
925
+ "is what makes a stuck one visible; for `scheduled`, when the Worker undertook it — and " +
926
+ "never when it will next run, which is scheduling and a non-goal."),
927
+ summary: z
928
+ .string()
929
+ .min(1)
930
+ .meta({
931
+ description: "ACTV-3. Human-readable, and parsed by nothing. The reader this surface exists for is a " +
932
+ "person asking what a Worker is doing; what a program acts on is the state.",
933
+ }),
934
+ })
935
+ .meta({
936
+ title: "Activity",
937
+ description: "ACTV-3. One thing a Worker is doing or has undertaken to do, while it holds it.",
938
+ });
939
+ /** ACTV-2 — what a read answers: the page envelope with its items narrowed to activities. */
940
+ export const activityPage = page
941
+ .extend({
942
+ items: z.array(activity).meta({ description: "ACTV-2. The activities the Worker holds." }),
943
+ })
944
+ .meta({
945
+ title: "Activity page",
946
+ description: "ACTV-2. One page of activities, in the envelope ENDP-20 fixes for every collection.",
947
+ });
948
+ /** ACTV-1 — the `activity` Capability entry. The address is required: this is answered over HTTP. */
949
+ export const activityEntry = capabilityEntry.extend({ address }).meta({
950
+ title: "Activity capability entry",
951
+ description: "ACTV-1. The shared Capability entry with the address required. Nothing else: what a Worker " +
952
+ "counts as an activity is its own business, so there is no catalog to declare.",
953
+ });
954
+ /**
955
+ * EVT-11 — where an event lands on the broker its entry declares.
956
+ *
957
+ * An object and not a string, because what a consumer needs in order to attach is not alike across
958
+ * brokers: a Kafka topic beside its bootstrap servers, an Event Hub inside a namespace, an SNS ARN
959
+ * with a region in it. One string would have made every consumer parse this Worker's own way of
960
+ * packing several facts into one, which is the work a catalog exists to remove.
961
+ *
962
+ * Open, and nothing here reads a key of it — the same move an Action's input already makes. What
963
+ * that costs is that two Workers on one broker may spell it differently; `spec/events.md` argues
964
+ * why a namespaced name would not have fixed it and convention is what does.
965
+ */
966
+ export const eventDestination = z.looseObject({}).meta({
967
+ title: "Event destination",
968
+ description: "EVT-11. Where on the declared broker these events land, in whatever shape that broker needs " +
969
+ "— a topic beside its servers, an Event Hub in a namespace, an ARN. The keys are the " +
970
+ "Worker's own and nothing here parses them. A Worker that publishes and does not say where " +
971
+ "leaves a consumer holding a cluster, an envelope layout and a list of names it cannot attach " +
972
+ "to anything.",
973
+ });
974
+ /** EVT-12, EVT-11 — one event type a Worker publishes. */
975
+ export const eventTypeDeclaration = z
976
+ .strictObject({
977
+ data: z.looseObject({}).meta({
978
+ description: "EVT-12. The JSON Schema of this event type's data — the `data` of the CloudEvents " +
979
+ "envelope EVT-1 fixes. The Worker's own shape: this protocol has no data model.",
980
+ }),
981
+ destination: eventDestination.optional().meta({
982
+ description: "EVT-11. Where THIS type lands, for a Worker that divides its events by subject. Absent, " +
983
+ "it lands at the entry's destination, which is the ordinary case.",
984
+ }),
985
+ })
986
+ .meta({
987
+ title: "Event type declaration",
988
+ description: "EVT-12. One event type, held under a qualified name (EVT-4, NAME-7) because a subscriber " +
989
+ "matches it against what it decided to consume, having never met the team that minted it.",
990
+ });
991
+ /**
992
+ * EVT-11, EVT-12, EVT-8 — the `events` Capability entry.
993
+ *
994
+ * The one entry with NO address, which is the single reason DESC-22 leaves the address optional in
995
+ * the shared entry at all. An event travels over a broker this protocol declines to name, and a
996
+ * Worker with no HTTP surface for it would otherwise have had to invent a URL that does not exist.
997
+ */
998
+ export const eventsEntry = capabilityEntry
999
+ .extend({
1000
+ broker: z
1001
+ .string()
1002
+ .min(1)
1003
+ .meta({
1004
+ description: "EVT-11. WHICH broker this Worker publishes to, named however its operators name it — " +
1005
+ "the cluster or the service, not the place on it, which is `destination`. Nothing here " +
1006
+ "parses it, exactly as nothing parses a metric unit.",
1007
+ }),
1008
+ protocolBinding: z
1009
+ .string()
1010
+ .min(1)
1011
+ .meta({
1012
+ description: "EVT-11. Which CloudEvents protocol binding the attributes are laid out under. Not fixed " +
1013
+ "and not parsed: a protocol binding is a property of a transport, and fixing one would " +
1014
+ "mean naming a broker or publishing a list of the ones somebody had thought of. It is " +
1015
+ "spelled in full because `binding` alone is what a deployment calls a resource it was " +
1016
+ "handed, which is a different thing that sits a few lines away in the same config.",
1017
+ }),
1018
+ destination: eventDestination,
1019
+ publishes: z.record(qualifiedName, eventTypeDeclaration).meta({
1020
+ description: "EVT-12. Every event type this Worker publishes, keyed by name.",
1021
+ }),
1022
+ republishWindowSeconds: z
1023
+ .number()
1024
+ .int()
1025
+ .min(1)
1026
+ .meta({
1027
+ description: "EVT-8. How long this Worker may publish the same `source` and `id` again. A consumer " +
1028
+ "that remembers them for at least this long sees each event once. Declared because " +
1029
+ "`remember forever` is not implementable, and a consumer that forgot too early would " +
1030
+ "process an event twice while believing it was protected — the same reasoning that has " +
1031
+ "ENDP-15 declare an idempotency window.",
1032
+ }),
1033
+ })
1034
+ .meta({
1035
+ title: "Events capability entry",
1036
+ description: "EVT-11. The shared Capability entry with NO address: the broker, the protocol binding and " +
1037
+ "the destination this Worker publishes to, what it publishes, and how long it may " +
1038
+ "republish one.",
1039
+ });
1040
+ registry.add(capabilityName, { id: "capability-name" });
1041
+ registry.add(qualifiedName, { id: "qualified-name" });
1042
+ registry.add(healthStatus, { id: "health-status" });
1043
+ registry.add(health, { id: "health" });
1044
+ registry.add(healthEntry, { id: "health-entry" });
1045
+ registry.add(capabilityEntry, { id: "capability-entry" });
1046
+ registry.add(metricGranularity, { id: "metric-granularity" });
1047
+ registry.add(metricDimension, { id: "metric-dimension" });
1048
+ registry.add(metricDeclaration, { id: "metric-declaration" });
1049
+ registry.add(metricsEntry, { id: "metrics-entry" });
1050
+ registry.add(metricBucket, { id: "metric-bucket" });
1051
+ registry.add(metricPage, { id: "metric-page" });
1052
+ registry.add(skillDeclaration, { id: "skill-declaration" });
1053
+ registry.add(descriptor, { id: "descriptor" });
1054
+ registry.add(error, { id: "error" });
1055
+ registry.add(page, { id: "page" });
1056
+ registry.add(idempotencyDeclaration, { id: "idempotency-declaration" });
1057
+ registry.add(actionDeclaration, { id: "action-declaration" });
1058
+ registry.add(actionsEntry, { id: "actions-entry" });
1059
+ registry.add(taskTypeDeclaration, { id: "task-type-declaration" });
1060
+ registry.add(tasksEntry, { id: "tasks-entry" });
1061
+ registry.add(task, { id: "task" });
1062
+ registry.add(taskPage, { id: "task-page" });
1063
+ registry.add(alertSeverity, { id: "alert-severity" });
1064
+ registry.add(alert, { id: "alert" });
1065
+ registry.add(alertPage, { id: "alert-page" });
1066
+ registry.add(alertsEntry, { id: "alerts-entry" });
1067
+ registry.add(nudge, { id: "nudge" });
1068
+ registry.add(nudgesEntry, { id: "nudges-entry" });
1069
+ registry.add(activityState, { id: "activity-state" });
1070
+ registry.add(activity, { id: "activity" });
1071
+ registry.add(activityPage, { id: "activity-page" });
1072
+ registry.add(activityEntry, { id: "activity-entry" });
1073
+ registry.add(eventDestination, { id: "event-destination" });
1074
+ registry.add(eventTypeDeclaration, { id: "event-type-declaration" });
1075
+ registry.add(eventsEntry, { id: "events-entry" });