@h-ai/audit 1.0.0-beta.17 → 1.0.0-beta.19

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/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { core, ok, err } from '@h-ai/core';
3
- import { migrateSchema, reldb, validateIdentifiers, schemaColumns, BaseReldbCrudRepository } from '@h-ai/reldb';
3
+ import { reldb, validateIdentifiers, BaseReldbCrudRepository } from '@h-ai/reldb';
4
4
  import { createHash } from 'crypto';
5
5
  import { AsyncLocalStorage } from 'async_hooks';
6
6
  import { Buffer } from 'buffer';
@@ -50,7 +50,12 @@ var AuditInitConfigSchema = z.object({
50
50
  /** Cron 使用的 IANA 时区,例如 Asia/Shanghai;UTC 不等于北京时间。 */
51
51
  timezone: z.string().trim().min(1).max(128).default("UTC")
52
52
  }).default({ enabled: false, cron: "* * * * *", timezone: "UTC" })
53
- }).default({ enabled: false, maxNotificationsPerCheck: 100, rules: [], schedule: { enabled: false, cron: "* * * * *", timezone: "UTC" } }).refine((alerts) => !alerts.enabled || alerts.rules.length > 0),
53
+ }).default({
54
+ enabled: false,
55
+ maxNotificationsPerCheck: 100,
56
+ rules: [],
57
+ schedule: { enabled: false, cron: "* * * * *", timezone: "UTC" }
58
+ }).refine((alerts) => !alerts.enabled || alerts.rules.length > 0),
54
59
  /** 可选的流式归档;自动执行由宿主现有 Scheduler 装配。 */
55
60
  backup: z.object({
56
61
  /** 是否允许手动/计划归档审计;需已初始化的私有 Storage,关闭不影响审计采集。 */
@@ -68,7 +73,12 @@ var AuditInitConfigSchema = z.object({
68
73
  /** 定时备份采用的 IANA 时区;Asia/Shanghai 为北京时间,UTC 的 02:00 对应北京时间 10:00。 */
69
74
  timezone: z.string().trim().min(1).max(128).default("UTC")
70
75
  }).default({ enabled: false, cron: "0 2 * * *", timezone: "UTC" })
71
- }).default({ enabled: false, batchSize: 200, maxRecordBytes: 1024 * 1024, schedule: { enabled: false, cron: "0 2 * * *", timezone: "UTC" } }),
76
+ }).default({
77
+ enabled: false,
78
+ batchSize: 200,
79
+ maxRecordBytes: 1024 * 1024,
80
+ schedule: { enabled: false, cron: "0 2 * * *", timezone: "UTC" }
81
+ }),
72
82
  /** 框架入口自动采集;显式 audit.log 不受此开关影响。 */
