@beignet/core 0.0.32 → 0.0.34

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.
Files changed (139) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +409 -34
  3. package/dist/application/index.d.ts.map +1 -1
  4. package/dist/application/index.js +63 -47
  5. package/dist/application/index.js.map +1 -1
  6. package/dist/contracts/contract-builder.d.ts +0 -4
  7. package/dist/contracts/contract-builder.d.ts.map +1 -1
  8. package/dist/contracts/contract-builder.js +0 -6
  9. package/dist/contracts/contract-builder.js.map +1 -1
  10. package/dist/events/index.d.ts +6 -0
  11. package/dist/events/index.d.ts.map +1 -1
  12. package/dist/events/index.js +20 -10
  13. package/dist/events/index.js.map +1 -1
  14. package/dist/flags/index.d.ts +0 -4
  15. package/dist/flags/index.d.ts.map +1 -1
  16. package/dist/flags/index.js +0 -4
  17. package/dist/flags/index.js.map +1 -1
  18. package/dist/jobs/index.d.ts +415 -4
  19. package/dist/jobs/index.d.ts.map +1 -1
  20. package/dist/jobs/index.js +392 -3
  21. package/dist/jobs/index.js.map +1 -1
  22. package/dist/notifications/index.d.ts +194 -10
  23. package/dist/notifications/index.d.ts.map +1 -1
  24. package/dist/notifications/index.js +397 -59
  25. package/dist/notifications/index.js.map +1 -1
  26. package/dist/outbox/index.d.ts +150 -1
  27. package/dist/outbox/index.d.ts.map +1 -1
  28. package/dist/outbox/index.js +170 -1
  29. package/dist/outbox/index.js.map +1 -1
  30. package/dist/ports/events.d.ts +2 -2
  31. package/dist/ports/index.d.ts +7 -2
  32. package/dist/ports/index.d.ts.map +1 -1
  33. package/dist/ports/index.js +2 -1
  34. package/dist/ports/index.js.map +1 -1
  35. package/dist/providers/instrumentation.d.ts +4 -0
  36. package/dist/providers/instrumentation.d.ts.map +1 -1
  37. package/dist/providers/instrumentation.js.map +1 -1
  38. package/dist/providers/provider.d.ts +1 -1
  39. package/dist/schedules/index.d.ts +8 -7
  40. package/dist/schedules/index.d.ts.map +1 -1
  41. package/dist/schedules/index.js +47 -16
  42. package/dist/schedules/index.js.map +1 -1
  43. package/dist/server/hooks/index.d.ts +1 -0
  44. package/dist/server/hooks/index.d.ts.map +1 -1
  45. package/dist/server/hooks/index.js +1 -0
  46. package/dist/server/hooks/index.js.map +1 -1
  47. package/dist/server/hooks/rate-limit.d.ts +26 -13
  48. package/dist/server/hooks/rate-limit.d.ts.map +1 -1
  49. package/dist/server/hooks/rate-limit.js +28 -34
  50. package/dist/server/hooks/rate-limit.js.map +1 -1
  51. package/dist/server/hooks/security.d.ts +179 -0
  52. package/dist/server/hooks/security.d.ts.map +1 -0
  53. package/dist/server/hooks/security.js +225 -0
  54. package/dist/server/hooks/security.js.map +1 -0
  55. package/dist/server/index.d.ts +8 -0
  56. package/dist/server/index.d.ts.map +1 -1
  57. package/dist/server/index.js +8 -0
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/instrumentation.d.ts.map +1 -1
  60. package/dist/server/instrumentation.js +22 -6
  61. package/dist/server/instrumentation.js.map +1 -1
  62. package/dist/server/request-context.d.ts +4 -0
  63. package/dist/server/request-context.d.ts.map +1 -1
  64. package/dist/server/request-context.js +3 -0
  65. package/dist/server/request-context.js.map +1 -1
  66. package/dist/server/runtime-integrity.d.ts +84 -0
  67. package/dist/server/runtime-integrity.d.ts.map +1 -0
  68. package/dist/server/runtime-integrity.js +160 -0
  69. package/dist/server/runtime-integrity.js.map +1 -0
  70. package/dist/server/server.d.ts +10 -10
  71. package/dist/server/server.d.ts.map +1 -1
  72. package/dist/server/server.js +75 -11
  73. package/dist/server/server.js.map +1 -1
  74. package/dist/server/trusted-proxy.d.ts +77 -0
  75. package/dist/server/trusted-proxy.d.ts.map +1 -0
  76. package/dist/server/trusted-proxy.js +129 -0
  77. package/dist/server/trusted-proxy.js.map +1 -0
  78. package/dist/tasks/index.d.ts +6 -7
  79. package/dist/tasks/index.d.ts.map +1 -1
  80. package/dist/tasks/index.js +22 -5
  81. package/dist/tasks/index.js.map +1 -1
  82. package/dist/tenancy/index.d.ts +41 -0
  83. package/dist/tenancy/index.d.ts.map +1 -0
  84. package/dist/tenancy/index.js +34 -0
  85. package/dist/tenancy/index.js.map +1 -0
  86. package/dist/testing/index.d.ts +1 -1
  87. package/dist/testing/index.d.ts.map +1 -1
  88. package/dist/testing/index.js +1 -0
  89. package/dist/testing/index.js.map +1 -1
  90. package/dist/tracing/execution.d.ts +14 -0
  91. package/dist/tracing/execution.d.ts.map +1 -0
  92. package/dist/tracing/execution.js +17 -0
  93. package/dist/tracing/execution.js.map +1 -0
  94. package/dist/tracing/index.d.ts +49 -0
  95. package/dist/tracing/index.d.ts.map +1 -1
  96. package/dist/tracing/index.js +59 -0
  97. package/dist/tracing/index.js.map +1 -1
  98. package/dist/uploads/client.d.ts.map +1 -1
  99. package/dist/uploads/client.js +44 -6
  100. package/dist/uploads/client.js.map +1 -1
  101. package/dist/uploads/index.d.ts +71 -0
  102. package/dist/uploads/index.d.ts.map +1 -1
  103. package/dist/uploads/index.js +344 -15
  104. package/dist/uploads/index.js.map +1 -1
  105. package/dist/webhooks/index.d.ts +55 -4
  106. package/dist/webhooks/index.d.ts.map +1 -1
  107. package/dist/webhooks/index.js +106 -2
  108. package/dist/webhooks/index.js.map +1 -1
  109. package/package.json +5 -5
  110. package/skills/app-architecture/SKILL.md +31 -2
  111. package/src/application/index.ts +88 -56
  112. package/src/contracts/contract-builder.ts +0 -7
  113. package/src/events/index.ts +27 -14
  114. package/src/flags/index.ts +0 -5
  115. package/src/jobs/index.ts +895 -7
  116. package/src/notifications/index.ts +678 -70
  117. package/src/outbox/index.ts +358 -2
  118. package/src/ports/events.ts +2 -2
  119. package/src/ports/index.ts +17 -1
  120. package/src/providers/instrumentation.ts +4 -0
  121. package/src/providers/provider.ts +1 -1
  122. package/src/schedules/index.ts +60 -26
  123. package/src/server/hooks/index.ts +10 -0
  124. package/src/server/hooks/rate-limit.ts +52 -46
  125. package/src/server/hooks/security.ts +503 -0
  126. package/src/server/index.ts +8 -0
  127. package/src/server/instrumentation.ts +25 -5
  128. package/src/server/request-context.ts +7 -0
  129. package/src/server/runtime-integrity.ts +322 -0
  130. package/src/server/server.ts +88 -35
  131. package/src/server/trusted-proxy.ts +249 -0
  132. package/src/tasks/index.ts +29 -12
  133. package/src/tenancy/index.ts +55 -0
  134. package/src/testing/index.ts +2 -0
  135. package/src/tracing/execution.ts +37 -0
  136. package/src/tracing/index.ts +137 -0
  137. package/src/uploads/client.ts +65 -6
  138. package/src/uploads/index.ts +539 -14
  139. 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: createTraceContext({
270
- traceparent: parseTraceparent(headerTraceparent)?.traceparent,
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
+ }
@@ -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
- return async (
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);
@@ -2856,30 +2941,7 @@ export function contractsFromRoutes<
2856
2941
  * });
