hierarchical-approval 2.6.0 → 2.8.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +58 -0
  3. package/dist/{ApprovalEngine-BWrblNQ0.d.cts → ApprovalEngine-D6BmYI8B.d.cts} +87 -8
  4. package/dist/{ApprovalEngine-D4DdSx-M.d.ts → ApprovalEngine-DKn7KymS.d.ts} +87 -8
  5. package/dist/{IAuditAdapter-DSor54iY.d.ts → IAuditAdapter-B33n15YP.d.ts} +1 -1
  6. package/dist/{IAuditAdapter-2UU5edKG.d.cts → IAuditAdapter-DxdK4UVt.d.cts} +1 -1
  7. package/dist/{IAuthorizationPolicy-DPpiKzyh.d.cts → IAuthorizationPolicy-BzGVzkLJ.d.cts} +1 -1
  8. package/dist/{IAuthorizationPolicy-BOWg-3AH.d.ts → IAuthorizationPolicy-DOm6KCwI.d.ts} +1 -1
  9. package/dist/{INotificationAdapter-C6HPrzh6.d.cts → INotificationAdapter-DFKvGpOp.d.cts} +13 -2
  10. package/dist/{INotificationAdapter-BNGivLgU.d.ts → INotificationAdapter-DMvA4R-c.d.ts} +13 -2
  11. package/dist/{IOperationMiddleware-CxnPzpqL.d.cts → IOperationMiddleware-C8i06wq4.d.cts} +1 -1
  12. package/dist/{IOperationMiddleware-CXtg2wzF.d.ts → IOperationMiddleware-Nk8Q1Z8o.d.ts} +1 -1
  13. package/dist/{IStorageAdapter-yX9ERfQE.d.cts → IStorageAdapter-C0qeZgs4.d.cts} +1 -1
  14. package/dist/{IStorageAdapter-ChPT7ZDp.d.ts → IStorageAdapter-C2QxUwbX.d.ts} +1 -1
  15. package/dist/adapters/MemoryAdapter.cjs +4 -0
  16. package/dist/adapters/MemoryAdapter.cjs.map +1 -1
  17. package/dist/adapters/MemoryAdapter.d.cts +2 -2
  18. package/dist/adapters/MemoryAdapter.d.ts +2 -2
  19. package/dist/adapters/MemoryAdapter.js +4 -0
  20. package/dist/adapters/MemoryAdapter.js.map +1 -1
  21. package/dist/adapters/PostgresAdapter.cjs +10 -4
  22. package/dist/adapters/PostgresAdapter.cjs.map +1 -1
  23. package/dist/adapters/PostgresAdapter.d.cts +2 -2
  24. package/dist/adapters/PostgresAdapter.d.ts +2 -2
  25. package/dist/adapters/PostgresAdapter.js +10 -4
  26. package/dist/adapters/PostgresAdapter.js.map +1 -1
  27. package/dist/index.cjs +149 -1
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +8 -8
  30. package/dist/index.d.ts +8 -8
  31. package/dist/index.js +149 -1
  32. package/dist/index.js.map +1 -1
  33. package/dist/{instance-weh2w7Ji.d.cts → instance-BvOyT00S.d.cts} +23 -1
  34. package/dist/{instance-weh2w7Ji.d.ts → instance-BvOyT00S.d.ts} +23 -1
  35. package/dist/nestjs.cjs +145 -1
  36. package/dist/nestjs.cjs.map +1 -1
  37. package/dist/nestjs.d.cts +7 -7
  38. package/dist/nestjs.d.ts +7 -7
  39. package/dist/nestjs.js +145 -1
  40. package/dist/nestjs.js.map +1 -1
  41. package/dist/plugins/audit.d.cts +2 -2
  42. package/dist/plugins/audit.d.ts +2 -2
  43. package/dist/plugins/notify.d.cts +2 -2
  44. package/dist/plugins/notify.d.ts +2 -2
  45. package/dist/plugins/resilience.d.cts +3 -3
  46. package/dist/plugins/resilience.d.ts +3 -3
  47. package/dist/plugins/tracing.d.cts +2 -2
  48. package/dist/plugins/tracing.d.ts +2 -2
  49. package/dist/plugins/webhook.d.cts +2 -2
  50. package/dist/plugins/webhook.d.ts +2 -2
  51. package/dist/testing.cjs +149 -1
  52. package/dist/testing.cjs.map +1 -1
  53. package/dist/testing.d.cts +7 -7
  54. package/dist/testing.d.ts +7 -7
  55. package/dist/testing.js +149 -1
  56. package/dist/testing.js.map +1 -1
  57. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,84 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  _Nothing yet._
9
9
 
