biz-a-cli 2.3.80-15238 → 2.3.80-15267

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.
@@ -70,7 +70,7 @@ export function directHubEvent(serverSocket, argv) {
70
70
  clientListener(clientSocket, argv);
71
71
  clientSocket.join(CLIENT_ROOM);
72
72
 
73
- // for ESB clientEmitter
73
+ // isolated Client in a room using clientInstanceId
74
74
  const clientInstanceId =
75
75
  clientSocket.handshake.query.clientInstanceId;
76
76
  if (clientInstanceId) {
@@ -79,6 +79,16 @@ export function directHubEvent(serverSocket, argv) {
79
79
  ClientEmitter.acknowledgeMessage(msgId, clientInstanceId);
80
80
  });
81
81
  }
82
+
83
+ // isolated Client in a room using argv.subdomain and UserId
84
+ const userId = clientSocket.handshake.query.userId;
85
+ if (
86
+ userId !== undefined &&
87
+ userId !== null &&
88
+ String(userId) !== "-1"
89
+ ) {
90
+ clientSocket.join(`${argv.subdomain}_USER_${userId}`);
91
+ }
82
92
  } else if (clientSocket.handshake.query.isDeploy) {
83
93
  setConnectListeners(clientSocket);
84
94
  deploymentListenerForVSCode(clientSocket, argv);
@@ -99,7 +109,11 @@ export function createSocketServer(httpServer, cliIpAddress = "127.0.0.1") {
99
109
  /\.vscode-cdn\.net$/,
100
110
  ].concat(
101
111
  process.env.NODE_ENV === "production"
102
- ? [`http://${cliIpAddress}:4200`, "http://localhost:4200", "http://localhost"]
112
+ ? [
113
+ `http://${cliIpAddress}:4200`,
114
+ "http://localhost:4200",
115
+ "http://localhost",
116
+ ]
103
117
  : [`http://${cliIpAddress}:4200`, "http://localhost:4200"],
104
118
  ),
105
119
  },
package/bin/hub.js CHANGED
@@ -295,7 +295,7 @@ if (argv.mode.trim().toLowerCase() === "agent") {
295
295
  // directHubEvent(createSocketServer(server, argv.cliAddress().ip), argv);
296
296
  directHubEvent(ioServerInstance, argv);
297
297
 
298
- ClientEmitter.init(ioServerInstance, socket);
298
+ ClientEmitter.init(ioServerInstance, socket, argv.subdomain);
299
299
 
300
300
  bootMicroservices(argv, ioServerInstance);
301
301
 
package/bin/hubEvent.js CHANGED
@@ -305,7 +305,8 @@ const EngineRegistry = {
305
305
  "bpm/workflowRT": () => import("../engine/bpm/workflow-runtime.js"),
306
306
  "domain/system": () => import("../engine/domain/system.js"),
307
307
  "domain/appConfig": () => import("../engine/domain/appConfig.js"),
308
- "domain/applicationConfig": () => import("../engine/domain/applicationConfig.js"),
308
+ "domain/applicationConfig": () =>
309
+ import("../engine/domain/applicationConfig.js"),
309
310
  };
310
311
 
311
312
  const handleCliCommand = async (data, cb, argv) => {
@@ -364,12 +365,42 @@ const handleCliCommand = async (data, cb, argv) => {
364
365
  cb(null, { success: true, data: result });
365
366
  break;
366
367
  }
368
+ case "emitter": {
369
+ const requestData = data.data || {};
370
+ const { action, userIds, groupIds, eventName, payload } =
371
+ requestData;
372
+
373
+ if (action === "emitUsers" && Array.isArray(userIds)) {
374
+ ClientEmitter.emitUsers(userIds, eventName, payload).catch(
375
+ (err) => console.error("[ESB] emitUsers Error:", err),
376
+ );
377
+ } else if (
378
+ action === "emitUserGroups" &&
379
+ Array.isArray(groupIds)
380
+ ) {
381
+ ClientEmitter.emitUserGroups(
382
+ groupIds,
383
+ eventName,
384
+ payload,
385
+ ).catch((err) =>
386
+ console.error("[ESB] emitUserGroups Error:", err),
387
+ );
388
+ } else if (action === "broadcastClient") {
389
+ ClientEmitter.broadcastClient(eventName, payload);
390
+ }
367
391
 
392
+ cb(null, {
393
+ success: true,
394
+ message: `Handed over ${action} to ESB ClientEmitter`,
395
+ });
396
+ break;
397
+ }
368
398
  default:
369
399
  cb(`Unknown CLI command '${command}'`, null);
370
400
  }
371
401
  } catch (err) {
372
402
  cb(err.message || err, null);
403
+ // console.error("[CLICommand] Error:", err);
373
404
  }
