@friggframework/core 2.0.0--canary.654.e9530b8.0 → 2.0.0--canary.654.0c31bb1.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/core/CLAUDE.md CHANGED
@@ -158,8 +158,9 @@ class MyIntegration extends Delegate {
158
158
 
159
159
  5. **Flush** (in `runInvocationScope`'s `finally`):
160
160
  ```javascript
161
- // Usage rollup first (unbounded), then telemetry + log sinks in parallel
162
- // under one deadline: min(flushTimeoutMs, remaining time - 50 ms).
161
+ // Telemetry + log sinks under one deadline, fixed at flush start:
162
+ // min(flushTimeoutMs, remaining time - 50 ms). The usage rollup runs in
163
+ // parallel with no bound of its own.
163
164
  ```
164
165
 
165
166
  ### SQS Job Processing Lifecycle
@@ -128,9 +128,19 @@ async function flushInvocation({
128
128
  flushTimeoutMs,
129
129
  context,
130
130
  }) {
131
- // ADR-048 §11: usage first and unbounded, then one deadline for the rest.
132
- await flushUsageRollup(usageRollup, eventSummary, shouldUseDatabase);
131
+ // ADR-048 §11: the deadline is fixed at flush start. The usage rollup
132
+ // emits no telemetry, so it runs in parallel with no bound of its own.
133
+ const deadline = Math.min(
134
+ flushTimeoutMs,
135
+ remainingTimeMs(context) - FLUSH_MARGIN_MS
136
+ );
137
+ await Promise.all([
138
+ flushUsageRollup(usageRollup, eventSummary, shouldUseDatabase),
139
+ flushBounded(telemetry, deadline),
140
+ ]);
141
+ }
133
142
 
143
+ function flushBounded(telemetry, deadline) {
134
144
  const flushes = [];
135
145
  if (isTelemetryEnabled(telemetry)) {
136
146
  flushes.push((signal) => flushTelemetry(telemetry, { signal }));
@@ -138,14 +148,8 @@ async function flushInvocation({
138
148
  if (hasFlushableSinks()) {
139
149
  flushes.push((signal) => flushSinks({ signal }));
140
150
  }
141
- if (!flushes.length) return;
142
-
143
- const deadline = Math.min(
144
- flushTimeoutMs,
145
- remainingTimeMs(context) - FLUSH_MARGIN_MS
146
- );
147
- if (!(deadline > 0)) return;
148
- await withDeadline(deadline, (signal) =>
151
+ if (!flushes.length || !(deadline > 0)) return undefined;
152
+ return withDeadline(deadline, (signal) =>
149
153
  Promise.allSettled(flushes.map((flush) => flush(signal)))
150
154
  );
151
155
  }
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  const { logger } = require('./logger');
12
- const { addDeniedKeys } = require('../../logs/redact');
12
+ const { registerDeniedKeys } = require('../../logs/denied-keys');
13
13
 
14
14
  /**
15
15
  * Core encryption schema (immutable - cannot be overridden by custom schemas)
@@ -121,7 +121,7 @@ function registerCustomSchema(schema) {
121
121
  }
122
122
 
123
123
  customSchema = { ...schema };
124
- addDeniedKeys(Object.values(schema).flatMap((config) => config.fields));
124
+ registerDeniedKeys(Object.values(schema).flatMap((config) => config.fields));
125
125
  logger.info(
126
126
  `Registered custom encryption schema for models: ${Object.keys(customSchema).join(', ')}`
127
127
  );
@@ -151,7 +151,7 @@ function extractCredentialFieldsFromModules(moduleDefinitions) {
151
151
  }
152
152
  }
153
153
 
154
- addDeniedKeys(fields);
154
+ registerDeniedKeys(fields);
155
155
  return [...new Set(fields)];
156
156
  }
157
157
 
@@ -17,9 +17,12 @@ class EncryptionLogger {
17
17
  this.minLevel = this._getMinLevel();
18
18
  }
19
19
 
20
+ // FRIGG_LOG_LEVEL also takes TRACE and FATAL (ADR-048); this logger has
21
+ // no such levels, so they map to its nearest ones.
20
22
  _getMinLevel() {
21
- const level = process.env.FRIGG_LOG_LEVEL || 'INFO';
22
- return LOG_LEVELS[level.toUpperCase()] ?? LOG_LEVELS.INFO;
23
+ const level = (process.env.FRIGG_LOG_LEVEL || 'INFO').trim().toUpperCase();
24
+ const mapped = { TRACE: 'DEBUG', FATAL: 'ERROR' }[level] ?? level;
25
+ return LOG_LEVELS[mapped] ?? LOG_LEVELS.INFO;
23
26
  }
24
27
 
25
28
  _shouldLog(level) {
@@ -2,8 +2,7 @@ const { findNearestBackendPackageJson } = require('@friggframework/core/utils');
2
2
  const path = require('node:path');
3
3
  const fs = require('fs-extra');
4
4
  const { resolveTelemetryConfig } = require('../telemetry/telemetry-config');
5
- const { addDeniedKeys } = require('../logs/redact');
6
- const { getLogger } = require('../logs');
5
+ const { registerDeniedKeys } = require('../logs/denied-keys');
7
6
  const {
8
7
  extractCredentialFieldsFromModules,
9
8
  } = require('../database/encryption/encryption-schema-registry');
@@ -13,32 +12,18 @@ const {
13
12
 
14
13
  // Handlers without a database never load the encryption registry, so the
15
14
  // credential leaf keys are registered for redaction here too.
16
- const warnedIgnoredKeys = new Set();
17
-
18
- function warnIgnoredKeys(ignored) {
19
- for (const key of ignored) {
20
- if (warnedIgnoredKeys.has(key)) continue;
21
- warnedIgnoredKeys.add(key);
22
- getLogger('frigg.logger').warn(
23
- 'Credential field name is a record field; it stays visible in logs',
24
- { eventName: 'frigg.logger.denied_key_ignored', key }
25
- );
26
- }
27
- }
28
-
29
15
  function registerCredentialLogKeys(appDefinition, integrations) {
30
16
  try {
31
- const fields = extractCredentialFieldsFromModules(
17
+ extractCredentialFieldsFromModules(
32
18
  getModulesDefinitionFromIntegrationClasses(integrations)
33
19
  );
34
- warnIgnoredKeys(addDeniedKeys(fields));
35
20
  } catch {
36
21
  // An odd integration shape must not stop the app from loading.
37
22
  }
38
23
  const schema = appDefinition.encryption?.schema;
39
24
  if (schema && typeof schema === 'object') {
40
25
  for (const config of Object.values(schema)) {
41
- warnIgnoredKeys(addDeniedKeys(config?.fields));
26
+ registerDeniedKeys(config?.fields);
42
27
  }
43
28
  }
44
29
  }
@@ -1,5 +1,5 @@
1
1
  const { createHandler } = require('@friggframework/core');
2
- const { createWebsocketConnectionRepository } = require('../../database/websocket-connection-repository-factory');
2
+ const { createWebsocketConnectionRepository } = require('../../websocket/repositories/websocket-connection-repository-factory');
3
3
  const { getLogger } = require('../../logs');
4
4
 
5
5
  const log = getLogger('frigg.websocket');
@@ -0,0 +1,21 @@
1
+ const { addDeniedKeys } = require('./redact');
2
+ const { getLogger } = require('./logger');
3
+
4
+ const warnedKeys = new Set();
5
+
6
+ // addDeniedKeys plus one WARN per record-contract key it had to skip, so a
7
+ // credential field named like a record field is visible wherever it is set.
8
+ function registerDeniedKeys(keys) {
9
+ const ignored = addDeniedKeys(keys);
10
+ for (const key of ignored) {
11
+ if (warnedKeys.has(key)) continue;
12
+ warnedKeys.add(key);
13
+ getLogger('frigg.logger').warn(
14
+ 'Credential field name is a record field; it stays visible in logs',
15
+ { eventName: 'frigg.logger.denied_key_ignored', key }
16
+ );
17
+ }
18
+ return ignored;
19
+ }
20
+
21
+ module.exports = { registerDeniedKeys };
package/logs/record.js CHANGED
@@ -84,6 +84,15 @@ function traceFields(spanContext) {
84
84
  };
85
85
  }
86
86
 
87
+ function sameValue(a, b) {
88
+ if (a === b) return true;
89
+ return (
90
+ typeof a === 'object' &&
91
+ typeof b === 'object' &&
92
+ JSON.stringify(a) === JSON.stringify(b)
93
+ );
94
+ }
95
+
87
96
  function byteLength(record) {
88
97
  return Buffer.byteLength(JSON.stringify(record));
89
98
  }
@@ -182,7 +191,8 @@ function buildRecord({
182
191
  isPlainObject(value)
183
192
  ) {
184
193
  record[key] = extendInvocation(record[key], value);
185
- } else {
194
+ } else if (!sameValue(record[key], value)) {
195
+ // A repeat of the winning value is no conflict.
186
196
  dropped.add(key);
187
197
  }
188
198
  continue;
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.e9530b8.0",
4
+ "version": "2.0.0--canary.654.0c31bb1.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.e9530b8.0",
51
- "@friggframework/prettier-config": "2.0.0--canary.654.e9530b8.0",
52
- "@friggframework/test": "2.0.0--canary.654.e9530b8.0",
50
+ "@friggframework/eslint-config": "2.0.0--canary.654.0c31bb1.0",
51
+ "@friggframework/prettier-config": "2.0.0--canary.654.0c31bb1.0",
52
+ "@friggframework/test": "2.0.0--canary.654.0c31bb1.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": "e9530b84cdbe42939e8061edc241acb55222cf34"
92
+ "gitHead": "0c31bb15ef0497cc321c4d2103a752de6449d234"
93
93
  }
@@ -274,7 +274,7 @@ class UserRepositoryDocumentDB extends UserRepositoryInterface {
274
274
  // Defensive check: verify document was found after update
275
275
  if (!updated) {
276
276
  throw new Error(
277
- 'Failed to update individual user: Document not found after update. ' +
277
+ `Failed to update individual user: Document not found after update (userId ${userId}). ` +
278
278
  'This indicates a database consistency issue.'
279
279
  );
280
280
  }
@@ -308,7 +308,7 @@ class UserRepositoryDocumentDB extends UserRepositoryInterface {
308
308
 
309
309
  if (!updated) {
310
310
  throw new Error(
311
- 'Failed to update organization user: Document not found after update. ' +
311
+ `Failed to update organization user: Document not found after update (userId ${userId}). ` +
312
312
  'This indicates a database consistency issue.'
313
313
  );
314
314
  }