10
+ ## [2.8.0] - 2026-09-04
11
+
12
+ ### Added — comment threads
13
+
14
+ - **Comments are addressable objects rather than audit entries alone.** There
15
+ was no id to point at, so no way to reply to a comment, and no way to tell
16
+ somebody a remark was aimed at them — discussion moved to email, where the
17
+ approval record could not see it.
18
+
19
+ ```ts
20
+ await engine.addComment(id, { actorId: 'mgr-1', comment: 'Need the quote.' });
21
+ const [question] = await engine.getComments(id);
22
+ await engine.addComment(id, {
23
+ actorId: 'buyer-1',
24
+ comment: 'Attached now.',
25
+ parentCommentId: question.id,
26
+ mentions: ['mgr-1'],
27
+ });
28
+ ```
29
+
30
+ - **`approval:commented` is addressed to the people the comment mentions**, not
31
+ to the current approvers. A remark aimed at somebody should reach them, and
32
+ one aimed at nobody should not page the whole level.
33
+
34
+ - **`getComments()` returns a flat list carrying `parentCommentId`**, oldest
35
+ first, rather than a nested tree: a UI that wants threads can build them, and
36
+ one that wants a chronological feed does not have to flatten a structure it
37
+ never wanted. Replying to a comment that is not on the approval is rejected.
38
+
39
+ Comments are still written to the audit trail — the record of who said what
40
+ belongs there. `addComment()` keeps its `Promise<void>` signature, so nothing
41
+ calling it needs to change.
42
+
43
+ New exports: `Comment`, `CommentedEvent`. `ApprovalInstance` gains `comments`.
44
+
45
+ ### Fixed — MemoryAdapter left some timestamps as strings
46
+
47
+ - **`attachments[].addedAt`, `infoRequest.askedAt` and `levels[].reminderDueAt`
48
+ read back as strings, not `Date`s.** `MemoryAdapter` clones through JSON and
49
+ revives date fields by an explicit list, which these were missing from —
50
+ while `PostgresAdapter` revived them correctly. The two adapters therefore
51
+ disagreed, and any code trusting the declared `Date` type broke under one of
52
+ them only. Affected `addedAt` since 1.7.0 and `askedAt` since 1.5.0.
53
+
54
+ ## [2.7.0] - 2026-09-04
55
+
56
+ ### Added — `explainChain()`
57
+
58
+ - **Explains why a chain resolves the way it does.** `previewApprovalChain()`
59
+ answers *what* the chain will be; nothing answered *why*, so "why does this
60
+ purchase order have a CFO level?" meant reading the template and
61
+ re-evaluating its conditions by hand — the most common support question about
62
+ an approval engine, and the one it was worst at answering.
63
+
64
+ ```ts
65
+ await engine.explainChain('purchase-order', data, 'buyer-1');
66
+ // levels: [{ level: 3, name: 'CFO', source: 'condition', addedByRule: 0, … }]
67
+ // skipped: [{ level: 2, name: 'Finance', skippedByRule: 1 }]
68
+ // rules: [{ index: 0, matched: true, addsLevels: [3], skipsLevels: [] }, …]
69
+ ```
70
+
71
+ - **Every rule is reported, matched or not**, along with what it *would* add or
72
+ skip — which is how you find the rule that was supposed to fire and didn't,
73
+ not just the ones that did.
74
+
75
+ - **Failures are described rather than thrown.** A level whose approvers cannot
76
+ be resolved is still listed, carrying `resolutionError`; a rule that throws —
77
+ an unregistered operator, a malformed group — is reported against that rule
78
+ and the rest of the explanation still returns. A diagnostic is least useful at
79
+ exactly the moment a broken rule would make it throw.
80
+
81
+ - Reads nothing and writes nothing, so it is safe to expose to a support UI.
82
+ Sub-workflow levels are marked with their child template and skip approver
83
+ resolution, since nobody approves them directly.
84
+
85
+ New exports: `ChainExplanation`, `ExplainedLevel`, `ExplainedSkip`,
86
+ `ExplainedRule`.
87
+
10
88
  ## [2.6.0] - 2026-09-04
11
89
 
12
90
  ### Added — escalation ladders
package/README.md CHANGED
@@ -468,6 +468,64 @@ The sweep is **not atomic**: it reports per-instance failures rather than rollin
468
468
  back. A partial transfer is the useful outcome — the approvals that can move
469
469
  should move, and the ones that cannot are named so a human can look at them.
470
470
 
471
+ ### Comment threads
472
+
473
+ Comments are addressable objects, so approvers can reply to each other and name
474
+ the person a remark is aimed at:
475
+
476
+ ```ts
477
+ await engine.addComment(id, { actorId: 'mgr-1', comment: 'Need the quote.' });
478
+ const [question] = await engine.getComments(id);
479
+
480
+ await engine.addComment(id, {
481
+ actorId: 'buyer-1',
482
+ comment: 'Attached now, @mgr-1.',
483
+ parentCommentId: question.id,
484
+ mentions: ['mgr-1'],
485
+ });
486
+ ```
487
+
488
+ `getComments()` returns a flat list carrying `parentCommentId`, oldest first — a
489
+ UI that wants threads can build them, and one that wants a chronological feed
490
+ (what most approval screens show) does not have to flatten a structure it never
491
+ wanted.
492
+
493
+ `approval:commented` is addressed to the people the comment **mentions**, not to
494
+ the current approvers: a remark aimed at somebody should reach them, and one
495
+ aimed at nobody should not page the whole level. Comments are still written to
496
+ the audit trail, since the record of who said what belongs there.
497
+
498
+ ### Why does this chain look like this?
499
+
500
+ `previewApprovalChain()` answers *what* the chain will be. `explainChain()`
501
+ answers *why* — the question behind "why does this purchase order have a CFO
502
+ level?":
503
+
504
+ ```ts
505
+ const explanation = await engine.explainChain('purchase-order', data, 'buyer-1');
506
+ // {
507
+ // templateName: 'purchase-order',
508
+ // levels: [
509
+ // { level: 1, name: 'Manager', source: 'template', resolvedApprovers: ['mgr-1'], … },
510
+ // { level: 3, name: 'CFO', source: 'condition', addedByRule: 0, resolvedApprovers: ['cfo'], … },
511
+ // ],
512
+ // skipped: [{ level: 2, name: 'Finance', skippedByRule: 1 }],
513
+ // rules: [
514
+ // { index: 0, matched: true, addsLevels: [3], skipsLevels: [] },
515
+ // { index: 1, matched: true, addsLevels: [], skipsLevels: [2] },
516
+ // ],
517
+ // }
518
+ ```
519
+
520
+ Every rule is reported, matched or not, along with what it *would* do — which is
521
+ usually how you find the rule that was supposed to fire and didn't. A level
522
+ whose approvers cannot be resolved is still listed, with `resolutionError`
523
+ naming the reason; a rule that throws is reported against that rule rather than
524
+ failing the whole explanation, since the explanation is least useful at exactly
525
+ the moment a broken rule would make it throw.
526
+
527
+ Reads nothing and writes nothing, so it is safe to expose to a support UI.
528
+
471
529
  ### Escalation ladders
