@ontrails/core 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +849 -0
- package/README.md +190 -0
- package/package.json +36 -0
- package/src/activation-provenance.ts +116 -0
- package/src/activation-source-compatibility.ts +430 -0
- package/src/activation-source-derivation.ts +227 -0
- package/src/activation-source.ts +93 -0
- package/src/blob-ref.ts +90 -0
- package/src/branded.ts +135 -0
- package/src/collections.ts +99 -0
- package/src/compose-batch.ts +69 -0
- package/src/compose-schema.ts +36 -0
- package/src/context.ts +66 -0
- package/src/derive.ts +485 -0
- package/src/detours.ts +8 -0
- package/src/diagnostics.ts +21 -0
- package/src/draft.ts +350 -0
- package/src/entity.ts +346 -0
- package/src/error-rendering.ts +87 -0
- package/src/errors.ts +483 -0
- package/src/execute.ts +1577 -0
- package/src/fetch.ts +138 -0
- package/src/fire.ts +1172 -0
- package/src/glob.ts +81 -0
- package/src/guards.ts +37 -0
- package/src/index.ts +704 -0
- package/src/internal/fork-ctx.ts +69 -0
- package/src/layer-field-rendering.ts +193 -0
- package/src/layer.ts +81 -0
- package/src/observe.ts +361 -0
- package/src/path-scope.ts +66 -0
- package/src/path-security.ts +98 -0
- package/src/patterns/bulk.ts +16 -0
- package/src/patterns/change.ts +12 -0
- package/src/patterns/date-range.ts +12 -0
- package/src/patterns/index.ts +8 -0
- package/src/patterns/pagination.ts +22 -0
- package/src/patterns/progress.ts +13 -0
- package/src/patterns/sorting.ts +14 -0
- package/src/patterns/status.ts +11 -0
- package/src/patterns/timestamps.ts +12 -0
- package/src/permits.ts +12 -0
- package/src/queue.ts +163 -0
- package/src/redaction/index.ts +3 -0
- package/src/redaction/patterns.ts +50 -0
- package/src/redaction/redactor.ts +178 -0
- package/src/resilience.ts +234 -0
- package/src/resource-config.ts +804 -0
- package/src/resource.ts +194 -0
- package/src/result.ts +212 -0
- package/src/run.ts +76 -0
- package/src/runtime-builtins.ts +69 -0
- package/src/schedule-runtime.ts +689 -0
- package/src/schedule.ts +326 -0
- package/src/serialization.ts +265 -0
- package/src/sha256.ts +136 -0
- package/src/signal-diagnostics.ts +633 -0
- package/src/signal-ref.ts +111 -0
- package/src/signal.ts +104 -0
- package/src/store/accessor-protocol.ts +56 -0
- package/src/store/index.ts +4 -0
- package/src/structured-examples.ts +248 -0
- package/src/surface-derivation.ts +91 -0
- package/src/surface-filter.ts +101 -0
- package/src/surface-overlay.ts +694 -0
- package/src/surface-versioning.ts +42 -0
- package/src/topo.ts +835 -0
- package/src/tracing.ts +346 -0
- package/src/trail-id-glob.ts +15 -0
- package/src/trail.ts +1351 -0
- package/src/trails/derive-trail.ts +835 -0
- package/src/trails/index.ts +9 -0
- package/src/trails/ingest.ts +152 -0
- package/src/trails-db.ts +212 -0
- package/src/transport-error-map.ts +163 -0
- package/src/type-utils.ts +87 -0
- package/src/types.ts +300 -0
- package/src/validate-established-topo.ts +73 -0
- package/src/validate-topo.ts +725 -0
- package/src/validation.ts +330 -0
- package/src/version-marker.ts +716 -0
- package/src/version-resolution.ts +308 -0
- package/src/version-runtime.ts +120 -0
- package/src/webhook.ts +461 -0
- package/src/workspace.ts +244 -0
- package/src/zod-wrappers.ts +72 -0
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime diagnostics for typed signal emission.
|
|
3
|
+
*
|
|
4
|
+
* Diagnostics are side-channel records: they make signal runtime problems
|
|
5
|
+
* observable without changing the best-effort producer API.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { z } from 'zod';
|
|
9
|
+
|
|
10
|
+
import type { ActivationProvenance } from './activation-provenance.js';
|
|
11
|
+
import { sha256Hex } from './sha256.js';
|
|
12
|
+
import type { Logger } from './types.js';
|
|
13
|
+
|
|
14
|
+
export const SIGNAL_DIAGNOSTICS_SINK_KEY =
|
|
15
|
+
'__trails_signal_diagnostics_sink' as const;
|
|
16
|
+
|
|
17
|
+
export const SIGNAL_DIAGNOSTICS_STRICT_MODE_KEY =
|
|
18
|
+
'__trails_signal_diagnostics_strict_mode' as const;
|
|
19
|
+
|
|
20
|
+
export const signalDiagnosticDefinitions = {
|
|
21
|
+
'signal.fire.suppressed': {
|
|
22
|
+
category: 'activation',
|
|
23
|
+
description:
|
|
24
|
+
'A signal fire was intentionally suppressed by a runtime guard.',
|
|
25
|
+
level: 'warning',
|
|
26
|
+
},
|
|
27
|
+
'signal.handler.failed': {
|
|
28
|
+
category: 'handler',
|
|
29
|
+
description: 'A signal consumer returned a failed Result.',
|
|
30
|
+
level: 'error',
|
|
31
|
+
},
|
|
32
|
+
'signal.handler.predicate_failed': {
|
|
33
|
+
category: 'activation',
|
|
34
|
+
description: 'A signal consumer activation predicate threw or rejected.',
|
|
35
|
+
level: 'error',
|
|
36
|
+
},
|
|
37
|
+
'signal.handler.rejected': {
|
|
38
|
+
category: 'handler',
|
|
39
|
+
description: 'A signal consumer rejected outside Result normalization.',
|
|
40
|
+
level: 'error',
|
|
41
|
+
},
|
|
42
|
+
'signal.invalid': {
|
|
43
|
+
category: 'validation',
|
|
44
|
+
description:
|
|
45
|
+
'A signal payload failed schema validation at the fire boundary.',
|
|
46
|
+
level: 'error',
|
|
47
|
+
},
|
|
48
|
+
'signal.unknown': {
|
|
49
|
+
category: 'topology',
|
|
50
|
+
description: 'A producer fired a signal that is not present in the topo.',
|
|
51
|
+
level: 'error',
|
|
52
|
+
},
|
|
53
|
+
} as const;
|
|
54
|
+
|
|
55
|
+
export type SignalDiagnosticCode = keyof typeof signalDiagnosticDefinitions;
|
|
56
|
+
|
|
57
|
+
export type SignalDiagnosticLevel =
|
|
58
|
+
(typeof signalDiagnosticDefinitions)[SignalDiagnosticCode]['level'];
|
|
59
|
+
|
|
60
|
+
export type SignalDiagnosticCategory =
|
|
61
|
+
(typeof signalDiagnosticDefinitions)[SignalDiagnosticCode]['category'];
|
|
62
|
+
|
|
63
|
+
export type SignalDiagnosticOrigin =
|
|
64
|
+
| 'fan-out-guard'
|
|
65
|
+
| 'fire-boundary'
|
|
66
|
+
| 'handler'
|
|
67
|
+
| 'predicate';
|
|
68
|
+
|
|
69
|
+
export type SignalPayloadShape =
|
|
70
|
+
| 'array'
|
|
71
|
+
| 'bigint'
|
|
72
|
+
| 'boolean'
|
|
73
|
+
| 'function'
|
|
74
|
+
| 'null'
|
|
75
|
+
| 'number'
|
|
76
|
+
| 'object'
|
|
77
|
+
| 'string'
|
|
78
|
+
| 'symbol'
|
|
79
|
+
| 'undefined';
|
|
80
|
+
|
|
81
|
+
export interface SignalPayloadSummary {
|
|
82
|
+
readonly byteLength: number;
|
|
83
|
+
readonly digest: string;
|
|
84
|
+
readonly redacted: true;
|
|
85
|
+
readonly shape: SignalPayloadShape;
|
|
86
|
+
readonly topLevelEntryCount?: number | undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export type SignalDiagnosticPathSegment = number | string;
|
|
90
|
+
|
|
91
|
+
export interface SignalDiagnosticSchemaIssue {
|
|
92
|
+
readonly code?: string | undefined;
|
|
93
|
+
readonly message: string;
|
|
94
|
+
readonly path: readonly SignalDiagnosticPathSegment[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface SignalDiagnosticCause {
|
|
98
|
+
readonly message: string;
|
|
99
|
+
readonly name?: string | undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface SignalDiagnosticSourceLocation {
|
|
103
|
+
readonly column?: number | undefined;
|
|
104
|
+
readonly filePath?: string | undefined;
|
|
105
|
+
readonly line?: number | undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
interface SignalDiagnosticBase<
|
|
109
|
+
TCode extends SignalDiagnosticCode,
|
|
110
|
+
TOrigin extends SignalDiagnosticOrigin,
|
|
111
|
+
> {
|
|
112
|
+
readonly activation?: ActivationProvenance | undefined;
|
|
113
|
+
readonly category: (typeof signalDiagnosticDefinitions)[TCode]['category'];
|
|
114
|
+
readonly code: TCode;
|
|
115
|
+
readonly level: (typeof signalDiagnosticDefinitions)[TCode]['level'];
|
|
116
|
+
readonly message: string;
|
|
117
|
+
readonly origin: TOrigin;
|
|
118
|
+
readonly producerTrailId?: string | undefined;
|
|
119
|
+
readonly runId?: string | undefined;
|
|
120
|
+
readonly signalId: string;
|
|
121
|
+
readonly sourceLocation?: SignalDiagnosticSourceLocation | undefined;
|
|
122
|
+
readonly traceId?: string | undefined;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface SignalInvalidDiagnostic extends SignalDiagnosticBase<
|
|
126
|
+
'signal.invalid',
|
|
127
|
+
'fire-boundary'
|
|
128
|
+
> {
|
|
129
|
+
readonly payload: SignalPayloadSummary;
|
|
130
|
+
readonly schemaIssues: readonly SignalDiagnosticSchemaIssue[];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export type SignalUnknownDiagnostic = SignalDiagnosticBase<
|
|
134
|
+
'signal.unknown',
|
|
135
|
+
'fire-boundary'
|
|
136
|
+
>;
|
|
137
|
+
|
|
138
|
+
export interface SignalHandlerFailedDiagnostic extends SignalDiagnosticBase<
|
|
139
|
+
'signal.handler.failed',
|
|
140
|
+
'handler'
|
|
141
|
+
> {
|
|
142
|
+
readonly cause: SignalDiagnosticCause;
|
|
143
|
+
readonly handlerTrailId: string;
|
|
144
|
+
readonly payload?: SignalPayloadSummary | undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface SignalHandlerRejectedDiagnostic extends SignalDiagnosticBase<
|
|
148
|
+
'signal.handler.rejected',
|
|
149
|
+
'handler'
|
|
150
|
+
> {
|
|
151
|
+
readonly cause: SignalDiagnosticCause;
|
|
152
|
+
readonly handlerTrailId: string;
|
|
153
|
+
readonly payload?: SignalPayloadSummary | undefined;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface SignalPredicateFailedDiagnostic extends SignalDiagnosticBase<
|
|
157
|
+
'signal.handler.predicate_failed',
|
|
158
|
+
'predicate'
|
|
159
|
+
> {
|
|
160
|
+
readonly cause: SignalDiagnosticCause;
|
|
161
|
+
readonly handlerTrailId: string;
|
|
162
|
+
readonly payload?: SignalPayloadSummary | undefined;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export interface SignalFireSuppressedDiagnostic extends SignalDiagnosticBase<
|
|
166
|
+
'signal.fire.suppressed',
|
|
167
|
+
'fan-out-guard'
|
|
168
|
+
> {
|
|
169
|
+
readonly fireStack?: readonly string[] | undefined;
|
|
170
|
+
readonly limit?: number | undefined;
|
|
171
|
+
readonly reason: 'cycle' | 'depth';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export type SignalDiagnostic =
|
|
175
|
+
| SignalFireSuppressedDiagnostic
|
|
176
|
+
| SignalHandlerFailedDiagnostic
|
|
177
|
+
| SignalHandlerRejectedDiagnostic
|
|
178
|
+
| SignalPredicateFailedDiagnostic
|
|
179
|
+
| SignalInvalidDiagnostic
|
|
180
|
+
| SignalUnknownDiagnostic;
|
|
181
|
+
|
|
182
|
+
export type SignalDiagnosticSink = (
|
|
183
|
+
diagnostic: SignalDiagnostic
|
|
184
|
+
) => Promise<void> | void;
|
|
185
|
+
|
|
186
|
+
export type SignalDiagnosticStrictMode =
|
|
187
|
+
| 'all'
|
|
188
|
+
| 'error'
|
|
189
|
+
| 'off'
|
|
190
|
+
| boolean
|
|
191
|
+
| readonly SignalDiagnosticCode[]
|
|
192
|
+
| ((diagnostic: SignalDiagnostic) => boolean);
|
|
193
|
+
|
|
194
|
+
export interface SignalDiagnosticContext {
|
|
195
|
+
readonly extensions?: Readonly<Record<string, unknown>> | undefined;
|
|
196
|
+
readonly logger?: Pick<Logger, 'warn'> | undefined;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export interface SignalDiagnosticRecordResult {
|
|
200
|
+
readonly delivered: boolean;
|
|
201
|
+
readonly diagnostic: SignalDiagnostic;
|
|
202
|
+
readonly promoted: boolean;
|
|
203
|
+
readonly sinkError?: SignalDiagnosticCause | undefined;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export interface SignalDiagnosticCommonInput {
|
|
207
|
+
readonly activation?: ActivationProvenance | undefined;
|
|
208
|
+
readonly message?: string | undefined;
|
|
209
|
+
readonly producerTrailId?: string | undefined;
|
|
210
|
+
readonly runId?: string | undefined;
|
|
211
|
+
readonly signalId: string;
|
|
212
|
+
readonly sourceLocation?: SignalDiagnosticSourceLocation | undefined;
|
|
213
|
+
readonly traceId?: string | undefined;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export interface CreateSignalInvalidDiagnosticInput extends SignalDiagnosticCommonInput {
|
|
217
|
+
readonly payload: unknown;
|
|
218
|
+
readonly schemaIssues: readonly z.core.$ZodIssue[];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export interface CreateSignalHandlerDiagnosticInput extends SignalDiagnosticCommonInput {
|
|
222
|
+
readonly cause: unknown;
|
|
223
|
+
readonly handlerTrailId: string;
|
|
224
|
+
readonly payload?: unknown;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export interface CreateSignalFireSuppressedDiagnosticInput extends SignalDiagnosticCommonInput {
|
|
228
|
+
readonly fireStack?: readonly string[] | undefined;
|
|
229
|
+
readonly limit?: number | undefined;
|
|
230
|
+
readonly reason: 'cycle' | 'depth';
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const payloadShape = (payload: unknown): SignalPayloadShape => {
|
|
234
|
+
if (payload === null) {
|
|
235
|
+
return 'null';
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const type = typeof payload;
|
|
239
|
+
if (type !== 'object') {
|
|
240
|
+
return type;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
if (Array.isArray(payload)) {
|
|
245
|
+
return 'array';
|
|
246
|
+
}
|
|
247
|
+
} catch {
|
|
248
|
+
return 'object';
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return 'object';
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const topLevelEntryCount = (payload: unknown): number | undefined => {
|
|
255
|
+
try {
|
|
256
|
+
if (Array.isArray(payload)) {
|
|
257
|
+
return payload.length;
|
|
258
|
+
}
|
|
259
|
+
if (payload !== null && typeof payload === 'object') {
|
|
260
|
+
return Object.keys(payload).length;
|
|
261
|
+
}
|
|
262
|
+
} catch {
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
return undefined;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const UNREADABLE_PAYLOAD_VALUE = '[Unreadable]';
|
|
269
|
+
|
|
270
|
+
const canonicalLeaf = (value: unknown): unknown => {
|
|
271
|
+
switch (typeof value) {
|
|
272
|
+
case 'bigint': {
|
|
273
|
+
return `[BigInt:${value.toString()}]`;
|
|
274
|
+
}
|
|
275
|
+
case 'function': {
|
|
276
|
+
return '[Function]';
|
|
277
|
+
}
|
|
278
|
+
case 'symbol': {
|
|
279
|
+
return `[Symbol:${value.description ?? ''}]`;
|
|
280
|
+
}
|
|
281
|
+
case 'undefined': {
|
|
282
|
+
return '[Undefined]';
|
|
283
|
+
}
|
|
284
|
+
default: {
|
|
285
|
+
return value;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const canonicalize = (value: unknown, seen: WeakSet<object>): unknown => {
|
|
291
|
+
if (Array.isArray(value)) {
|
|
292
|
+
if (seen.has(value)) {
|
|
293
|
+
return '[Circular]';
|
|
294
|
+
}
|
|
295
|
+
seen.add(value);
|
|
296
|
+
const length = topLevelEntryCount(value) ?? 0;
|
|
297
|
+
const entries: unknown[] = [];
|
|
298
|
+
for (let index = 0; index < length; index += 1) {
|
|
299
|
+
try {
|
|
300
|
+
entries.push(canonicalize(value[index], seen));
|
|
301
|
+
} catch {
|
|
302
|
+
entries.push(UNREADABLE_PAYLOAD_VALUE);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return entries;
|
|
306
|
+
}
|
|
307
|
+
if (value instanceof Date) {
|
|
308
|
+
return Number.isNaN(value.getTime())
|
|
309
|
+
? '[Invalid Date]'
|
|
310
|
+
: value.toISOString();
|
|
311
|
+
}
|
|
312
|
+
if (value instanceof RegExp) {
|
|
313
|
+
return value.toString();
|
|
314
|
+
}
|
|
315
|
+
if (value !== null && typeof value === 'object') {
|
|
316
|
+
if (seen.has(value)) {
|
|
317
|
+
return '[Circular]';
|
|
318
|
+
}
|
|
319
|
+
seen.add(value);
|
|
320
|
+
const sorted: Record<string, unknown> = {};
|
|
321
|
+
let keys: string[];
|
|
322
|
+
try {
|
|
323
|
+
keys = Object.keys(value).toSorted();
|
|
324
|
+
} catch {
|
|
325
|
+
return UNREADABLE_PAYLOAD_VALUE;
|
|
326
|
+
}
|
|
327
|
+
for (const key of keys) {
|
|
328
|
+
try {
|
|
329
|
+
const next = (value as Record<string, unknown>)[key];
|
|
330
|
+
sorted[key] = canonicalize(next, seen);
|
|
331
|
+
} catch {
|
|
332
|
+
sorted[key] = UNREADABLE_PAYLOAD_VALUE;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return sorted;
|
|
336
|
+
}
|
|
337
|
+
return canonicalLeaf(value);
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const stableJson = (value: unknown): string => {
|
|
341
|
+
try {
|
|
342
|
+
return (
|
|
343
|
+
JSON.stringify(canonicalize(value, new WeakSet<object>())) ??
|
|
344
|
+
'"[Undefined]"'
|
|
345
|
+
);
|
|
346
|
+
} catch {
|
|
347
|
+
return `"${UNREADABLE_PAYLOAD_VALUE}"`;
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// Pure sha256 keeps payload summarization on the execution path portable:
|
|
352
|
+
// node:crypto is unavailable on edge runtimes without compat flags.
|
|
353
|
+
const hashText = (text: string): string => sha256Hex(text);
|
|
354
|
+
|
|
355
|
+
export const summarizeSignalPayload = (
|
|
356
|
+
payload: unknown
|
|
357
|
+
): SignalPayloadSummary => {
|
|
358
|
+
const canonical = stableJson(payload);
|
|
359
|
+
return {
|
|
360
|
+
byteLength: new TextEncoder().encode(canonical).byteLength,
|
|
361
|
+
digest: hashText(canonical),
|
|
362
|
+
redacted: true,
|
|
363
|
+
shape: payloadShape(payload),
|
|
364
|
+
topLevelEntryCount: topLevelEntryCount(payload),
|
|
365
|
+
};
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
export const schemaIssuesFromZod = (
|
|
369
|
+
issues: readonly z.core.$ZodIssue[]
|
|
370
|
+
): readonly SignalDiagnosticSchemaIssue[] =>
|
|
371
|
+
issues.map((issue) => ({
|
|
372
|
+
code: typeof issue.code === 'string' ? issue.code : undefined,
|
|
373
|
+
message: issue.message,
|
|
374
|
+
path: issue.path.map((segment) =>
|
|
375
|
+
typeof segment === 'number' ? segment : String(segment)
|
|
376
|
+
),
|
|
377
|
+
}));
|
|
378
|
+
|
|
379
|
+
export const signalDiagnosticCauseFromUnknown = (
|
|
380
|
+
cause: unknown
|
|
381
|
+
): SignalDiagnosticCause => {
|
|
382
|
+
if (cause instanceof Error) {
|
|
383
|
+
return {
|
|
384
|
+
message: cause.message,
|
|
385
|
+
name: cause.name,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
return {
|
|
389
|
+
message: String(cause),
|
|
390
|
+
};
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
export const shouldPromoteSignalDiagnostic = (
|
|
394
|
+
diagnostic: SignalDiagnostic,
|
|
395
|
+
strictMode?: SignalDiagnosticStrictMode
|
|
396
|
+
): boolean => {
|
|
397
|
+
if (
|
|
398
|
+
strictMode === undefined ||
|
|
399
|
+
strictMode === false ||
|
|
400
|
+
strictMode === 'off'
|
|
401
|
+
) {
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
if (strictMode === true || strictMode === 'all') {
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
if (strictMode === 'error') {
|
|
408
|
+
return diagnostic.level === 'error';
|
|
409
|
+
}
|
|
410
|
+
if (Array.isArray(strictMode)) {
|
|
411
|
+
return strictMode.includes(diagnostic.code);
|
|
412
|
+
}
|
|
413
|
+
if (typeof strictMode !== 'function') {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
try {
|
|
417
|
+
return strictMode(diagnostic);
|
|
418
|
+
} catch {
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
const signalDiagnosticSinkFrom = (
|
|
424
|
+
ctx: SignalDiagnosticContext | undefined
|
|
425
|
+
): SignalDiagnosticSink | undefined => {
|
|
426
|
+
const sink = ctx?.extensions?.[SIGNAL_DIAGNOSTICS_SINK_KEY];
|
|
427
|
+
return typeof sink === 'function'
|
|
428
|
+
? (sink as SignalDiagnosticSink)
|
|
429
|
+
: undefined;
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
const signalDiagnosticStrictModeFrom = (
|
|
433
|
+
ctx: SignalDiagnosticContext | undefined
|
|
434
|
+
): SignalDiagnosticStrictMode | undefined => {
|
|
435
|
+
const strictMode = ctx?.extensions?.[SIGNAL_DIAGNOSTICS_STRICT_MODE_KEY];
|
|
436
|
+
if (
|
|
437
|
+
strictMode === true ||
|
|
438
|
+
strictMode === false ||
|
|
439
|
+
strictMode === 'all' ||
|
|
440
|
+
strictMode === 'error' ||
|
|
441
|
+
strictMode === 'off' ||
|
|
442
|
+
Array.isArray(strictMode) ||
|
|
443
|
+
typeof strictMode === 'function'
|
|
444
|
+
) {
|
|
445
|
+
return strictMode as SignalDiagnosticStrictMode;
|
|
446
|
+
}
|
|
447
|
+
return undefined;
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
export const recordSignalDiagnostic = async (
|
|
451
|
+
ctx: SignalDiagnosticContext | undefined,
|
|
452
|
+
diagnostic: SignalDiagnostic
|
|
453
|
+
): Promise<SignalDiagnosticRecordResult> => {
|
|
454
|
+
const promoted = shouldPromoteSignalDiagnostic(
|
|
455
|
+
diagnostic,
|
|
456
|
+
signalDiagnosticStrictModeFrom(ctx)
|
|
457
|
+
);
|
|
458
|
+
const sink = signalDiagnosticSinkFrom(ctx);
|
|
459
|
+
if (sink === undefined) {
|
|
460
|
+
return {
|
|
461
|
+
delivered: false,
|
|
462
|
+
diagnostic,
|
|
463
|
+
promoted,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
try {
|
|
467
|
+
await sink(diagnostic);
|
|
468
|
+
return {
|
|
469
|
+
delivered: true,
|
|
470
|
+
diagnostic,
|
|
471
|
+
promoted,
|
|
472
|
+
};
|
|
473
|
+
} catch (error) {
|
|
474
|
+
const sinkError = signalDiagnosticCauseFromUnknown(error);
|
|
475
|
+
ctx?.logger?.warn('Signal diagnostic sink failed', {
|
|
476
|
+
code: diagnostic.code,
|
|
477
|
+
error: sinkError.message,
|
|
478
|
+
signalId: diagnostic.signalId,
|
|
479
|
+
});
|
|
480
|
+
return {
|
|
481
|
+
delivered: false,
|
|
482
|
+
diagnostic,
|
|
483
|
+
promoted,
|
|
484
|
+
sinkError,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
export const createSignalInvalidDiagnostic = (
|
|
490
|
+
input: CreateSignalInvalidDiagnosticInput
|
|
491
|
+
): SignalInvalidDiagnostic => {
|
|
492
|
+
const definition = signalDiagnosticDefinitions['signal.invalid'];
|
|
493
|
+
return {
|
|
494
|
+
activation: input.activation,
|
|
495
|
+
category: definition.category,
|
|
496
|
+
code: 'signal.invalid',
|
|
497
|
+
level: definition.level,
|
|
498
|
+
message: input.message ?? `Invalid payload for signal "${input.signalId}"`,
|
|
499
|
+
origin: 'fire-boundary',
|
|
500
|
+
payload: summarizeSignalPayload(input.payload),
|
|
501
|
+
producerTrailId: input.producerTrailId,
|
|
502
|
+
runId: input.runId,
|
|
503
|
+
schemaIssues: schemaIssuesFromZod(input.schemaIssues),
|
|
504
|
+
signalId: input.signalId,
|
|
505
|
+
sourceLocation: input.sourceLocation,
|
|
506
|
+
traceId: input.traceId,
|
|
507
|
+
};
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
export const createSignalUnknownDiagnostic = (
|
|
511
|
+
input: SignalDiagnosticCommonInput
|
|
512
|
+
): SignalUnknownDiagnostic => {
|
|
513
|
+
const definition = signalDiagnosticDefinitions['signal.unknown'];
|
|
514
|
+
return {
|
|
515
|
+
activation: input.activation,
|
|
516
|
+
category: definition.category,
|
|
517
|
+
code: 'signal.unknown',
|
|
518
|
+
level: definition.level,
|
|
519
|
+
message: input.message ?? `Unknown signal "${input.signalId}"`,
|
|
520
|
+
origin: 'fire-boundary',
|
|
521
|
+
producerTrailId: input.producerTrailId,
|
|
522
|
+
runId: input.runId,
|
|
523
|
+
signalId: input.signalId,
|
|
524
|
+
sourceLocation: input.sourceLocation,
|
|
525
|
+
traceId: input.traceId,
|
|
526
|
+
};
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
export const createSignalHandlerFailedDiagnostic = (
|
|
530
|
+
input: CreateSignalHandlerDiagnosticInput
|
|
531
|
+
): SignalHandlerFailedDiagnostic => {
|
|
532
|
+
const definition = signalDiagnosticDefinitions['signal.handler.failed'];
|
|
533
|
+
return {
|
|
534
|
+
activation: input.activation,
|
|
535
|
+
category: definition.category,
|
|
536
|
+
cause: signalDiagnosticCauseFromUnknown(input.cause),
|
|
537
|
+
code: 'signal.handler.failed',
|
|
538
|
+
handlerTrailId: input.handlerTrailId,
|
|
539
|
+
level: definition.level,
|
|
540
|
+
message:
|
|
541
|
+
input.message ??
|
|
542
|
+
`Signal handler "${input.handlerTrailId}" failed for "${input.signalId}"`,
|
|
543
|
+
origin: 'handler',
|
|
544
|
+
payload:
|
|
545
|
+
input.payload === undefined
|
|
546
|
+
? undefined
|
|
547
|
+
: summarizeSignalPayload(input.payload),
|
|
548
|
+
producerTrailId: input.producerTrailId,
|
|
549
|
+
runId: input.runId,
|
|
550
|
+
signalId: input.signalId,
|
|
551
|
+
sourceLocation: input.sourceLocation,
|
|
552
|
+
traceId: input.traceId,
|
|
553
|
+
};
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
export const createSignalHandlerRejectedDiagnostic = (
|
|
557
|
+
input: CreateSignalHandlerDiagnosticInput
|
|
558
|
+
): SignalHandlerRejectedDiagnostic => {
|
|
559
|
+
const definition = signalDiagnosticDefinitions['signal.handler.rejected'];
|
|
560
|
+
return {
|
|
561
|
+
activation: input.activation,
|
|
562
|
+
category: definition.category,
|
|
563
|
+
cause: signalDiagnosticCauseFromUnknown(input.cause),
|
|
564
|
+
code: 'signal.handler.rejected',
|
|
565
|
+
handlerTrailId: input.handlerTrailId,
|
|
566
|
+
level: definition.level,
|
|
567
|
+
message:
|
|
568
|
+
input.message ??
|
|
569
|
+
`Signal handler "${input.handlerTrailId}" rejected for "${input.signalId}"`,
|
|
570
|
+
origin: 'handler',
|
|
571
|
+
payload:
|
|
572
|
+
input.payload === undefined
|
|
573
|
+
? undefined
|
|
574
|
+
: summarizeSignalPayload(input.payload),
|
|
575
|
+
producerTrailId: input.producerTrailId,
|
|
576
|
+
runId: input.runId,
|
|
577
|
+
signalId: input.signalId,
|
|
578
|
+
sourceLocation: input.sourceLocation,
|
|
579
|
+
traceId: input.traceId,
|
|
580
|
+
};
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
export const createSignalPredicateFailedDiagnostic = (
|
|
584
|
+
input: CreateSignalHandlerDiagnosticInput
|
|
585
|
+
): SignalPredicateFailedDiagnostic => {
|
|
586
|
+
const definition =
|
|
587
|
+
signalDiagnosticDefinitions['signal.handler.predicate_failed'];
|
|
588
|
+
return {
|
|
589
|
+
activation: input.activation,
|
|
590
|
+
category: definition.category,
|
|
591
|
+
cause: signalDiagnosticCauseFromUnknown(input.cause),
|
|
592
|
+
code: 'signal.handler.predicate_failed',
|
|
593
|
+
handlerTrailId: input.handlerTrailId,
|
|
594
|
+
level: definition.level,
|
|
595
|
+
message:
|
|
596
|
+
input.message ??
|
|
597
|
+
`Signal handler predicate for "${input.handlerTrailId}" failed for "${input.signalId}"`,
|
|
598
|
+
origin: 'predicate',
|
|
599
|
+
payload:
|
|
600
|
+
input.payload === undefined
|
|
601
|
+
? undefined
|
|
602
|
+
: summarizeSignalPayload(input.payload),
|
|
603
|
+
producerTrailId: input.producerTrailId,
|
|
604
|
+
runId: input.runId,
|
|
605
|
+
signalId: input.signalId,
|
|
606
|
+
sourceLocation: input.sourceLocation,
|
|
607
|
+
traceId: input.traceId,
|
|
608
|
+
};
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
export const createSignalFireSuppressedDiagnostic = (
|
|
612
|
+
input: CreateSignalFireSuppressedDiagnosticInput
|
|
613
|
+
): SignalFireSuppressedDiagnostic => {
|
|
614
|
+
const definition = signalDiagnosticDefinitions['signal.fire.suppressed'];
|
|
615
|
+
return {
|
|
616
|
+
activation: input.activation,
|
|
617
|
+
category: definition.category,
|
|
618
|
+
code: 'signal.fire.suppressed',
|
|
619
|
+
fireStack: input.fireStack,
|
|
620
|
+
level: definition.level,
|
|
621
|
+
limit: input.limit,
|
|
622
|
+
message:
|
|
623
|
+
input.message ??
|
|
624
|
+
`Signal "${input.signalId}" fire was suppressed by ${input.reason} guard`,
|
|
625
|
+
origin: 'fan-out-guard',
|
|
626
|
+
producerTrailId: input.producerTrailId,
|
|
627
|
+
reason: input.reason,
|
|
628
|
+
runId: input.runId,
|
|
629
|
+
signalId: input.signalId,
|
|
630
|
+
sourceLocation: input.sourceLocation,
|
|
631
|
+
traceId: input.traceId,
|
|
632
|
+
};
|
|
633
|
+
};
|