@voltro/protocol 0.81.0 → 0.83.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/inspect.d.ts CHANGED
@@ -2,9 +2,9 @@ import { ParseOptions } from 'effect/SchemaAST';
2
2
  import { Schema } from 'effect';
3
3
 
4
4
  export declare const decodeInspectBackfillInput: (u: unknown, overrideOptions?: ParseOptions) => {
5
- readonly from: string;
6
5
  readonly name: string;
7
6
  readonly limit: number;
7
+ readonly from: string;
8
8
  readonly to: string;
9
9
  readonly requestId: string;
10
10
  readonly confirm: boolean;
@@ -71,6 +71,18 @@ export declare const InspectBackfillResultSchema: Schema.Union<[Schema.Struct<{
71
71
  reason: typeof Schema.String;
72
72
  }>]>;
73
73
 
74
+ export declare type InspectCommandRejection = typeof InspectCommandRejectionSchema.Type;
75
+
76
+ export declare const inspectCommandRejection: (status: number, message: string) => InspectCommandRejection | undefined;
77
+
78
+ /** Explicit non-acceptance from an Inspect command endpoint. Transport errors,
79
+ * conflicts with an earlier request and server failures are not this outcome. */
80
+ export declare const InspectCommandRejectionSchema: Schema.Struct<{
81
+ kind: Schema.Literal<["rejected"]>;
82
+ status: Schema.Literal<[400, 401, 403, 404]>;
83
+ message: typeof Schema.String;
84
+ }>;
85
+
74
86
  export declare type InspectReplicaSelection = {
75
87
  readonly mode: 'all';
76
88
  } | {
@@ -78,10 +90,82 @@ export declare type InspectReplicaSelection = {
78
90
  readonly replicaIds: ReadonlyArray<string>;
79
91
  };
80
92
 
93
+ export declare type InspectWorkflowExecutionObservation = typeof InspectWorkflowExecutionObservationSchema.Type;
94
+
95
+ /** A projection of engine evidence, not a run-row or recorder-entry claim.
96
+ * Deliberately excludes payloads, outputs, raw causes and journal identifiers. */
97
+ export declare const InspectWorkflowExecutionObservationSchema: Schema.Union<[Schema.Struct<{
98
+ kind: Schema.Literal<["not-observed"]>;
99
+ workflowName: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
100
+ executionId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
101
+ }>, Schema.Struct<{
102
+ kind: Schema.Literal<["pending"]>;
103
+ workflowName: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
104
+ executionId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
105
+ }>, Schema.Struct<{
106
+ kind: Schema.Literal<["delivery-failed"]>;
107
+ workflowName: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
108
+ executionId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
109
+ }>, Schema.Struct<{
110
+ kind: Schema.Literal<["suspended"]>;
111
+ workflowName: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
112
+ executionId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
113
+ }>, Schema.Struct<{
114
+ kind: Schema.Literal<["completed"]>;
115
+ outcome: Schema.Literal<["succeeded", "failed", "cancelled"]>;
116
+ workflowName: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
117
+ executionId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
118
+ }>]>;
119
+
120
+ export declare type InspectWorkflowExecutionTarget = typeof InspectWorkflowExecutionTargetSchema.Type;
121
+
122
+ /** Read an existing execution identity; this is never a start command. */
123
+ export declare const InspectWorkflowExecutionTargetSchema: Schema.Struct<{
124
+ workflowName: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
125
+ executionId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
126
+ }>;
127
+
128
+ export declare type InspectWorkflowQueuedStartObservation = typeof InspectWorkflowQueuedStartObservationSchema.Type;
129
+
130
+ /** Read-only admission evidence. Accepted means an execution exists, not that it succeeded. */
131
+ export declare const InspectWorkflowQueuedStartObservationSchema: Schema.Union<[Schema.Struct<{
132
+ kind: Schema.Literal<["pending"]>;
133
+ workflowName: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
134
+ receiptId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
135
+ tenantId: Schema.NullOr<Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>>;
136
+ }>, Schema.Struct<{
137
+ kind: Schema.Literal<["accepted"]>;
138
+ executionId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
139
+ workflowName: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
140
+ receiptId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
141
+ tenantId: Schema.NullOr<Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>>;
142
+ }>, Schema.Struct<{
143
+ kind: Schema.Literal<["not-executed"]>;
144
+ reason: Schema.Literal<["dropped", "skipped", "superseded", "cancelled", "abandoned", "rejected"]>;
145
+ workflowName: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
146
+ receiptId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
147
+ tenantId: Schema.NullOr<Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>>;
148
+ }>]>;
149
+
150
+ export declare type InspectWorkflowQueuedStartTarget = typeof InspectWorkflowQueuedStartTargetSchema.Type;
151
+
152
+ /** A caller receipt, never an execution or a reusable admission slot. */
153
+ export declare const InspectWorkflowQueuedStartTargetSchema: Schema.Struct<{
154
+ workflowName: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
155
+ receiptId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
156
+ tenantId: Schema.NullOr<Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>>;
157
+ }>;
158
+
81
159
  export declare class InvalidReplicaSelection extends Error {
82
160
  readonly name = "InvalidReplicaSelection";
83
161
  }
84
162
 
163
+ export declare const isInspectCommandRejection: (u: unknown, overrideOptions?: ParseOptions | number) => u is {
164
+ readonly status: 400 | 401 | 403 | 404;
165
+ readonly kind: "rejected";
166
+ readonly message: string;
167
+ };
168
+
85
169
  /** Repeated parameters preserve IDs containing commas, spaces or punctuation. */
86
170
  export declare const parseReplicaSelection: (params: URLSearchParams) => InspectReplicaSelection | undefined;
87
171
 
package/dist/inspect.js CHANGED
@@ -1,85 +1,130 @@
1
1
  import { Schema as e } from "effect";
2
- //#region src/inspectBackfill.ts
3
- var t = e.String.pipe(e.filter((e) => Number.isFinite(Date.parse(e)))), n = e.String.pipe(e.minLength(1), e.maxLength(256)), r = e.Struct({
4
- id: n,
5
- name: n,
6
- from: t,
7
- to: t,
2
+ //#region src/inspectWorkflowQueuedStart.ts
3
+ var t = e.NonEmptyString.pipe(e.maxLength(1024), e.filter((e) => e.trim().length > 0 && !/[\u0000-\u001f\u007f]/.test(e))), n = e.Struct({
4
+ workflowName: t,
5
+ receiptId: t,
6
+ tenantId: e.NullOr(t)
7
+ }), r = n.fields, i = e.Union(e.Struct({
8
+ ...r,
9
+ kind: e.Literal("pending")
10
+ }), e.Struct({
11
+ ...r,
12
+ kind: e.Literal("accepted"),
13
+ executionId: t
14
+ }), e.Struct({
15
+ ...r,
16
+ kind: e.Literal("not-executed"),
17
+ reason: e.Literal("dropped", "skipped", "superseded", "cancelled", "abandoned", "rejected")
18
+ })), a = e.Struct({
19
+ kind: e.Literal("rejected"),
20
+ status: e.Literal(400, 401, 403, 404),
21
+ message: e.String
22
+ }), o = e.is(a), s = (e, t) => {
23
+ let n = {
24
+ kind: "rejected",
25
+ status: e,
26
+ message: t
27
+ };
28
+ return o(n) ? n : void 0;
29
+ }, c = e.String.pipe(e.filter((e) => Number.isFinite(Date.parse(e)))), l = e.String.pipe(e.minLength(1), e.maxLength(256)), u = e.Struct({
30
+ id: l,
31
+ name: l,
32
+ from: c,
33
+ to: c,
8
34
  occurrences: e.Array(e.Number),
9
35
  status: e.Literal("queued", "running", "completed", "interrupted"),
10
36
  owner: e.NullOr(e.String),
11
- createdAt: t,
12
- updatedAt: t,
37
+ createdAt: c,
38
+ updatedAt: c,
13
39
  cursor: e.Number,
14
40
  activeIndex: e.NullOr(e.Number),
15
41
  results: e.Array(e.Struct({
16
- runId: n,
42
+ runId: l,
17
43
  status: e.Literal("succeeded", "failed", "skipped", "missed")
18
44
  })),
19
45
  error: e.NullOr(e.String)
20
- }), i = e.Struct({
21
- requestId: n,
22
- name: n,
23
- from: t,
24
- to: t,
46
+ }), d = e.Struct({
47
+ requestId: l,
48
+ name: l,
49
+ from: c,
50
+ to: c,
25
51
  confirm: e.Boolean,
26
52
  limit: e.Number.pipe(e.int(), e.between(1, 1e4))
27
- }), a = e.decodeUnknownSync(i), o = e.Union(e.Struct({
53
+ }), f = e.decodeUnknownSync(d), p = e.Union(e.Struct({
28
54
  kind: e.Literal("accepted"),
29
- request: r
55
+ request: u
30
56
  }), e.Struct({
31
57
  kind: e.Literal("refused"),
32
- name: n,
58
+ name: l,
33
59
  occurrences: e.Number,
34
60
  reason: e.String
35
- })), s = class extends Error {
61
+ })), m = class extends Error {
36
62
  name = "ScheduleBackfillRequestConflict";
37
- }, c = (e) => typeof e == "object" && e && !Array.isArray(e) ? e : void 0, l = (e) => Array.isArray(e) && e.every((e) => c(e) !== void 0) ? e : void 0, u = (e) => typeof e == "number" && Number.isFinite(e) ? e : 0, d = /* @__PURE__ */ new Set([
63
+ }, h = e.NonEmptyString.pipe(e.maxLength(1024), e.filter((e) => e.trim().length > 0 && !/[\u0000-\u001f\u007f]/.test(e))), g = e.Struct({
64
+ workflowName: h,
65
+ executionId: h
66
+ }), _ = g.fields, v = e.Union(e.Struct({
67
+ ..._,
68
+ kind: e.Literal("not-observed")
69
+ }), e.Struct({
70
+ ..._,
71
+ kind: e.Literal("pending")
72
+ }), e.Struct({
73
+ ..._,
74
+ kind: e.Literal("delivery-failed")
75
+ }), e.Struct({
76
+ ..._,
77
+ kind: e.Literal("suspended")
78
+ }), e.Struct({
79
+ ..._,
80
+ kind: e.Literal("completed"),
81
+ outcome: e.Literal("succeeded", "failed", "cancelled")
82
+ })), y = (e) => typeof e == "object" && e && !Array.isArray(e) ? e : void 0, b = (e) => Array.isArray(e) && e.every((e) => y(e) !== void 0) ? e : void 0, x = (e) => typeof e == "number" && Number.isFinite(e) ? e : 0, S = /* @__PURE__ */ new Set([
38
83
  "counter",
39
84
  "gauge",
40
85
  "histogram",
41
86
  "summary",
42
87
  "frequency"
43
- ]), f = (e) => {
44
- let t = l(e);
45
- return t?.every((e) => typeof e.name == "string" && d.has(String(e.type))) ? t : void 0;
46
- }, p = (e) => {
47
- let t = c(e), n = c(t?.data), r = l(n?.results);
88
+ ]), C = (e) => {
89
+ let t = b(e);
90
+ return t?.every((e) => typeof e.name == "string" && S.has(String(e.type))) ? t : void 0;
91
+ }, w = (e) => {
92
+ let t = y(e), n = y(t?.data), r = b(n?.results);
48
93
  if (!t || !n?.selection || !r) return e;
49
94
  let i = r.filter((e) => e.status === "answered").map((e) => ({
50
95
  replicaId: String(e.replicaId),
51
- observation: c(e.observation),
52
- data: c(e.observation)?.data
96
+ observation: y(e.observation),
97
+ data: y(e.observation)?.data
53
98
  }));
54
99
  if (i.length === 0) throw Error("None of the selected replicas answered. Check replica availability and retry.");
55
100
  let a = i.map((e) => ({
56
101
  ...e,
57
- object: c(e.data)
58
- })), o = (e) => a.reduce((t, n) => t + u(n.object?.[e]), 0), s = Math.max(...i.map((e) => u(e.observation?.capturedAt))), d = {
102
+ object: y(e.data)
103
+ })), o = (e) => a.reduce((t, n) => t + x(n.object?.[e]), 0), s = Math.max(...i.map((e) => x(e.observation?.capturedAt))), c = {
59
104
  capturedAt: s,
60
105
  bufferSize: o("bufferSize"),
61
106
  bufferCapacity: o("bufferCapacity")
62
- }, p;
63
- if (a.every((e) => l(e.object?.records))) {
64
- let e = a.flatMap((e) => l(e.object?.records).map((t) => ({
107
+ }, l;
108
+ if (a.every((e) => b(e.object?.records))) {
109
+ let e = a.flatMap((e) => b(e.object?.records).map((t) => ({
65
110
  ...t,
66
111
  fields: {
67
- ...c(t.fields),
112
+ ...y(t.fields),
68
113
  "voltro.replica": e.replicaId
69
114
  }
70
- }))).sort((e, t) => u(e.ts) - u(t.ts));
71
- p = {
72
- ...d,
115
+ }))).sort((e, t) => x(e.ts) - x(t.ts));
116
+ l = {
117
+ ...c,
73
118
  returned: e.length,
74
119
  records: e
75
120
  };
76
- } else if (a.every((e) => l(e.object?.traces))) {
121
+ } else if (a.every((e) => b(e.object?.traces))) {
77
122
  let e = /* @__PURE__ */ new Map();
78
- for (let t of a) for (let n of l(t.object?.traces)) {
79
- let r = String(n.traceId), i = (l(n.spans) ?? []).map((e) => ({
123
+ for (let t of a) for (let n of b(t.object?.traces)) {
124
+ let r = String(n.traceId), i = (b(n.spans) ?? []).map((e) => ({
80
125
  ...e,
81
126
  attributes: {
82
- ...c(e.attributes),
127
+ ...y(e.attributes),
83
128
  "voltro.replica": t.replicaId
84
129
  }
85
130
  })), a = e.get(r) ?? [];
@@ -89,7 +134,7 @@ var t = e.String.pipe(e.filter((e) => Number.isFinite(Date.parse(e)))), n = e.St
89
134
  }), e.set(r, a);
90
135
  }
91
136
  let t = [...e.values()].map((e) => {
92
- let t = e.flatMap((e) => l(e.spans) ?? []).sort((e, t) => u(e.startMs) - u(t.startMs)), n = [...e].sort((e, t) => u(e.startMs) - u(t.startMs))[0], r = Math.min(...e.map((e) => u(e.startMs))), i = Math.max(...e.map((e) => u(e.startMs) + u(e.durationMs)));
137
+ let t = e.flatMap((e) => b(e.spans) ?? []).sort((e, t) => x(e.startMs) - x(t.startMs)), n = [...e].sort((e, t) => x(e.startMs) - x(t.startMs))[0], r = Math.min(...e.map((e) => x(e.startMs))), i = Math.max(...e.map((e) => x(e.startMs) + x(e.durationMs)));
93
138
  return {
94
139
  ...n,
95
140
  startMs: r,
@@ -97,65 +142,65 @@ var t = e.String.pipe(e.filter((e) => Number.isFinite(Date.parse(e)))), n = e.St
97
142
  spanCount: t.length,
98
143
  hasError: e.some((e) => e.hasError === !0),
99
144
  spans: t,
100
- ...e.some((e) => e.deliveryCount !== void 0) ? { deliveryCount: e.reduce((e, t) => e + u(t.deliveryCount), 0) } : {},
101
- ...e.some((e) => e.lastDeliveryMs !== void 0) ? { lastDeliveryMs: Math.max(...e.map((e) => u(e.lastDeliveryMs))) } : {}
145
+ ...e.some((e) => e.deliveryCount !== void 0) ? { deliveryCount: e.reduce((e, t) => e + x(t.deliveryCount), 0) } : {},
146
+ ...e.some((e) => e.lastDeliveryMs !== void 0) ? { lastDeliveryMs: Math.max(...e.map((e) => x(e.lastDeliveryMs))) } : {}
102
147
  };
103
148
  }).sort((e, t) => t.startMs - e.startMs);
104
- p = {
105
- ...d,
149
+ l = {
150
+ ...c,
106
151
  returned: t.length,
107
152
  traces: t
108
153
  };
109
- } else if (a.every((e) => l(e.object?.buckets) && typeof e.object?.windowMs == "number")) p = {
154
+ } else if (a.every((e) => b(e.object?.buckets) && typeof e.object?.windowMs == "number")) l = {
110
155
  capturedAt: s,
111
- windowMs: Math.max(...a.map((e) => u(e.object?.windowMs))),
156
+ windowMs: Math.max(...a.map((e) => x(e.object?.windowMs))),
112
157
  totalSamples: o("totalSamples"),
113
- buckets: a.flatMap((e) => l(e.object?.buckets).map((t) => ({
158
+ buckets: a.flatMap((e) => b(e.object?.buckets).map((t) => ({
114
159
  ...t,
115
160
  tag: `${String(t.tag)} [${e.replicaId}]`
116
161
  })))
117
162
  };
118
- else if (i.every((e) => f(e.data))) p = i.flatMap((e) => f(e.data).map((t) => ({
163
+ else if (i.every((e) => C(e.data))) l = i.flatMap((e) => C(e.data).map((t) => ({
119
164
  ...t,
120
165
  labels: {
121
- ...c(t.labels),
166
+ ...y(t.labels),
122
167
  voltro_replica: e.replicaId
123
168
  }
124
169
  })));
125
- else if (a.every((e) => f(e.object?.samples))) p = { samples: a.flatMap((e) => f(e.object?.samples).map((t) => ({
170
+ else if (a.every((e) => C(e.object?.samples))) l = { samples: a.flatMap((e) => C(e.object?.samples).map((t) => ({
126
171
  ...t,
127
172
  labels: {
128
- ...c(t.labels),
173
+ ...y(t.labels),
129
174
  voltro_replica: e.replicaId
130
175
  }
131
176
  }))) };
132
- else if (r.length === 1 && i.length === 1) p = i[0].data;
177
+ else if (r.length === 1 && i.length === 1) l = i[0].data;
133
178
  else throw Error("This view must compare observations per replica; its values cannot be safely combined.");
134
179
  return {
135
180
  ...t,
136
- data: p
181
+ data: l
137
182
  };
138
- }, m = class extends Error {
183
+ }, T = class extends Error {
139
184
  name = "InvalidReplicaSelection";
140
- }, h = (e) => {
185
+ }, E = (e) => {
141
186
  let t = e.getAll("replicas"), n = e.getAll("replica");
142
187
  if (t.length !== 0 || n.length !== 0) {
143
- if (t.length > 1 || t.length === 1 && t[0] !== "all" || t.length > 0 && n.length > 0) throw new m("Choose all replicas or explicit replica IDs, not both.");
188
+ if (t.length > 1 || t.length === 1 && t[0] !== "all" || t.length > 0 && n.length > 0) throw new T("Choose all replicas or explicit replica IDs, not both.");
144
189
  if (t[0] === "all") return { mode: "all" };
145
- if (n.some((e) => e.trim().length === 0 || e.length > 512 || /[\u0000-\u001f\u007f]/.test(e))) throw new m("Replica IDs must be nonempty and contain no control characters.");
190
+ if (n.some((e) => e.trim().length === 0 || e.length > 512 || /[\u0000-\u001f\u007f]/.test(e))) throw new T("Replica IDs must be nonempty and contain no control characters.");
146
191
  return {
147
192
  mode: "selected",
148
193
  replicaIds: [...new Set(n)].sort()
149
194
  };
150
195
  }
151
- }, g = (e, t) => {
196
+ }, D = (e, t) => {
152
197
  let n = e.indexOf("#"), r = n < 0 ? "" : e.slice(n), i = n < 0 ? e : e.slice(0, n), a = i.indexOf("?"), o = a < 0 ? i : i.slice(0, a), s = new URLSearchParams(a < 0 ? "" : i.slice(a + 1));
153
198
  if (s.delete("replica"), s.delete("replicas"), t.mode === "all") s.set("replicas", "all");
154
199
  else {
155
- if (t.replicaIds.length === 0) throw new m("Select at least one replica.");
200
+ if (t.replicaIds.length === 0) throw new T("Select at least one replica.");
156
201
  for (let e of [...new Set(t.replicaIds)].sort()) s.append("replica", e);
157
202
  }
158
- return h(s), `${o}?${s}${r}`;
203
+ return E(s), `${o}?${s}${r}`;
159
204
  };
160
205
  //#endregion
161
- export { i as InspectBackfillInputSchema, r as InspectBackfillReceiptSchema, o as InspectBackfillResultSchema, m as InvalidReplicaSelection, s as ScheduleBackfillRequestConflict, a as decodeInspectBackfillInput, h as parseReplicaSelection, p as projectReplicaObservation, g as withReplicaSelection };
206
+ export { d as InspectBackfillInputSchema, u as InspectBackfillReceiptSchema, p as InspectBackfillResultSchema, a as InspectCommandRejectionSchema, v as InspectWorkflowExecutionObservationSchema, g as InspectWorkflowExecutionTargetSchema, i as InspectWorkflowQueuedStartObservationSchema, n as InspectWorkflowQueuedStartTargetSchema, T as InvalidReplicaSelection, m as ScheduleBackfillRequestConflict, f as decodeInspectBackfillInput, s as inspectCommandRejection, o as isInspectCommandRejection, E as parseReplicaSelection, w as projectReplicaObservation, D as withReplicaSelection };
package/dist/jwt.d.ts CHANGED
@@ -26,7 +26,7 @@ declare interface AuthStrategy {
26
26
  readonly resolve: (input: AuthStrategyInput) => Promise<StrategyResolution> | StrategyResolution;
27
27
  /**
28
28
  * The bearer-token PREFIX this strategy claims, when it gates on one
29
- * (`'sk_'`, `'awb_'`). Declared so a collision is DETECTABLE.
29
+ * (`'sk_'`, `'acme_'`). Declared so a collision is DETECTABLE.
30
30
  *
31
31
  * Two strategies claiming the same prefix is not a harmless duplicate: the
32
32
  * chain is first-match-wins, so whichever runs first decides the Subject —
package/dist/rest.d.ts CHANGED
@@ -440,7 +440,7 @@ declare interface NestedTargetFields<Input = unknown> {
440
440
  /** Guard WHICH cached query entries this target patches: only entries whose
441
441
  * CURRENT value satisfies the predicate. Pure + browser-safe. Prevents a
442
442
  * patch from bleeding across sibling subscriptions that share a source table
443
- * (the guard AWB hand-writes as `roadmap.id === input.roadmapId`). Absent →
443
+ * (the guard an app would otherwise hand-write as `roadmap.id === input.roadmapId`). Absent →
444
444
  * every entry matching the target `table` is patched. */
445
445
  readonly match?: ((value: unknown, input: Input) => boolean) | undefined;
446
446
  }
@@ -799,7 +799,8 @@ export declare const problemTypeOf: (tag: string) => string;
799
799
  * and the status `errorStatus` assigns (`422` by default, the status the
800
800
  * OpenAPI projection already documents for a typed error); a plugin's
801
801
  * cross-cutting error answers under the status the plugin names. Anything
802
- * else is rethrown for the desugar's shared mapping (`ScopeError` → 403/401, a
802
+ * else is rethrown for the desugar's shared mapping (`ScopeError` → 403/401,
803
+ * a resource guard's `NoSuchResource` → 404, a
803
804
  * `status`-carrying error → itself, the rest → 500). The body is the tagged
804
805
  * `{ error, …fields }` form; `projectedFailureFor` speaks the profile.
805
806
  */
@@ -986,6 +987,13 @@ declare interface PublicApiSpec {
986
987
  * optional, the rest inherited. See `PublicApiProfileConfig`.
987
988
  */
988
989
  readonly format?: PublicApiProfileConfig;
990
+ /**
991
+ * GET only: how long a CDN may keep a successful answer, and the tags it can
992
+ * purge it by (`Cache-Tag`, `Surrogate-Key`). A request with a credential is
993
+ * always answered `private` — see `RestCache`. `tags` receives the answer as
994
+ * it goes on the wire.
995
+ */
996
+ readonly cache?: RestCache;
989
997
  }
990
998
 
991
999
  /**
@@ -1179,6 +1187,27 @@ export declare interface RestByteResponse {
1179
1187
  readonly headers?: Readonly<Record<string, string | ReadonlyArray<string>>>;
1180
1188
  }
1181
1189
 
1190
+ /**
1191
+ * Caching for a GET route's successful (200) answers.
1192
+ *
1193
+ * An anonymous request's answer is `public` for `maxAge` seconds (`max-age`
1194
+ * and `s-maxage`), `stale-while-revalidate` when set, and carries `tags` as
1195
+ * `Cache-Tag` (comma-separated) and `Surrogate-Key` (space-separated) — the
1196
+ * headers CDNs purge by. A request that carries a credential (`Authorization`
1197
+ * or a cookie) gets `private, no-cache` and no tags, whatever is declared:
1198
+ * what a key or a session may see must never land in a shared cache. Every
1199
+ * such answer says `Vary: Authorization, Cookie`. Errors are not affected.
1200
+ */
1201
+ export declare interface RestCache {
1202
+ /** Seconds a cache may serve the answer without asking again. */
1203
+ readonly maxAge: number;
1204
+ /** Seconds after that a stale answer may still be served while the cache refetches. */
1205
+ readonly staleWhileRevalidate?: number;
1206
+ /** Purge tags from the answer as it goes on the wire, and the input.
1207
+ * A tag with whitespace or a comma is dropped (no header can carry it). */
1208
+ readonly tags?: (output: unknown, input: unknown) => ReadonlyArray<string>;
1209
+ }
1210
+
1182
1211
  export declare type RestDates = 'iso' | 'epoch-ms';
1183
1212
 
1184
1213
  export declare type RestEnvelope = 'none' | 'data' | 'jsonapi';
@@ -1270,6 +1299,11 @@ export declare interface RestGuardRejection {
1270
1299
  readonly message: string;
1271
1300
  }
1272
1301
 
1302
+ /** What a REST handler may return: the decoded output, a `response(...)` with
1303
+ * its own status and headers, an `sse(...)` stream, or `bytes(...)` for a
1304
+ * non-JSON body (a file, an export, an XML document). */
1305
+ export declare type RestHandlerResult<O> = O | RestResponse<O> | RestStreamResponse | RestByteResponse;
1306
+
1273
1307
  export declare type RestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
1274
1308
 
1275
1309
  export declare type RestNaming = 'camelCase' | 'snake_case';
@@ -1333,7 +1367,7 @@ export declare interface RestRouteDescriptor<I, O> {
1333
1367
  /** Output schema. The handler returns the decoded `Type`; the desugar
1334
1368
  * encodes it to JSON for the `200` response. */
1335
1369
  readonly output: Schema.Schema<O, any, never>;
1336
- readonly handler: (input: I, ctx: RestRouteContext) => Promise<O | RestResponse<O>> | O | RestResponse<O>;
1370
+ readonly handler: (input: I, ctx: RestRouteContext) => Promise<RestHandlerResult<O>> | RestHandlerResult<O>;
1337
1371
  readonly summary?: string;
1338
1372
  readonly description?: string;
1339
1373
  readonly example?: RestRouteExample;
@@ -1390,6 +1424,9 @@ export declare interface RestRouteDescriptor<I, O> {
1390
1424
  * of the JSON), so it is correct across content-encodings — the
1391
1425
  * transport's compression varies the bytes, not the representation. */
1392
1426
  readonly etag?: boolean;
1427
+ /** GET only: how long a shared cache (a CDN) may keep a successful answer,
1428
+ * and the tags it can be purged by. See {@link RestCache}. */
1429
+ readonly cache?: RestCache;
1393
1430
  }
1394
1431
 
1395
1432
  /** Intentional non-2xx REST control flow with an application-defined JSON