472
530
 
473
531
  A single `escalation` fires once, so a request that stalls past its second
@@ -1,14 +1,14 @@
1
- import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-yX9ERfQE.cjs';
2
- import { l as ConditionExpression, q as ResolverFn, g as ApprovalTemplateConfig, A as ApprovalTemplate, k as AuditContext, a as ApprovalInstance, e as ApprovalMode, b as AuditEntry } from './instance-weh2w7Ji.cjs';
3
- import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-C6HPrzh6.cjs';
1
+ import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-C0qeZgs4.cjs';
2
+ import { m as ConditionExpression, r as ResolverFn, g as ApprovalTemplateConfig, A as ApprovalTemplate, k as AuditContext, a as ApprovalInstance, C as Comment, e as ApprovalMode, b as AuditEntry } from './instance-BvOyT00S.cjs';
3
+ import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-DFKvGpOp.cjs';
4
4
  import { z } from 'zod';
5
5
  import { L as Logger } from './Logger-BplhlU7l.cjs';
6
6
  import { C as Clock } from './Clock-3FnOczFJ.cjs';
7
- import { I as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-CxnPzpqL.cjs';
8
- import { I as IAuditAdapter } from './IAuditAdapter-2UU5edKG.cjs';
7
+ import { I as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-C8i06wq4.cjs';
8
+ import { I as IAuditAdapter } from './IAuditAdapter-DxdK4UVt.cjs';
9
9
  import { I as IMetricsAdapter } from './IMetricsAdapter-DWq8IFaf.cjs';
10
10
  import { I as ISchedulerAdapter } from './ISchedulerAdapter-DKv_QjVN.cjs';
11
- import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-DPpiKzyh.cjs';
11
+ import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-BzGVzkLJ.cjs';
12
12
 
13
13
  declare const SubmitOptionsSchema: z.ZodObject<{
14
14
  templateName: z.ZodString;
@@ -66,6 +66,8 @@ declare const ResubmitOptionsSchema: z.ZodObject<{
66
66
  declare const AddCommentOptionsSchema: z.ZodObject<{
67
67
  actorId: z.ZodString;
68
68
  comment: z.ZodString;
69
+ parentCommentId: z.ZodOptional<z.ZodString>;
70
+ mentions: z.ZodOptional<z.ZodArray<z.ZodString>>;
69
71
  }, z.core.$strip>;
70
72
  declare const OverrideOptionsSchema: z.ZodObject<{
71
73
  overriddenBy: z.ZodString;
@@ -199,7 +201,7 @@ interface BusinessHoursCalendarOptions extends WeekendCalendarOptions {
199
201
  */
200
202
  declare function businessHoursCalendar(options?: BusinessHoursCalendarOptions): Required<Pick<BusinessCalendar, 'addBusinessDays' | 'addBusinessHours'>>;
201
203
 
202
- type IdGeneratorPrefix = 'inst' | 'tpl' | 'att';
204
+ type IdGeneratorPrefix = 'inst' | 'tpl' | 'att' | 'cmt';
203
205
  type IdGeneratorFn = (prefix: IdGeneratorPrefix) => string;
204
206
  declare const defaultIdGenerator: IdGeneratorFn;
205
207
 
@@ -299,6 +301,52 @@ interface PreviewResult {
299
301
  /** Indices (0-based) of conditions that fired for this data. */
300
302
  conditionsApplied: number[];
301
303
  }
304
+ /** Where one level in an explained chain came from. */
305
+ interface ExplainedLevel {
306
+ level: number;
307
+ name: string;
308
+ mode: ApprovalMode;
309
+ /** `'template'` for a statically declared level, `'condition'` for one a rule added. */
310
+ source: 'template' | 'condition';
311
+ /** Index of the condition rule that added it, when `source` is `'condition'`. */
312
+ addedByRule?: number;
313
+ resolvedApprovers: string[];
314
+ /** Why approver resolution failed, when it did. The level is still listed. */
315
+ resolutionError?: string;
316
+ /** Set when this level hands off to a child approval. */
317
+ subWorkflowTemplate?: string;
318
+ }
319
+ /** A level the template declares that will not run, and the rule that removed it. */
320
+ interface ExplainedSkip {
321
+ level: number;
322
+ name: string;
323
+ /** Index of the condition rule whose `skipLevels` removed it. */
324
+ skippedByRule: number;
325
+ }
326
+ /** How one condition rule evaluated against the data. */
327
+ interface ExplainedRule {
328
+ index: number;
329
+ matched: boolean;
330
+ /** Levels this rule would add. Present whether or not it matched. */
331
+ addsLevels: number[];
332
+ /** Levels this rule would skip. Present whether or not it matched. */
333
+ skipsLevels: number[];
334
+ /** Why the rule could not be evaluated, e.g. an unregistered operator. */
335
+ error?: string;
336
+ }
337
+ /**
338
+ * A full account of why a chain looks the way it does.
339
+ *
340
+ * `previewApprovalChain()` answers *what* the chain will be; this answers *why*,
341
+ * which is the question a support engineer actually has when a purchase order
342
+ * arrives with a level nobody expected.
343
+ */
344
+ interface ChainExplanation {
345
+ templateName: string;
346
+ levels: ExplainedLevel[];
347
+ skipped: ExplainedSkip[];
348
+ rules: ExplainedRule[];
349
+ }
302
350
  interface BulkResult {
303
351
  succeeded: ApprovalInstance[];
304
352
  failed: Array<{
@@ -672,11 +720,42 @@ declare class ApprovalEngine {
672
720
  removeAttachment(instanceId: string, raw: RemoveAttachmentOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
673
721
  /** Add a comment to an instance without approving or rejecting. */
674
722
  addComment(instanceId: string, raw: AddCommentOptions, auditCtx?: AuditContext): Promise<void>;
723
+ /**
724
+ * Comments on an approval, oldest first.
725
+ *
726
+ * Returns a flat list carrying `parentCommentId`, rather than a nested tree:
727
+ * a UI that wants threads can build them, and one that wants a chronological
728
+ * feed — which is what most approval screens actually show — does not have to
729
+ * flatten a structure it never wanted.
730
+ */
731
+ getComments(instanceId: string): Promise<Comment[]>;
675
732
  /** Resubmit a rejected instance, creating a new linked instance from level 1. */
676
733
  resubmit(instanceId: string, raw: ResubmitOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
677
734
  /** Preview the resolved approval chain for a template and document data, without creating an instance. */
678
735
  previewApprovalChain(templateName: string, data: Record<string, unknown>, submittedBy: string): Promise<PreviewResult>;
679
736
  /** Check whether a user is eligible to approve a specific instance. Never throws. */
737
+ /**
738
+ * Explain why a chain resolves the way it does for a given document.
739
+ *
740
+ * `previewApprovalChain()` answers *what* the chain will be. This answers
741
+ * *why*: which rule added a level, which rule removed one, which rules were
742
+ * evaluated and did not match, and where each level's approvers came from —
743
+ * the question behind "why does this purchase order have a CFO level?", which
744
+ * previously meant reading the template and re-evaluating the conditions by
745
+ * hand.
746
+ *
747
+ * A rule that throws — an operator nobody registered, a malformed group — is
748
+ * reported against that rule rather than failing the whole explanation. The
749
+ * explanation is a diagnostic tool, and it is least useful at exactly the
750
+ * moment a broken rule makes it throw.
751
+ *
752
+ * Reads nothing and writes nothing; safe to expose to a support UI.
753
+ *
754
+ * @param templateName - Template to explain.
755
+ * @param data - Document data the conditions are evaluated against.
756
+ * @param submittedBy - Submitter, used for approver resolution.
757
+ */
758
+ explainChain(templateName: string, data: Record<string, unknown>, submittedBy: string): Promise<ChainExplanation>;
680
759
  canApprove(instanceId: string, userId: string): Promise<CanApproveResult>;
681
760
  /** Emergency bypass — completes the instance as 'approved', skipping remaining levels. Requires template.allowOverride = true. */
682
761
  override(instanceId: string, raw: OverrideOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
@@ -964,4 +1043,4 @@ declare class ApprovalEngine {
964
1043
  private runExternalAudit;
965
1044
  }
966
1045
 
967
- export { type AddCommentOptions as A, type BulkResult as B, type CanApproveResult as C, type DelegateOptions as D, type EscalateOptions as E, toComparableNumber as F, validateConditionExpression as G, type HealthResult as H, type IdGeneratorFn as I, weekendCalendar as J, type OrgProvider as O, type PreviewChainLevel as P, type ReassignOptions as R, type SubmitOptions as S, TEMPLATE_BUNDLE_VERSION as T, type UpdateDataOptions as U, type ValidationResult as V, type WeekendCalendarOptions as W, ApprovalEngine as a, type ApprovalEngineOptions as b, type ApprovalStatistics as c, type ApproveOptions as d, type ApproverResolverFn as e, type ApproverWorkload as f, type BusinessCalendar as g, type BusinessHoursCalendarOptions as h, type CancelOptions as i, type ConditionOperatorFn as j, type CycleTimeStats as k, type IdempotencyKeyFn as l, type ImportResult as m, type OutOfOfficeProvider as n, type OverrideOptions as o, type PreviewResult as p, type ProvideInfoOptions as q, type PurgeResult as r, type RejectOptions as s, type RequestInfoOptions as t, type ResubmitOptions as u, type RetryPolicy as v, type TemplateBundle as w, type TransferResult as x, businessHoursCalendar as y, defaultIdGenerator as z };
1046
+ export { type AddCommentOptions as A, type BulkResult as B, type CanApproveResult as C, type DelegateOptions as D, type EscalateOptions as E, type TemplateBundle as F, type TransferResult as G, type HealthResult as H, type IdGeneratorFn as I, businessHoursCalendar as J, defaultIdGenerator as K, toComparableNumber as L, validateConditionExpression as M, weekendCalendar as N, type OrgProvider as O, type PreviewChainLevel as P, type ReassignOptions as R, type SubmitOptions as S, TEMPLATE_BUNDLE_VERSION as T, type UpdateDataOptions as U, type ValidationResult as V, type WeekendCalendarOptions as W, ApprovalEngine as a, type ApprovalEngineOptions as b, type ApprovalStatistics as c, type ApproveOptions as d, type ApproverResolverFn as e, type ApproverWorkload as f, type BusinessCalendar as g, type BusinessHoursCalendarOptions as h, type CancelOptions as i, type ChainExplanation as j, type ConditionOperatorFn as k, type CycleTimeStats as l, type ExplainedLevel as m, type ExplainedRule as n, type ExplainedSkip as o, type IdempotencyKeyFn as p, type ImportResult as q, type OutOfOfficeProvider as r, type OverrideOptions as s, type PreviewResult as t, type ProvideInfoOptions as u, type PurgeResult as v, type RejectOptions as w, type RequestInfoOptions as x, type ResubmitOptions as y, type RetryPolicy as z };
@@ -1,14 +1,14 @@
1
- import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-ChPT7ZDp.js';
2
- import { l as ConditionExpression, q as ResolverFn, g as ApprovalTemplateConfig, A as ApprovalTemplate, k as AuditContext, a as ApprovalInstance, e as ApprovalMode, b as AuditEntry } from './instance-weh2w7Ji.js';
3
- import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-BNGivLgU.js';
1
+ import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-C2QxUwbX.js';
2
+ import { m as ConditionExpression, r as ResolverFn, g as ApprovalTemplateConfig, A as ApprovalTemplate, k as AuditContext, a as ApprovalInstance, C as Comment, e as ApprovalMode, b as AuditEntry } from './instance-BvOyT00S.js';
3
+ import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-DMvA4R-c.js';
4
4
  import { z } from 'zod';
5
5
  import { L as Logger } from './Logger-BplhlU7l.js';
6
6
  import { C as Clock } from './Clock-3FnOczFJ.js';
7
- import { I as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-CXtg2wzF.js';
8
- import { I as IAuditAdapter } from './IAuditAdapter-DSor54iY.js';
7
+ import { I as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-Nk8Q1Z8o.js';
8
+ import { I as IAuditAdapter } from './IAuditAdapter-B33n15YP.js';
9
9
  import { I as IMetricsAdapter } from './IMetricsAdapter-DWq8IFaf.js';
10
10
  import { I as ISchedulerAdapter } from './ISchedulerAdapter-DKv_QjVN.js';
11
- import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-BOWg-3AH.js';
11
+ import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-DOm6KCwI.js';
12
12
 
13
13
  declare const SubmitOptionsSchema: z.ZodObject<{
14
14
  templateName: z.ZodString;
@@ -66,6 +66,8 @@ declare const ResubmitOptionsSchema: z.ZodObject<{
66
66
  declare const AddCommentOptionsSchema: z.ZodObject<{
67
67
  actorId: z.ZodString;
68
68
  comment: z.ZodString;
69
+ parentCommentId: z.ZodOptional<z.ZodString>;
70
+ mentions: z.ZodOptional<z.ZodArray<z.ZodString>>;
69
71
  }, z.core.$strip>;
70
72
  declare const OverrideOptionsSchema: z.ZodObject<{
71
73
  overriddenBy: z.ZodString;
@@ -199,7 +201,7 @@ interface BusinessHoursCalendarOptions extends WeekendCalendarOptions {
199
201
  */
200
202
  declare function businessHoursCalendar(options?: BusinessHoursCalendarOptions): Required<Pick<BusinessCalendar, 'addBusinessDays' | 'addBusinessHours'>>;
201
203
 
202
- type IdGeneratorPrefix = 'inst' | 'tpl' | 'att';
204
+ type IdGeneratorPrefix = 'inst' | 'tpl' | 'att' | 'cmt';
203
205
  type IdGeneratorFn = (prefix: IdGeneratorPrefix) => string;
204
206
  declare const defaultIdGenerator: IdGeneratorFn;
205
207
 
@@ -299,6 +301,52 @@ interface PreviewResult {
299
301
  /** Indices (0-based) of conditions that fired for this data. */
300
302
  conditionsApplied: number[];
301
303
  }
304
+ /** Where one level in an explained chain came from. */
305
+ interface ExplainedLevel {
306
+ level: number;
307
+ name: string;
308
+ mode: ApprovalMode;
309
+ /** `'template'` for a statically declared level, `'condition'` for one a rule added. */
310
+ source: 'template' | 'condition';
311
+ /** Index of the condition rule that added it, when `source` is `'condition'`. */
312
+ addedByRule?: number;
313
+ resolvedApprovers: string[];
314
+ /** Why approver resolution failed, when it did. The level is still listed. */
315
+ resolutionError?: string;
316
+ /** Set when this level hands off to a child approval. */
317
+ subWorkflowTemplate?: string;
318
+ }
319
+ /** A level the template declares that will not run, and the rule that removed it. */
320
+ interface ExplainedSkip {
321
+ level: number;
322
+ name: string;
323
+ /** Index of the condition rule whose `skipLevels` removed it. */
324
+ skippedByRule: number;
325
+ }
326
+ /** How one condition rule evaluated against the data. */
327
+ interface ExplainedRule {
328
+ index: number;
329
+ matched: boolean;
330
+ /** Levels this rule would add. Present whether or not it matched. */
331
+ addsLevels: number[];
332
+ /** Levels this rule would skip. Present whether or not it matched. */
333
+ skipsLevels: number[];
334
+ /** Why the rule could not be evaluated, e.g. an unregistered operator. */
335
+ error?: string;
336
+ }
337
+ /**
338
+ * A full account of why a chain looks the way it does.
339
+ *
340
+ * `previewApprovalChain()` answers *what* the chain will be; this answers *why*,
341
+ * which is the question a support engineer actually has when a purchase order
342
+ * arrives with a level nobody expected.
343
+ */
344
+ interface ChainExplanation {
345
+ templateName: string;
346
+ levels: ExplainedLevel[];
347
+ skipped: ExplainedSkip[];
348
+ rules: ExplainedRule[];
349
+ }
302
350
  interface BulkResult {
303
351
  succeeded: ApprovalInstance[];
304
352
  failed: Array<{
@@ -672,11 +720,42 @@ declare class ApprovalEngine {
672
720
  removeAttachment(instanceId: string, raw: RemoveAttachmentOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
673
721
  /** Add a comment to an instance without approving or rejecting. */
674
722
  addComment(instanceId: string, raw: AddCommentOptions, auditCtx?: AuditContext): Promise<void>;
723
+ /**
724
+ * Comments on an approval, oldest first.
725
+ *
726
+ * Returns a flat list carrying `parentCommentId`, rather than a nested tree:
727
+ * a UI that wants threads can build them, and one that wants a chronological
728
+ * feed — which is what most approval screens actually show — does not have to
729
+ * flatten a structure it never wanted.
730
+ */
731
+ getComments(instanceId: string): Promise<Comment[]>;
675
732
  /** Resubmit a rejected instance, creating a new linked instance from level 1. */
676
733
  resubmit(instanceId: string, raw: ResubmitOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
677
734
  /** Preview the resolved approval chain for a template and document data, without creating an instance. */
678
735
  previewApprovalChain(templateName: string, data: Record<string, unknown>, submittedBy: string): Promise<PreviewResult>;
679
736
  /** Check whether a user is eligible to approve a specific instance. Never throws. */
737
+ /**
738
+ * Explain why a chain resolves the way it does for a given document.
739
+ *
740
+ * `previewApprovalChain()` answers *what* the chain will be. This answers
741
+ * *why*: which rule added a level, which rule removed one, which rules were
742
+ * evaluated and did not match, and where each level's approvers came from —
743
+ * the question behind "why does this purchase order have a CFO level?", which
744
+ * previously meant reading the template and re-evaluating the conditions by
745
+ * hand.
746
+ *
747
+ * A rule that throws — an operator nobody registered, a malformed group — is
748
+ * reported against that rule rather than failing the whole explanation. The
749
+ * explanation is a diagnostic tool, and it is least useful at exactly the
750
+ * moment a broken rule makes it throw.
751
+ *
752
+ * Reads nothing and writes nothing; safe to expose to a support UI.
753
+ *
754
+ * @param templateName - Template to explain.
755
+ * @param data - Document data the conditions are evaluated against.
756
+ * @param submittedBy - Submitter, used for approver resolution.
757
+ */
758
+ explainChain(templateName: string, data: Record<string, unknown>, submittedBy: string): Promise<ChainExplanation>;
680
759
  canApprove(instanceId: string, userId: string): Promise<CanApproveResult>;
681
760
  /** Emergency bypass — completes the instance as 'approved', skipping remaining levels. Requires template.allowOverride = true. */
682
761
  override(instanceId: string, raw: OverrideOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
@@ -964,4 +1043,4 @@ declare class ApprovalEngine {
964
1043
  private runExternalAudit;
965
1044
  }
966
1045
 
967
- export { type AddCommentOptions as A, type BulkResult as B, type CanApproveResult as C, type DelegateOptions as D, type EscalateOptions as E, toComparableNumber as F, validateConditionExpression as G, type HealthResult as H, type IdGeneratorFn as I, weekendCalendar as J, type OrgProvider as O, type PreviewChainLevel as P, type ReassignOptions as R, type SubmitOptions as S, TEMPLATE_BUNDLE_VERSION as T, type UpdateDataOptions as U, type ValidationResult as V, type WeekendCalendarOptions as W, ApprovalEngine as a, type ApprovalEngineOptions as b, type ApprovalStatistics as c, type ApproveOptions as d, type ApproverResolverFn as e, type ApproverWorkload as f, type BusinessCalendar as g, type BusinessHoursCalendarOptions as h, type CancelOptions as i, type ConditionOperatorFn as j, type CycleTimeStats as k, type IdempotencyKeyFn as l, type ImportResult as m, type OutOfOfficeProvider as n, type OverrideOptions as o, type PreviewResult as p, type ProvideInfoOptions as q, type PurgeResult as r, type RejectOptions as s, type RequestInfoOptions as t, type ResubmitOptions as u, type RetryPolicy as v, type TemplateBundle as w, type TransferResult as x, businessHoursCalendar as y, defaultIdGenerator as z };
1046
+ export { type AddCommentOptions as A, type BulkResult as B, type CanApproveResult as C, type DelegateOptions as D, type EscalateOptions as E, type TemplateBundle as F, type TransferResult as G, type HealthResult as H, type IdGeneratorFn as I, businessHoursCalendar as J, defaultIdGenerator as K, toComparableNumber as L, validateConditionExpression as M, weekendCalendar as N, type OrgProvider as O, type PreviewChainLevel as P, type ReassignOptions as R, type SubmitOptions as S, TEMPLATE_BUNDLE_VERSION as T, type UpdateDataOptions as U, type ValidationResult as V, type WeekendCalendarOptions as W, ApprovalEngine as a, type ApprovalEngineOptions as b, type ApprovalStatistics as c, type ApproveOptions as d, type ApproverResolverFn as e, type ApproverWorkload as f, type BusinessCalendar as g, type BusinessHoursCalendarOptions as h, type CancelOptions as i, type ChainExplanation as j, type ConditionOperatorFn as k, type CycleTimeStats as l, type ExplainedLevel as m, type ExplainedRule as n, type ExplainedSkip as o, type IdempotencyKeyFn as p, type ImportResult as q, type OutOfOfficeProvider as r, type OverrideOptions as s, type PreviewResult as t, type ProvideInfoOptions as u, type PurgeResult as v, type RejectOptions as w, type RequestInfoOptions as x, type ResubmitOptions as y, type RetryPolicy as z };
@@ -1,4 +1,4 @@
1
- import { b as AuditEntry, a as ApprovalInstance } from './instance-weh2w7Ji.js';
1
+ import { b as AuditEntry, a as ApprovalInstance } from './instance-BvOyT00S.js';
2
2
 
3
3
  interface IAuditAdapter {
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { b as AuditEntry, a as ApprovalInstance } from './instance-weh2w7Ji.cjs';
1
+ import { b as AuditEntry, a as ApprovalInstance } from './instance-BvOyT00S.cjs';
2
2
 
3
3
  interface IAuditAdapter {
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-weh2w7Ji.cjs';
1
+ import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-BvOyT00S.cjs';
2
2
 
3
3
  interface AuthorizationContext {
4
4
  operation: 'submit' | 'approve' | 'reject' | 'delegate' | 'reassign' | 'cancel' | 'escalate' | 'override' | 'resubmit' | 'addComment' | 'updateData' | 'requestInfo' | 'provideInfo' | 'addAttachment' | 'removeAttachment';
@@ -1,4 +1,4 @@
1
- import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-weh2w7Ji.js';
1
+ import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-BvOyT00S.js';
2
2
 
3
3
  interface AuthorizationContext {
4
4
  operation: 'submit' | 'approve' | 'reject' | 'delegate' | 'reassign' | 'cancel' | 'escalate' | 'override' | 'resubmit' | 'addComment' | 'updateData' | 'requestInfo' | 'provideInfo' | 'addAttachment' | 'removeAttachment';
@@ -1,4 +1,4 @@
1
- import { a as ApprovalInstance, b as AuditEntry } from './instance-weh2w7Ji.cjs';
1
+ import { a as ApprovalInstance, b as AuditEntry } from './instance-BvOyT00S.cjs';
2
2
 
3
3
  interface ApprovalEvent {
4
4
  instanceId: string;
@@ -120,6 +120,16 @@ interface SubWorkflowEvent extends ApprovalEvent {
120
120
  /** Set on completion: the outcome the child returned. */
121
121
  outcome?: 'approved' | 'rejected' | 'cancelled' | 'expired';
122
122
  }
123
+ /** Emitted when a comment is posted. Recipients are the users it mentions. */
124
+ interface CommentedEvent extends ApprovalEvent {
125
+ commentId: string;
126
+ authorId: string;
127
+ body: string;
128
+ level?: number;
129
+ parentCommentId?: string;
130
+ /** Users named in the comment. */
131
+ recipients: string[];
132
+ }
123
133
  interface ApprovalEventMap {
124
134
  'approval:submitted': SubmittedEvent;
125
135
  'approval:approved': ApprovedEvent;
@@ -142,6 +152,7 @@ interface ApprovalEventMap {
142
152
  'approval:attachment_removed': AttachmentEvent;
143
153
  'approval:subworkflow_started': SubWorkflowEvent;
144
154
  'approval:subworkflow_completed': SubWorkflowEvent;
155
+ 'approval:commented': CommentedEvent;
145
156
  }
146
157
  type ApprovalEventName = keyof ApprovalEventMap;
147
158
  interface HistoryEntry extends AuditEntry {
@@ -165,4 +176,4 @@ interface INotificationAdapter {
165
176
  notify(event: NotificationEvent): Promise<void>;
166
177
  }
167
178
 
168
- export type { ApprovalEvent as A, CancelledEvent as C, DataUpdatedEvent as D, EscalatedEvent as E, HistoryEntry as H, INotificationAdapter as I, LevelAdvancedEvent as L, NotificationEvent as N, OverriddenEvent as O, ReassignedEvent as R, SlaBreachedEvent as S, ApprovalEventMap as a, ApprovalEventName as b, ApprovedEvent as c, AttachmentEvent as d, DelegatedEvent as e, ExpiredEvent as f, InfoProvidedEvent as g, InfoRequestedEvent as h, RejectedEvent as i, ReminderEvent as j, ResubmittedEvent as k, SubWorkflowEvent as l, SubmittedEvent as m };
179
+ export type { ApprovalEvent as A, CancelledEvent as C, DataUpdatedEvent as D, EscalatedEvent as E, HistoryEntry as H, INotificationAdapter as I, LevelAdvancedEvent as L, NotificationEvent as N, OverriddenEvent as O, ReassignedEvent as R, SlaBreachedEvent as S, ApprovalEventMap as a, ApprovalEventName as b, ApprovedEvent as c, AttachmentEvent as d, CommentedEvent as e, DelegatedEvent as f, ExpiredEvent as g, InfoProvidedEvent as h, InfoRequestedEvent as i, RejectedEvent as j, ReminderEvent as k, ResubmittedEvent as l, SubWorkflowEvent as m, SubmittedEvent as n };
@@ -1,4 +1,4 @@
1
- import { a as ApprovalInstance, b as AuditEntry } from './instance-weh2w7Ji.js';
1
+ import { a as ApprovalInstance, b as AuditEntry } from './instance-BvOyT00S.js';
2
2
 
3
3
  interface ApprovalEvent {
4
4
  instanceId: string;
@@ -120,6 +120,16 @@ interface SubWorkflowEvent extends ApprovalEvent {
120
120
  /** Set on completion: the outcome the child returned. */
121
121
  outcome?: 'approved' | 'rejected' | 'cancelled' | 'expired';
122
122
  }
123
+ /** Emitted when a comment is posted. Recipients are the users it mentions. */
124
+ interface CommentedEvent extends ApprovalEvent {
125
+ commentId: string;
126
+ authorId: string;
127
+ body: string;
128
+ level?: number;
129
+ parentCommentId?: string;
130
+ /** Users named in the comment. */
131
+ recipients: string[];
132
+ }
123
133
  interface ApprovalEventMap {
124
134
  'approval:submitted': SubmittedEvent;
125
135
  'approval:approved': ApprovedEvent;
@@ -142,6 +152,7 @@ interface ApprovalEventMap {
142
152
  'approval:attachment_removed': AttachmentEvent;
143
153
  'approval:subworkflow_started': SubWorkflowEvent;
144
154
  'approval:subworkflow_completed': SubWorkflowEvent;
155
+ 'approval:commented': CommentedEvent;
145
156
  }
146
157
  type ApprovalEventName = keyof ApprovalEventMap;
147
158
  interface HistoryEntry extends AuditEntry {
@@ -165,4 +176,4 @@ interface INotificationAdapter {
165
176
  notify(event: NotificationEvent): Promise<void>;
166
177
  }
167
178
 
168
- export type { ApprovalEvent as A, CancelledEvent as C, DataUpdatedEvent as D, EscalatedEvent as E, HistoryEntry as H, INotificationAdapter as I, LevelAdvancedEvent as L, NotificationEvent as N, OverriddenEvent as O, ReassignedEvent as R, SlaBreachedEvent as S, ApprovalEventMap as a, ApprovalEventName as b, ApprovedEvent as c, AttachmentEvent as d, DelegatedEvent as e, ExpiredEvent as f, InfoProvidedEvent as g, InfoRequestedEvent as h, RejectedEvent as i, ReminderEvent as j, ResubmittedEvent as k, SubWorkflowEvent as l, SubmittedEvent as m };
179
+ export type { ApprovalEvent as A, CancelledEvent as C, DataUpdatedEvent as D, EscalatedEvent as E, HistoryEntry as H, INotificationAdapter as I, LevelAdvancedEvent as L, NotificationEvent as N, OverriddenEvent as O, ReassignedEvent as R, SlaBreachedEvent as S, ApprovalEventMap as a, ApprovalEventName as b, ApprovedEvent as c, AttachmentEvent as d, CommentedEvent as e, DelegatedEvent as f, ExpiredEvent as g, InfoProvidedEvent as h, InfoRequestedEvent as i, RejectedEvent as j, ReminderEvent as k, ResubmittedEvent as l, SubWorkflowEvent as m, SubmittedEvent as n };
@@ -1,4 +1,4 @@
1
- import { a as ApprovalInstance } from './instance-weh2w7Ji.cjs';
1
+ import { a as ApprovalInstance } from './instance-BvOyT00S.cjs';
2
2
 
3
3
  declare class ApprovalError extends Error {
4
4
  readonly code: string;
@@ -1,4 +1,4 @@
1
- import { a as ApprovalInstance } from './instance-weh2w7Ji.js';
1
+ import { a as ApprovalInstance } from './instance-BvOyT00S.js';
2
2
 
3
3
  declare class ApprovalError extends Error {
4
4
  readonly code: string;
@@ -1,4 +1,4 @@
1
- import { A as ApprovalTemplate, a as ApprovalInstance, f as ApprovalStatus, b as AuditEntry } from './instance-weh2w7Ji.cjs';
1
+ import { A as ApprovalTemplate, a as ApprovalInstance, f as ApprovalStatus, b as AuditEntry } from './instance-BvOyT00S.cjs';
2
2
 
3
3
  interface PaginationOpts {
4
4
  limit: number;
@@ -1,4 +1,4 @@
1
- import { A as ApprovalTemplate, a as ApprovalInstance, f as ApprovalStatus, b as AuditEntry } from './instance-weh2w7Ji.js';
1
+ import { A as ApprovalTemplate, a as ApprovalInstance, f as ApprovalStatus, b as AuditEntry } from './instance-BvOyT00S.js';
2
2
 
3
3
  interface PaginationOpts {
4
4
  limit: number;
@@ -44,10 +44,14 @@ function reviveDates(instance) {
44
44
  slaDeadlineAt: instance.slaDeadlineAt ? new Date(instance.slaDeadlineAt) : void 0,
45
45
  slaBreachedAt: instance.slaBreachedAt ? new Date(instance.slaBreachedAt) : void 0,
46
46
  auditLog: instance.auditLog.map((e) => ({ ...e, timestamp: new Date(e.timestamp) })),
47
+ infoRequest: instance.infoRequest ? { ...instance.infoRequest, askedAt: new Date(instance.infoRequest.askedAt) } : void 0,
48
+ attachments: instance.attachments ? instance.attachments.map((a) => ({ ...a, addedAt: new Date(a.addedAt) })) : void 0,
49
+ comments: instance.comments ? instance.comments.map((c) => ({ ...c, createdAt: new Date(c.createdAt) })) : void 0,
47
50
  levels: instance.levels.map((l) => {
48
51
  const level = { ...l };
49
52
  if (l.escalationDueAt) level.escalationDueAt = new Date(l.escalationDueAt);
50
53
  if (l.delegatedUntil) level.delegatedUntil = new Date(l.delegatedUntil);
54
+ if (l.reminderDueAt) level.reminderDueAt = new Date(l.reminderDueAt);
51
55
  return level;
52
56
  })
53
57
  };