73
83
  capture: z.object({
74
84
  /** 是否自动记录已接入框架入口的重要操作、失败和拒绝;关闭不阻止显式 audit.log 写入。 */
@@ -104,8 +114,17 @@ function getAuditContext() {
104
114
  function withAuditContext(context, operation) {
105
115
  const previous = getAuditContext();
106
116
  const actor = Object.hasOwn(context, "actor") ? context.actor : previous.actor;
107
- const snapshot = actor ? { ...actor, ...actor.roles ? { roles: [...actor.roles] } : {} } : actor;
108
- return storage.run({ ...previous, ...context, actor: snapshot }, operation);
117
+ const snapshot = actor ? {
118
+ ...actor,
119
+ ...actor.roles ? {
120
+ roles: [...actor.roles]
121
+ } : {}
122
+ } : actor;
123
+ return storage.run({
124
+ ...previous,
125
+ ...context,
126
+ actor: snapshot
127
+ }, operation);
109
128
  }
110
129
 
111
130
  // messages/en-US.json
@@ -273,7 +292,12 @@ function createAuditAlerts(input) {
273
292
  if (!time.success)
274
293
  return time;
275
294
  const now = time.data.getTime();
276
- const report = { submitted: 0, queued: 0, failed: 0, more: false };
295
+ const report = {
296
+ submitted: 0,
297
+ queued: 0,
298
+ failed: 0,
299
+ more: false
300
+ };
277
301
  let attempted = 0;
278
302
  for (const rule of input.config.rules) {
279
303
  const duration = rule.windowSeconds * 1e3;
@@ -281,12 +305,26 @@ function createAuditAlerts(input) {
281
305
  for (const start of [current - duration, current]) {
282
306
  signal.throwIfAborted();
283
307
  const end = Math.min(start + duration, now + 1);
284
- const actions = [...new Set(rule.actions)].sort();
285
- const outcomes = [...new Set(rule.outcomes)].sort();
308
+ const actions = [
309
+ ...new Set(rule.actions)
310
+ ].sort();
311
+ const outcomes = [
312
+ ...new Set(rule.outcomes)
313
+ ].sort();
286
314
  const severities = levels.slice(levels.indexOf(rule.minimumSeverity));
287
315
  const date = (value) => type === "sqlite" ? value : new Date(value);
288
- const conditions = ["created_at >= ?", "created_at < ?", "resource <> ?", `action IN (${actions.map(() => "?").join(",")})`];
289
- const params = [date(start), date(end), "audit.alert", ...actions];
316
+ const conditions = [
317
+ "created_at >= ?",
318
+ "created_at < ?",
319
+ "resource <> ?",
320
+ `action IN (${actions.map(() => "?").join(",")})`
321
+ ];
322
+ const params = [
323
+ date(start),
324
+ date(end),
325
+ "audit.alert",
326
+ ...actions
327
+ ];
290
328
  if (outcomes.length) {
291
329
  conditions.push(`outcome IN (${outcomes.map(() => "?").join(",")})`);
292
330
  params.push(...outcomes);
@@ -309,7 +347,16 @@ function createAuditAlerts(input) {
309
347
  continue;
310
348
  for (const target of rule.targets) {
311
349
  signal.throwIfAborted();
312
- const id = hash({ rule: { ...rule, actions, outcomes, targets: void 0 }, target, start });
350
+ const id = hash({
351
+ rule: {
352
+ ...rule,
353
+ actions,
354
+ outcomes,
355
+ targets: void 0
356
+ },
357
+ target,
358
+ start
359
+ });
313
360
  if (attempted >= input.config.maxNotificationsPerCheck) {
314
361
  const existing = await reldb.sql.get(`SELECT id FROM ${table} WHERE id=?`, [id]);
315
362
  if (!existing.success)
@@ -320,14 +367,36 @@ function createAuditAlerts(input) {
320
367
  }
321
368
  const selection = `SELECT ?, ?, ?, ?, 'claimed' WHERE ? <= ${clock} AND ? > ${clock}`;
322
369
  const statement = type === "mysql" ? `INSERT IGNORE INTO ${table} (id, rule_id, target_id, window_start, status) ${selection}` : `INSERT INTO ${table} (id, rule_id, target_id, window_start, status) ${selection} ON CONFLICT(id) DO NOTHING`;
323
- const claimed = await reldb.sql.execute(statement, [id, rule.id, target.id, start, start, start + duration * 2]);
370
+ const claimed = await reldb.sql.execute(statement, [
371
+ id,
372
+ rule.id,
373
+ target.id,
374
+ start,
375
+ start,
376
+ start + duration * 2
377
+ ]);
324
378
  if (!claimed.success)
325
379
  return claimed;
326
380
  if (claimed.data.changes === 0)
327
381
  continue;
328
382
  attempted++;
329
- const details = { ruleId: rule.id, targetId: target.id, count, windowStart: new Date(start).toISOString(), windowEnd: new Date(start + duration).toISOString(), level: rule.severity };
330
- const recorded = await input.log({ action: "audit.alert.claimed", resource: "audit.alert", resourceId: id, category: "system", severity: rule.severity, outcome: "success", details });
383
+ const details = {
384
+ ruleId: rule.id,
385
+ targetId: target.id,
386
+ count,
387
+ windowStart: new Date(start).toISOString(),
388
+ windowEnd: new Date(start + duration).toISOString(),
389
+ level: rule.severity
390
+ };
391
+ const recorded = await input.log({
392
+ action: "audit.alert.claimed",
393
+ resource: "audit.alert",
394
+ resourceId: id,
395
+ category: "system",
396
+ severity: rule.severity,
397
+ outcome: "success",
398
+ details
399
+ });
331
400
  if (!recorded.success)
332
401
  return recorded;
333
402
  signal.throwIfAborted();
@@ -336,10 +405,23 @@ function createAuditAlerts(input) {
336
405
  return sending;
337
406
  if (sending.data.changes !== 1)
338
407
  continue;
339
- const sent = await input.reach.send({ provider: target.provider, to: target.to, subject: auditM("audit_alertSubject", { params: { severity: rule.severity, rule: rule.id } }), body: JSON.stringify(details), idempotencyKey: id });
408
+ const sent = await input.reach.send({
409
+ provider: target.provider,
410
+ to: target.to,
411
+ subject: auditM("audit_alertSubject", {
412
+ params: { severity: rule.severity, rule: rule.id }
413
+ }),
414
+ body: JSON.stringify(details),
415
+ idempotencyKey: id
416
+ });
340
417
  const accepted = sent.success && sent.data.success;
341
418
  const status = accepted ? sent.data.deferred ? "queued" : "submitted" : "unknown";
342
- const updated = await reldb.sql.execute(`UPDATE ${table} SET status=?, queue_id=?, error_code=? WHERE id=? AND status='sending'`, [status, sent.success && sent.data.deferred ? sent.data.queueId : null, sent.success ? null : sent.error.code, id]);
419
+ const updated = await reldb.sql.execute(`UPDATE ${table} SET status=?, queue_id=?, error_code=? WHERE id=? AND status='sending'`, [
420
+ status,
421
+ sent.success && sent.data.deferred ? sent.data.queueId : null,
422
+ sent.success ? null : sent.error.code,
423
+ id
424
+ ]);
343
425
  if (!updated.success || updated.data.changes !== 1)
344
426
  return err(HaiAuditError.ALERTS_FAILED, auditM("audit_alertsFailed"), updated.success ? void 0 : updated.error);
345
427
  if (status === "queued")
@@ -348,11 +430,30 @@ function createAuditAlerts(input) {
348
430
  report.submitted++;
349
431
  else
350
432
  report.failed++;
351
- const outcome = await input.log({ action: "audit.alert.notification", resource: "audit.alert", resourceId: id, category: "system", severity: rule.severity, outcome: accepted ? "success" : "failure", details: { ...details, status, ...sent.success ? {} : { reasonCode: sent.error.code }, ...sent.success && sent.data.deferred ? { queueId: sent.data.queueId } : {} } });
433
+ const outcome = await input.log({
434
+ action: "audit.alert.notification",
435
+ resource: "audit.alert",
436
+ resourceId: id,
437
+ category: "system",
438
+ severity: rule.severity,
439
+ outcome: accepted ? "success" : "failure",
440
+ details: {
441
+ ...details,
442
+ status,
443
+ ...sent.success ? {} : { reasonCode: sent.error.code },
444
+ ...sent.success && sent.data.deferred ? { queueId: sent.data.queueId } : {}
445
+ }
446
+ });
352
447
  if (!outcome.success)
353
448
  return outcome;
354
- if (!accepted)
355
- logger.error("Audit alert notification outcome requires review", { id, ruleId: rule.id, targetId: target.id, errorCode: sent.success ? void 0 : sent.error.code });
449
+ if (!accepted) {
450
+ logger.error("Audit alert notification outcome requires review", {
451
+ id,
452
+ ruleId: rule.id,
453
+ targetId: target.id,
454
+ errorCode: sent.success ? void 0 : sent.error.code
455
+ });
456
+ }
356
457
  }
357
458
  }
358
459
  }
@@ -371,7 +472,10 @@ function createAuditAlerts(input) {
371
472
  if (options?.signal?.aborted)
372
473
  cancel();
373
474
  controllers.add(controller);
374
- const operation = withAuditContext({ actor: { id: "system:audit-alerts", username: "scheduler" }, category: "system" }, () => run(controller.signal)).catch((error) => {
475
+ const operation = withAuditContext({
476
+ actor: { id: "system:audit-alerts", username: "scheduler" },
477
+ category: "system"
478
+ }, () => run(controller.signal)).catch((error) => {
375
479
  logger.error("Audit alert check failed", { error });
376
480
  return err(HaiAuditError.ALERTS_FAILED, auditM("audit_alertsFailed"), error);
377
481
  });
@@ -386,15 +490,25 @@ function createAuditAlerts(input) {
386
490
  let schedule = null;
387
491
  if (input.config.enabled && input.config.schedule.enabled) {
388
492
  schedule = {
389
- tasks: [{ id: "hai-audit-alerts", name: auditM("audit_alertTaskName"), idempotent: true, cron: input.config.schedule.cron, timezone: input.config.schedule.timezone }],
390
- hooks: { onTaskExecute: async ({ context, signal }) => {
391
- if (context.taskId !== "hai-audit-alerts")
392
- throw core.error.buildHaiErrorInst(HaiAuditError.ALERTS_FAILED, auditM("audit_alertsFailed"));
393
- const result = await check({ signal });
394
- if (!result.success || result.data.failed > 0)
395
- throw core.error.buildHaiErrorInst(HaiAuditError.ALERTS_FAILED, auditM("audit_alertsFailed"), result.success ? void 0 : result.error);
396
- return result.data;
397
- } }
493
+ tasks: [
494
+ {
495
+ id: "hai-audit-alerts",
496
+ name: auditM("audit_alertTaskName"),
497
+ idempotent: true,
498
+ cron: input.config.schedule.cron,
499
+ timezone: input.config.schedule.timezone
500
+ }
501
+ ],
502
+ hooks: {
503
+ onTaskExecute: async ({ context, signal }) => {
504
+ if (context.taskId !== "hai-audit-alerts")
505
+ throw core.error.buildHaiErrorInst(HaiAuditError.ALERTS_FAILED, auditM("audit_alertsFailed"));
506
+ const result = await check({ signal });
507
+ if (!result.success || result.data.failed > 0)
508
+ throw core.error.buildHaiErrorInst(HaiAuditError.ALERTS_FAILED, auditM("audit_alertsFailed"), result.success ? void 0 : result.error);
509
+ return result.data;
510
+ }
511
+ }
398
512
  };
399
513
  }
400
514
  return {
@@ -408,7 +522,12 @@ function createAuditAlerts(input) {
408
522
  };
409
523
  }
410
524
  var logger2 = core.logger.child({ module: "audit", scope: "writer" });
411
- var actorSchema = z.object({ id: z.string().min(1).max(256), username: z.string().max(256).optional(), displayName: z.string().max(256).optional(), roles: z.array(z.string().max(256)).max(1e3).optional() });
525
+ var actorSchema = z.object({
526
+ id: z.string().min(1).max(256),
527
+ username: z.string().max(256).optional(),
528
+ displayName: z.string().max(256).optional(),
529
+ roles: z.array(z.string().max(256)).max(1e3).optional()
530
+ });
412
531
  var detailsSchema = z.record(z.string(), z.unknown());
413
532
  var inputSchema = z.object({
414
533
  userId: z.string().min(1).max(256).nullable().optional(),
@@ -436,7 +555,14 @@ var querySchema = z.object({
436
555
  category: z.enum(["system", "business"]).optional(),
437
556
  outcome: z.enum(["success", "failure", "denied"]).optional(),
438
557
  severity: z.enum(["info", "warning", "error", "critical"]).optional(),
439
- sortBy: z.enum(["createdAt", "action", "resource", "category", "outcome", "severity"]).optional(),
558
+ sortBy: z.enum([
559
+ "createdAt",
560
+ "action",
561
+ "resource",
562
+ "category",
563
+ "outcome",
564
+ "severity"
565
+ ]).optional(),
440
566
  sortDirection: z.enum(["asc", "desc"]).optional(),
441
567
  startDate: z.date().optional(),
442
568
  endDate: z.date().optional()
@@ -459,8 +585,15 @@ function createAuditWriter(repository, config) {
459
585
  try {
460
586
  const parsed = inputSchema.safeParse(input);
461
587
  if (!parsed.success) {
462
- logger2.error("Audit input rejected", { issues: parsed.error.issues.map((issue) => issue.path.join(".")) });
463
- return err(HaiAuditError.LOG_FAILED, auditM("audit_invalidInput", { params: { field: "event", reason: auditM("audit_eventFieldsInvalid") } }));
588
+ logger2.error("Audit input rejected", {
589
+ issues: parsed.error.issues.map((issue) => issue.path.join("."))
590
+ });
591
+ return err(HaiAuditError.LOG_FAILED, auditM("audit_invalidInput", {
592
+ params: {
593
+ field: "event",
594
+ reason: auditM("audit_eventFieldsInvalid")
595
+ }
596
+ }));
464
597
  }
465
598
  const value = parsed.data;
466
599
  const context = getAuditContext();
@@ -468,8 +601,15 @@ function createAuditWriter(repository, config) {
468
601
  const actor = Object.hasOwn(value, "actorSnapshot") ? value.actorSnapshot : context.actor;
469
602
  const userId = value.userId !== void 0 ? value.userId : actor?.id ?? correlation.userId ?? null;
470
603
  if (value.actorSnapshot && value.actorSnapshot.id !== userId) {
471
- logger2.error("Audit actor snapshot rejected", { reason: auditM("audit_identityMismatch") });
472
- return err(HaiAuditError.LOG_FAILED, auditM("audit_invalidInput", { params: { field: "actorSnapshot", reason: auditM("audit_identityMismatch") } }));
604
+ logger2.error("Audit actor snapshot rejected", {
605
+ reason: auditM("audit_identityMismatch")
606
+ });
607
+ return err(HaiAuditError.LOG_FAILED, auditM("audit_invalidInput", {
608
+ params: {
609
+ field: "actorSnapshot",
610
+ reason: auditM("audit_identityMismatch")
611
+ }
612
+ }));
473
613
  }
474
614
  const snapshot = actor?.id === userId ? readAuditActor(actor) : userId ? await repository.captureActor(userId, tx) : null;
475
615
  const outcome = value.outcome ?? "success";
@@ -497,12 +637,22 @@ function createAuditWriter(repository, config) {
497
637
  userAgent: value.userAgent ?? context.userAgent ?? null,
498
638
  details
499
639
  }, tx);
500
- if (!result.success)
501
- logger2.error("Audit event persistence failed", { action: value.action, resource: value.resource, requestId: value.requestId ?? context.requestId, error: result.error });
640
+ if (!result.success) {
641
+ logger2.error("Audit event persistence failed", {
642
+ action: value.action,
643
+ resource: value.resource,
644
+ requestId: value.requestId ?? context.requestId,
645
+ error: result.error
646
+ });
647
+ }
502
648
  return result;
503
649
  } catch (error) {
504
650
  logger2.error("Audit event processing failed", { requestId: getAuditContext().requestId, error });
505
- return err(HaiAuditError.LOG_FAILED, auditM("audit_logFailed", { params: { error: auditM("audit_eventProcessingFailed") } }), error);
651
+ return err(HaiAuditError.LOG_FAILED, auditM("audit_logFailed", {
652
+ params: {
653
+ error: auditM("audit_eventProcessingFailed")
654
+ }
655
+ }), error);
506
656
  }
507
657
  };
508
658
  }
@@ -520,8 +670,21 @@ var backupHeaderSchema = z.object({
520
670
  from: z.iso.datetime().nullable(),
521
671
  requestHash: digestSchema
522
672
  });
523
- var footerSchema = z.object({ type: z.literal("footer"), recordCount: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER), sha256: digestSchema });
524
- var entrySchema = z.object({ type: z.literal("entry"), record: z.object({ id: z.string().min(1), action: z.string().min(1), resource: z.string().min(1), createdAt: z.iso.datetime(), actorSnapshot: z.unknown().optional() }).passthrough() });
673
+ var footerSchema = z.object({
674
+ type: z.literal("footer"),
675
+ recordCount: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
676
+ sha256: digestSchema
677
+ });
678
+ var entrySchema = z.object({
679
+ type: z.literal("entry"),
680
+ record: z.object({
681
+ id: z.string().min(1),
682
+ action: z.string().min(1),
683
+ resource: z.string().min(1),
684
+ createdAt: z.iso.datetime(),
685
+ actorSnapshot: z.unknown().optional()
686
+ }).passthrough()
687
+ });
525
688
  function archiveLine(value, maxRecordBytes) {
526
689
  const encoded = Buffer.from(`${JSON.stringify(value)}
527
690
  `, "utf8");
@@ -583,17 +746,32 @@ async function verifyArchive(source, options) {
583
746
  const entry = entrySchema.parse(value);
584
747
  readAuditActor(entry.record.actorSnapshot);
585
748
  if (!Number.isSafeInteger(++count))
586
- throw new Error("Audit archive record count exceeds safe range");
749
+ throw new TypeError("Audit archive record count exceeds safe range");
587
750
  hash2.update(line);
588
751
  }
589
752
  if (!header || !footer)
590
753
  throw new Error("Audit archive is incomplete");
591
- return { info: { id: header.id, key: options.key, recordCount: footer.recordCount, sha256: footer.sha256, createdAt: new Date(header.createdAt), cutoff: new Date(header.cutoff) }, requestHash: header.requestHash };
754
+ return {
755
+ info: {
756
+ id: header.id,
757
+ key: options.key,
758
+ recordCount: footer.recordCount,
759
+ sha256: footer.sha256,
760
+ createdAt: new Date(header.createdAt),
761
+ cutoff: new Date(header.cutoff)
762
+ },
763
+ requestHash: header.requestHash
764
+ };
592
765
  }
593
766
 
594
767
  // src/audit-backup-functions.ts
595
768
  var logger3 = core.logger.child({ module: "audit", scope: "backup" });
596
- var optionsSchema = z.object({ id: backupIdSchema.optional(), from: z.date().optional(), to: z.date().optional(), signal: z.instanceof(AbortSignal).optional() });
769
+ var optionsSchema = z.object({
770
+ id: backupIdSchema.optional(),
771
+ from: z.date().optional(),
772
+ to: z.date().optional(),
773
+ signal: z.instanceof(AbortSignal).optional()
774
+ });
597
775
  function createAuditBackup(input) {
598
776
  let active = true;
599
777
  const controllers = /* @__PURE__ */ new Set();
@@ -658,7 +836,12 @@ function createAuditBackup(input) {
658
836
  const from = values.from ? new Date(values.from.getTime()) : void 0;
659
837
  const key = `${AUDIT_BACKUP_PREFIX}${id}.jsonl`;
660
838
  const database = reldb.config;
661
- const source = database?.type === "sqlite" ? { type: database.type, database: database.database } : { type: database?.type, host: database?.host, port: database?.port, database: database?.database };
839
+ const source = database?.type === "sqlite" ? { type: database.type, database: database.database } : {
840
+ type: database?.type,
841
+ host: database?.host,
842
+ port: database?.port,
843
+ database: database?.database
844
+ };
662
845
  const requestHash = createHash("sha256").update(JSON.stringify({ source, from: from?.toISOString() ?? null, to: values.to?.toISOString() ?? null })).digest("hex");
663
846
  return track(async (signal) => {
664
847
  const time = await readAuditTime();
@@ -676,14 +859,28 @@ function createAuditBackup(input) {
676
859
  let completed;
677
860
  async function* content() {
678
861
  const hash2 = createHash("sha256");
679
- const header = archiveLine({ type: "header", format: "hai-audit", version: 1, id, createdAt: createdAt.toISOString(), cutoff: cutoff.toISOString(), from: from?.toISOString() ?? null, requestHash }, input.config.maxRecordBytes);
862
+ const header = archiveLine({
863
+ type: "header",
864
+ format: "hai-audit",
865
+ version: 1,
866
+ id,
867
+ createdAt: createdAt.toISOString(),
868
+ cutoff: cutoff.toISOString(),
869
+ from: from?.toISOString() ?? null,
870
+ requestHash
871
+ }, input.config.maxRecordBytes);
680
872
  hash2.update(header);
681
873
  yield header;
682
874
  let afterId;
683
875
  let count = 0;
684
876
  while (true) {
685
877
  signal.throwIfAborted();
686
- const page = await input.repository.archivePage({ afterId, from, cutoff, limit: input.config.batchSize });
878
+ const page = await input.repository.archivePage({
879
+ afterId,
880
+ from,
881
+ cutoff,
882
+ limit: input.config.batchSize
883
+ });
687
884
  if (!page.success)
688
885
  throw core.error.buildHaiErrorInst(HaiAuditError.BACKUP_FAILED, auditM("audit_backupFailed"), page.error);
689
886
  for (const record of page.data) {
@@ -692,7 +889,7 @@ function createAuditBackup(input) {
692
889
  hash2.update(line);
693
890
  yield line;
694
891
  if (!Number.isSafeInteger(++count))
695
- throw new Error("Audit archive record count exceeds safe range");
892
+ throw new TypeError("Audit archive record count exceeds safe range");
696
893
  afterId = record.id;
697
894
  }
698
895
  if (page.data.length < input.config.batchSize)
@@ -700,12 +897,24 @@ function createAuditBackup(input) {
700
897
  }
701
898
  const sha256 = hash2.digest("hex");
702
899
  yield archiveLine({ type: "footer", recordCount: count, sha256 }, input.config.maxRecordBytes);
703
- completed = { id, key, recordCount: count, sha256, createdAt, cutoff };
900
+ completed = {
901
+ id,
902
+ key,
903
+ recordCount: count,
904
+ sha256,
905
+ createdAt,
906
+ cutoff
907
+ };
704
908
  }
705
909
  const stream = content();
706
910
  let uploaded;
707
911
  try {
708
- uploaded = await input.storage.file.put(key, stream, { ifNoneMatch: "*", contentType: "application/x-ndjson", cacheControl: "private, no-store", signal });
912
+ uploaded = await input.storage.file.put(key, stream, {
913
+ ifNoneMatch: "*",
914
+ contentType: "application/x-ndjson",
915
+ cacheControl: "private, no-store",
916
+ signal
917
+ });
709
918
  } finally {
710
919
  await stream.return(void 0);
711
920
  }
@@ -735,15 +944,28 @@ function createAuditBackup(input) {
735
944
  let schedule = null;
736
945
  if (input.config.enabled && input.config.schedule.enabled) {
737
946
  schedule = {
738
- tasks: [{ id: "hai-audit-backup", name: auditM("audit_backupTaskName"), idempotent: true, cron: input.config.schedule.cron, timezone: input.config.schedule.timezone }],
739
- hooks: { onTaskExecute: async ({ context, signal }) => {
740
- if (context.taskId !== "hai-audit-backup" || !context.occurrenceId)
741
- throw core.error.buildHaiErrorInst(HaiAuditError.BACKUP_INVALID, auditM("audit_backupInvalid"));
742
- const result = await withAuditContext({ actor: { id: "system:audit-backup", username: "scheduler" }, requestId: context.occurrenceId }, () => operations.run({ id: context.occurrenceId, signal }));
743
- if (!result.success)
744
- throw core.error.buildHaiErrorInst(HaiAuditError.BACKUP_FAILED, result.error.message, result.error);
745
- return result.data;
746
- } }
947
+ tasks: [
948
+ {
949
+ id: "hai-audit-backup",
950
+ name: auditM("audit_backupTaskName"),
951
+ idempotent: true,
952
+ cron: input.config.schedule.cron,
953
+ timezone: input.config.schedule.timezone
954
+ }
955
+ ],
956
+ hooks: {
957
+ onTaskExecute: async ({ context, signal }) => {
958
+ if (context.taskId !== "hai-audit-backup" || !context.occurrenceId)
959
+ throw core.error.buildHaiErrorInst(HaiAuditError.BACKUP_INVALID, auditM("audit_backupInvalid"));
960
+ const result = await withAuditContext({
961
+ actor: { id: "system:audit-backup", username: "scheduler" },
962
+ requestId: context.occurrenceId
963
+ }, () => operations.run({ id: context.occurrenceId, signal }));
964
+ if (!result.success)
965
+ throw core.error.buildHaiErrorInst(HaiAuditError.BACKUP_FAILED, result.error.message, result.error);
966
+ return result.data;
967
+ }
968
+ }
747
969
  };
748
970
  }
749
971
  return {
@@ -766,15 +988,37 @@ function toVoid(result) {
766
988
  function createHelper(logFn) {
767
989
  return {
768
990
  async login(userId, ip, ua) {
769
- const result = await logFn({ userId, category: "system", action: "login", resource: "auth", ipAddress: ip, userAgent: ua });
991
+ const result = await logFn({
992
+ userId,
993
+ category: "system",
994
+ action: "login",
995
+ resource: "auth",
996
+ ipAddress: ip,
997
+ userAgent: ua
998
+ });
770
999
  return toVoid(result);
771
1000
  },
772
1001
  async logout(userId, ip, ua) {
773
- const result = await logFn({ userId, category: "system", action: "logout", resource: "auth", ipAddress: ip, userAgent: ua });
1002
+ const result = await logFn({
1003
+ userId,
1004
+ category: "system",
1005
+ action: "logout",
1006
+ resource: "auth",
1007
+ ipAddress: ip,
1008
+ userAgent: ua
1009
+ });
774
1010
  return toVoid(result);
775
1011
  },
776
1012
  async register(userId, ip, ua) {
777
- const result = await logFn({ userId, category: "system", action: "register", resource: "auth", resourceId: userId, ipAddress: ip, userAgent: ua });
1013
+ const result = await logFn({
1014
+ userId,
1015
+ category: "system",
1016
+ action: "register",
1017
+ resource: "auth",
1018
+ resourceId: userId,
1019
+ ipAddress: ip,
1020
+ userAgent: ua
1021
+ });
778
1022
  return toVoid(result);
779
1023
  },
780
1024
  async passwordResetRequest(email, ip, ua) {
@@ -789,7 +1033,14 @@ function createHelper(logFn) {
789
1033
  return toVoid(result);
790
1034
  },
791
1035
  async passwordResetComplete(userId, ip, ua) {
792
- const result = await logFn({ userId, category: "system", action: "password_reset", resource: "auth", ipAddress: ip, userAgent: ua });
1036
+ const result = await logFn({
1037
+ userId,
1038
+ category: "system",
1039
+ action: "password_reset",
1040
+ resource: "auth",
1041
+ ipAddress: ip,
1042
+ userAgent: ua
1043
+ });
793
1044
  return toVoid(result);
794
1045
  },
795
1046
  async crud(input) {
@@ -799,129 +1050,150 @@ function createHelper(logFn) {
799
1050
  }
800
1051
  };
801
1052
  }
802
- var columns = { category: "VARCHAR(16)", outcome: "VARCHAR(16)", severity: "VARCHAR(16)", request_id: "TEXT", trace_id: "TEXT", scope_id: "TEXT", actor_snapshot: "JSON" };
1053
+ var columns = {
1054
+ category: "VARCHAR(16)",
1055
+ outcome: "VARCHAR(16)",
1056
+ severity: "VARCHAR(16)",
1057
+ request_id: "TEXT",
1058
+ trace_id: "TEXT",
1059
+ scope_id: "TEXT",
1060
+ actor_snapshot: "JSON"
1061
+ };
803
1062
  var triggers = ["hai_audit_protect_update", "hai_audit_protect_delete", "hai_audit_protect_insert"];
804
1063
  async function existingTriggers(sql) {
805
1064
  const result = reldb.config?.type === "sqlite" ? await sql.query("SELECT name FROM sqlite_master WHERE type='trigger' AND tbl_name='hai_audit_logs'") : await sql.query(`SELECT trigger_name AS name FROM information_schema.triggers WHERE trigger_schema=${reldb.config?.type === "mysql" ? "DATABASE()" : "current_schema()"} AND event_object_table='hai_audit_logs'`);
806
1065
  return result.success ? ok(result.data.map((row) => row.name)) : result;
807
1066
  }
808
- var steps = [{
809
- version: 1,
810
- name: "event-context-and-identity",
811
- async needed(sql) {
812
- const found = await schemaColumns(reldb, "hai_audit_logs", sql);
813
- return found.success ? ok(found.data.length > 0 && Object.keys(columns).some((name) => !found.data.includes(name))) : found;
814
- },
815
- async apply(sql) {
816
- const found = await schemaColumns(reldb, "hai_audit_logs", sql);
817
- if (!found.success)
818
- return found;
819
- for (const [name, definition] of Object.entries(columns)) {
820
- if (found.data.includes(name))
821
- continue;
822
- const added = await sql.execute(`ALTER TABLE hai_audit_logs ADD COLUMN ${name} ${definition}`);
823
- if (!added.success)
824
- return added;
1067
+ var steps = [
1068
+ {
1069
+ version: 1,
1070
+ name: "event-context-and-identity",
1071
+ async needed(sql) {
1072
+ const found = await reldb.schemaColumns("hai_audit_logs", sql);
1073
+ return found.success ? ok(found.data.length > 0 && Object.keys(columns).some((name) => !found.data.includes(name))) : found;
1074
+ },
1075
+ async apply(sql) {
1076
+ const found = await reldb.schemaColumns("hai_audit_logs", sql);
1077
+ if (!found.success)
1078
+ return found;
1079
+ for (const [name, definition] of Object.entries(columns)) {
1080
+ if (found.data.includes(name))
1081
+ continue;
1082
+ const added = await sql.execute(`ALTER TABLE hai_audit_logs ADD COLUMN ${name} ${definition}`);
1083
+ if (!added.success)
1084
+ return added;
1085
+ }
1086
+ return ok(void 0);
825
1087
  }
826
- return ok(void 0);
827
- }
828
- }, {
829
- version: 2,
830
- name: "persistent-retention-policy",
831
- async needed(sql) {
832
- const found = await schemaColumns(reldb, "hai_audit_policy", sql);
833
- if (!found.success || found.data.length === 0)
834
- return found.success ? ok(true) : found;
835
- const row = await sql.get("SELECT id FROM hai_audit_policy WHERE id=1");
836
- return row.success ? ok(!row.data) : row;
837
1088
  },
838
- async apply(sql) {
839
- const created = await sql.execute("CREATE TABLE IF NOT EXISTS hai_audit_policy (id INTEGER PRIMARY KEY, enabled INTEGER NOT NULL, min_days INTEGER NOT NULL)");
840
- if (!created.success)
841
- return created;
842
- return sql.execute(reldb.config?.type === "mysql" ? "INSERT IGNORE INTO hai_audit_policy (id, enabled, min_days) VALUES (1, 0, 90)" : "INSERT INTO hai_audit_policy (id, enabled, min_days) VALUES (1, 0, 90) ON CONFLICT(id) DO NOTHING");
843
- }
844
- }, {
845
- version: 3,
846
- name: "retention-write-guards",
847
- async needed(sql) {
848
- const table2 = await schemaColumns(reldb, "hai_audit_logs", sql);
849
- if (!table2.success || !table2.data.length)
850
- return table2.success ? ok(false) : table2;
851
- const found = await existingTriggers(sql);
852
- return found.success ? ok(triggers.some((name) => !found.data.includes(name))) : found;
853
- },
854
- async apply(sql) {
855
- const found = await existingTriggers(sql);
856
- if (!found.success)
857
- return found;
858
- const enabled = "COALESCE((SELECT enabled FROM hai_audit_policy WHERE id=1),1)=1";
859
- const days = "COALESCE((SELECT min_days FROM hai_audit_policy WHERE id=1),90)";
860
- const cutoff = reldb.config?.type === "sqlite" ? `CAST((julianday('now')-2440587.5)*86400000 AS INTEGER) - ${days}*86400000` : reldb.config?.type === "postgresql" ? `CURRENT_TIMESTAMP - (${days} * INTERVAL '1 day')` : `TIMESTAMPADD(DAY, -${days}, CURRENT_TIMESTAMP(3))`;
861
- const existingProtected = `${enabled} AND EXISTS(SELECT 1 FROM hai_audit_logs a WHERE a.id=NEW.id AND a.created_at >= ${cutoff})`;
862
- if (reldb.config?.type === "postgresql") {
863
- const fn = await sql.execute(`CREATE OR REPLACE FUNCTION hai_audit_protect() RETURNS trigger AS $$ BEGIN IF TG_OP='INSERT' THEN IF ${existingProtected} THEN RAISE EXCEPTION 'Audit record is protected by retention policy'; END IF; RETURN NEW; END IF; IF ${enabled} AND OLD.created_at >= ${cutoff} THEN RAISE EXCEPTION 'Audit record is protected by retention policy'; END IF; IF TG_OP='DELETE' THEN RETURN OLD; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql`);
864
- if (!fn.success)
865
- return fn;
866
- }
867
- for (const [index, operation] of ["UPDATE", "DELETE", "INSERT"].entries()) {
868
- const name = triggers[index];
869
- if (found.data.includes(name))
870
- continue;
871
- const protectedRow = operation === "INSERT" ? existingProtected : `${enabled} AND OLD.created_at >= ${cutoff}`;
872
- const statement = reldb.config?.type === "sqlite" ? `CREATE TRIGGER ${name} BEFORE ${operation} ON hai_audit_logs WHEN ${protectedRow} BEGIN SELECT RAISE(ABORT, 'Audit record is protected by retention policy'); END` : reldb.config?.type === "postgresql" ? `CREATE TRIGGER ${name} BEFORE ${operation} ON hai_audit_logs FOR EACH ROW EXECUTE FUNCTION hai_audit_protect()` : `CREATE TRIGGER ${name} BEFORE ${operation} ON hai_audit_logs FOR EACH ROW BEGIN IF ${protectedRow} THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Audit record is protected by retention policy'; END IF; END`;
873
- const created = reldb.config?.type === "mysql" ? await sql.query(statement) : await sql.execute(statement);
1089
+ {
1090
+ version: 2,
1091
+ name: "persistent-retention-policy",
1092
+ async needed(sql) {
1093
+ const found = await reldb.schemaColumns("hai_audit_policy", sql);
1094
+ if (!found.success || found.data.length === 0) {
1095
+ return found.success ? ok(true) : found;
1096
+ }
1097
+ const row = await sql.get("SELECT id FROM hai_audit_policy WHERE id=1");
1098
+ return row.success ? ok(!row.data) : row;
1099
+ },
1100
+ async apply(sql) {
1101
+ const created = await sql.execute("CREATE TABLE IF NOT EXISTS hai_audit_policy (id INTEGER PRIMARY KEY, enabled INTEGER NOT NULL, min_days INTEGER NOT NULL)");
874
1102
  if (!created.success)
875
1103
  return created;
1104
+ return sql.execute(reldb.config?.type === "mysql" ? "INSERT IGNORE INTO hai_audit_policy (id, enabled, min_days) VALUES (1, 0, 90)" : "INSERT INTO hai_audit_policy (id, enabled, min_days) VALUES (1, 0, 90) ON CONFLICT(id) DO NOTHING");
876
1105
  }
877
- return ok(void 0);
878
- }
879
- }, {
880
- version: 4,
881
- name: "millisecond-event-time",
882
- async needed(sql) {
883
- if (reldb.config?.type !== "mysql")
884
- return ok(false);
885
- const column = await sql.get("SELECT DATETIME_PRECISION AS fractional_digits FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name=? AND column_name=?", ["hai_audit_logs", "created_at"]);
886
- return column.success ? ok(Boolean(column.data && Number(column.data.fractional_digits ?? 0) < 3)) : column;
887
1106
  },
888
- async apply(sql) {
889
- const changed = await sql.execute("ALTER TABLE hai_audit_logs MODIFY COLUMN created_at DATETIME(3)");
890
- return changed.success ? ok(void 0) : changed;
891
- }
892
- }, {
893
- version: 5,
894
- name: "alert-notification-claims",
895
- async needed(sql) {
896
- const found = await schemaColumns(reldb, "hai_audit_alert_claims", sql);
897
- return found.success ? ok(found.data.length === 0) : found;
1107
+ {
1108
+ version: 3,
1109
+ name: "retention-write-guards",
1110
+ async needed(sql) {
1111
+ const table2 = await reldb.schemaColumns("hai_audit_logs", sql);
1112
+ if (!table2.success || !table2.data.length) {
1113
+ return table2.success ? ok(false) : table2;
1114
+ }
1115
+ const found = await existingTriggers(sql);
1116
+ return found.success ? ok(triggers.some((name) => !found.data.includes(name))) : found;
1117
+ },
1118
+ async apply(sql) {
1119
+ const found = await existingTriggers(sql);
1120
+ if (!found.success)
1121
+ return found;
1122
+ const enabled = "COALESCE((SELECT enabled FROM hai_audit_policy WHERE id=1),1)=1";
1123
+ const days = "COALESCE((SELECT min_days FROM hai_audit_policy WHERE id=1),90)";
1124
+ const cutoff = reldb.config?.type === "sqlite" ? `CAST((julianday('now')-2440587.5)*86400000 AS INTEGER) - ${days}*86400000` : reldb.config?.type === "postgresql" ? `CURRENT_TIMESTAMP - (${days} * INTERVAL '1 day')` : `TIMESTAMPADD(DAY, -${days}, CURRENT_TIMESTAMP(3))`;
1125
+ const existingProtected = `${enabled} AND EXISTS(SELECT 1 FROM hai_audit_logs a WHERE a.id=NEW.id AND a.created_at >= ${cutoff})`;
1126
+ if (reldb.config?.type === "postgresql") {
1127
+ const fn = await sql.execute(`CREATE OR REPLACE FUNCTION hai_audit_protect() RETURNS trigger AS $$ BEGIN IF TG_OP='INSERT' THEN IF ${existingProtected} THEN RAISE EXCEPTION 'Audit record is protected by retention policy'; END IF; RETURN NEW; END IF; IF ${enabled} AND OLD.created_at >= ${cutoff} THEN RAISE EXCEPTION 'Audit record is protected by retention policy'; END IF; IF TG_OP='DELETE' THEN RETURN OLD; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql`);
1128
+ if (!fn.success)
1129
+ return fn;
1130
+ }
1131
+ for (const [index, operation] of ["UPDATE", "DELETE", "INSERT"].entries()) {
1132
+ const name = triggers[index];
1133
+ if (found.data.includes(name))
1134
+ continue;
1135
+ const protectedRow = operation === "INSERT" ? existingProtected : `${enabled} AND OLD.created_at >= ${cutoff}`;
1136
+ const statement = reldb.config?.type === "sqlite" ? `CREATE TRIGGER ${name} BEFORE ${operation} ON hai_audit_logs WHEN ${protectedRow} BEGIN SELECT RAISE(ABORT, 'Audit record is protected by retention policy'); END` : reldb.config?.type === "postgresql" ? `CREATE TRIGGER ${name} BEFORE ${operation} ON hai_audit_logs FOR EACH ROW EXECUTE FUNCTION hai_audit_protect()` : `CREATE TRIGGER ${name} BEFORE ${operation} ON hai_audit_logs FOR EACH ROW BEGIN IF ${protectedRow} THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Audit record is protected by retention policy'; END IF; END`;
1137
+ const created = reldb.config?.type === "mysql" ? await sql.query(statement) : await sql.execute(statement);
1138
+ if (!created.success)
1139
+ return created;
1140
+ }
1141
+ return ok(void 0);
1142
+ }
898
1143
  },
899
- async apply(sql) {
900
- const created = await sql.execute("CREATE TABLE IF NOT EXISTS hai_audit_alert_claims (id VARCHAR(64) PRIMARY KEY, rule_id VARCHAR(64) NOT NULL, target_id VARCHAR(64) NOT NULL, window_start BIGINT NOT NULL, status VARCHAR(16) NOT NULL, queue_id VARCHAR(64), error_code VARCHAR(128))");
901
- return created.success ? ok(void 0) : created;
902
- }
903
- }, {
904
- version: 6,
905
- name: "event-time-index",
906
- async needed(sql) {
907
- const table2 = await schemaColumns(reldb, "hai_audit_logs", sql);
908
- if (!table2.success || !table2.data.length)
909
- return table2.success ? ok(false) : table2;
910
- const found = reldb.config?.type === "sqlite" ? await sql.get("SELECT name FROM sqlite_master WHERE type='index' AND name='hai_audit_event_time_idx'") : reldb.config?.type === "postgresql" ? await sql.get("SELECT indexname FROM pg_indexes WHERE schemaname=current_schema() AND tablename='hai_audit_logs' AND indexname='hai_audit_event_time_idx'") : await sql.get("SELECT index_name FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='hai_audit_logs' AND index_name='hai_audit_event_time_idx' LIMIT 1");
911
- return found.success ? ok(!found.data) : found;
1144
+ {
1145
+ version: 4,
1146
+ name: "millisecond-event-time",
1147
+ async needed(sql) {
1148
+ if (reldb.config?.type !== "mysql")
1149
+ return ok(false);
1150
+ const column = await sql.get("SELECT DATETIME_PRECISION AS fractional_digits FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name=? AND column_name=?", ["hai_audit_logs", "created_at"]);
1151
+ return column.success ? ok(Boolean(column.data && Number(column.data.fractional_digits ?? 0) < 3)) : column;
1152
+ },
1153
+ async apply(sql) {
1154
+ const changed = await sql.execute("ALTER TABLE hai_audit_logs MODIFY COLUMN created_at DATETIME(3)");
1155
+ return changed.success ? ok(void 0) : changed;
1156
+ }
912
1157
  },
913
- async apply(sql) {
914
- const created = await sql.execute("CREATE INDEX hai_audit_event_time_idx ON hai_audit_logs (created_at)");
915
- return created.success ? ok(void 0) : created;
1158
+ {
1159
+ version: 5,
1160
+ name: "alert-notification-claims",
1161
+ async needed(sql) {
1162
+ const found = await reldb.schemaColumns("hai_audit_alert_claims", sql);
1163
+ return found.success ? ok(found.data.length === 0) : found;
1164
+ },
1165
+ async apply(sql) {
1166
+ const created = await sql.execute("CREATE TABLE IF NOT EXISTS hai_audit_alert_claims (id VARCHAR(64) PRIMARY KEY, rule_id VARCHAR(64) NOT NULL, target_id VARCHAR(64) NOT NULL, window_start BIGINT NOT NULL, status VARCHAR(16) NOT NULL, queue_id VARCHAR(64), error_code VARCHAR(128))");
1167
+ return created.success ? ok(void 0) : created;
1168
+ }
1169
+ },
1170
+ {
1171
+ version: 6,
1172
+ name: "event-time-index",
1173
+ async needed(sql) {
1174
+ const table2 = await reldb.schemaColumns("hai_audit_logs", sql);
1175
+ if (!table2.success || !table2.data.length) {
1176
+ return table2.success ? ok(false) : table2;
1177
+ }
1178
+ const found = reldb.config?.type === "sqlite" ? await sql.get("SELECT name FROM sqlite_master WHERE type='index' AND name='hai_audit_event_time_idx'") : reldb.config?.type === "postgresql" ? await sql.get("SELECT indexname FROM pg_indexes WHERE schemaname=current_schema() AND tablename='hai_audit_logs' AND indexname='hai_audit_event_time_idx'") : await sql.get("SELECT index_name FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='hai_audit_logs' AND index_name='hai_audit_event_time_idx' LIMIT 1");
1179
+ return found.success ? ok(!found.data) : found;
1180
+ },
1181
+ async apply(sql) {
1182
+ const created = await sql.execute("CREATE INDEX hai_audit_event_time_idx ON hai_audit_logs (created_at)");
1183
+ return created.success ? ok(void 0) : created;
1184
+ }
916
1185
  }
917
- }];
1186
+ ];
918
1187
  function migrateAudit(options = {}) {
919
- return migrateSchema(reldb, "audit", steps, options);
1188
+ return reldb.migrateSchema("audit", steps, options);
920
1189
  }
921
1190
  async function configureAuditRetention(policy) {
922
1191
  if (!policy)
923
1192
  return ok(void 0);
924
- const result = await reldb.sql.execute("UPDATE hai_audit_policy SET enabled=?, min_days=? WHERE id=1", [Number(policy.enabled), policy.minDays]);
1193
+ const result = await reldb.sql.execute("UPDATE hai_audit_policy SET enabled=?, min_days=? WHERE id=1", [
1194
+ Number(policy.enabled),
1195
+ policy.minDays
1196
+ ]);
925
1197
  return result.success && result.data.changes === 1 ? ok(void 0) : err(HaiAuditError.CONFIG_ERROR, auditM("audit_retentionPolicyFailed"), result.success ? void 0 : result.error);
926
1198
  }
927
1199
  async function auditRetentionDays() {
@@ -956,22 +1228,134 @@ var AuditLogRepository = class extends BaseReldbCrudRepository {
956
1228
  idColumn: "id",
957
1229
  generateId: () => core.id.withPrefix("audit_"),
958
1230
  fields: [
959
- { fieldName: "id", columnName: "id", def: { type: "TEXT", primaryKey: true }, select: true, create: true, update: false },
960
- { fieldName: "userId", columnName: "user_id", def: { type: "TEXT" }, select: true, create: true, update: false },
961
- { fieldName: "action", columnName: "action", def: { type: "TEXT", notNull: true }, select: true, create: true, update: false },
962
- { fieldName: "resource", columnName: "resource", def: { type: "TEXT", notNull: true }, select: true, create: true, update: false },
963
- { fieldName: "resourceId", columnName: "resource_id", def: { type: "TEXT" }, select: true, create: true, update: false },
964
- { fieldName: "details", columnName: "details", def: { type: "JSON" }, select: true, create: true, update: false },
965
- { fieldName: "ipAddress", columnName: "ip_address", def: { type: "TEXT" }, select: true, create: true, update: false },
966
- { fieldName: "userAgent", columnName: "user_agent", def: { type: "TEXT" }, select: true, create: true, update: false },
967
- { fieldName: "category", columnName: "category", def: { type: "TEXT" }, select: true, create: true, update: false },
968
- { fieldName: "outcome", columnName: "outcome", def: { type: "TEXT" }, select: true, create: true, update: false },
969
- { fieldName: "severity", columnName: "severity", def: { type: "TEXT" }, select: true, create: true, update: false },
970
- { fieldName: "requestId", columnName: "request_id", def: { type: "TEXT" }, select: true, create: true, update: false },
971
- { fieldName: "traceId", columnName: "trace_id", def: { type: "TEXT" }, select: true, create: true, update: false },
972
- { fieldName: "scopeId", columnName: "scope_id", def: { type: "TEXT" }, select: true, create: true, update: false },
973
- { fieldName: "actorSnapshot", columnName: "actor_snapshot", def: { type: "JSON" }, select: true, create: true, update: false },
974
- { fieldName: "createdAt", columnName: "created_at", def: { type: "TIMESTAMP" }, select: true, create: true, update: false }
1231
+ {
1232
+ fieldName: "id",
1233
+ columnName: "id",
1234
+ def: { type: "TEXT", primaryKey: true },
1235
+ select: true,
1236
+ create: true,
1237
+ update: false
1238
+ },
1239
+ {
1240
+ fieldName: "userId",
1241
+ columnName: "user_id",
1242
+ def: { type: "TEXT" },
1243
+ select: true,
1244
+ create: true,
1245
+ update: false
1246
+ },
1247
+ {
1248
+ fieldName: "action",
1249
+ columnName: "action",
1250
+ def: { type: "TEXT", notNull: true },
1251
+ select: true,
1252
+ create: true,
1253
+ update: false
1254
+ },
1255
+ {
1256
+ fieldName: "resource",
1257
+ columnName: "resource",
1258
+ def: { type: "TEXT", notNull: true },
1259
+ select: true,
1260
+ create: true,
1261
+ update: false
1262
+ },
1263
+ {
1264
+ fieldName: "resourceId",
1265
+ columnName: "resource_id",
1266
+ def: { type: "TEXT" },
1267
+ select: true,
1268
+ create: true,
1269
+ update: false
1270
+ },
1271
+ {
1272
+ fieldName: "details",
1273
+ columnName: "details",
1274
+ def: { type: "JSON" },
1275
+ select: true,
1276
+ create: true,
1277
+ update: false
1278
+ },
1279
+ {
1280
+ fieldName: "ipAddress",
1281
+ columnName: "ip_address",
1282
+ def: { type: "TEXT" },
1283
+ select: true,
1284
+ create: true,
1285
+ update: false
1286
+ },
1287
+ {
1288
+ fieldName: "userAgent",
1289
+ columnName: "user_agent",
1290
+ def: { type: "TEXT" },
1291
+ select: true,
1292
+ create: true,
1293
+ update: false
1294
+ },
1295
+ {
1296
+ fieldName: "category",
1297
+ columnName: "category",
1298
+ def: { type: "TEXT" },
1299
+ select: true,
1300
+ create: true,
1301
+ update: false
1302
+ },
1303
+ {
1304
+ fieldName: "outcome",
1305
+ columnName: "outcome",
1306
+ def: { type: "TEXT" },
1307
+ select: true,
1308
+ create: true,
1309
+ update: false
1310
+ },
1311
+ {
1312
+ fieldName: "severity",
1313
+ columnName: "severity",
1314
+ def: { type: "TEXT" },
1315
+ select: true,
1316
+ create: true,
1317
+ update: false
1318
+ },
1319
+ {
1320
+ fieldName: "requestId",
1321
+ columnName: "request_id",
1322
+ def: { type: "TEXT" },
1323
+ select: true,
1324
+ create: true,
1325
+ update: false
1326
+ },
1327
+ {
1328
+ fieldName: "traceId",
1329
+ columnName: "trace_id",
1330
+ def: { type: "TEXT" },
1331
+ select: true,
1332
+ create: true,
1333
+ update: false
1334
+ },
1335
+ {
1336
+ fieldName: "scopeId",
1337
+ columnName: "scope_id",
1338
+ def: { type: "TEXT" },
1339
+ select: true,
1340
+ create: true,
1341
+ update: false
1342
+ },
1343
+ {
1344
+ fieldName: "actorSnapshot",
1345
+ columnName: "actor_snapshot",
1346
+ def: { type: "JSON" },
1347
+ select: true,
1348
+ create: true,
1349
+ update: false
1350
+ },
1351
+ {
1352
+ fieldName: "createdAt",
1353
+ columnName: "created_at",
1354
+ def: { type: "TIMESTAMP" },
1355
+ select: true,
1356
+ create: true,
1357
+ update: false
1358
+ }
975
1359
  ]
976
1360
  });
977
1361
  this.repoConfig = config;
@@ -1006,12 +1390,25 @@ var AuditLogRepository = class extends BaseReldbCrudRepository {
1006
1390
  logger4.warn("Audit actor name unavailable", { userId, error: result.error });
1007
1391
  return { id: userId };
1008
1392
  }
1009
- return { id: userId, ...result.data?.username ? { username: result.data.username } : {} };
1393
+ return {
1394
+ id: userId,
1395
+ ...result.data?.username ? { username: result.data.username } : {}
1396
+ };
1010
1397
  }
1011
1398
  filters(options) {
1012
1399
  const conditions = [];
1013
1400
  const params = [];
1014
- for (const [field, column] of [["userId", "user_id"], ["action", "action"], ["resource", "resource"], ["category", "category"], ["outcome", "outcome"], ["severity", "severity"], ["scopeId", "scope_id"], ["requestId", "request_id"], ["traceId", "trace_id"]]) {
1401
+ for (const [field, column] of [
1402
+ ["userId", "user_id"],
1403
+ ["action", "action"],
1404
+ ["resource", "resource"],
1405
+ ["category", "category"],
1406
+ ["outcome", "outcome"],
1407
+ ["severity", "severity"],
1408
+ ["scopeId", "scope_id"],
1409
+ ["requestId", "request_id"],
1410
+ ["traceId", "trace_id"]
1411
+ ]) {
1015
1412
  if (options[field] !== void 0) {
1016
1413
  conditions.push(`a.${column} = ?`);
1017
1414
  params.push(options[field]);
@@ -1047,7 +1444,15 @@ var AuditLogRepository = class extends BaseReldbCrudRepository {
1047
1444
  if (!time.success)
1048
1445
  return time;
1049
1446
  const now = time.data;
1050
- const context = { category: input.category ?? null, outcome: input.outcome ?? null, severity: input.severity ?? null, requestId: input.requestId ?? null, traceId: input.traceId ?? null, scopeId: input.scopeId ?? null, actorSnapshot: input.actorSnapshot ?? null };
1447
+ const context = {
1448
+ category: input.category ?? null,
1449
+ outcome: input.outcome ?? null,
1450
+ severity: input.severity ?? null,
1451
+ requestId: input.requestId ?? null,
1452
+ traceId: input.traceId ?? null,
1453
+ scopeId: input.scopeId ?? null,
1454
+ actorSnapshot: input.actorSnapshot ?? null
1455
+ };
1051
1456
  const data = {
1052
1457
  ...context,
1053
1458
  id,
@@ -1066,7 +1471,9 @@ var AuditLogRepository = class extends BaseReldbCrudRepository {
1066
1471
  logger4.error("Failed to record audit log", { action: input.action, error: createResult.error.message });
1067
1472
  return err(
1068
1473
  HaiAuditError.LOG_FAILED,
1069
- auditM("audit_logFailed", { params: { error: createResult.error.message } }),
1474
+ auditM("audit_logFailed", {
1475
+ params: { error: createResult.error.message }
1476
+ }),
1070
1477
  createResult.error
1071
1478
  );
1072
1479
  }
@@ -1088,7 +1495,11 @@ var AuditLogRepository = class extends BaseReldbCrudRepository {
1088
1495
  logger4.error("Failed to record audit log", { action: input.action, error });
1089
1496
  return err(
1090
1497
  HaiAuditError.LOG_FAILED,
1091
- auditM("audit_logFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
1498
+ auditM("audit_logFailed", {
1499
+ params: {
1500
+ error: error instanceof Error ? error.message : String(error)
1501
+ }
1502
+ }),
1092
1503
  error
1093
1504
  );
1094
1505
  }
@@ -1108,11 +1519,24 @@ var AuditLogRepository = class extends BaseReldbCrudRepository {
1108
1519
  const { userTable, userIdColumn, userNameColumn } = this.repoConfig;
1109
1520
  const quote = this.db.config?.type === "mysql" ? "`" : '"';
1110
1521
  const identifier = (name) => `${quote}${name.replaceAll(quote, quote + quote)}${quote}`;
1111
- const sortColumns = { createdAt: "created_at", action: "action", resource: "resource", category: "category", outcome: "outcome", severity: "severity" };
1522
+ const sortColumns = {
1523
+ createdAt: "created_at",
1524
+ action: "action",
1525
+ resource: "resource",
1526
+ category: "category",
1527
+ outcome: "outcome",
1528
+ severity: "severity"
1529
+ };
1112
1530
  const sortBy = options.sortBy ?? "createdAt";
1113
1531
  const direction = options.sortDirection ?? "desc";
1114
- if (!Object.hasOwn(sortColumns, sortBy) || !["asc", "desc"].includes(direction))
1115
- return err(HaiAuditError.QUERY_FAILED, auditM("audit_invalidInput", { params: { field: "sort", reason: auditM("audit_sortInvalid") } }));
1532
+ if (!Object.hasOwn(sortColumns, sortBy) || !["asc", "desc"].includes(direction)) {
1533
+ return err(HaiAuditError.QUERY_FAILED, auditM("audit_invalidInput", {
1534
+ params: {
1535
+ field: "sort",
1536
+ reason: auditM("audit_sortInvalid")
1537
+ }
1538
+ }));
1539
+ }
1116
1540
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1117
1541
  try {
1118
1542
  const result = await this.sql().queryPage({
@@ -1132,16 +1556,28 @@ var AuditLogRepository = class extends BaseReldbCrudRepository {
1132
1556
  logger4.error("Failed to query audit logs", { error: result.error.message });
1133
1557
  return err(
1134
1558
  HaiAuditError.QUERY_FAILED,
1135
- auditM("audit_queryFailed", { params: { error: result.error.message } }),
1559
+ auditM("audit_queryFailed", {
1560
+ params: { error: result.error.message }
1561
+ }),
1136
1562
  result.error
1137
1563
  );
1138
1564
  }
1139
- return ok({ ...result.data, items: result.data.items.map((row) => ({ ...normalizeAuditLog(row), username: readAuditActor(row.actorSnapshot)?.username ?? (row.actorSnapshot == null ? row.username ?? null : null) })) });
1565
+ return ok({
1566
+ ...result.data,
1567
+ items: result.data.items.map((row) => ({
1568
+ ...normalizeAuditLog(row),
1569
+ username: readAuditActor(row.actorSnapshot)?.username ?? (row.actorSnapshot == null ? row.username ?? null : null)
1570
+ }))
1571
+ });
1140
1572
  } catch (error) {
1141
1573
  logger4.error("Failed to query audit logs", { error });
1142
1574
  return err(
1143
1575
  HaiAuditError.QUERY_FAILED,
1144
- auditM("audit_queryFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
1576
+ auditM("audit_queryFailed", {
1577
+ params: {
1578
+ error: error instanceof Error ? error.message : String(error)
1579
+ }
1580
+ }),
1145
1581
  error
1146
1582
  );
1147
1583
  }
@@ -1171,7 +1607,9 @@ var AuditLogRepository = class extends BaseReldbCrudRepository {
1171
1607
  ["actor_snapshot", "actorSnapshot"]
1172
1608
  ].map(([column, alias]) => `${column} AS ${quote}${alias}${quote}`).join(", ");
1173
1609
  const conditions = ["created_at <= ?"];
1174
- const params = [this.toDateParam(options.cutoff)];
1610
+ const params = [
1611
+ this.toDateParam(options.cutoff)
1612
+ ];
1175
1613
  if (options.from) {
1176
1614
  conditions.push("created_at >= ?");
1177
1615
  params.push(this.toDateParam(options.from));
@@ -1209,7 +1647,9 @@ var AuditLogRepository = class extends BaseReldbCrudRepository {
1209
1647
  if (!result.success) {
1210
1648
  return err(
1211
1649
  HaiAuditError.QUERY_FAILED,
1212
- auditM("audit_queryFailed", { params: { error: result.error.message } }),
1650
+ auditM("audit_queryFailed", {
1651
+ params: { error: result.error.message }
1652
+ }),
1213
1653
  result.error
1214
1654
  );
1215
1655
  }
@@ -1217,7 +1657,11 @@ var AuditLogRepository = class extends BaseReldbCrudRepository {
1217
1657
  } catch (error) {
1218
1658
  return err(
1219
1659
  HaiAuditError.QUERY_FAILED,
1220
- auditM("audit_queryFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
1660
+ auditM("audit_queryFailed", {
1661
+ params: {
1662
+ error: error instanceof Error ? error.message : String(error)
1663
+ }
1664
+ }),
1221
1665
  error
1222
1666
  );
1223
1667
  }
@@ -1242,13 +1686,17 @@ var AuditLogRepository = class extends BaseReldbCrudRepository {
1242
1686
  try {
1243
1687
  const result = await this.sql().execute(
1244
1688
  `DELETE FROM ${AUDIT_TABLE} WHERE created_at < ?`,
1245
- [this.toDateParam(cutoff)]
1689
+ [
1690
+ this.toDateParam(cutoff)
1691
+ ]
1246
1692
  );
1247
1693
  if (!result.success) {
1248
1694
  logger4.error("Failed to cleanup audit logs", { error: result.error.message });
1249
1695
  return err(
1250
1696
  HaiAuditError.CLEANUP_FAILED,
1251
- auditM("audit_cleanupFailed", { params: { error: result.error.message } }),
1697
+ auditM("audit_cleanupFailed", {
1698
+ params: { error: result.error.message }
1699
+ }),
1252
1700
  result.error
1253
1701
  );
1254
1702
  }
@@ -1262,7 +1710,11 @@ var AuditLogRepository = class extends BaseReldbCrudRepository {
1262
1710
  logger4.error("Failed to cleanup audit logs", { error });
1263
1711
  return err(
1264
1712
  HaiAuditError.CLEANUP_FAILED,
1265
- auditM("audit_cleanupFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
1713
+ auditM("audit_cleanupFailed", {
1714
+ params: {
1715
+ error: error instanceof Error ? error.message : String(error)
1716
+ }
1717
+ }),
1266
1718
  error
1267
1719
  );
1268
1720
  }
@@ -1294,11 +1746,16 @@ var AuditLogRepository = class extends BaseReldbCrudRepository {
1294
1746
  logger4.error("Failed to query audit statistics", { error: result.error.message });
1295
1747
  return err(
1296
1748
  HaiAuditError.STATS_FAILED,
1297
- auditM("audit_statsFailed", { params: { error: result.error.message } }),
1749
+ auditM("audit_statsFailed", {
1750
+ params: { error: result.error.message }
1751
+ }),
1298
1752
  result.error
1299
1753
  );
1300
1754
  }
1301
- const statistics = result.data.map((item) => ({ action: item.action, count: Number(item.count) }));
1755
+ const statistics = result.data.map((item) => ({
1756
+ action: item.action,
1757
+ count: Number(item.count)
1758
+ }));
1302
1759
  if (statistics.some((item) => !Number.isSafeInteger(item.count) || item.count < 0))
1303
1760
  return err(HaiAuditError.STATS_FAILED, auditM("audit_invalidStatCount"));
1304
1761
  return ok(statistics);
@@ -1306,7 +1763,11 @@ var AuditLogRepository = class extends BaseReldbCrudRepository {
1306
1763
  logger4.error("Failed to query audit statistics", { error });
1307
1764
  return err(
1308
1765
  HaiAuditError.STATS_FAILED,
1309
- auditM("audit_statsFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
1766
+ auditM("audit_statsFailed", {
1767
+ params: {
1768
+ error: error instanceof Error ? error.message : String(error)
1769
+ }
1770
+ }),
1310
1771
  error
1311
1772
  );
1312
1773
  }
@@ -1384,7 +1845,9 @@ var audit = {
1384
1845
  logger5.error("Audit config validation failed", { error: parseResult.error.message });
1385
1846
  return err(
1386
1847
  HaiAuditError.CONFIG_ERROR,
1387
- auditM("audit_configError", { params: { error: parseResult.error.message } }),
1848
+ auditM("audit_configError", {
1849
+ params: { error: parseResult.error.message }
1850
+ }),
1388
1851
  parseResult.error
1389
1852
  );
1390
1853
  }
@@ -1400,7 +1863,9 @@ var audit = {
1400
1863
  logger5.error("Audit config contains invalid identifiers", { error: identifierResult.error.message });
1401
1864
  return err(
1402
1865
  HaiAuditError.CONFIG_ERROR,
1403
- auditM("audit_configError", { params: { error: identifierResult.error.message } }),
1866
+ auditM("audit_configError", {
1867
+ params: { error: identifierResult.error.message }
1868
+ }),
1404
1869
  identifierResult.error
1405
1870
  );
1406
1871
  }
@@ -1414,7 +1879,9 @@ var audit = {
1414
1879
  });
1415
1880
  const ready = await candidate.ready();
1416
1881
  if (!ready.success) {
1417
- return err(HaiAuditError.CONFIG_ERROR, auditM("audit_initFailed", { params: { error: ready.error.message } }), ready.error);
1882
+ return err(HaiAuditError.CONFIG_ERROR, auditM("audit_initFailed", {
1883
+ params: { error: ready.error.message }
1884
+ }), ready.error);
1418
1885
  }
1419
1886
  const protection = await migrateAudit();
1420
1887
  if (!protection.success)
@@ -1428,7 +1895,12 @@ var audit = {
1428
1895
  currentWriter = createAuditWriter(candidate, parsed);
1429
1896
  capturePolicy = parsed.capture;
1430
1897
  currentHelper = createHelper((input) => audit.log(input));
1431
- currentBackup = createAuditBackup({ repository: candidate, config: parsed.backup, storage: config?.storage, capture: (input, options) => audit.capture(input, options) });
1898
+ currentBackup = createAuditBackup({
1899
+ repository: candidate,
1900
+ config: parsed.backup,
1901
+ storage: config?.storage,
1902
+ capture: (input, options) => audit.capture(input, options)
1903
+ });
1432
1904
  currentAlerts = createAuditAlerts({ config: parsed.alerts, reach: config?.reach, log: currentWriter });
1433
1905
  logger5.info("Audit module initialized");
1434
1906
  return ok(void 0);
@@ -1436,7 +1908,11 @@ var audit = {
1436
1908
  logger5.error("Audit module initialization failed", { error });
1437
1909
  return err(
1438
1910
  HaiAuditError.CONFIG_ERROR,
1439
- auditM("audit_initFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
1911
+ auditM("audit_initFailed", {
1912
+ params: {
1913
+ error: error instanceof Error ? error.message : String(error)
1914
+ }
1915
+ }),
1440
1916
  error
1441
1917
  );
1442
1918
  } finally {
@@ -1480,12 +1956,20 @@ var audit = {
1480
1956
  if (!currentRepo) {
1481
1957
  return Promise.resolve(notInitialized.result());
1482
1958
  }
1483
- if (!validAuditQuery(options))
1484
- return Promise.resolve(err(HaiAuditError.QUERY_FAILED, auditM("audit_invalidInput", { params: { field: "query", reason: auditM("audit_queryInvalid") } })));
1959
+ if (!validAuditQuery(options)) {
1960
+ return Promise.resolve(err(HaiAuditError.QUERY_FAILED, auditM("audit_invalidInput", {
1961
+ params: {
1962
+ field: "query",
1963
+ reason: auditM("audit_queryInvalid")
1964
+ }
1965
+ })));
1966
+ }
1485
1967
  if (options?.startDate && options?.endDate && options.startDate > options.endDate) {
1486
1968
  return Promise.resolve(err(
1487
1969
  HaiAuditError.QUERY_FAILED,
1488
- auditM("audit_invalidInput", { params: { field: "dateRange", reason: "startDate must be before endDate" } })
1970
+ auditM("audit_invalidInput", {
1971
+ params: { field: "dateRange", reason: "startDate must be before endDate" }
1972
+ })
1489
1973
  ));
1490
1974
  }
1491
1975
  return currentRepo.listWithUser(options);
@@ -1504,13 +1988,17 @@ var audit = {
1504
1988
  if (!isNonEmptyString(userId)) {
1505
1989
  return Promise.resolve(err(
1506
1990
  HaiAuditError.QUERY_FAILED,
1507
- auditM("audit_invalidInput", { params: { field: "userId", reason: "must be non-empty string" } })
1991
+ auditM("audit_invalidInput", {
1992
+ params: { field: "userId", reason: "must be non-empty string" }
1993
+ })
1508
1994
  ));
1509
1995
  }
1510
1996
  if (typeof limit === "number" && (!Number.isInteger(limit) || limit <= 0)) {
1511
1997
  return Promise.resolve(err(
1512
1998
  HaiAuditError.QUERY_FAILED,
1513
- auditM("audit_invalidInput", { params: { field: "limit", reason: "must be a positive integer" } })
1999
+ auditM("audit_invalidInput", {
2000
+ params: { field: "limit", reason: "must be a positive integer" }
2001
+ })
1514
2002
  ));
1515
2003
  }
1516
2004
  return currentRepo.getUserRecent(userId, limit);
@@ -1528,7 +2016,9 @@ var audit = {
1528
2016
  if (typeof olderThanDays === "number" && (!Number.isInteger(olderThanDays) || olderThanDays < 0)) {
1529
2017
  return Promise.resolve(err(
1530
2018
  HaiAuditError.CLEANUP_FAILED,
1531
- auditM("audit_invalidInput", { params: { field: "olderThanDays", reason: "must be a non-negative integer" } })
2019
+ auditM("audit_invalidInput", {
2020
+ params: { field: "olderThanDays", reason: "must be a non-negative integer" }
2021
+ })
1532
2022
  ));
1533
2023
  }
1534
2024
  return currentRepo.cleanupOld(olderThanDays);
@@ -1543,12 +2033,20 @@ var audit = {
1543
2033
  if (!currentRepo) {
1544
2034
  return Promise.resolve(notInitialized.result());
1545
2035
  }
1546
- if (!validAuditQuery(filters))
1547
- return Promise.resolve(err(HaiAuditError.STATS_FAILED, auditM("audit_invalidInput", { params: { field: "query", reason: auditM("audit_queryInvalid") } })));
2036
+ if (!validAuditQuery(filters)) {
2037
+ return Promise.resolve(err(HaiAuditError.STATS_FAILED, auditM("audit_invalidInput", {
2038
+ params: {
2039
+ field: "query",
2040
+ reason: auditM("audit_queryInvalid")
2041
+ }
2042
+ })));
2043
+ }
1548
2044
  if (typeof days === "number" && (!Number.isInteger(days) || days < 0)) {
1549
2045
  return Promise.resolve(err(
1550
2046
  HaiAuditError.STATS_FAILED,
1551
- auditM("audit_invalidInput", { params: { field: "days", reason: "must be a non-negative integer" } })
2047
+ auditM("audit_invalidInput", {
2048
+ params: { field: "days", reason: "must be a non-negative integer" }
2049
+ })
1552
2050
  ));
1553
2051
  }
1554
2052
  return currentRepo.getStats(days, filters);
@@ -1576,14 +2074,19 @@ var audit = {
1576
2074
  if (!backup && !alerts)
1577
2075
  return null;
1578
2076
  return {
1579
- tasks: [...backup?.tasks ?? [], ...alerts?.tasks ?? []].map((task) => ({ ...task })),
1580
- hooks: { onTaskExecute: async (input) => {
1581
- if (input.context.taskId === "hai-audit-backup" && backup)
1582
- return backup.hooks.onTaskExecute(input);
1583
- if (input.context.taskId === "hai-audit-alerts" && alerts)
1584
- return alerts.hooks.onTaskExecute(input);
1585
- throw core.error.buildHaiErrorInst(HaiAuditError.CONFIG_ERROR, auditM("audit_unknownTask"));
1586
- } }
2077
+ tasks: [
2078
+ ...backup?.tasks ?? [],
2079
+ ...alerts?.tasks ?? []
2080
+ ].map((task) => ({ ...task })),
2081
+ hooks: {
2082
+ onTaskExecute: async (input) => {
2083
+ if (input.context.taskId === "hai-audit-backup" && backup)
2084
+ return backup.hooks.onTaskExecute(input);
2085
+ if (input.context.taskId === "hai-audit-alerts" && alerts)
2086
+ return alerts.hooks.onTaskExecute(input);
2087
+ throw core.error.buildHaiErrorInst(HaiAuditError.CONFIG_ERROR, auditM("audit_unknownTask"));
2088
+ }
2089
+ }
1587
2090
  };
1588
2091
  },
1589
2092
  /**