@friggframework/core 2.0.0--canary.654.42e42b2.0 → 2.0.0--canary.654.3cd4278.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/.eslintrc.json CHANGED
@@ -1,3 +1,20 @@
1
1
  {
2
- "extends": "@friggframework/eslint-config"
2
+ "extends": "@friggframework/eslint-config",
3
+ "rules": {
4
+ "no-restricted-syntax": [
5
+ "warn",
6
+ {
7
+ "selector": "CallExpression[callee.object.object.name='process'][callee.object.property.name=/^(stdout|stderr)$/][callee.property.name='write']",
8
+ "message": "Write through the logger; only logs/sinks.js writes to fd 1 (ADR-048 §14)"
9
+ }
10
+ ]
11
+ },
12
+ "overrides": [
13
+ {
14
+ "files": ["*.test.js", "database/utils/prisma-runner.js"],
15
+ "rules": {
16
+ "no-restricted-syntax": "off"
17
+ }
18
+ }
19
+ ]
3
20
  }
package/core/Worker.js CHANGED
@@ -29,28 +29,14 @@ class Worker {
29
29
 
30
30
  for (const record of records) {
31
31
  await runMessageScope(record, async () => {
32
- // Log record entry with SQS-provided attributes useful for tracing
33
- // delivery history (ApproximateReceiveCount for retries, etc.).
34
- let parsedEvent;
35
- try {
36
- parsedEvent = JSON.parse(record.body)?.event;
37
- } catch {
38
- parsedEvent = undefined;
39
- }
40
- console.log(`[Worker] record begin`, {
41
- messageId: record.messageId,
42
- event: parsedEvent,
43
- receiveCount: record.attributes?.ApproximateReceiveCount,
44
- });
32
+ // messageId, receiveCount and the event come from the scope.
33
+ log.debug('Record started', { eventName: 'frigg.worker.record_started' });
45
34
 
46
35
  try {
47
36
  const runParams = JSON.parse(record.body);
48
37
  this._validateParams(runParams);
49
38
  await this._run(runParams, context);
50
- console.log(`[Worker] record success`, {
51
- messageId: record.messageId,
52
- event: runParams?.event,
53
- });
39
+ log.debug('Record succeeded', { eventName: 'frigg.worker.record_succeeded' });
54
40
  } catch (error) {
55
41
  if (error.isHaltError) {
56
42
  // HaltError means "discard this message, don't retry".
@@ -6,7 +6,7 @@ const { getLogger, toSanitizedSurrogate } = require('../logs');
6
6
  const {
7
7
  summarizeLambdaEvent,
8
8
  toScopeInvocation,
9
- toRequestDetails,
9
+ toRequestInvocation,
10
10
  } = require('../logs/summarize-event');
11
11
  const {
12
12
  runInvocationScope,
@@ -58,6 +58,8 @@ const createHandler = (optionByName = {}) => {
58
58
  routeKey: eventSummary.routeKey,
59
59
  invocation: toScopeInvocation(eventSummary),
60
60
  };
61
+ // The full redacted summary goes only on the entry and failure records.
62
+ const requestInvocation = toRequestInvocation(eventSummary);
61
63
 
62
64
  return runInvocationScope(
63
65
  scopeFields,
@@ -65,7 +67,7 @@ const createHandler = (optionByName = {}) => {
65
67
  try {
66
68
  log.info('Handler invoked', {
67
69
  eventName: 'frigg.handler.invoked',
68
- ...toRequestDetails(eventSummary),
70
+ invocation: requestInvocation,
69
71
  });
70
72
 
71
73
  // If enabled (i.e. if SECRET_ARN is set in process.env) Fetch secrets from AWS Secrets Manager, and set them as environment variables.
@@ -86,45 +88,28 @@ const createHandler = (optionByName = {}) => {
86
88
  // Run the Lambda
87
89
  return await method(event, context);
88
90
  } catch (error) {
89
- // Don't leak implementation details to end users.
90
- if (isUserFacingResponse) {
91
- // Allow client-safe errors to pass through with their actual message
92
- if (error.isClientSafe === true) {
93
- const statusCode = error.statusCode || 400;
94
- log.warn('Request rejected', {
95
- eventName: 'frigg.handler.rejected',
96
- statusCode,
97
- error,
98
- });
99
- return {
100
- statusCode,
101
- body: JSON.stringify({
102
- error: error.message,
103
- }),
104
- };
105
- }
106
-
107
- log.error('Handler failed', {
108
- eventName: 'frigg.handler.failed',
91
+ // Allow client-safe errors to pass through with their actual message
92
+ if (isUserFacingResponse && error.isClientSafe === true) {
93
+ const statusCode = error.statusCode || 400;
94
+ log.warn('Request rejected', {
95
+ eventName: 'frigg.handler.rejected',
96
+ statusCode,
109
97
  error,
110
98
  });
111
-
112
- // Hide other errors with generic message
113
99
  return {
114
- statusCode: 500,
100
+ statusCode,
115
101
  body: JSON.stringify({
116
- error: 'An Internal Error Occurred',
102
+ error: error.message,
117
103
  }),
118
104
  };
119
105
  }
120
106
 
121
- // Handle server-to-server responses.
122
-
123
- // Halt errors succeed and won't be retried, so the halt
124
- // itself is the failure record.
125
- if (error.isHaltError === true) {
107
+ // Server-to-server: halt errors succeed and won't be
108
+ // retried, so the halt itself is the failure record.
109
+ if (!isUserFacingResponse && error.isHaltError === true) {
126
110
  log.error('Handler halted', {
127
111
  eventName: 'frigg.handler.halted',
112
+ invocation: requestInvocation,
128
113
  error,
129
114
  });
130
115
  return;
@@ -132,8 +117,20 @@ const createHandler = (optionByName = {}) => {
132
117
 
133
118
  log.error('Handler failed', {
134
119
  eventName: 'frigg.handler.failed',
120
+ invocation: requestInvocation,
135
121
  error,
136
122
  });
123
+
124
+ // Don't leak implementation details to end users.
125
+ if (isUserFacingResponse) {
126
+ return {
127
+ statusCode: 500,
128
+ body: JSON.stringify({
129
+ error: 'An Internal Error Occurred',
130
+ }),
131
+ };
132
+ }
133
+
137
134
  // The Lambda runtime writes a rethrown error itself, so
138
135
  // rethrow a sanitized copy (ADR-048 §6 item 11).
139
136
  throw toSanitizedSurrogate(error);
@@ -1,5 +1,6 @@
1
1
  const { createHandler } = require('@friggframework/core');
2
2
  const { getLogger } = require('../logs');
3
+ const { summarizeExpressRequest } = require('../logs/summarize-event');
3
4
  const express = require('express');
4
5
  const bodyParser = require('body-parser');
5
6
  const cors = require('cors');
@@ -35,6 +36,7 @@ const createApp = (applyMiddleware) => {
35
36
  log.error('Request failed', {
36
37
  eventName: 'frigg.http.request_failed',
37
38
  statusCode,
39
+ invocation: summarizeExpressRequest(req),
38
40
  error: boomError,
39
41
  });
40
42
  res.status(statusCode).json({ error: 'Internal Server Error' });
@@ -273,11 +273,6 @@ const createQueueWorker = (integrationClass) => {
273
273
  return;
274
274
  }
275
275
 
276
- console.error(
277
- `Error in ${params.event} for ${integrationName}:`,
278
- error
279
- );
280
-
281
276
  // 4xx HTTP errors are permanent — the requester already
282
277
  // attempted token refresh (401) and backoff (429/5xx).
283
278
  // By the time a 4xx reaches here, retrying won't help.
@@ -27,24 +27,22 @@ function extractQueueName(eventSourceARN) {
27
27
  // The body can hold credentials, so only its size and digest are logged.
28
28
  function describeBody(body) {
29
29
  const text = typeof body === 'string' ? body : String(body ?? '');
30
+ let parsed = true;
31
+ try {
32
+ JSON.parse(text);
33
+ } catch {
34
+ parsed = false;
35
+ }
30
36
  return {
37
+ parsed,
31
38
  bodyLength: Buffer.byteLength(text),
32
39
  bodySha256: crypto.createHash('sha256').update(text).digest('hex'),
33
40
  };
34
41
  }
35
42
 
36
- function isJson(body) {
37
- try {
38
- JSON.parse(body);
39
- return true;
40
- } catch {
41
- return false;
42
- }
43
- }
44
-
45
43
  function logRecord(record) {
46
- const body = describeBody(record.body);
47
- if (!isJson(record.body)) {
44
+ const { parsed, ...body } = describeBody(record.body);
45
+ if (!parsed) {
48
46
  log.warn('DLQ message body is not JSON', {
49
47
  eventName: 'frigg.queue.dlq.body_unparsed',
50
48
  ...body,
@@ -778,7 +778,7 @@ class IntegrationBase {
778
778
  if (this.events[eventName]) {
779
779
  if (typeof handlers[eventName] === 'string') {
780
780
  this.logger.warn(
781
- `Binding handler "${handlers[eventName]}" for event "${eventName}" is ignored because this.events["${eventName}"] was already set`,
781
+ 'Binding handler is ignored: the event is already set',
782
782
  {
783
783
  eventName: `${this.logger.name}.extension_handler_shadowed`,
784
784
  bindingName,
@@ -1,22 +1,59 @@
1
- // Fake values only. They avoid real provider prefixes so push protection stays quiet.
1
+ // Fake values, generated at load time so secret scanners find no literal in
2
+ // the source. A fixed seed per name keeps every run identical.
3
+ const MIXED = 'BCDFGHJKLMNPQRSTVWXZbcdfghjkmnpqrstvwxz23456789';
4
+ const HEX = '0123456789abcdef';
5
+
6
+ function seededRandom(name) {
7
+ let seed = [...name].reduce(
8
+ (h, c) => Math.imul(h ^ c.charCodeAt(0), 16777619),
9
+ 2166136261
10
+ );
11
+ return () => {
12
+ seed = (seed + 0x6d2b79f5) | 0;
13
+ let t = Math.imul(seed ^ (seed >>> 15), seed | 1);
14
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
15
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
16
+ };
17
+ }
18
+
19
+ function draw(name, alphabet, length) {
20
+ const next = seededRandom(name);
21
+ let out = '';
22
+ for (let i = 0; i < length; i += 1) {
23
+ out += alphabet[Math.floor(next() * alphabet.length)];
24
+ }
25
+ return out;
26
+ }
27
+
28
+ // The fixed 'Zq' + 'B7k' start puts upper case, lower case and a digit in every token.
29
+ function fakeToken(name, length = 28) {
30
+ return `ZqB7k${draw(name, MIXED, length - 5)}`;
31
+ }
32
+
33
+ const base64url = (value) => Buffer.from(value).toString('base64url');
34
+
2
35
  const SECRETS = Object.freeze({
3
- bearer: 'ZqvbLv8tZrQ8kNM5Jq4SRrvrMpRC',
4
- apiKeyQuery: 'ZqGkGLq6NLF7vXc7cJTWV4BwX3Ld',
5
- friggApiKey: 'Zq4kPQhzWCfJXwXxq7KKSMcpt5bk',
6
- cookie: 'ZqKBfkjbBx63hpkzVMfQnw8S3wNS',
7
- oauthCode: 'ZqMNW4x97kwsvhwk48BnsXD3JCCB',
8
- codeVerifier: 'Zq64cfH4zmrmJmxHpNRG6z8QTdDh',
9
- password: 'ZqtkDW2Cbh5kH7SnbK4FGQLRxJLT',
10
- clientSecret: 'ZqVbQLw3kDGwxHgrBW32MKLHJgtr',
11
- accessToken: 'Zqnw6cnh5ZXpQLB5njSxZKQDPhJc',
12
- refreshToken: 'ZqhDdt92NR5cvMsvQ6DFmM2qfJFS',
13
- idToken: 'ZqCGFMSgJDmq8NKJ6ZG4vSjVd6Gw',
14
- hashword: 'ZqvVPT8cjhxtt9cddR2NqrHSzP66',
15
- dbPassword: 'ZqZkGTVwpk38sVnHhKZzWFg9djPW',
16
- signature: 'Zq7b634PtgG9Ft4BJ9cjrMTZXjNX',
17
- jwt: 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJmYWtlLXVzZXItMTIzNDUifQ.ZmFrZVNpZ25hdHVyZUZvclRlc3RzT25seQ',
18
- hexToken: '9f2c4e7a1b3d5f8e0a6c2b4d7e9f1a3c5b7d9e2f',
19
- base64Run: 'QmFzZTY0RmFrZVNlY3JldFZhbHVlRm9yVGVzdHNPbmx5MTIzNDU2Nzg5MA==',
36
+ bearer: fakeToken('bearer'),
37
+ apiKeyQuery: fakeToken('apiKeyQuery'),
38
+ friggApiKey: fakeToken('friggApiKey'),
39
+ cookie: fakeToken('cookie'),
40
+ oauthCode: fakeToken('oauthCode'),
41
+ codeVerifier: fakeToken('codeVerifier'),
42
+ password: fakeToken('password'),
43
+ clientSecret: fakeToken('clientSecret'),
44
+ accessToken: fakeToken('accessToken'),
45
+ refreshToken: fakeToken('refreshToken'),
46
+ idToken: fakeToken('idToken'),
47
+ hashword: fakeToken('hashword'),
48
+ dbPassword: fakeToken('dbPassword'),
49
+ signature: fakeToken('signature'),
50
+ jwt: [
51
+ base64url(JSON.stringify({ alg: 'HS256' })),
52
+ base64url(JSON.stringify({ sub: `fake-${draw('jwtSub', MIXED, 8)}` })),
53
+ base64url(draw('jwtSig', MIXED, 24)),
54
+ ].join('.'),
55
+ hexToken: draw('hexToken', HEX, 40),
56
+ base64Run: Buffer.from(draw('base64Run', MIXED, 42)).toString('base64'),
20
57
  });
21
58
 
22
- module.exports = { SECRETS };
59
+ module.exports = { SECRETS, fakeToken };
@@ -1,14 +1,14 @@
1
1
  // ADR-048 §14 redaction vectors. Each vector is one log call that carries
2
2
  // secrets; the suite asserts that no 8-char window of them reaches a record.
3
- const { SECRETS: S } = require('./secrets');
3
+ const { SECRETS: S, fakeToken } = require('./secrets');
4
4
  const { httpApiV2Event, restV1Event, sqsEvent } = require('./events');
5
5
  const { summarizeLambdaEvent } = require('../summarize-event');
6
6
 
7
7
  const secretUrl = `https://api.example.com/v1/items?api_key=${S.apiKeyQuery}`;
8
- const SIGNATURE_VALUE = 'fakeSgnValue2Gh9Tc5Wp7LmQx3';
8
+ const SIGNATURE_VALUE = fakeToken('signatureValue', 27);
9
9
 
10
10
  // Built by concatenation so push protection does not match the prefixes.
11
- const TOKEN_TAIL = 'ZqPr3fixTail8Hn2Kd8Ws1Yc6';
11
+ const TOKEN_TAIL = fakeToken('prefixTail', 25);
12
12
  const PREFIXED_TOKENS = [
13
13
  'sk_' + 'live_' + TOKEN_TAIL,
14
14
  'whsec' + '_' + TOKEN_TAIL,
@@ -3,8 +3,7 @@ const { getLogger } = require('./logger');
3
3
  const { getContext } = require('./context');
4
4
  const {
5
5
  summarizeLambdaEvent,
6
- toScopeInvocation,
7
- toRequestDetails,
6
+ toRequestInvocation,
8
7
  } = require('./summarize-event');
9
8
 
10
9
  // Deprecated. Kept for one major version; use getLogger or this.logger.
@@ -32,10 +31,8 @@ function initDebugLog(...initMessages) {
32
31
  const log = legacy();
33
32
  if (!log.isLevelEnabled('DEBUG')) return;
34
33
  const event = initMessages.find((m) => m && typeof m === 'object');
35
- const summary = summarizeLambdaEvent(event);
36
34
  log.debug('Debug log initialized', {
37
- invocation: toScopeInvocation(summary),
38
- ...toRequestDetails(summary),
35
+ invocation: toRequestInvocation(summarizeLambdaEvent(event)),
39
36
  });
40
37
  }
41
38
 
package/logs/record.js CHANGED
@@ -78,6 +78,29 @@ function buildMessage(message, callSite) {
78
78
  return '[non-string message]';
79
79
  }
80
80
 
81
+ const INVOCATION_KEY = 'invocation';
82
+
83
+ function isPlainObject(value) {
84
+ return (
85
+ value !== null &&
86
+ typeof value === 'object' &&
87
+ Object.getPrototypeOf(value) === Object.prototype
88
+ );
89
+ }
90
+
91
+ // A boundary adds request detail to the scope's invocation; scope keys win.
92
+ function extendInvocation(scoped, detail, dropped) {
93
+ const merged = { ...scoped };
94
+ for (const [key, value] of Object.entries(detail)) {
95
+ if (!Object.prototype.hasOwnProperty.call(scoped, key)) {
96
+ merged[key] = value;
97
+ } else if (JSON.stringify(scoped[key]) !== JSON.stringify(value)) {
98
+ dropped.add(`${INVOCATION_KEY}.${key}`);
99
+ }
100
+ }
101
+ return merged;
102
+ }
103
+
81
104
  function traceFields(spanContext) {
82
105
  if (
83
106
  !spanContext ||
@@ -135,7 +158,8 @@ function buildRecord({
135
158
  OWNED_KEYS.has(key) ||
136
159
  RESERVED_KEYS.has(key) ||
137
160
  (dropPayloads && PAYLOAD_KEYS.has(key)) ||
138
- Object.prototype.hasOwnProperty.call(record, key)
161
+ (Object.prototype.hasOwnProperty.call(record, key) &&
162
+ !canExtendInvocation(key, value))
139
163
  ) {
140
164
  dropped.add(key);
141
165
  continue;
@@ -149,10 +173,18 @@ function buildRecord({
149
173
  const value =
150
174
  key === 'error' ? serializeError(accepted[key]) : safe?.[key];
151
175
  if (value === undefined || value === null) continue;
176
+ if (key === INVOCATION_KEY && isPlainObject(record[key])) {
177
+ record[key] = extendInvocation(record[key], value, dropped);
178
+ continue;
179
+ }
152
180
  record[key] = value;
153
181
  if (isCallSite) callSiteKeys.push(key);
154
182
  }
155
183
  };
184
+ const canExtendInvocation = (key, value) =>
185
+ key === INVOCATION_KEY &&
186
+ isPlainObject(record[key]) &&
187
+ isPlainObject(value);
156
188
  place(readFields(scope), false);
157
189
  place(readFields(bindings), false);
158
190
  place(callSite, true);
package/logs/redact.js CHANGED
@@ -56,7 +56,9 @@ const MAX_SCRUB_INPUT = 65536;
56
56
  const URL_IN_TEXT = /\b[a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^\s"'<>`]+/g;
57
57
  const URL_TRAILING_PUNCT = /[.,;:!?)\]}]+$/;
58
58
  const ABSOLUTE_URL = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//;
59
- const ROOT_SLASH = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^/?#]*\//;
59
+ const RAW_ABSOLUTE_PATH = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^/?#]*([^?#]*)/;
60
+ const RAW_RELATIVE_PATH = /^[^?#]*/;
61
+ const PATH_TOKEN_SEGMENT = /^[A-Za-z0-9_.~-]{20,}$/;
60
62
  const PREFIXED_TOKEN =
61
63
  /(?<![A-Za-z0-9_-])(?:(?:sk|rk)_(?:live|test)_|whsec_|xox[abprs]-|gh[opsu]_|github_pat_|shp(?:at|ss)_|glpat-|npm_)[A-Za-z0-9_-]{16,}|\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g;
62
64
  const JWT = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*/g;
@@ -196,11 +198,18 @@ function redactHash(hash) {
196
198
  return scrubTokens(hash);
197
199
  }
198
200
 
201
+ // A long segment mixing upper case, lower case and digits is an opaque
202
+ // token (webhook secrets live in paths); hex ids and UUIDs lack one class.
203
+ function redactPathSegment(segment) {
204
+ if (PATH_TOKEN_SEGMENT.test(segment) && hasMixedClasses(segment)) {
205
+ return redacted(segment);
206
+ }
207
+ return scrubTokens(segment, { segment: true });
208
+ }
209
+
210
+ // The raw path, not URL#pathname, which percent-encodes `{proxy+}`.
199
211
  function redactPath(pathname) {
200
- return pathname
201
- .split('/')
202
- .map((segment) => scrubTokens(segment, { segment: true }))
203
- .join('/');
212
+ return pathname.split('/').map(redactPathSegment).join('/');
204
213
  }
205
214
 
206
215
  function redactUrlFallback(raw) {
@@ -221,16 +230,15 @@ function redactUrl(url) {
221
230
  try {
222
231
  if (ABSOLUTE_URL.test(raw)) {
223
232
  const u = new URL(raw);
224
- const path =
225
- u.pathname === '/' && !ROOT_SLASH.test(raw) ? '' : u.pathname;
233
+ const path = raw.match(RAW_ABSOLUTE_PATH)[1];
226
234
  return `${u.protocol}//${u.host}${redactPath(path)}${redactParams(
227
235
  u.search,
228
236
  '?'
229
237
  )}${redactHash(u.hash)}`;
230
238
  }
231
239
  if (raw.startsWith('/')) {
232
- const u = new URL(raw, 'http://relative.invalid');
233
- return `${redactPath(u.pathname)}${redactParams(
240
+ const u = new URL(raw, 'https://relative.invalid');
241
+ return `${redactPath(raw.match(RAW_RELATIVE_PATH)[0])}${redactParams(
234
242
  u.search,
235
243
  '?'
236
244
  )}${redactHash(u.hash)}`;
@@ -1,3 +1,5 @@
1
+ const { redactUrl } = require('./redact');
2
+
1
3
  // Best-effort extraction of the logical event/processId/integrationId from a
2
4
  // JSON message body. Used only for log correlation. Never throws.
3
5
  function summarizeMessageBody(bodyStr) {
@@ -118,19 +120,43 @@ function toScopeInvocation(summary) {
118
120
  return { source: summary.source };
119
121
  }
120
122
 
121
- // Per-request detail for the one entry record, never for the scope.
123
+ // Per-request detail for the entry and failure records, never for the
124
+ // scope. A raw path can hold a token, so it goes through redactUrl.
122
125
  function toRequestDetails(summary) {
123
126
  if (!summary || summary.source !== 'http') return {};
124
127
  return {
125
- path: summary.path,
128
+ path: redactUrl(summary.path ?? ''),
126
129
  queryKeys: summary.queryKeys ?? [],
127
130
  headerNames: summary.headerNames ?? [],
128
131
  };
129
132
  }
130
133
 
134
+ // The full redacted request summary (ADR-048 §6: it goes under `invocation`).
135
+ function toRequestInvocation(summary) {
136
+ return { ...toScopeInvocation(summary), ...toRequestDetails(summary) };
137
+ }
138
+
139
+ // The same shape from an express request, for the express error boundary.
140
+ function summarizeExpressRequest(req) {
141
+ try {
142
+ if (!req || typeof req !== 'object') return { source: 'http' };
143
+ return {
144
+ source: 'http',
145
+ method: req.method,
146
+ path: redactUrl(req.path ?? ''),
147
+ queryKeys: objectKeys(req.query),
148
+ headerNames: unique(objectKeys(req.headers).map((name) => name.toLowerCase())),
149
+ };
150
+ } catch {
151
+ return { source: 'http' };
152
+ }
153
+ }
154
+
131
155
  module.exports = {
132
156
  summarizeLambdaEvent,
133
157
  summarizeMessageBody,
134
158
  toScopeInvocation,
135
159
  toRequestDetails,
160
+ toRequestInvocation,
161
+ summarizeExpressRequest,
136
162
  };
@@ -357,7 +357,7 @@ class OAuth2Requester extends Requester {
357
357
  eventName: `${this.logger.name}.token_refresh_failed`,
358
358
  statusCode:
359
359
  error?.statusCode ?? error?.status ?? error?.response?.status,
360
- error,
360
+ reason: error?.message,
361
361
  });
362
362
 
363
363
  if (!this._isDefinitiveAuthRejection(error)) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@friggframework/core",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.654.42e42b2.0",
4
+ "version": "2.0.0--canary.654.3cd4278.0",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
7
7
  "@aws-sdk/client-kms": "^3.588.0",
@@ -47,9 +47,9 @@
47
47
  }
48
48
  },
49
49
  "devDependencies": {
50
- "@friggframework/eslint-config": "2.0.0--canary.654.42e42b2.0",
51
- "@friggframework/prettier-config": "2.0.0--canary.654.42e42b2.0",
52
- "@friggframework/test": "2.0.0--canary.654.42e42b2.0",
50
+ "@friggframework/eslint-config": "2.0.0--canary.654.3cd4278.0",
51
+ "@friggframework/prettier-config": "2.0.0--canary.654.3cd4278.0",
52
+ "@friggframework/test": "2.0.0--canary.654.3cd4278.0",
53
53
  "@prisma/client": "^6.19.3",
54
54
  "@types/lodash": "4.17.15",
55
55
  "@typescript-eslint/eslint-plugin": "^8.0.0",
@@ -89,5 +89,5 @@
89
89
  "publishConfig": {
90
90
  "access": "public"
91
91
  },
92
- "gitHead": "42e42b2b48e90e9b46ffe6bcf009150a66a010cd"
92
+ "gitHead": "3cd42784313ae866c499d7c7549b7c28e39d3045"
93
93
  }
@@ -148,13 +148,8 @@ class UserRepositoryDocumentDB extends UserRepositoryInterface {
148
148
 
149
149
  // Defensive check: verify document was found after insert
150
150
  if (!created) {
151
- log.error('User not found after insert', {
152
- eventName: 'frigg.user.not_found_after_insert',
153
- insertedId: fromObjectId(insertedId),
154
- paramKeys: Object.keys(params),
155
- });
156
151
  throw new Error(
157
- 'Failed to create individual user: Document not found after insert. ' +
152
+ `Failed to create individual user: Document not found after insert (insertedId ${fromObjectId(insertedId)}). ` +
158
153
  'This indicates a database consistency issue.'
159
154
  );
160
155
  }
@@ -192,13 +187,8 @@ class UserRepositoryDocumentDB extends UserRepositoryInterface {
192
187
 
193
188
  // Defensive check: verify document was found after insert
194
189
  if (!created) {
195
- log.error('Organization user not found after insert', {
196
- eventName: 'frigg.user.organization_not_found_after_insert',
197
- insertedId: fromObjectId(insertedId),
198
- paramKeys: Object.keys(params),
199
- });
200
190
  throw new Error(
201
- 'Failed to create organization user: Document not found after insert. ' +
191
+ `Failed to create organization user: Document not found after insert (insertedId ${fromObjectId(insertedId)}). ` +
202
192
  'This indicates a database consistency issue.'
203
193
  );
204
194
  }
@@ -284,7 +274,8 @@ class UserRepositoryDocumentDB extends UserRepositoryInterface {
284
274
  // Defensive check: verify document was found after update
285
275
  if (!updated) {
286
276
  throw new Error(
287
- `Failed to update user ${userId}: not found after update`
277
+ 'Failed to update individual user: Document not found after update. ' +
278
+ 'This indicates a database consistency issue.'
288
279
  );
289
280
  }
290
281
 
@@ -317,7 +308,8 @@ class UserRepositoryDocumentDB extends UserRepositoryInterface {
317
308
 
318
309
  if (!updated) {
319
310
  throw new Error(
320
- `Failed to update user ${userId}: not found after update`
311
+ 'Failed to update organization user: Document not found after update. ' +
312
+ 'This indicates a database consistency issue.'
321
313
  );
322
314
  }
323
315