@beignet/core 0.0.32 → 0.0.33
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 +35 -0
- package/README.md +344 -19
- package/dist/application/index.d.ts.map +1 -1
- package/dist/application/index.js +63 -47
- package/dist/application/index.js.map +1 -1
- package/dist/events/index.d.ts +6 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +20 -10
- package/dist/events/index.js.map +1 -1
- package/dist/jobs/index.d.ts +415 -4
- package/dist/jobs/index.d.ts.map +1 -1
- package/dist/jobs/index.js +392 -3
- package/dist/jobs/index.js.map +1 -1
- package/dist/outbox/index.d.ts +150 -1
- package/dist/outbox/index.d.ts.map +1 -1
- package/dist/outbox/index.js +170 -1
- package/dist/outbox/index.js.map +1 -1
- package/dist/ports/index.d.ts +6 -1
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/index.js +1 -0
- package/dist/ports/index.js.map +1 -1
- package/dist/providers/instrumentation.d.ts +4 -0
- package/dist/providers/instrumentation.d.ts.map +1 -1
- package/dist/providers/instrumentation.js.map +1 -1
- package/dist/schedules/index.d.ts +8 -7
- package/dist/schedules/index.d.ts.map +1 -1
- package/dist/schedules/index.js +47 -16
- package/dist/schedules/index.js.map +1 -1
- package/dist/server/hooks/index.d.ts +1 -0
- package/dist/server/hooks/index.d.ts.map +1 -1
- package/dist/server/hooks/index.js +1 -0
- package/dist/server/hooks/index.js.map +1 -1
- package/dist/server/hooks/rate-limit.d.ts +26 -13
- package/dist/server/hooks/rate-limit.d.ts.map +1 -1
- package/dist/server/hooks/rate-limit.js +28 -34
- package/dist/server/hooks/rate-limit.js.map +1 -1
- package/dist/server/hooks/security.d.ts +179 -0
- package/dist/server/hooks/security.d.ts.map +1 -0
- package/dist/server/hooks/security.js +225 -0
- package/dist/server/hooks/security.js.map +1 -0
- package/dist/server/index.d.ts +8 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +8 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/instrumentation.d.ts.map +1 -1
- package/dist/server/instrumentation.js +22 -6
- package/dist/server/instrumentation.js.map +1 -1
- package/dist/server/request-context.d.ts +4 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js +3 -0
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/runtime-integrity.d.ts +84 -0
- package/dist/server/runtime-integrity.d.ts.map +1 -0
- package/dist/server/runtime-integrity.js +160 -0
- package/dist/server/runtime-integrity.js.map +1 -0
- package/dist/server/server.d.ts +10 -0
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +55 -1
- package/dist/server/server.js.map +1 -1
- package/dist/server/trusted-proxy.d.ts +77 -0
- package/dist/server/trusted-proxy.d.ts.map +1 -0
- package/dist/server/trusted-proxy.js +129 -0
- package/dist/server/trusted-proxy.js.map +1 -0
- package/dist/tasks/index.d.ts +6 -7
- package/dist/tasks/index.d.ts.map +1 -1
- package/dist/tasks/index.js +22 -5
- package/dist/tasks/index.js.map +1 -1
- package/dist/tenancy/index.d.ts +41 -0
- package/dist/tenancy/index.d.ts.map +1 -0
- package/dist/tenancy/index.js +34 -0
- package/dist/tenancy/index.js.map +1 -0
- package/dist/tracing/execution.d.ts +14 -0
- package/dist/tracing/execution.d.ts.map +1 -0
- package/dist/tracing/execution.js +17 -0
- package/dist/tracing/execution.js.map +1 -0
- package/dist/tracing/index.d.ts +49 -0
- package/dist/tracing/index.d.ts.map +1 -1
- package/dist/tracing/index.js +59 -0
- package/dist/tracing/index.js.map +1 -1
- package/dist/uploads/client.d.ts.map +1 -1
- package/dist/uploads/client.js +44 -6
- package/dist/uploads/client.js.map +1 -1
- package/dist/uploads/index.d.ts +71 -0
- package/dist/uploads/index.d.ts.map +1 -1
- package/dist/uploads/index.js +344 -15
- package/dist/uploads/index.js.map +1 -1
- package/dist/webhooks/index.d.ts +55 -4
- package/dist/webhooks/index.d.ts.map +1 -1
- package/dist/webhooks/index.js +106 -2
- package/dist/webhooks/index.js.map +1 -1
- package/package.json +5 -1
- package/skills/app-architecture/SKILL.md +23 -2
- package/src/application/index.ts +88 -56
- package/src/events/index.ts +27 -14
- package/src/jobs/index.ts +895 -7
- package/src/outbox/index.ts +358 -2
- package/src/ports/index.ts +17 -0
- package/src/providers/instrumentation.ts +4 -0
- package/src/schedules/index.ts +60 -26
- package/src/server/hooks/index.ts +10 -0
- package/src/server/hooks/rate-limit.ts +52 -46
- package/src/server/hooks/security.ts +503 -0
- package/src/server/index.ts +8 -0
- package/src/server/instrumentation.ts +25 -5
- package/src/server/request-context.ts +7 -0
- package/src/server/runtime-integrity.ts +322 -0
- package/src/server/server.ts +86 -1
- package/src/server/trusted-proxy.ts +249 -0
- package/src/tasks/index.ts +29 -12
- package/src/tenancy/index.ts +55 -0
- package/src/tracing/execution.ts +37 -0
- package/src/tracing/index.ts +137 -0
- package/src/uploads/client.ts +65 -6
- package/src/uploads/index.ts +539 -14
- package/src/webhooks/index.ts +238 -9
|
@@ -12,7 +12,9 @@ import {
|
|
|
12
12
|
import {
|
|
13
13
|
createTraceContext,
|
|
14
14
|
parseTraceparent,
|
|
15
|
+
resolveTracingPort,
|
|
15
16
|
type TraceContext,
|
|
17
|
+
type TracingPort,
|
|
16
18
|
} from "../tracing/index.js";
|
|
17
19
|
import type { HttpRequestLike, HttpResponseLike, ServerHook } from "./http.js";
|
|
18
20
|
import {
|
|
@@ -129,6 +131,7 @@ type TraceContextFields = {
|
|
|
129
131
|
spanId?: string;
|
|
130
132
|
parentSpanId?: string;
|
|
131
133
|
traceparent?: string;
|
|
134
|
+
tracestate?: string;
|
|
132
135
|
};
|
|
133
136
|
|
|
134
137
|
function getContextRequestId(ctx: unknown): string | undefined {
|
|
@@ -144,7 +147,8 @@ function getContextTraceContext(ctx: unknown): TraceContextFields | undefined {
|
|
|
144
147
|
!context.traceId &&
|
|
145
148
|
!context.spanId &&
|
|
146
149
|
!context.parentSpanId &&
|
|
147
|
-
!context.traceparent
|
|
150
|
+
!context.traceparent &&
|
|
151
|
+
!context.tracestate
|
|
148
152
|
) {
|
|
149
153
|
return undefined;
|
|
150
154
|
}
|
|
@@ -153,6 +157,7 @@ function getContextTraceContext(ctx: unknown): TraceContextFields | undefined {
|
|
|
153
157
|
spanId: context.spanId,
|
|
154
158
|
parentSpanId: context.parentSpanId,
|
|
155
159
|
traceparent: context.traceparent,
|
|
160
|
+
tracestate: context.tracestate,
|
|
156
161
|
};
|
|
157
162
|
}
|
|
158
163
|
|
|
@@ -249,6 +254,7 @@ export function createServerInstrumentation<Ctx>(
|
|
|
249
254
|
const ignorePaths = resolvedOptions.ignorePaths ?? ["/api/devtools"];
|
|
250
255
|
|
|
251
256
|
let port: ProviderInstrumentationPort | undefined;
|
|
257
|
+
let tracing: TracingPort | undefined;
|
|
252
258
|
const correlations = new WeakMap<HttpRequestLike, RequestCorrelation>();
|
|
253
259
|
|
|
254
260
|
const prepareRequest = (req: HttpRequestLike): RequestCorrelation => {
|
|
@@ -263,12 +269,19 @@ export function createServerInstrumentation<Ctx>(
|
|
|
263
269
|
traceContextHeader === false
|
|
264
270
|
? undefined
|
|
265
271
|
: (req.headers.get(traceContextHeader) ?? undefined);
|
|
272
|
+
const parsedTraceparent = parseTraceparent(headerTraceparent);
|
|
266
273
|
|
|
274
|
+
const activeTrace = tracing?.current();
|
|
267
275
|
const correlation: RequestCorrelation = {
|
|
268
276
|
requestId: headerRequestId ?? createRequestId(),
|
|
269
|
-
trace:
|
|
270
|
-
|
|
271
|
-
|
|
277
|
+
trace:
|
|
278
|
+
activeTrace ??
|
|
279
|
+
createTraceContext({
|
|
280
|
+
traceparent: parsedTraceparent?.traceparent,
|
|
281
|
+
tracestate: parsedTraceparent
|
|
282
|
+
? (req.headers.get("tracestate") ?? undefined)
|
|
283
|
+
: undefined,
|
|
284
|
+
}),
|
|
272
285
|
};
|
|
273
286
|
correlations.set(req, correlation);
|
|
274
287
|
return correlation;
|
|
@@ -284,6 +297,9 @@ export function createServerInstrumentation<Ctx>(
|
|
|
284
297
|
req: HttpRequestLike;
|
|
285
298
|
ctx?: unknown;
|
|
286
299
|
}): TraceContext => {
|
|
300
|
+
const activeTrace = tracing?.current();
|
|
301
|
+
if (activeTrace) return activeTrace;
|
|
302
|
+
|
|
287
303
|
const contextTrace = getContextTraceContext(args.ctx);
|
|
288
304
|
if (contextTrace) {
|
|
289
305
|
return createTraceContext(contextTrace);
|
|
@@ -334,6 +350,7 @@ export function createServerInstrumentation<Ctx>(
|
|
|
334
350
|
spanId: trace.spanId,
|
|
335
351
|
parentSpanId: trace.parentSpanId,
|
|
336
352
|
traceparent: trace.traceparent,
|
|
353
|
+
tracestate: trace.tracestate,
|
|
337
354
|
actor: readContextActor(args.ctx),
|
|
338
355
|
tenant: readContextTenant(args.ctx),
|
|
339
356
|
});
|
|
@@ -416,6 +433,7 @@ export function createServerInstrumentation<Ctx>(
|
|
|
416
433
|
spanId: trace.spanId,
|
|
417
434
|
parentSpanId: trace.parentSpanId,
|
|
418
435
|
traceparent: trace.traceparent,
|
|
436
|
+
tracestate: trace.tracestate,
|
|
419
437
|
method: req.method,
|
|
420
438
|
path,
|
|
421
439
|
contractName: contract.name,
|
|
@@ -452,6 +470,7 @@ export function createServerInstrumentation<Ctx>(
|
|
|
452
470
|
spanId: trace.spanId,
|
|
453
471
|
parentSpanId: trace.parentSpanId,
|
|
454
472
|
traceparent: trace.traceparent,
|
|
473
|
+
tracestate: trace.tracestate,
|
|
455
474
|
message: getErrorMessage(error),
|
|
456
475
|
stack: getErrorStack(error),
|
|
457
476
|
contractName: contract.name,
|
|
@@ -466,13 +485,14 @@ export function createServerInstrumentation<Ctx>(
|
|
|
466
485
|
|
|
467
486
|
return {
|
|
468
487
|
attachPorts(ports) {
|
|
488
|
+
tracing = resolveTracingPort(ports);
|
|
469
489
|
if (!enabled) return;
|
|
470
490
|
port = resolveProviderInstrumentationPort(ports);
|
|
471
491
|
},
|
|
472
492
|
prepareRequest,
|
|
473
493
|
createServiceCorrelation: () => ({
|
|
474
494
|
requestId: createRequestId(),
|
|
475
|
-
trace: createTraceContext(),
|
|
495
|
+
trace: tracing?.current() ?? createTraceContext(),
|
|
476
496
|
}),
|
|
477
497
|
hook: enabled ? hook : undefined,
|
|
478
498
|
};
|
|
@@ -16,6 +16,7 @@ export interface ActiveRequestContext {
|
|
|
16
16
|
spanId?: string;
|
|
17
17
|
parentSpanId?: string;
|
|
18
18
|
traceparent?: string;
|
|
19
|
+
tracestate?: string;
|
|
19
20
|
/**
|
|
20
21
|
* Actor for the current request or service execution, when known.
|
|
21
22
|
*/
|
|
@@ -116,7 +117,10 @@ export function inheritActiveRequestContext<
|
|
|
116
117
|
Event extends {
|
|
117
118
|
requestId?: string;
|
|
118
119
|
traceId?: string;
|
|
120
|
+
spanId?: string;
|
|
121
|
+
parentSpanId?: string;
|
|
119
122
|
traceparent?: string;
|
|
123
|
+
tracestate?: string;
|
|
120
124
|
},
|
|
121
125
|
>(event: Event): Event {
|
|
122
126
|
const context = getActiveRequestContext();
|
|
@@ -126,6 +130,9 @@ export function inheritActiveRequestContext<
|
|
|
126
130
|
...event,
|
|
127
131
|
requestId: event.requestId ?? context.requestId,
|
|
128
132
|
traceId: event.traceId ?? context.traceId,
|
|
133
|
+
spanId: event.spanId ?? context.spanId,
|
|
134
|
+
parentSpanId: event.parentSpanId ?? context.parentSpanId,
|
|
129
135
|
traceparent: event.traceparent ?? context.traceparent,
|
|
136
|
+
tracestate: event.tracestate ?? context.tracestate,
|
|
130
137
|
};
|
|
131
138
|
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import type { EventPayloadDef, ListenerDef } from "../events/index.js";
|
|
2
|
+
import type { JobDef } from "../jobs/index.js";
|
|
3
|
+
import type { OutboxRegistry } from "../outbox/index.js";
|
|
4
|
+
import type { ScheduleDef } from "../schedules/index.js";
|
|
5
|
+
import type { TaskDef } from "../tasks/index.js";
|
|
6
|
+
|
|
7
|
+
export type RuntimeIntegrityArtifact =
|
|
8
|
+
| "listener"
|
|
9
|
+
| "schedule"
|
|
10
|
+
| "task"
|
|
11
|
+
| "outboxEvent"
|
|
12
|
+
| "outboxJob";
|
|
13
|
+
|
|
14
|
+
export type RuntimeIntegrityFindingCode =
|
|
15
|
+
| "missing_listener_registration"
|
|
16
|
+
| "missing_schedule_registration"
|
|
17
|
+
| "missing_task_registration"
|
|
18
|
+
| "missing_outbox_event_registration"
|
|
19
|
+
| "missing_outbox_job_registration"
|
|
20
|
+
| "duplicate_runtime_manifest_entry"
|
|
21
|
+
| "duplicate_runtime_registry_entry";
|
|
22
|
+
|
|
23
|
+
export type RuntimeIntegrityFindingLocation = "manifest" | "registry";
|
|
24
|
+
|
|
25
|
+
export interface RuntimeIntegrityFinding {
|
|
26
|
+
readonly code: RuntimeIntegrityFindingCode;
|
|
27
|
+
readonly artifact: RuntimeIntegrityArtifact;
|
|
28
|
+
readonly name: string;
|
|
29
|
+
readonly message: string;
|
|
30
|
+
readonly location?: RuntimeIntegrityFindingLocation;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface RuntimeIntegrityResult {
|
|
34
|
+
readonly ok: boolean;
|
|
35
|
+
readonly findings: readonly RuntimeIntegrityFinding[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface RuntimeOutboxManifest {
|
|
39
|
+
readonly events?: readonly EventPayloadDef[];
|
|
40
|
+
readonly jobs?: readonly JobDef[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface RuntimeManifestInput {
|
|
44
|
+
readonly listeners?: readonly ListenerDef[];
|
|
45
|
+
readonly schedules?: readonly ScheduleDef[];
|
|
46
|
+
readonly tasks?: readonly TaskDef[];
|
|
47
|
+
readonly outbox?: RuntimeOutboxManifest;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface RuntimeManifest {
|
|
51
|
+
readonly listeners: readonly ListenerDef[];
|
|
52
|
+
readonly schedules: readonly ScheduleDef[];
|
|
53
|
+
readonly tasks: readonly TaskDef[];
|
|
54
|
+
readonly outbox: {
|
|
55
|
+
readonly events: readonly EventPayloadDef[];
|
|
56
|
+
readonly jobs: readonly JobDef[];
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface RuntimeRegistriesInput {
|
|
61
|
+
readonly listeners?: readonly ListenerDef[];
|
|
62
|
+
readonly schedules?: readonly ScheduleDef[];
|
|
63
|
+
readonly tasks?: readonly TaskDef[];
|
|
64
|
+
readonly outbox?: OutboxRegistry;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface RuntimeRegistries {
|
|
68
|
+
readonly listeners: readonly ListenerDef[];
|
|
69
|
+
readonly schedules: readonly ScheduleDef[];
|
|
70
|
+
readonly tasks: readonly TaskDef[];
|
|
71
|
+
readonly outbox?: OutboxRegistry;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type RuntimeIntegrityMode = "error" | "warn";
|
|
75
|
+
|
|
76
|
+
export interface RuntimeIntegrityLogger {
|
|
77
|
+
warn(message: string): void;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface CreateRuntimeIntegrityOptions {
|
|
81
|
+
readonly manifest: RuntimeManifest;
|
|
82
|
+
readonly registries: RuntimeRegistries;
|
|
83
|
+
/**
|
|
84
|
+
* How to handle integrity findings.
|
|
85
|
+
*
|
|
86
|
+
* @default "error"
|
|
87
|
+
*/
|
|
88
|
+
readonly mode?: RuntimeIntegrityMode;
|
|
89
|
+
/**
|
|
90
|
+
* Logger used when `mode` is `"warn"`.
|
|
91
|
+
*
|
|
92
|
+
* @default console
|
|
93
|
+
*/
|
|
94
|
+
readonly logger?: RuntimeIntegrityLogger;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface RuntimeIntegrityCheck {
|
|
98
|
+
readonly kind: "runtime-integrity";
|
|
99
|
+
check(): RuntimeIntegrityResult;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
type NamedDefinition = {
|
|
103
|
+
readonly name: string;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
type IntegrityArtifactConfig = {
|
|
107
|
+
readonly artifact: RuntimeIntegrityArtifact;
|
|
108
|
+
readonly label: string;
|
|
109
|
+
readonly manifestNames: readonly string[];
|
|
110
|
+
readonly registryNames: readonly string[];
|
|
111
|
+
readonly missingCode: RuntimeIntegrityFindingCode;
|
|
112
|
+
readonly registryDescription: string;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export class RuntimeIntegrityError extends Error {
|
|
116
|
+
readonly findings: readonly RuntimeIntegrityFinding[];
|
|
117
|
+
|
|
118
|
+
constructor(findings: readonly RuntimeIntegrityFinding[]) {
|
|
119
|
+
super(formatRuntimeIntegrityFailure(findings));
|
|
120
|
+
this.name = "RuntimeIntegrityError";
|
|
121
|
+
this.findings = findings;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function defineRuntimeManifest(
|
|
126
|
+
input: RuntimeManifestInput,
|
|
127
|
+
): RuntimeManifest {
|
|
128
|
+
return {
|
|
129
|
+
listeners: input.listeners ?? [],
|
|
130
|
+
schedules: input.schedules ?? [],
|
|
131
|
+
tasks: input.tasks ?? [],
|
|
132
|
+
outbox: {
|
|
133
|
+
events: input.outbox?.events ?? [],
|
|
134
|
+
jobs: input.outbox?.jobs ?? [],
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function defineRuntimeRegistries(
|
|
140
|
+
input: RuntimeRegistriesInput,
|
|
141
|
+
): RuntimeRegistries {
|
|
142
|
+
return {
|
|
143
|
+
listeners: input.listeners ?? [],
|
|
144
|
+
schedules: input.schedules ?? [],
|
|
145
|
+
tasks: input.tasks ?? [],
|
|
146
|
+
outbox: input.outbox,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function inspectRuntimeIntegrity(
|
|
151
|
+
manifest: RuntimeManifest,
|
|
152
|
+
registries: RuntimeRegistries,
|
|
153
|
+
): RuntimeIntegrityResult {
|
|
154
|
+
const configs: readonly IntegrityArtifactConfig[] = [
|
|
155
|
+
{
|
|
156
|
+
artifact: "listener",
|
|
157
|
+
label: "listener",
|
|
158
|
+
manifestNames: namesFromDefinitions(manifest.listeners),
|
|
159
|
+
registryNames: namesFromDefinitions(registries.listeners),
|
|
160
|
+
missingCode: "missing_listener_registration",
|
|
161
|
+
registryDescription: "runtime registries.listeners",
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
artifact: "schedule",
|
|
165
|
+
label: "schedule",
|
|
166
|
+
manifestNames: namesFromDefinitions(manifest.schedules),
|
|
167
|
+
registryNames: namesFromDefinitions(registries.schedules),
|
|
168
|
+
missingCode: "missing_schedule_registration",
|
|
169
|
+
registryDescription: "runtime registries.schedules",
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
artifact: "task",
|
|
173
|
+
label: "task",
|
|
174
|
+
manifestNames: namesFromDefinitions(manifest.tasks),
|
|
175
|
+
registryNames: namesFromDefinitions(registries.tasks),
|
|
176
|
+
missingCode: "missing_task_registration",
|
|
177
|
+
registryDescription: "runtime registries.tasks",
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
artifact: "outboxEvent",
|
|
181
|
+
label: "outbox event",
|
|
182
|
+
manifestNames: namesFromDefinitions(manifest.outbox.events),
|
|
183
|
+
registryNames: namesFromMap(registries.outbox?.events),
|
|
184
|
+
missingCode: "missing_outbox_event_registration",
|
|
185
|
+
registryDescription: "runtime registries.outbox.events",
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
artifact: "outboxJob",
|
|
189
|
+
label: "outbox job",
|
|
190
|
+
manifestNames: namesFromDefinitions(manifest.outbox.jobs),
|
|
191
|
+
registryNames: namesFromMap(registries.outbox?.jobs),
|
|
192
|
+
missingCode: "missing_outbox_job_registration",
|
|
193
|
+
registryDescription: "runtime registries.outbox.jobs",
|
|
194
|
+
},
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
const findings = configs.flatMap((config) =>
|
|
198
|
+
inspectArtifactIntegrity(config),
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
ok: findings.length === 0,
|
|
203
|
+
findings,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function createRuntimeIntegrity(
|
|
208
|
+
options: CreateRuntimeIntegrityOptions,
|
|
209
|
+
): RuntimeIntegrityCheck {
|
|
210
|
+
const mode = options.mode ?? "error";
|
|
211
|
+
const logger = options.logger ?? console;
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
kind: "runtime-integrity",
|
|
215
|
+
check() {
|
|
216
|
+
const result = inspectRuntimeIntegrity(
|
|
217
|
+
options.manifest,
|
|
218
|
+
options.registries,
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
if (result.ok) return result;
|
|
222
|
+
|
|
223
|
+
if (mode === "warn") {
|
|
224
|
+
logger.warn(
|
|
225
|
+
`[beignet] ${formatRuntimeIntegrityFailure(result.findings)}`,
|
|
226
|
+
);
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
throw new RuntimeIntegrityError(result.findings);
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function runRuntimeIntegrityCheck(
|
|
236
|
+
check: RuntimeIntegrityCheck | undefined,
|
|
237
|
+
): RuntimeIntegrityResult | undefined {
|
|
238
|
+
return check?.check();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function inspectArtifactIntegrity(
|
|
242
|
+
config: IntegrityArtifactConfig,
|
|
243
|
+
): RuntimeIntegrityFinding[] {
|
|
244
|
+
const findings: RuntimeIntegrityFinding[] = [];
|
|
245
|
+
const manifestDuplicates = duplicateNames(config.manifestNames);
|
|
246
|
+
const registryDuplicates = duplicateNames(config.registryNames);
|
|
247
|
+
|
|
248
|
+
for (const name of manifestDuplicates) {
|
|
249
|
+
findings.push({
|
|
250
|
+
code: "duplicate_runtime_manifest_entry",
|
|
251
|
+
artifact: config.artifact,
|
|
252
|
+
name,
|
|
253
|
+
location: "manifest",
|
|
254
|
+
message: `Duplicate ${config.label} "${name}" in runtime manifest.`,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
for (const name of registryDuplicates) {
|
|
259
|
+
findings.push({
|
|
260
|
+
code: "duplicate_runtime_registry_entry",
|
|
261
|
+
artifact: config.artifact,
|
|
262
|
+
name,
|
|
263
|
+
location: "registry",
|
|
264
|
+
message: `Duplicate ${config.label} "${name}" in ${config.registryDescription}.`,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const registryNameSet = new Set(config.registryNames);
|
|
269
|
+
for (const name of uniqueNames(config.manifestNames)) {
|
|
270
|
+
if (registryNameSet.has(name)) continue;
|
|
271
|
+
findings.push({
|
|
272
|
+
code: config.missingCode,
|
|
273
|
+
artifact: config.artifact,
|
|
274
|
+
name,
|
|
275
|
+
message: `Missing ${config.label} registration "${name}": declared in runtime manifest but not present in ${config.registryDescription}.`,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return findings;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function namesFromDefinitions(
|
|
283
|
+
definitions: readonly NamedDefinition[] | undefined,
|
|
284
|
+
): string[] {
|
|
285
|
+
return (definitions ?? []).map((definition) => definition.name);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function namesFromMap<T extends NamedDefinition>(
|
|
289
|
+
definitions: ReadonlyMap<string, T> | undefined,
|
|
290
|
+
): string[] {
|
|
291
|
+
return [...(definitions?.keys() ?? [])];
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function duplicateNames(names: readonly string[]): string[] {
|
|
295
|
+
const seen = new Set<string>();
|
|
296
|
+
const duplicates = new Set<string>();
|
|
297
|
+
|
|
298
|
+
for (const name of names) {
|
|
299
|
+
if (seen.has(name)) {
|
|
300
|
+
duplicates.add(name);
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
seen.add(name);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return [...duplicates].sort();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function uniqueNames(names: readonly string[]): string[] {
|
|
310
|
+
return [...new Set(names)].sort();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function formatRuntimeIntegrityFailure(
|
|
314
|
+
findings: readonly RuntimeIntegrityFinding[],
|
|
315
|
+
): string {
|
|
316
|
+
const summary = `Runtime integrity check failed with ${findings.length} finding${findings.length === 1 ? "" : "s"}.`;
|
|
317
|
+
const details = findings
|
|
318
|
+
.map((finding) => `- [${finding.code}] ${finding.message}`)
|
|
319
|
+
.join("\n");
|
|
320
|
+
|
|
321
|
+
return `${summary}\n${details}`;
|
|
322
|
+
}
|
package/src/server/server.ts
CHANGED
|
@@ -44,6 +44,12 @@ import {
|
|
|
44
44
|
resolveProviderInstrumentationPort,
|
|
45
45
|
type ServiceProvider,
|
|
46
46
|
} from "../providers/index.js";
|
|
47
|
+
import {
|
|
48
|
+
parseTraceparent,
|
|
49
|
+
resolveTracingPort,
|
|
50
|
+
runWithTracing,
|
|
51
|
+
type TraceContextInput,
|
|
52
|
+
} from "../tracing/index.js";
|
|
47
53
|
import type {
|
|
48
54
|
ContextSeed,
|
|
49
55
|
ServerContextConfig,
|
|
@@ -87,6 +93,8 @@ import {
|
|
|
87
93
|
runWithActiveRequestContext,
|
|
88
94
|
setActiveRequestIdentity,
|
|
89
95
|
} from "./request-context.js";
|
|
96
|
+
import type { RuntimeIntegrityCheck } from "./runtime-integrity.js";
|
|
97
|
+
import { runRuntimeIntegrityCheck } from "./runtime-integrity.js";
|
|
90
98
|
import type {
|
|
91
99
|
AnyUseCaseLike,
|
|
92
100
|
AnyUseCaseRouteDef,
|
|
@@ -353,6 +361,15 @@ export type CreateServerOptions<
|
|
|
353
361
|
* Server hooks that wrap every registered route.
|
|
354
362
|
*/
|
|
355
363
|
hooks?: ServerHook<Ctx, Ports & InferProviderPorts<Providers>>[];
|
|
364
|
+
/**
|
|
365
|
+
* Optional pure startup check for app workflow registrations.
|
|
366
|
+
*
|
|
367
|
+
* Runtime integrity compares app-declared workflow artifacts against the
|
|
368
|
+
* registries passed to runtime entrypoints. It performs no filesystem,
|
|
369
|
+
* provider, database, network, worker, or background-loop work, so it is safe
|
|
370
|
+
* to run during serverless cold starts.
|
|
371
|
+
*/
|
|
372
|
+
integrity?: RuntimeIntegrityCheck;
|
|
356
373
|
/**
|
|
357
374
|
* Server-owned request instrumentation.
|
|
358
375
|
*
|
|
@@ -1192,7 +1209,7 @@ function createRequestExecutor<
|
|
|
1192
1209
|
const warnedNativeReplacementHooks = new WeakSet<object>();
|
|
1193
1210
|
const maxRequestBodyBytes = requestBodyLimit(options.requestBody);
|
|
1194
1211
|
|
|
1195
|
-
|
|
1212
|
+
const executeRequest = async (
|
|
1196
1213
|
target: ExecutionTarget<Ctx, C>,
|
|
1197
1214
|
req: HttpRequestLike,
|
|
1198
1215
|
preMatchedParams?: Record<string, string>,
|
|
@@ -2131,6 +2148,72 @@ function createRequestExecutor<
|
|
|
2131
2148
|
);
|
|
2132
2149
|
}
|
|
2133
2150
|
};
|
|
2151
|
+
|
|
2152
|
+
return async (
|
|
2153
|
+
target: ExecutionTarget<Ctx, C>,
|
|
2154
|
+
req: HttpRequestLike,
|
|
2155
|
+
preMatchedParams?: Record<string, string>,
|
|
2156
|
+
) => {
|
|
2157
|
+
const tracing = resolveTracingPort(finalPorts);
|
|
2158
|
+
const instrumentationOptions =
|
|
2159
|
+
options.instrumentation === false ? undefined : options.instrumentation;
|
|
2160
|
+
const pathname = (() => {
|
|
2161
|
+
try {
|
|
2162
|
+
return new URL(req.url).pathname;
|
|
2163
|
+
} catch {
|
|
2164
|
+
return req.url;
|
|
2165
|
+
}
|
|
2166
|
+
})();
|
|
2167
|
+
const ignored = (
|
|
2168
|
+
instrumentationOptions?.ignorePaths ?? ["/api/devtools"]
|
|
2169
|
+
).some((prefix) => {
|
|
2170
|
+
const normalized = prefix.replace(/\/+$/, "");
|
|
2171
|
+
return pathname === normalized || pathname.startsWith(`${normalized}/`);
|
|
2172
|
+
});
|
|
2173
|
+
|
|
2174
|
+
if (!tracing || ignored) {
|
|
2175
|
+
return executeRequest(target, req, preMatchedParams);
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
const traceContextHeader =
|
|
2179
|
+
instrumentationOptions?.traceContextHeader ?? "traceparent";
|
|
2180
|
+
const active = tracing.current();
|
|
2181
|
+
const parsedTraceparent =
|
|
2182
|
+
traceContextHeader === false
|
|
2183
|
+
? undefined
|
|
2184
|
+
: parseTraceparent(req.headers.get(traceContextHeader));
|
|
2185
|
+
const parent: TraceContextInput | undefined = active
|
|
2186
|
+
? undefined
|
|
2187
|
+
: !parsedTraceparent
|
|
2188
|
+
? undefined
|
|
2189
|
+
: {
|
|
2190
|
+
traceparent: parsedTraceparent.traceparent,
|
|
2191
|
+
tracestate: req.headers.get("tracestate") ?? undefined,
|
|
2192
|
+
};
|
|
2193
|
+
const traceAttributes = {
|
|
2194
|
+
"beignet.contract.name": target.contract.name,
|
|
2195
|
+
"http.request.method": req.method.toUpperCase(),
|
|
2196
|
+
"http.route": target.contract.path,
|
|
2197
|
+
} as const;
|
|
2198
|
+
|
|
2199
|
+
return await runWithTracing(
|
|
2200
|
+
tracing,
|
|
2201
|
+
{
|
|
2202
|
+
name: `beignet.request ${target.contract.name}`,
|
|
2203
|
+
type: "request",
|
|
2204
|
+
kind: active ? "internal" : "server",
|
|
2205
|
+
parent,
|
|
2206
|
+
attributes: traceAttributes,
|
|
2207
|
+
metricAttributes: traceAttributes,
|
|
2208
|
+
},
|
|
2209
|
+
async (span) => {
|
|
2210
|
+
const response = await executeRequest(target, req, preMatchedParams);
|
|
2211
|
+
span?.setAttribute("http.response.status_code", response.status);
|
|
2212
|
+
if (response.status >= 500) span?.setStatus("error");
|
|
2213
|
+
return response;
|
|
2214
|
+
},
|
|
2215
|
+
);
|
|
2216
|
+
};
|
|
2134
2217
|
}
|
|
2135
2218
|
|
|
2136
2219
|
/**
|
|
@@ -2581,6 +2664,8 @@ export async function createServer<
|
|
|
2581
2664
|
}
|
|
2582
2665
|
}
|
|
2583
2666
|
|
|
2667
|
+
runRuntimeIntegrityCheck(options.integrity);
|
|
2668
|
+
|
|
2584
2669
|
try {
|
|
2585
2670
|
for (const provider of providers) {
|
|
2586
2671
|
const cfg = await loadProviderConfig(provider, env, overrides);
|