@forklaunch/core 3.1.0 → 3.2.1

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 (36) hide show
  1. package/bin/gateway-mock/routes/email.mjs +91 -4
  2. package/lib/http/emailClient.d.ts +48 -1
  3. package/lib/http/emailClient.d.ts.map +1 -1
  4. package/lib/http/index.d.ts +1 -0
  5. package/lib/http/index.d.ts.map +1 -1
  6. package/lib/http/index.js +307 -96
  7. package/lib/http/index.js.map +1 -1
  8. package/lib/http/index.mjs +266 -58
  9. package/lib/http/index.mjs.map +1 -1
  10. package/lib/http/instanceGateway.d.ts +10 -6
  11. package/lib/http/instanceGateway.d.ts.map +1 -1
  12. package/lib/http/middleware/request/enrichDetails.middleware.d.ts.map +1 -1
  13. package/lib/http/modelGatewayClient.d.ts +13 -12
  14. package/lib/http/modelGatewayClient.d.ts.map +1 -1
  15. package/lib/http/smsClient.d.ts +2 -2
  16. package/lib/http/smsClient.d.ts.map +1 -1
  17. package/lib/http/telemetry/auditLogger.d.ts +5 -1
  18. package/lib/http/telemetry/auditLogger.d.ts.map +1 -1
  19. package/lib/http/telemetry/requestAudit.d.ts +30 -0
  20. package/lib/http/telemetry/requestAudit.d.ts.map +1 -0
  21. package/lib/http/voiceClient.d.ts +4 -4
  22. package/lib/http/voiceClient.d.ts.map +1 -1
  23. package/lib/http/whatsappClient.d.ts +5 -5
  24. package/lib/persistence/complianceChanges.d.ts +30 -0
  25. package/lib/persistence/complianceChanges.d.ts.map +1 -0
  26. package/lib/persistence/compliancePropertyBuilder.d.ts.map +1 -1
  27. package/lib/persistence/compliantField.d.ts +41 -8
  28. package/lib/persistence/compliantField.d.ts.map +1 -1
  29. package/lib/persistence/defineComplianceEntity.d.ts.map +1 -1
  30. package/lib/persistence/index.d.ts +2 -1
  31. package/lib/persistence/index.d.ts.map +1 -1
  32. package/lib/persistence/index.js +156 -19
  33. package/lib/persistence/index.js.map +1 -1
  34. package/lib/persistence/index.mjs +154 -19
  35. package/lib/persistence/index.mjs.map +1 -1
  36. package/package.json +10 -10