374
405
  };
375
406
 
@@ -1066,6 +1066,51 @@ export const buildGeneratorSyncPlan = (table, generatorRows, triggerRows) => {
1066
1066
  };
1067
1067
  };
1068
1068
 
1069
+ export const buildAppendOnlyStructureQueries = (table) => {
1070
+ const triggerName = toDbName(table.appendOnlyTriggerName ?? buildObjectName(table.name, "NOUPD"));
1071
+ return {
1072
+ exceptionSql: [
1073
+ "SELECT COUNT(*) AS EXCEPTION_EXISTS",
1074
+ "FROM RDB$EXCEPTIONS",
1075
+ `WHERE UPPER(TRIM(RDB$EXCEPTION_NAME)) = '${firebirdDdl.APPEND_ONLY_EXCEPTION_NAME}';`,
1076
+ ].join(" "),
1077
+ triggerSql: [
1078
+ "SELECT COUNT(*) AS TRIGGER_EXISTS",
1079
+ "FROM RDB$TRIGGERS T",
1080
+ `WHERE UPPER(TRIM(T.RDB$TRIGGER_NAME)) = '${triggerName}'`,
1081
+ " AND COALESCE(T.RDB$SYSTEM_FLAG, 0) = 0;",
1082
+ ].join(" "),
1083
+ };
1084
+ };
1085
+
1086
+ export const buildAppendOnlySyncPlan = (table, exceptionRows, triggerRows) => {
1087
+ if (!table?.appendOnly) {
1088
+ return { statements: [], differences: [] };
1089
+ }
1090
+ const triggerName = toDbName(table.appendOnlyTriggerName ?? buildObjectName(table.name, "NOUPD"));
1091
+ const statements = [];
1092
+ const differences = [];
1093
+
1094
+ const exceptionExists =
1095
+ Number(exceptionRows?.[0]?.["EXCEPTION_EXISTS"] ?? exceptionRows?.[0]?.["exception_exists"] ?? 0) === 1;
1096
+ const triggerExists =
1097
+ Number(triggerRows?.[0]?.["TRIGGER_EXISTS"] ?? triggerRows?.[0]?.["trigger_exists"] ?? 0) === 1;
1098
+
1099
+ if (!exceptionExists) {
1100
+ differences.push(`Exception "${firebirdDdl.APPEND_ONLY_EXCEPTION_NAME}" is missing.`);
1101
+ statements.push(firebirdDdl.buildCreateAppendOnlyExceptionStatement());
1102
+ }
1103
+ if (!triggerExists) {
1104
+ differences.push(`Append-only trigger "${triggerName}" is missing.`);
1105
+ statements.push(firebirdDdl.buildAppendOnlyTriggerStatement(table.name, triggerName));
1106
+ }
1107
+
1108
+ return {
1109
+ statements: [...new Set(statements.map((statement) => toSingleLine(statement)))],
1110
+ differences,
1111
+ };
1112
+ };
1113
+
1069
1114
  export const buildDomainDdlPreview = (domainConfigContent, defaultDbIndex) => {
1070
1115
  const buildResult = buildDomainSchemaInternal(domainConfigContent, defaultDbIndex);
1071
1116
  const schema = buildResult.schema;
@@ -1373,6 +1418,11 @@ const buildEntityTable = (entityName, entity, defaultDbIndex) => {
1373
1418
  table.triggerName = buildObjectName(tableName, "BI");
1374
1419
  }
1375
1420
 
1421
+ if (entity?.appendOnly === true) {
1422
+ table.appendOnly = true;
1423
+ table.appendOnlyTriggerName = buildObjectName(tableName, "NOUPD");
1424
+ }
1425
+
1376
1426
  return {
1377
1427
  entityName,
1378
1428
  tableName,
@@ -2057,6 +2107,20 @@ const loadGeneratorSyncPlan = async (table, dbIndex, config) => {
2057
2107
  return buildGeneratorSyncPlan(table, generatorRows, triggerRows);
2058
2108
  };
2059
2109
 
2110
+ const loadAppendOnlySyncPlan = async (table, dbIndex, config) => {
2111
+ if (!table?.appendOnly) {
2112
+ return { statements: [], differences: [] };
2113
+ }
2114
+
2115
+ const queries = buildAppendOnlyStructureQueries(table);
2116
+ const [exceptionRows, triggerRows] = await Promise.all([
2117
+ execRawSql(queries.exceptionSql, dbIndex, config),
2118
+ execRawSql(queries.triggerSql, dbIndex, config),
2119
+ ]);
2120
+
2121
+ return buildAppendOnlySyncPlan(table, exceptionRows, triggerRows);
2122
+ };
2123
+
2060
2124
  const toFirebird25CompatibleDdl = (statement) => {
2061
2125
  const sanitizedStatement = sanitizeSql(statement);
2062
2126
  const notNullMatch = sanitizedStatement.match(
@@ -2187,6 +2251,22 @@ const applyGeneratorSyncPlan = async (table, generatorSyncPlan, dbIndex, config,
2187
2251
  return null;
2188
2252
  };
2189
2253
 
2254
+ const applyAppendOnlySyncPlan = async (table, appendOnlySyncPlan, dbIndex, config, ctx) => {
2255
+ appendDryRunReport(ctx.report, ctx.migrationMode, table.name, "append-only-sync", {
2256
+ statements: appendOnlySyncPlan.statements,
2257
+ warnings: [],
2258
+ differences: appendOnlySyncPlan.differences,
2259
+ });
2260
+ if (appendOnlySyncPlan.statements.length === 0) {
2261
+ return null;
2262
+ }
2263
+ if (appendOnlySyncPlan.differences.length > 0) {
2264
+ console.log(`[AppConfig Publish] Table ${table.name} append-only:`, appendOnlySyncPlan.differences);
2265
+ }
2266
+ await executeSqlStatements(appendOnlySyncPlan.statements, dbIndex, config, ctx);
2267
+ return null;
2268
+ };
2269
+
2190
2270
  const splitSqlStatements = (sqlBatch) => {
2191
2271
  const source = sqlBatch ?? "";
2192
2272
  const statements = [];
@@ -2253,6 +2333,9 @@ const syncTable = async (table, ctx) => {
2253
2333
  const generatorSyncPlan = await loadGeneratorSyncPlan(table, tableDbIndex, ctx.config);
2254
2334
  await applyGeneratorSyncPlan(table, generatorSyncPlan, tableDbIndex, ctx.config, ctx);
2255
2335
 
2336
+ const createAppendOnlyPlan = await loadAppendOnlySyncPlan(table, tableDbIndex, ctx.config);
2337
+ await applyAppendOnlySyncPlan(table, createAppendOnlyPlan, tableDbIndex, ctx.config, ctx);
2338
+
2256
2339
  const latestSnapshot = await loadTableSnapshot(table.name, true, tableDbIndex, ctx.config);
2257
2340
  const postCreateSyncPlan = buildTableSyncPlan(table, latestSnapshot, { migrationMode: ctx.migrationMode });
2258
2341
  if (postCreateSyncPlan.warnings.length > 0) {
@@ -2273,6 +2356,9 @@ const syncTable = async (table, ctx) => {
2273
2356
  const generatorSyncPlan = await loadGeneratorSyncPlan(table, tableDbIndex, ctx.config);
2274
2357
  await applyGeneratorSyncPlan(table, generatorSyncPlan, tableDbIndex, ctx.config, ctx);
2275
2358
 
2359
+ const existingAppendOnlyPlan = await loadAppendOnlySyncPlan(table, tableDbIndex, ctx.config);
2360
+ await applyAppendOnlySyncPlan(table, existingAppendOnlyPlan, tableDbIndex, ctx.config, ctx);
2361
+
2276
2362
  if (compareResult.isSame) {
2277
2363
  return null;
2278
2364
  }
@@ -237,6 +237,7 @@ export const buildStructureSnapshotFromRows = (input) => {
237
237
 
238
238
  export const generateExecuteBlockSql = (schema) => {
239
239
  const statements = [];
240
+ let hasAppendOnly = false;
240
241
 
241
242
  for (const table of schema.tables) {
242
243
  const createTable = buildCreateTableStatement(table);
@@ -288,6 +289,18 @@ export const generateExecuteBlockSql = (schema) => {
288
289
  ),
289
290
  );
290
291
  }
292
+
293
+ if (table.appendOnly) {
294
+ hasAppendOnly = true;
295
+ statements.push(
296
+ buildAppendOnlyTriggerStatement(table.name, resolveAppendOnlyTriggerName(table)),
297
+ );
298
+ }
299
+ }
300
+
301
+ if (hasAppendOnly) {
302
+ // the shared exception must exist before any NOUPD trigger references it
303
+ statements.unshift(buildCreateAppendOnlyExceptionStatement());
291
304
  }
292
305
 
293
306
  return []
@@ -472,6 +485,30 @@ const resolveIdentityValueExpression = (table, identityColumn, generatorName) =>
472
485
  return `GEN_ID(${normalizeIdentifier(generatorName)}, 1)`;
473
486
  };
474
487
 
488
+ // Shared exception raised by every append-only table's NOUPD trigger.
489
+ export const APPEND_ONLY_EXCEPTION_NAME = 'E_APPEND_ONLY';
490
+ const APPEND_ONLY_EXCEPTION_MESSAGE = 'This table is append-only; UPDATE and DELETE are not allowed.';
491
+
492
+ export const buildCreateAppendOnlyExceptionStatement = () => {
493
+ return `CREATE EXCEPTION ${APPEND_ONLY_EXCEPTION_NAME} '${APPEND_ONLY_EXCEPTION_MESSAGE}'`;
494
+ };
495
+
496
+ // A BEFORE UPDATE OR DELETE trigger that makes the table append-only (raises E_APPEND_ONLY).
497
+ export const buildAppendOnlyTriggerStatement = (tableName, triggerName) => {
498
+ const normalizedTableName = normalizeIdentifier(tableName);
499
+ const normalizedTriggerName = normalizeIdentifier(triggerName);
500
+ return [
501
+ `CREATE TRIGGER ${normalizedTriggerName} FOR ${normalizedTableName} ACTIVE`,
502
+ 'BEFORE UPDATE OR DELETE POSITION 0 AS BEGIN',
503
+ `EXCEPTION ${APPEND_ONLY_EXCEPTION_NAME};`,
504
+ 'END',
505
+ ].join(' ');
506
+ };
507
+
508
+ export const resolveAppendOnlyTriggerName = (table) => {
509
+ return normalizeIdentifier(table.appendOnlyTriggerName ?? `${table.name}_NOUPD`);
510
+ };
511
+
475
512
  const wrapCreateIfMissing = (statement) => {
476
513
  return [
477
514
  'EXECUTE BLOCK AS',
@@ -1,5 +1,6 @@
1
1
  import crypto from "node:crypto";
2
2
  import { createLogger, transports, format } from "winston";
3
+ import { queryData } from "../db/db.js";
3
4
 
4
5
  export const CLIENT_ROOM = "clientRoom";
5
6
  const MAX_RETRIES = 3;
@@ -16,14 +17,16 @@ class ClientEmitterService {
16
17
  constructor() {
17
18
  this.ioServerInstance = null;
18
19
  this.hubSocket = null;
20
+ this.subdomain = null;
19
21
 
20
22
  // Map<clientInstanceId, Map<msgId, PendingData>>
21
23
  this.clientQueues = new Map();
22
24
  }
23
25
 
24
- init(ioServerInstance, hubSocket) {
26
+ init(ioServerInstance, hubSocket, subdomain) {
25
27
  this.ioServerInstance = ioServerInstance;
26
28
  this.hubSocket = hubSocket;
29
+ this.subdomain = subdomain;
27
30
  }
28
31
 
29
32
  /**
@@ -149,6 +152,7 @@ class ClientEmitterService {
149
152
  this.ioServerInstance
150
153
  .to(CLIENT_ROOM)
151
154
  .emit(eventName, wrappedPayload);
155
+
152
156
  if (this.hubSocket && this.hubSocket.connected) {
153
157
  this.hubSocket.emit("broadcastToClient", {
154
158
  eventName,
@@ -166,6 +170,119 @@ class ClientEmitterService {
166
170
  this.emitClient(payload.clientInstanceId, payload.eventName, data);
167
171
  }
168
172
  }
173
+
174
+ async emitUsers(userIds, eventName, payload) {
175
+ if (!Array.isArray(userIds) || userIds.length === 0) return;
176
+
177
+ // Use Set to natively guarantee unique values
178
+ let targetInstances = new Set();
179
+
180
+ // 1. Fetch instances from directHub
181
+ if (this.ioServerInstance && this.subdomain) {
182
+ for (const uid of userIds) {
183
+ const roomName = `${this.subdomain}_USER_${uid}`;
184
+ const sockets = await this.ioServerInstance
185
+ .in(roomName)
186
+ .fetchSockets();
187
+ sockets.forEach((s) => {
188
+ const cid = s.handshake?.query?.clientInstanceId;
189
+ if (cid !== undefined && cid !== null) {
190
+ targetInstances.add(cid);
191
+ }
192
+ });
193
+ }
194
+ }
195
+
196
+ // 2. Fetch instances from Hub Server
197
+ if (this.hubSocket && this.hubSocket.connected) {
198
+ try {
199
+ const cloudInstances = await new Promise((resolve, reject) => {
200
+ this.hubSocket
201
+ .timeout(5000)
202
+ .emit(
203
+ "clientInstancesByUserIds",
204
+ { userIds },
205
+ (err, res) => {
206
+ if (err) reject(err);
207
+ else resolve(res);
208
+ },
209
+ );
210
+ });
211
+
212
+ // Explicitly deduplicate the dual-channel overlap
213
+ if (Array.isArray(cloudInstances)) {
214
+ cloudInstances.forEach((cid) => {
215
+ if (!targetInstances.has(cid)) {
216
+ targetInstances.add(cid);
217
+ }
218
+ });
219
+ }
220
+ } catch (e) {
221
+ console.error(
222
+ "[ESB] Timeout/Error fetching instances from Hub Server",
223
+ e.message,
224
+ );
225
+ }
226
+ }
227
+
228
+ // 3. Blast the Targeted Emits
229
+ if (targetInstances.size > 0) {
230
+ // console.log(
231
+ // `[ESB] Routing '${eventName}' to ${targetInstances.size} active instance(s) for users: ${userIds.join(", ")}`,
232
+ // );
233
+ for (const cid of targetInstances) {
234
+ this.emitClient(cid, eventName, payload);
235
+ }
236
+ }
237
+ }
238
+
239
+ /**
240
+ * TARGETED EMIT BY GROUP: Queries the DB for User IDs, then hands off to emitUsers
241
+ */
242
+ async emitUserGroups(groupIds, eventName, payload) {
243
+ if (!Array.isArray(groupIds) || groupIds.length === 0) return;
244
+
245
+ try {
246
+ // 1. Construct the exact payload format for orm.pas
247
+ const dbPayload = {
248
+ distinct: "true", // Triggers the DISTINCT keyword in orm.pas
249
+ columns: [{ data: "SYS$UGMEMBER.USER_ID", key: "USER_ID" }],
250
+ filter: [
251
+ {
252
+ junction: "",
253
+ column: "SYS$UGMEMBER.SYS$USERGROUP_ID",
254
+ operator: "IN",
255
+ value1: `(${groupIds.join(",")})`, // Generates: IN (1,2,3)
256
+ },
257
+ ],
258
+ };
259
+
260
+ // 2. Execute the local LAN query via the DataSnap Adapter
261
+ const result = await queryData(dbPayload);
262
+ // 3. Extract the unique User IDs from the mapped result
263
+ if (Array.isArray(result) && result.length > 0) {
264
+ const extractedUserIds = result.map((row) => row.USER_ID);
265
+
266
+ // console.log(
267
+ // `[ESB] emitUserGroups mapped ${groupIds.length} group(s) to ${extractedUserIds.length} unique user(s).`,
268
+ // );
269
+
270
+ // 4. Hand off to the proven emitUsers Strict DLQ engine
271
+ if (extractedUserIds.length > 0) {
272
+ await this.emitUsers(extractedUserIds, eventName, payload);
273
+ }
274
+ } else {
275
+ // console.log(
276
+ // `[ESB] emitUserGroups found no active users for groups: ${groupIds.join(", ")}`,
277
+ // );
278
+ }
279
+ } catch (err) {
280
+ console.error(
281
+ "[ESB] emitUserGroups Query Error:",
282
+ err.message || err,
283
+ );
284
+ }
285
+ }
169
286
  }
170
287
 
171
288
  export const ClientEmitter = new ClientEmitterService();
@@ -75,6 +75,20 @@ export class CLI_Dispatcher {
75
75
  return;
76
76
  }
77
77
 
78
+ // --- EMIT BY USERS ---
79
+ if (topic === "emitUsers") {
80
+ const { userIds, eventName, data } = payload;
81
+ ClientEmitter.emitUsers(userIds, eventName, data);
82
+ return;
83
+ }
84
+
85
+ // --- EMIT BY USER GROUPS ---
86
+ if (topic === "emitUserGroups") {
87
+ const { groupIds, eventName, data } = payload;
88
+ ClientEmitter.emitUserGroups(groupIds, eventName, data);
89
+ return;
90
+ }
91
+
78
92
  // --- INTERNAL SYSTEM ROUTING ---
79
93
  // ex : this.lib.emitCLI("email:send", data);
80
94
  if (this.mode === "monolithic" && this.localBus) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "biz-a-cli",
3
- "version": "2.3.80-15238",
3
+ "version": "2.3.80-15267",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "type": "module",
@@ -5,6 +5,7 @@ import * as dayjs from "dayjs";
5
5
  import timezone from "dayjs/plugin/timezone.js";
6
6
  import utc from "dayjs/plugin/utc.js";
7
7
  import crypto from "crypto";
8
+ import { secretProvider } from "../security/secretProvider.js";
8
9
  import { createLogger, transports, format } from "winston";
9
10
  import { execCLIScriptWorker } from "../worker/cliWorkerPool.js";
10
11
 
@@ -199,6 +200,23 @@ export async function extractFunctionScript(
199
200
  axios: axiosInstance, //SCY BZ 4316
200
201
  dayjs: dayjs.default,
201
202
  crypto: crypto,
203
+ // Keyed HMAC by NAME. The key never enters the CLI script — it stays in the
204
+ // platform secret provider (file -> DPAPI -> Vault, swappable). Fails closed.
205
+ hmac: (name, data) => {
206
+ const { key } = secretProvider.resolveForSign(name);
207
+ return crypto.createHmac("sha256", key).update(String(data)).digest("hex");
208
+ },
209
+ // Rotation-aware verify: resolves the key by the row's stored keyId.
210
+ hmacVerify: (name, data, digest, keyId) => {
211
+ const { key } = secretProvider.resolveForVerify(name, keyId);
212
+ const expected = crypto
213
+ .createHmac("sha256", key)
214
+ .update(String(data))
215
+ .digest("hex");
216
+ const a = Buffer.from(expected, "hex");
217
+ const b = Buffer.from(String(digest || ""), "hex");
218
+ return a.length === b.length && crypto.timingSafeEqual(a, b);
219
+ },
202
220
  log: logger,
203
221
  data: { sendModel, queryData, crudData, genId, getUrlAndParam },
204
222
  ...extraLib,
@@ -87,6 +87,24 @@ export const run = async (apiConfig, scriptIdentifier, scriptData) => {
87
87
  payload: { eventName, data },
88
88
  });
89
89
  };
90
+
91
+ // 4. Targeted by Users
92
+ lib.emitUsers = (userIds, eventName, data) => {
93
+ workerpool.workerEmit({
94
+ type: "IPC_RELAY",
95
+ topic: "emitUsers",
96
+ payload: { userIds, eventName, data },
97
+ });
98
+ };
99
+
100
+ // 5. Targeted by User Groups
101
+ lib.emitUserGroups = (groupIds, eventName, data) => {
102
+ workerpool.workerEmit({
103
+ type: "IPC_RELAY",
104
+ topic: "emitUserGroups",
105
+ payload: { groupIds, eventName, data },
106
+ });
107
+ };
90
108
  };
91
109
 
92
110
  const injectDBMethods = (lib, baseConfig) => {
@@ -15,7 +15,10 @@ export const cliScriptWorkerPool = workerpool.pool(
15
15
  workerType: "thread",
16
16
  emitStdStreams: true, // the worker will emit stdout and stderr events instead of passing it through to the parent streams
17
17
  workerThreadOpts: {
18
- env: { NODE_ENV: process.env.NODE_ENV || "production" },
18
+ // worker env is a whitelist (not the full process.env). No application secrets here:
19
+ // CLI scripts get keyed HMAC via lib.hmac(name,...), resolved by the platform secret
20
+ // provider from the secrets store. BIZA_SECRETS_FILE (optional) points at that store.
21
+ env: { NODE_ENV: process.env.NODE_ENV || "production", BIZA_SECRETS_FILE: process.env.BIZA_SECRETS_FILE },
19
22
  },
20
23
  },
21
24
  );