2857
2942
  * ```
2858
2943
  */
2859
- export function defineRouteGroup<Ctx>(): RouteGroupBuilder<Ctx>;
2860
- /**
2861
- * @deprecated Prefer `defineRouteGroup<Ctx>()({ ... })`. TypeScript cannot
2862
- * infer the route tuple generic when callers provide only `Ctx`, so the direct
2863
- * form preserves context checking but erases per-route contract output checks.
2864
- */
2865
- export function defineRouteGroup<
2866
- Ctx,
2867
- // biome-ignore lint/suspicious/noExplicitAny: route contract types are erased at this level
2868
- const R extends readonly RouteDef<Ctx, any>[] = readonly RouteDef<Ctx, any>[],
2869
- >(group: {
2870
- name: string;
2871
- hooks?: readonly RouteHook<Ctx, object>[];
2872
- routes: R & ValidatedRouteInputs<Ctx, R>;
2873
- }): RouteGroup<Ctx, R>;
2874
- export function defineRouteGroup<
2875
- Ctx,
2876
- // biome-ignore lint/suspicious/noExplicitAny: route contract types are erased at this level
2877
- const R extends readonly RouteDef<any, any>[] = readonly RouteDef<any, any>[],
2878
- >(group?: {
2879
- name: string;
2880
- hooks?: readonly RouteHook<Ctx, object>[];
2881
- routes: R;
2882
- }): RouteGroup<Ctx, R> | RouteGroupBuilder<Ctx> {
2944
+ export function defineRouteGroup<Ctx>(): RouteGroupBuilder<Ctx> {
2883
2945
  const createGroup = <
2884
2946
  const GroupHooks extends readonly RouteHook<Ctx, object>[] = readonly [],
2885
2947
  const GroupRoutes extends readonly AnyRouteDef[] = readonly AnyRouteDef[],
@@ -2894,16 +2956,7 @@ export function defineRouteGroup<
2894
2956
  routes: input.routes,
2895
2957
  });
2896
2958
 
2897
- if (!group) {
2898
- return createGroup;
2899
- }
2900
-
2901
- return {
2902
- kind: ROUTE_GROUP_KIND,
2903
- name: group.name,
2904
- hooks: group.hooks,
2905
- routes: group.routes,
2906
- };
2959
+ return createGroup as RouteGroupBuilder<Ctx>;
2907
2960
  }
2908
2961
 
2909
2962
  function isRouteGroup<Ctx>(route: RouteInput<Ctx>): route is RouteGroup<Ctx> {