@@ -686,8 +686,8 @@ async function getCachedJwks(jwksPublicKeyUrl) {
686
686
  return cachedJwks.value;
687
687
  } else {
688
688
  const jwksResponse = await fetch(jwksPublicKeyUrl);
689
- const text = await jwksResponse.text();
690
- if (!text) {
689
+ const text2 = await jwksResponse.text();
690
+ if (!text2) {
691
691
  if (cachedJwks.value) {
692
692
  return cachedJwks.value;
693
693
  }
@@ -695,7 +695,7 @@ async function getCachedJwks(jwksPublicKeyUrl) {
695
695
  `JWKS endpoint returned empty response (status ${jwksResponse.status})`
696
696
  );
697
697
  }
698
- const jwks = JSON.parse(text).keys;
698
+ const jwks = JSON.parse(text2).keys;
699
699
  cachedJwks.value = jwks;
700
700
  cachedJwks.lastUpdated = /* @__PURE__ */ new Date();
701
701
  cachedJwks.ttl = parseInt(
@@ -1157,7 +1157,166 @@ async function parseRequestAuth(req, res, next) {
1157
1157
  }
1158
1158
 
1159
1159
  // src/http/middleware/request/enrichDetails.middleware.ts
1160
+ import { getEnvVar as getEnvVar4 } from "@forklaunch/common";
1161
+
1162
+ // src/http/telemetry/requestAudit.ts
1160
1163
  import { getEnvVar as getEnvVar3 } from "@forklaunch/common";
1164
+
1165
+ // src/persistence/encryptedType.ts
1166
+ import { Type } from "@mikro-orm/core";
1167
+ import { AsyncLocalStorage } from "async_hooks";
1168
+ var _tenantContext = new AsyncLocalStorage();
1169
+
1170
+ // src/persistence/complianceChanges.ts
1171
+ var LISTENERS = globalThis[/* @__PURE__ */ Symbol.for("forklaunch.compliance.changeListeners")] ??= /* @__PURE__ */ new Set();
1172
+ function onComplianceChange(listener) {
1173
+ LISTENERS.add(listener);
1174
+ return () => LISTENERS.delete(listener);
1175
+ }
1176
+
1177
+ // src/persistence/compliantField.ts
1178
+ import {
1179
+ Type as Type2
1180
+ } from "@mikro-orm/core";
1181
+
1182
+ // src/persistence/fieldEncryptor.ts
1183
+ var HKDF_SALT = Buffer.alloc(0);
1184
+
1185
+ // src/persistence/compliantField.ts
1186
+ var LISTENERS2 = globalThis[/* @__PURE__ */ Symbol.for("forklaunch.compliance.listeners")] ??= /* @__PURE__ */ new Set();
1187
+ function onComplianceAccess(listener) {
1188
+ LISTENERS2.add(listener);
1189
+ return () => LISTENERS2.delete(listener);
1190
+ }
1191
+ var STATE = globalThis[/* @__PURE__ */ Symbol.for("forklaunch.compliance.state")] ??= /* @__PURE__ */ new WeakMap();
1192
+ var SLOTS = globalThis[/* @__PURE__ */ Symbol.for("forklaunch.compliance.slots")] ??= /* @__PURE__ */ new WeakMap();
1193
+ var LOADED_TENANTS = globalThis[/* @__PURE__ */ Symbol.for("forklaunch.compliance.loadedTenants")] ??= /* @__PURE__ */ new WeakMap();
1194
+
1195
+ // src/http/telemetry/auditLogger.ts
1196
+ import { createHash as createHash2 } from "crypto";
1197
+ var AuditLogger = class {
1198
+ _otel;
1199
+ constructor(otel) {
1200
+ this._otel = otel;
1201
+ }
1202
+ append(entry) {
1203
+ try {
1204
+ this._otel.info(
1205
+ {
1206
+ "log.type": "audit",
1207
+ "audit.timestamp": entry.timestamp,
1208
+ "audit.userId": entry.userId ?? "",
1209
+ "audit.tenantId": entry.tenantId ?? "",
1210
+ "audit.route": entry.route,
1211
+ "audit.method": entry.method,
1212
+ "audit.bodyHash": entry.bodyHash,
1213
+ "audit.status": entry.status,
1214
+ "audit.duration": entry.duration,
1215
+ "audit.redactedFields": entry.redactedFields.join(","),
1216
+ "audit.eventType": entry.eventType,
1217
+ _meta: true
1218
+ },
1219
+ `audit:${entry.eventType} ${entry.method} ${entry.route} ${entry.status}`
1220
+ );
1221
+ } catch (error) {
1222
+ console.error("Failed to emit audit log via OTEL:", error);
1223
+ }
1224
+ }
1225
+ static hashBody(body) {
1226
+ if (body === void 0 || body === null || body === "") {
1227
+ return "";
1228
+ }
1229
+ return createHash2("sha256").update(body).digest("hex");
1230
+ }
1231
+ };
1232
+
1233
+ // src/http/telemetry/requestAudit.ts
1234
+ function auditEnabled() {
1235
+ return getEnvVar3("FORKLAUNCH_AUDIT") !== "off";
1236
+ }
1237
+ var loggers = /* @__PURE__ */ new WeakMap();
1238
+ function loggerFor(otel) {
1239
+ let logger2 = loggers.get(otel);
1240
+ if (!logger2) {
1241
+ logger2 = new AuditLogger(otel);
1242
+ loggers.set(otel, logger2);
1243
+ }
1244
+ return logger2;
1245
+ }
1246
+ function auditEventType(status) {
1247
+ if (status === 401) return "auth_failure";
1248
+ if (status === 403) return "rbac_deny";
1249
+ if (status === 429) return "rate_limit";
1250
+ return "http";
1251
+ }
1252
+ var text = (value) => typeof value === "string" && value ? value : null;
1253
+ function bodyText(body) {
1254
+ if (body == null || body === "") return void 0;
1255
+ if (typeof body === "string" || Buffer.isBuffer(body)) return String(body);
1256
+ try {
1257
+ return JSON.stringify(body);
1258
+ } catch {
1259
+ return void 0;
1260
+ }
1261
+ }
1262
+ function recordRequestAudit(otel, req, status, durationMs) {
1263
+ if (!otel || !auditEnabled()) return;
1264
+ const session = req.session && typeof req.session === "object" ? req.session : void 0;
1265
+ loggerFor(otel).append({
1266
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1267
+ userId: text(session?.sub) ?? text(session?.userId),
1268
+ tenantId: text(session?.activeOrganizationId) ?? text(session?.organizationId),
1269
+ route: req.originalPath ?? req.path ?? "unknown",
1270
+ method: req.method,
1271
+ bodyHash: AuditLogger.hashBody(bodyText(req.body)),
1272
+ status,
1273
+ duration: Math.max(0, Math.round(durationMs)),
1274
+ redactedFields: [],
1275
+ eventType: auditEventType(status)
1276
+ });
1277
+ }
1278
+ var accessAudit;
1279
+ function auditComplianceAccess(otel) {
1280
+ if (!accessAudit && auditEnabled()) {
1281
+ const logger2 = loggerFor(otel);
1282
+ const remove = onComplianceAccess(
1283
+ (event) => logger2.append({
1284
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1285
+ userId: null,
1286
+ tenantId: event.tenantId || null,
1287
+ route: event.field,
1288
+ method: "READ",
1289
+ bodyHash: "",
1290
+ status: 0,
1291
+ duration: 0,
1292
+ redactedFields: [event.field],
1293
+ eventType: "data_access"
1294
+ })
1295
+ );
1296
+ const removeChanges = onComplianceChange(
1297
+ (event) => logger2.append({
1298
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1299
+ userId: null,
1300
+ tenantId: event.tenantId,
1301
+ route: `${event.entity}#${event.id}`,
1302
+ method: event.action.toUpperCase(),
1303
+ bodyHash: "",
1304
+ status: 0,
1305
+ duration: 0,
1306
+ redactedFields: event.sensitiveFields,
1307
+ eventType: "data_change"
1308
+ })
1309
+ );
1310
+ accessAudit = () => {
1311
+ remove();
1312
+ removeChanges();
1313
+ accessAudit = void 0;
1314
+ };
1315
+ }
1316
+ return accessAudit ?? (() => void 0);
1317
+ }
1318
+
1319
+ // src/http/middleware/request/enrichDetails.middleware.ts
1161
1320
  function enrichDetails(path, contractDetails, requestSchema, responseSchemas, openTelemetryCollector, globalOptions) {
1162
1321
  return (req, res, next) => {
1163
1322
  req.originalPath = path;
@@ -1168,12 +1327,20 @@ function enrichDetails(path, contractDetails, requestSchema, responseSchemas, op
1168
1327
  req._globalOptions = globalOptions ?? (() => void 0);
1169
1328
  req.context?.span?.setAttribute(ATTR_API_NAME, req.contractDetails?.name);
1170
1329
  const startTime = process.hrtime();
1330
+ if (openTelemetryCollector) auditComplianceAccess(openTelemetryCollector);
1171
1331
  res.on("finish", () => {
1332
+ const [auditSeconds, auditNanoseconds] = process.hrtime(startTime);
1333
+ recordRequestAudit(
1334
+ openTelemetryCollector,
1335
+ req,
1336
+ Number(res.statusCode),
1337
+ auditSeconds * 1e3 + auditNanoseconds / 1e6
1338
+ );
1172
1339
  const [seconds, nanoseconds] = process.hrtime(startTime);
1173
1340
  const durationMs = seconds + nanoseconds / 1e9;
1174
1341
  httpServerDurationHistogram.record(durationMs, {
1175
- [ATTR_SERVICE_NAME]: getEnvVar3("OTEL_SERVICE_NAME") || "unknown",
1176
- [ATTR_APPLICATION_ID]: getEnvVar3("OTEL_APPLICATION_ID"),
1342
+ [ATTR_SERVICE_NAME]: getEnvVar4("OTEL_SERVICE_NAME") || "unknown",
1343
+ [ATTR_APPLICATION_ID]: getEnvVar4("OTEL_APPLICATION_ID"),
1177
1344
  [ATTR_API_NAME]: req.contractDetails?.name || "unknown",
1178
1345
  [ATTR_HTTP_REQUEST_METHOD]: req.method,
1179
1346
  [ATTR_HTTP_ROUTE]: req.originalPath || "unknown",
@@ -2422,7 +2589,7 @@ function instanceGatewaySettings(options2 = {}, feature = "The instance gateway"
2422
2589
  const hmacKey = options2.hmacKey ?? process.env.INSTANCE_HMAC_KEY;
2423
2590
  if (!gatewayUrl || !instanceId || !hmacKey) {
2424
2591
  throw new Error(
2425
- `${feature} is only available to instances hosted in managed mode: PLATFORM_GATEWAY_URL, INSTANCE_ID and INSTANCE_HMAC_KEY must be set`
2592
+ `${feature} goes through the ForkLaunch managed gateway: PLATFORM_GATEWAY_URL, INSTANCE_ID and INSTANCE_HMAC_KEY must be set. The platform sets them for a managed instance, and at deploy for a component that declares the capability (\`forklaunch infra add <service> <capability>\`), which also points them at the local gateway mock`
2426
2593
  );
2427
2594
  }
2428
2595
  return { gatewayUrl, instanceId, hmacKey };
@@ -2657,17 +2824,17 @@ async function readPreviewCatalog(response) {
2657
2824
  if (!response.body) return invalidCatalog();
2658
2825
  const reader = response.body.getReader();
2659
2826
  const decoder = new TextDecoder();
2660
- let text = "", bytes = 0;
2827
+ let text2 = "", bytes = 0;
2661
2828
  try {
2662
2829
  for (; ; ) {
2663
2830
  const { done, value } = await reader.read();
2664
2831
  if (done) break;
2665
2832
  bytes += value.byteLength;
2666
2833
  if (bytes > 8192) return invalidCatalog();
2667
- text += decoder.decode(value, { stream: true });
2834
+ text2 += decoder.decode(value, { stream: true });
2668
2835
  }
2669
- text += decoder.decode();
2670
- return previewCatalog(JSON.parse(text));
2836
+ text2 += decoder.decode();
2837
+ return previewCatalog(JSON.parse(text2));
2671
2838
  } catch {
2672
2839
  return invalidCatalog();
2673
2840
  } finally {
@@ -2723,10 +2890,16 @@ var EMAIL_LIMITS = {
2723
2890
  maxSubjectLength: 998,
2724
2891
  /** text + html, UTF-8 bytes. */
2725
2892
  maxBodyBytes: 512 * 1024,
2726
- maxTags: 10
2893
+ maxTags: 10,
2894
+ /** `listUnsubscribe.url`; keeps the header within SES's limit. */
2895
+ maxUnsubscribeUrlLength: 512
2727
2896
  };
2728
2897
  var ADDRESS = /^[^\s@<>()[\]\\,;:"]+@[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)+$/;
2729
2898
  var TAG_PART = /^[A-Za-z0-9_-]{1,256}$/;
2899
+ var HEADER_SAFE = /^[\x21-\x7e]+$/;
2900
+ var URL_DELIMITERS = /[<>,"\\]/;
2901
+ var MAILBOX = /^[A-Za-z0-9._+-]+@[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)+$/;
2902
+ var UNSUBSCRIBE_FIELDS = /* @__PURE__ */ new Set(["url", "mailto", "oneClick"]);
2730
2903
  function isEmailAddress(address) {
2731
2904
  return typeof address === "string" && address.length <= EMAIL_LIMITS.maxAddressLength && ADDRESS.test(address);
2732
2905
  }
@@ -2751,6 +2924,72 @@ function addresses(value, field) {
2751
2924
  }
2752
2925
  return list;
2753
2926
  }
2927
+ function fail(problem) {
2928
+ refuse(`listUnsubscribe: ${problem}`);
2929
+ }
2930
+ function unsubscribeUrlProblem(url) {
2931
+ if (typeof url !== "string" || url === "") return "url must be a string";
2932
+ if (url.length > EMAIL_LIMITS.maxUnsubscribeUrlLength) {
2933
+ return `url is longer than ${EMAIL_LIMITS.maxUnsubscribeUrlLength} characters`;
2934
+ }
2935
+ if (!HEADER_SAFE.test(url)) {
2936
+ return "url must be printable ASCII with no spaces or line breaks; percent-encode anything else";
2937
+ }
2938
+ if (URL_DELIMITERS.test(url)) {
2939
+ return 'url must not contain < > , " or \\; percent-encode them';
2940
+ }
2941
+ let parsed;
2942
+ try {
2943
+ parsed = new URL(url);
2944
+ } catch {
2945
+ return "url is not a URL";
2946
+ }
2947
+ if (parsed.protocol !== "https:") return "url must be https";
2948
+ if (parsed.username || parsed.password) {
2949
+ return "url must not carry a user name or password";
2950
+ }
2951
+ return null;
2952
+ }
2953
+ function validateListUnsubscribe(value) {
2954
+ if (value === void 0 || value === null) return void 0;
2955
+ if (typeof value !== "object" || Array.isArray(value)) {
2956
+ fail("must be an object");
2957
+ }
2958
+ const unknownFields = Object.keys(value).filter(
2959
+ (key) => !UNSUBSCRIBE_FIELDS.has(key)
2960
+ );
2961
+ if (unknownFields.length) {
2962
+ fail(`unknown field ${unknownFields.map((k) => `'${k}'`).join(", ")}`);
2963
+ }
2964
+ const url = "url" in value ? value.url : void 0;
2965
+ const mailto = "mailto" in value ? value.mailto : void 0;
2966
+ const oneClick = "oneClick" in value ? value.oneClick : void 0;
2967
+ if (url === void 0 && mailto === void 0) {
2968
+ fail("needs a url or a mailto");
2969
+ }
2970
+ if (url !== void 0) {
2971
+ const problem = unsubscribeUrlProblem(url);
2972
+ if (problem) fail(problem);
2973
+ }
2974
+ if (mailto !== void 0 && (typeof mailto !== "string" || mailto.length > EMAIL_LIMITS.maxAddressLength || !MAILBOX.test(mailto))) {
2975
+ fail(
2976
+ `mailto: '${String(mailto).slice(0, 80)}' is not a plain mailbox (letters, digits, . _ + - before the @)`
2977
+ );
2978
+ }
2979
+ if (oneClick !== void 0 && typeof oneClick !== "boolean") {
2980
+ fail("oneClick must be true or false");
2981
+ }
2982
+ if (typeof url === "string") {
2983
+ return {
2984
+ url,
2985
+ ...typeof mailto === "string" ? { mailto } : {},
2986
+ ...oneClick === true ? { oneClick: true } : {}
2987
+ };
2988
+ }
2989
+ if (oneClick === true) fail("oneClick needs a url (RFC 8058 posts to it)");
2990
+ if (typeof mailto === "string") return { mailto };
2991
+ return fail("needs a url or a mailto");
2992
+ }
2754
2993
  function validateEmailSendRequest(request) {
2755
2994
  const to = addresses(request.to, "to") ?? refuse("to is required");
2756
2995
  const replyTo = addresses(request.replyTo, "replyTo");
@@ -2782,13 +3021,15 @@ function validateEmailSendRequest(request) {
2782
3021
  refuse(`tag '${name}' is reserved`);
2783
3022
  }
2784
3023
  }
3024
+ const listUnsubscribe = validateListUnsubscribe(request.listUnsubscribe);
2785
3025
  return {
2786
3026
  to,
2787
3027
  subject,
2788
3028
  ...request.text !== void 0 ? { text: request.text } : {},
2789
3029
  ...request.html !== void 0 ? { html: request.html } : {},
2790
3030
  ...replyTo ? { replyTo } : {},
2791
- ...names.length ? { tags } : {}
3031
+ ...names.length ? { tags } : {},
3032
+ ...listUnsubscribe ? { listUnsubscribe } : {}
2792
3033
  };
2793
3034
  }
2794
3035
  function createEmailClient(options2 = {}) {
@@ -2900,7 +3141,7 @@ function validateSmsSend(request) {
2900
3141
  );
2901
3142
  }
2902
3143
  }
2903
- var NOT_MANAGED = "SMS is platform-held: this service must run as a managed instance (PLATFORM_GATEWAY_URL, INSTANCE_ID and INSTANCE_HMAC_KEY set). Locally, run the gateway mock (npx -p @forklaunch/core forklaunch-gateway-mock) and point those variables at it; do not add a Twilio or AWS SDK.";
3144
+ var NOT_MANAGED = "SMS goes through the ForkLaunch managed gateway: PLATFORM_GATEWAY_URL, INSTANCE_ID and INSTANCE_HMAC_KEY must be set (the platform sets them for a managed instance, and at deploy for a component that declares sms). Locally, run the gateway mock (npx -p @forklaunch/core forklaunch-gateway-mock) and point those variables at it; do not add a Twilio or AWS SDK.";
2904
3145
  var SmsClient = class {
2905
3146
  transport;
2906
3147
  constructor(options2 = {}) {
@@ -3069,7 +3310,7 @@ function voiceRequestProblem(request) {
3069
3310
  }
3070
3311
  var VoiceClient = class {
3071
3312
  transport;
3072
- /** Throws when the process is not a managed instance (see instanceGatewaySettings). */
3313
+ /** Throws without the managed gateway settings (see instanceGatewaySettings). */
3073
3314
  constructor(options2 = {}) {
3074
3315
  this.transport = createInstanceGatewayTransport(
3075
3316
  options2,
@@ -3097,8 +3338,8 @@ var VoiceClient = class {
3097
3338
  async send(method, route, body) {
3098
3339
  try {
3099
3340
  const response = await this.transport.request(method, route, body);
3100
- const text = await response.text();
3101
- return text ? JSON.parse(text) : void 0;
3341
+ const text2 = await response.text();
3342
+ return text2 ? JSON.parse(text2) : void 0;
3102
3343
  } catch (error) {
3103
3344
  if (error instanceof InstanceGatewayRequestError) {
3104
3345
  throw new VoiceRequestError(
@@ -3231,7 +3472,7 @@ function createStripeClient(options2 = {}) {
3231
3472
  if (!explicitGateway && !isManagedInstance()) {
3232
3473
  if (!options2.apiKey) {
3233
3474
  throw new Error(
3234
- "createStripeClient: not a managed instance (PLATFORM_GATEWAY_URL, INSTANCE_ID and INSTANCE_HMAC_KEY are unset) and no apiKey was given \u2014 pass { apiKey } for local development, or run the gateway mock (forklaunch-gateway-mock) with the managed env"
3475
+ "createStripeClient: no managed gateway (PLATFORM_GATEWAY_URL, INSTANCE_ID and INSTANCE_HMAC_KEY are unset) and no apiKey was given \u2014 pass { apiKey } for local development, or run the gateway mock (forklaunch-gateway-mock) with the managed env"
3235
3476
  );
3236
3477
  }
3237
3478
  return new StripeClass(options2.apiKey, options2.config);
@@ -4727,7 +4968,7 @@ import {
4727
4968
  import { Readable, Transform } from "stream";
4728
4969
 
4729
4970
  // src/http/telemetry/recordMetric.ts
4730
- import { getEnvVar as getEnvVar4 } from "@forklaunch/common";
4971
+ import { getEnvVar as getEnvVar5 } from "@forklaunch/common";
4731
4972
  import {
4732
4973
  ATTR_HTTP_REQUEST_METHOD as ATTR_HTTP_REQUEST_METHOD3,
4733
4974
  ATTR_HTTP_RESPONSE_STATUS_CODE as ATTR_HTTP_RESPONSE_STATUS_CODE3,
@@ -4738,10 +4979,10 @@ function recordMetric(req, res) {
4738
4979
  if (res.metricRecorded) {
4739
4980
  return;
4740
4981
  }
4741
- const serviceName = getEnvVar4("OTEL_SERVICE_NAME") || "unknown";
4982
+ const serviceName = getEnvVar5("OTEL_SERVICE_NAME") || "unknown";
4742
4983
  const attrs = {
4743
4984
  [ATTR_SERVICE_NAME3]: serviceName,
4744
- [ATTR_APPLICATION_ID]: getEnvVar4("OTEL_APPLICATION_ID"),
4985
+ [ATTR_APPLICATION_ID]: getEnvVar5("OTEL_APPLICATION_ID"),
4745
4986
  [ATTR_API_NAME]: req.contractDetails?.name,
4746
4987
  [ATTR_HTTP_REQUEST_METHOD3]: req.method,
4747
4988
  [ATTR_HTTP_ROUTE3]: req.originalPath,
@@ -5234,44 +5475,6 @@ function generateOpenApiSpecs(schemaValidator, serverUrls, serverDescriptions, a
5234
5475
  );
5235
5476
  }
5236
5477
 
5237
- // src/http/telemetry/auditLogger.ts
5238
- import { createHash as createHash2 } from "crypto";
5239
- var AuditLogger = class {
5240
- _otel;
5241
- constructor(otel) {
5242
- this._otel = otel;
5243
- }
5244
- append(entry) {
5245
- try {
5246
- this._otel.info(
5247
- {
5248
- "log.type": "audit",
5249
- "audit.timestamp": entry.timestamp,
5250
- "audit.userId": entry.userId ?? "",
5251
- "audit.tenantId": entry.tenantId ?? "",
5252
- "audit.route": entry.route,
5253
- "audit.method": entry.method,
5254
- "audit.bodyHash": entry.bodyHash,
5255
- "audit.status": entry.status,
5256
- "audit.duration": entry.duration,
5257
- "audit.redactedFields": entry.redactedFields.join(","),
5258
- "audit.eventType": entry.eventType,
5259
- _meta: true
5260
- },
5261
- `audit:${entry.eventType} ${entry.method} ${entry.route} ${entry.status}`
5262
- );
5263
- } catch (error) {
5264
- console.error("Failed to emit audit log via OTEL:", error);
5265
- }
5266
- }
5267
- static hashBody(body) {
5268
- if (body === void 0 || body === null || body === "") {
5269
- return "";
5270
- }
5271
- return createHash2("sha256").update(body).digest("hex");
5272
- }
5273
- };
5274
-
5275
5478
  // src/http/telemetry/evaluateTelemetryOptions.ts
5276
5479
  function evaluateTelemetryOptions(telemetryOptions) {
5277
5480
  return {
@@ -5394,6 +5597,9 @@ export {
5394
5597
  VoiceClient,
5395
5598
  VoiceRequestError,
5396
5599
  WhatsAppRequestError,
5600
+ auditComplianceAccess,
5601
+ auditEnabled,
5602
+ auditEventType,
5397
5603
  createContext,
5398
5604
  createEmailClient,
5399
5605
  createHmacToken,
@@ -5446,6 +5652,7 @@ export {
5446
5652
  post,
5447
5653
  put,
5448
5654
  recordMetric,
5655
+ recordRequestAudit,
5449
5656
  resetProcessHandlersForTesting,
5450
5657
  signPlatformEvent,
5451
5658
  smsSegments,
@@ -5454,6 +5661,7 @@ export {
5454
5661
  typedAuthHandler,
5455
5662
  typedHandler,
5456
5663
  validateEmailSendRequest,
5664
+ validateListUnsubscribe,
5457
5665
  validateSmsSend,
5458
5666
  verifyPlatformEvent,
5459
5667
  voiceRequestProblem