hierarchical-approval 0.8.0 → 1.0.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 +83 -0
  2. package/README.md +70 -0
  3. package/dist/{ApprovalEngine-DFY53zRK.d.ts → ApprovalEngine-DB62pkEk.d.ts} +86 -8
  4. package/dist/{ApprovalEngine-DVMMtiSm.d.cts → ApprovalEngine-DzHUWix1.d.cts} +86 -8
  5. package/dist/{IAuditAdapter-CaM3A2Kt.d.ts → IAuditAdapter-RKagmrfi.d.ts} +1 -1
  6. package/dist/{IAuditAdapter-BODIlw4h.d.cts → IAuditAdapter-zVO7XpWm.d.cts} +1 -1
  7. package/dist/{IAuthorizationPolicy-DFD0ELtO.d.ts → IAuthorizationPolicy-DEDQnNzq.d.ts} +2 -2
  8. package/dist/{IAuthorizationPolicy-cT7LSHtl.d.cts → IAuthorizationPolicy-Dx9NzWx8.d.cts} +2 -2
  9. package/dist/{IMetricsAdapter-D9PUz4tM.d.cts → IMetricsAdapter-t7QpfA1I.d.cts} +1 -1
  10. package/dist/{IMetricsAdapter-D9PUz4tM.d.ts → IMetricsAdapter-t7QpfA1I.d.ts} +1 -1
  11. package/dist/{INotificationAdapter-Hn70VShV.d.ts → INotificationAdapter-Dld84ihs.d.cts} +19 -2
  12. package/dist/{INotificationAdapter-Ci0gf6ac.d.cts → INotificationAdapter-u5ZE7wS6.d.ts} +19 -2
  13. package/dist/{IOperationMiddleware-DOlOraCX.d.cts → IOperationMiddleware-BSfjsnTe.d.cts} +1 -1
  14. package/dist/{IOperationMiddleware-BCcAqSzT.d.ts → IOperationMiddleware-DndObWfg.d.ts} +1 -1
  15. package/dist/{IStorageAdapter-D7jsZT6O.d.ts → IStorageAdapter-BYlHnm7_.d.ts} +1 -1
  16. package/dist/{IStorageAdapter-egPQD6Ry.d.cts → IStorageAdapter-D-yIRiET.d.cts} +1 -1
  17. package/dist/adapters/MemoryAdapter.d.cts +2 -2
  18. package/dist/adapters/MemoryAdapter.d.ts +2 -2
  19. package/dist/adapters/PostgresAdapter.d.cts +2 -2
  20. package/dist/adapters/PostgresAdapter.d.ts +2 -2
  21. package/dist/index.cjs +372 -43
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +9 -9
  24. package/dist/index.d.ts +9 -9
  25. package/dist/index.js +372 -43
  26. package/dist/index.js.map +1 -1
  27. package/dist/{instance-CtkmEkYa.d.cts → instance-iaAIdMbs.d.cts} +14 -1
  28. package/dist/{instance-CtkmEkYa.d.ts → instance-iaAIdMbs.d.ts} +14 -1
  29. package/dist/nestjs.cjs +372 -43
  30. package/dist/nestjs.cjs.map +1 -1
  31. package/dist/nestjs.d.cts +8 -8
  32. package/dist/nestjs.d.ts +8 -8
  33. package/dist/nestjs.js +372 -43
  34. package/dist/nestjs.js.map +1 -1
  35. package/dist/plugins/audit.d.cts +2 -2
  36. package/dist/plugins/audit.d.ts +2 -2
  37. package/dist/plugins/metrics.cjs +1 -0
  38. package/dist/plugins/metrics.cjs.map +1 -1
  39. package/dist/plugins/metrics.d.cts +1 -1
  40. package/dist/plugins/metrics.d.ts +1 -1
  41. package/dist/plugins/metrics.js +1 -0
  42. package/dist/plugins/metrics.js.map +1 -1
  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 +372 -43
  52. package/dist/testing.cjs.map +1 -1
  53. package/dist/testing.d.cts +8 -8
  54. package/dist/testing.d.ts +8 -8
  55. package/dist/testing.js +372 -43
  56. package/dist/testing.js.map +1 -1
  57. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,89 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  _Nothing yet._
9
9
 
10
+ ## [1.0.0] - 2026-09-04
11
+
12
+ First stable release. The public API is now considered settled: breaking changes
13
+ from here get a major version.
14
+
15
+ ### Added — parallel branch groups
16
+
17
+ - **Levels sharing a `group` name activate together and join before the chain
18
+ advances.** Approvals were strictly sequential, so "Finance and Legal review
19
+ concurrently, then it goes to the CEO" could only be modelled by picking an
20
+ arbitrary order and making one wait on the other — inflating cycle time for no
21
+ business reason. This was the last item on the roadmap in `IMPROVEMENTS.md`.
22
+
23
+ ```ts
24
+ levels: [
25
+ { level: 1, name: 'Manager', ... },
26
+ { level: 2, name: 'Finance', group: 'review', ... },
27
+ { level: 3, name: 'Legal', group: 'review', ... },
28
+ { level: 4, name: 'CEO', ... },
29
+ ]
30
+ ```
31
+
32
+ Both branches open at once, decisions arrive in any order, and level 4 stays
33
+ `waiting` until every branch is approved. Rejecting any branch rejects the
34
+ instance, as a rejection always has. A group may also lead the chain, in which
35
+ case it opens at submit.
36
+
37
+ - **`approve()` and `reject()` take an optional `level`.** Inside a parallel
38
+ group one person can sit on several open branches; recording their decision
39
+ against a guessed branch would be silently wrong, so the engine throws and
40
+ asks which branch is meant. Sequential templates never see this.
41
+
42
+ - **`getCurrentApprovers()` returns the union across every open branch.**
43
+ `canApprove()` likewise considers any open branch the user is assigned to.
44
+
45
+ - **`validateTemplate()` rejects a non-contiguous group** — a group's levels must
46
+ occupy consecutive level numbers, so that "advance past the group" and
47
+ "advance past a level" cannot disagree about what comes next.
48
+
49
+ **Backward compatible.** A level without a `group` is its own group of one, and
50
+ all 673 pre-existing tests pass unchanged against the new engine.
51
+
52
+ ## [0.9.0] - 2026-09-04
53
+
54
+ ### Added — `updateData()`: edit a pending document and recompute its chain
55
+
56
+ - **`engine.updateData(instanceId, opts)` changes an in-flight instance's
57
+ document data and re-evaluates the template's conditions against it.**
58
+ Documents change after submission — a corrected line item, a reclassified
59
+ vendor, a revised amount — and the chain computed at submit time can be wrong
60
+ the moment that happens. The only previous remedy was to cancel and resubmit,
61
+ which discarded every approval already collected along with its audit trail.
62
+
63
+ ```ts
64
+ await engine.updateData(instance.id, {
65
+ updatedBy: 'buyer-1',
66
+ data: { amount: 20000 }, // merged by default; mode: 'replace' swaps wholesale
67
+ reason: 'Corrected line items',
68
+ });
69
+ ```
70
+
71
+ `recomputeChain: false` applies a data correction without touching the chain.
72
+
73
+ - **Decided history is frozen.** Only levels after the current one are
74
+ recomputed. A level that is already approved, or is actively collecting
75
+ decisions, is never removed: a `skipLevels` condition that would drop it is
76
+ ignored, because editing data must not retract an approval that was given. A
77
+ condition that would *insert* a level at or before the current level throws
78
+ `ApprovalValidationError` rather than silently dropping an approval step the
79
+ template says is required.
80
+
81
+ A future level that survives re-evaluation is preserved object-identical, so a
82
+ delegation already arranged on it survives an unrelated edit elsewhere in the
83
+ document.
84
+
85
+ - **Wiring:** emits `approval:data_updated` (`changedFields`, `addedLevels`,
86
+ `removedLevels`), records a `data_updated` audit entry carrying the before and
87
+ after data, increments `approval.data_updated`, and adds `updateData` to the
88
+ authorization-policy operation set — so it runs through the same
89
+ authz/middleware/audit/notification pipeline as every other operation.
90
+
91
+ New exports: `UpdateDataOptions`, `DataUpdatedEvent`.
92
+
10
93
  ## [0.8.0] - 2026-09-04
11
94
 
12
95
  ### Added — boolean condition expressions
package/README.md CHANGED
@@ -399,6 +399,76 @@ engine.registerConditionOperator('between', (actual, expected) => {
399
399
  });
400
400
  ```
401
401
 
402
+ ### Parallel branch groups
403
+
404
+ Give levels the same `group` name and they activate together, joining before
405
+ the chain moves on — "Finance and Legal review concurrently, then it goes to the
406
+ CEO":
407
+
408
+ ```ts
409
+ levels: [
410
+ { level: 1, name: 'Manager', approvers: [{ type: 'user', userId: 'mgr' }], mode: 'any' },
411
+ { level: 2, name: 'Finance', group: 'review', approvers: [{ type: 'role', role: 'finance' }], mode: 'any' },
412
+ { level: 3, name: 'Legal', group: 'review', approvers: [{ type: 'role', role: 'legal' }], mode: 'any' },
413
+ { level: 4, name: 'CEO', approvers: [{ type: 'user', userId: 'ceo' }], mode: 'any' },
414
+ ]
415
+ ```
416
+
417
+ Both branches open at once when the group is reached, decisions can arrive in
418
+ either order, and level 4 stays `waiting` until every branch is approved.
419
+ Rejecting any branch rejects the instance. A group may also lead the chain, in
420
+ which case it opens at submit.
421
+
422
+ `getCurrentApprovers()` returns the union across every open branch. A level
423
+ without a `group` is its own group of one, so existing sequential templates
424
+ behave exactly as before.
425
+
426
+ **When one person sits on two open branches**, `approve()` refuses to guess and
427
+ throws — pass `level` to say which branch the decision is for:
428
+
429
+ ```ts
430
+ await engine.approve(id, { approverId: 'cfo', level: 2 });
431
+ ```
432
+
433
+ A group's levels must occupy a contiguous run of level numbers;
434
+ `validateTemplate()` rejects an interleaved group.
435
+
436
+ ### Editing a document mid-flight
437
+
438
+ Documents change after submission — a line item is corrected, a vendor
439
+ reclassified, an amount revised. `updateData()` applies the change and
440
+ re-evaluates the template's conditions, so the chain reflects the corrected
441
+ document without cancelling and resubmitting (which would throw away the
442
+ approvals already collected):
443
+
444
+ ```ts
445
+ const updated = await engine.updateData(instance.id, {
446
+ updatedBy: 'buyer-1',
447
+ data: { amount: 20000 }, // merged into existing data
448
+ reason: 'Corrected line items',
449
+ });
450
+ // amount crossed the 10k threshold -> the CFO level is now in the chain
451
+ ```
452
+
453
+ | Option | Default | Meaning |
454
+ |---|---|---|
455
+ | `data` | required | Fields to apply |
456
+ | `mode` | `'merge'` | `'merge'` keeps untouched keys; `'replace'` swaps the whole object |
457
+ | `reason` | — | Recorded on the audit entry |
458
+ | `recomputeChain` | `true` | Set `false` to correct data without touching the chain |
459
+
460
+ **Decided history is frozen.** Only levels *after* the current one are
461
+ recomputed. A level that is already approved, or is actively collecting
462
+ decisions, is never removed — a `skipLevels` condition that would drop it is
463
+ ignored, because an approval that has been given cannot be retracted by editing
464
+ data. A condition that would *insert* a level at or before the current one
465
+ throws rather than silently dropping a required approval step; cancel and
466
+ resubmit if the chain genuinely must change retroactively.
467
+
468
+ Emits `approval:data_updated` with `changedFields`, `addedLevels` and
469
+ `removedLevels`, and records a `data_updated` audit entry holding the before and
470
+ after data. Only pending instances can be edited.
471
+
402
472
  ---
403
473
 
404
474
  ## Installation and setup
@@ -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-D7jsZT6O.js';
2
- import { k as ConditionExpression, o as ResolverFn, g as ApprovalTemplateConfig, A as ApprovalTemplate, j as AuditContext, a as ApprovalInstance, e as ApprovalMode, b as AuditEntry } from './instance-CtkmEkYa.js';
3
- import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-Hn70VShV.js';
1
+ import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-BYlHnm7_.js';
2
+ import { k as ConditionExpression, o as ResolverFn, g as ApprovalTemplateConfig, A as ApprovalTemplate, j as AuditContext, a as ApprovalInstance, e as ApprovalMode, b as AuditEntry } from './instance-iaAIdMbs.js';
3
+ import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-u5ZE7wS6.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-BCcAqSzT.js';
8
- import { I as IAuditAdapter } from './IAuditAdapter-CaM3A2Kt.js';
9
- import { I as IMetricsAdapter } from './IMetricsAdapter-D9PUz4tM.js';
7
+ import { I as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-DndObWfg.js';
8
+ import { I as IAuditAdapter } from './IAuditAdapter-RKagmrfi.js';
9
+ import { I as IMetricsAdapter } from './IMetricsAdapter-t7QpfA1I.js';
10
10
  import { I as ISchedulerAdapter } from './ISchedulerAdapter-DKv_QjVN.js';
11
- import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-DFD0ELtO.js';
11
+ import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-DEDQnNzq.js';
12
12
 
13
13
  declare const SubmitOptionsSchema: z.ZodObject<{
14
14
  templateName: z.ZodString;
@@ -26,6 +26,7 @@ declare const SubmitOptionsSchema: z.ZodObject<{
26
26
  declare const ApproveOptionsSchema: z.ZodObject<{
27
27
  approverId: z.ZodString;
28
28
  comment: z.ZodOptional<z.ZodString>;
29
+ level: z.ZodOptional<z.ZodNumber>;
29
30
  }, z.core.$strip>;
30
31
  declare const RejectOptionsSchema: z.ZodObject<{
31
32
  approverId: z.ZodString;
@@ -34,6 +35,7 @@ declare const RejectOptionsSchema: z.ZodObject<{
34
35
  originator: "originator";
35
36
  previous: "previous";
36
37
  }>>;
38
+ level: z.ZodOptional<z.ZodNumber>;
37
39
  }, z.core.$strip>;
38
40
  declare const DelegateOptionsSchema: z.ZodObject<{
39
41
  fromApprover: z.ZodString;
@@ -67,6 +69,16 @@ declare const OverrideOptionsSchema: z.ZodObject<{
67
69
  overriddenBy: z.ZodString;
68
70
  justification: z.ZodString;
69
71
  }, z.core.$strip>;
72
+ declare const UpdateDataOptionsSchema: z.ZodObject<{
73
+ updatedBy: z.ZodString;
74
+ data: z.ZodRecord<z.ZodString, z.ZodUnknown>;
75
+ mode: z.ZodDefault<z.ZodEnum<{
76
+ merge: "merge";
77
+ replace: "replace";
78
+ }>>;
79
+ reason: z.ZodOptional<z.ZodString>;
80
+ recomputeChain: z.ZodDefault<z.ZodBoolean>;
81
+ }, z.core.$strip>;
70
82
  type SubmitOptions = z.infer<typeof SubmitOptionsSchema>;
71
83
  type ApproveOptions = z.infer<typeof ApproveOptionsSchema>;
72
84
  type RejectOptions = z.infer<typeof RejectOptionsSchema>;
@@ -77,6 +89,7 @@ type EscalateOptions = z.infer<typeof EscalateOptionsSchema>;
77
89
  type ResubmitOptions = z.infer<typeof ResubmitOptionsSchema>;
78
90
  type AddCommentOptions = z.infer<typeof AddCommentOptionsSchema>;
79
91
  type OverrideOptions = z.infer<typeof OverrideOptionsSchema>;
92
+ type UpdateDataOptions = z.infer<typeof UpdateDataOptionsSchema>;
80
93
 
81
94
  /**
82
95
  * Computes deadline dates from a number of days. The default engine behaviour
@@ -373,6 +386,42 @@ declare class ApprovalEngine {
373
386
  reassign(instanceId: string, raw: ReassignOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
374
387
  cancel(instanceId: string, raw: CancelOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
375
388
  escalate(instanceId: string, raw: EscalateOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
389
+ /**
390
+ * Change an instance's document data while it is still pending, and
391
+ * re-evaluate the template's conditions against the new values.
392
+ *
393
+ * Documents change after submission — a line item is corrected, a vendor is
394
+ * reclassified, an amount is revised — and the approval chain that was
395
+ * computed at submit time can be wrong the moment that happens. Without this,
396
+ * the only way to reflect a correction was to cancel and resubmit, losing the
397
+ * approvals already collected and the audit trail with them.
398
+ *
399
+ * **Only the part of the chain that has not been reached is recomputed.**
400
+ * Levels before {@link ApprovalInstance.currentLevel}, and the current level
401
+ * itself, are frozen: an approval that has already been given cannot be
402
+ * retracted by editing data, and a level that is actively collecting
403
+ * decisions is not pulled out from under its approvers. Conditions that would
404
+ * skip such a level are therefore ignored — history cannot be rewritten — and
405
+ * a condition that would *insert* a level at or before the current one throws
406
+ * rather than silently dropping an approval step that should have run.
407
+ *
408
+ * @param instanceId - The pending instance to update.
409
+ * @param raw - Who is updating, the new data, and how to apply it.
410
+ * @param auditCtx - Optional compliance context recorded on the audit entry.
411
+ * @returns The updated instance.
412
+ * @throws ApprovalError if the instance is not pending.
413
+ * @throws ApprovalValidationError if re-evaluation would insert a level at or
414
+ * before the current level, or would leave the chain with no levels.
415
+ */
416
+ updateData(instanceId: string, raw: UpdateDataOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
417
+ /**
418
+ * Recompute the not-yet-reached portion of an instance's level chain against
419
+ * new data, mutating `instance.levels` in place.
420
+ *
421
+ * Levels at or before `currentLevel` are treated as immutable history. See
422
+ * {@link updateData} for why.
423
+ */
424
+ private recomputeFutureChain;
376
425
  /** Add a comment to an instance without approving or rejecting. */
377
426
  addComment(instanceId: string, raw: AddCommentOptions, auditCtx?: AuditContext): Promise<void>;
378
427
  /** Resubmit a rejected instance, creating a new linked instance from level 1. */
@@ -442,7 +491,36 @@ declare class ApprovalEngine {
442
491
  /** Compute a deadline `days` from `from`, honouring the business calendar if one is configured. */
443
492
  private deadlineFrom;
444
493
  private requireInstance;
494
+ /**
495
+ * Identity of the parallel branch group a level belongs to.
496
+ *
497
+ * An ungrouped level is its own group of one, keyed by its level number, so
498
+ * sequential and parallel levels can be handled by the same code paths. The
499
+ * `#` prefix keeps a synthetic key from ever colliding with a real group
500
+ * name a template author chose.
501
+ */
502
+ private static groupKeyOf;
503
+ /** Every level currently collecting decisions — more than one inside a parallel group. */
504
+ private pendingLevels;
505
+ /** All levels sharing a group with the given level, including it. */
506
+ private groupMembers;
507
+ /**
508
+ * Pick which pending level an actor is operating on.
509
+ *
510
+ * With sequential levels there is only ever one candidate. Inside a parallel
511
+ * group an approver may sit on several branches at once, and guessing which
512
+ * one they meant would silently record the decision against the wrong branch —
513
+ * so that case demands an explicit `level`.
514
+ */
515
+ private resolveActorLevel;
445
516
  private currentLevelInstance;
517
+ /**
518
+ * The next group of levels to activate: every level sharing the group of the
519
+ * lowest-numbered waiting level.
520
+ */
521
+ private findNextGroup;
522
+ /** Resolve approvers for a group, set its deadlines, and mark it pending. */
523
+ private activateGroup;
446
524
  private findNextLevel;
447
525
  private findPreviousLevel;
448
526
  private guardBulkSize;
@@ -453,4 +531,4 @@ declare class ApprovalEngine {
453
531
  private runExternalAudit;
454
532
  }
455
533
 
456
- export { type AddCommentOptions as A, type BulkResult as B, type CanApproveResult as C, type DelegateOptions as D, type EscalateOptions as E, type HealthResult as H, type IdGeneratorFn as I, type OrgProvider as O, type PreviewChainLevel as P, type ReassignOptions as R, type SubmitOptions as S, 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 BusinessCalendar as f, type CancelOptions as g, type ConditionOperatorFn as h, type CycleTimeStats as i, type IdempotencyKeyFn as j, type OverrideOptions as k, type PreviewResult as l, type RejectOptions as m, type ResubmitOptions as n, type RetryPolicy as o, defaultIdGenerator as p, toComparableNumber as t, validateConditionExpression as v, weekendCalendar as w };
534
+ export { type AddCommentOptions as A, type BulkResult as B, type CanApproveResult as C, type DelegateOptions as D, type EscalateOptions as E, type HealthResult as H, type IdGeneratorFn as I, type OrgProvider as O, type PreviewChainLevel as P, type ReassignOptions as R, type SubmitOptions as S, 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 BusinessCalendar as f, type CancelOptions as g, type ConditionOperatorFn as h, type CycleTimeStats as i, type IdempotencyKeyFn as j, type OverrideOptions as k, type PreviewResult as l, type RejectOptions as m, type ResubmitOptions as n, type RetryPolicy as o, defaultIdGenerator as p, toComparableNumber as t, validateConditionExpression as v, weekendCalendar as w };
@@ -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-egPQD6Ry.cjs';
2
- import { k as ConditionExpression, o as ResolverFn, g as ApprovalTemplateConfig, A as ApprovalTemplate, j as AuditContext, a as ApprovalInstance, e as ApprovalMode, b as AuditEntry } from './instance-CtkmEkYa.cjs';
3
- import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-Ci0gf6ac.cjs';
1
+ import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-D-yIRiET.cjs';
2
+ import { k as ConditionExpression, o as ResolverFn, g as ApprovalTemplateConfig, A as ApprovalTemplate, j as AuditContext, a as ApprovalInstance, e as ApprovalMode, b as AuditEntry } from './instance-iaAIdMbs.cjs';
3
+ import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-Dld84ihs.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-DOlOraCX.cjs';
8
- import { I as IAuditAdapter } from './IAuditAdapter-BODIlw4h.cjs';
9
- import { I as IMetricsAdapter } from './IMetricsAdapter-D9PUz4tM.cjs';
7
+ import { I as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-BSfjsnTe.cjs';
8
+ import { I as IAuditAdapter } from './IAuditAdapter-zVO7XpWm.cjs';
9
+ import { I as IMetricsAdapter } from './IMetricsAdapter-t7QpfA1I.cjs';
10
10
  import { I as ISchedulerAdapter } from './ISchedulerAdapter-DKv_QjVN.cjs';
11
- import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-cT7LSHtl.cjs';
11
+ import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-Dx9NzWx8.cjs';
12
12
 
13
13
  declare const SubmitOptionsSchema: z.ZodObject<{
14
14
  templateName: z.ZodString;
@@ -26,6 +26,7 @@ declare const SubmitOptionsSchema: z.ZodObject<{
26
26
  declare const ApproveOptionsSchema: z.ZodObject<{
27
27
  approverId: z.ZodString;
28
28
  comment: z.ZodOptional<z.ZodString>;
29
+ level: z.ZodOptional<z.ZodNumber>;
29
30
  }, z.core.$strip>;
30
31
  declare const RejectOptionsSchema: z.ZodObject<{
31
32
  approverId: z.ZodString;
@@ -34,6 +35,7 @@ declare const RejectOptionsSchema: z.ZodObject<{
34
35
  originator: "originator";
35
36
  previous: "previous";
36
37
  }>>;
38
+ level: z.ZodOptional<z.ZodNumber>;
37
39
  }, z.core.$strip>;
38
40
  declare const DelegateOptionsSchema: z.ZodObject<{
39
41
  fromApprover: z.ZodString;
@@ -67,6 +69,16 @@ declare const OverrideOptionsSchema: z.ZodObject<{
67
69
  overriddenBy: z.ZodString;
68
70
  justification: z.ZodString;
69
71
  }, z.core.$strip>;
72
+ declare const UpdateDataOptionsSchema: z.ZodObject<{
73
+ updatedBy: z.ZodString;
74
+ data: z.ZodRecord<z.ZodString, z.ZodUnknown>;
75
+ mode: z.ZodDefault<z.ZodEnum<{
76
+ merge: "merge";
77
+ replace: "replace";
78
+ }>>;
79
+ reason: z.ZodOptional<z.ZodString>;
80
+ recomputeChain: z.ZodDefault<z.ZodBoolean>;
81
+ }, z.core.$strip>;
70
82
  type SubmitOptions = z.infer<typeof SubmitOptionsSchema>;
71
83
  type ApproveOptions = z.infer<typeof ApproveOptionsSchema>;
72
84
  type RejectOptions = z.infer<typeof RejectOptionsSchema>;
@@ -77,6 +89,7 @@ type EscalateOptions = z.infer<typeof EscalateOptionsSchema>;
77
89
  type ResubmitOptions = z.infer<typeof ResubmitOptionsSchema>;
78
90
  type AddCommentOptions = z.infer<typeof AddCommentOptionsSchema>;
79
91
  type OverrideOptions = z.infer<typeof OverrideOptionsSchema>;
92
+ type UpdateDataOptions = z.infer<typeof UpdateDataOptionsSchema>;
80
93
 
81
94
  /**
82
95
  * Computes deadline dates from a number of days. The default engine behaviour
@@ -373,6 +386,42 @@ declare class ApprovalEngine {
373
386
  reassign(instanceId: string, raw: ReassignOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
374
387
  cancel(instanceId: string, raw: CancelOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
375
388
  escalate(instanceId: string, raw: EscalateOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
389
+ /**
390
+ * Change an instance's document data while it is still pending, and
391
+ * re-evaluate the template's conditions against the new values.
392
+ *
393
+ * Documents change after submission — a line item is corrected, a vendor is
394
+ * reclassified, an amount is revised — and the approval chain that was
395
+ * computed at submit time can be wrong the moment that happens. Without this,
396
+ * the only way to reflect a correction was to cancel and resubmit, losing the
397
+ * approvals already collected and the audit trail with them.
398
+ *
399
+ * **Only the part of the chain that has not been reached is recomputed.**
400
+ * Levels before {@link ApprovalInstance.currentLevel}, and the current level
401
+ * itself, are frozen: an approval that has already been given cannot be
402
+ * retracted by editing data, and a level that is actively collecting
403
+ * decisions is not pulled out from under its approvers. Conditions that would
404
+ * skip such a level are therefore ignored — history cannot be rewritten — and
405
+ * a condition that would *insert* a level at or before the current one throws
406
+ * rather than silently dropping an approval step that should have run.
407
+ *
408
+ * @param instanceId - The pending instance to update.
409
+ * @param raw - Who is updating, the new data, and how to apply it.
410
+ * @param auditCtx - Optional compliance context recorded on the audit entry.
411
+ * @returns The updated instance.
412
+ * @throws ApprovalError if the instance is not pending.
413
+ * @throws ApprovalValidationError if re-evaluation would insert a level at or
414
+ * before the current level, or would leave the chain with no levels.
415
+ */
416
+ updateData(instanceId: string, raw: UpdateDataOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
417
+ /**
418
+ * Recompute the not-yet-reached portion of an instance's level chain against
419
+ * new data, mutating `instance.levels` in place.
420
+ *
421
+ * Levels at or before `currentLevel` are treated as immutable history. See
422
+ * {@link updateData} for why.
423
+ */
424
+ private recomputeFutureChain;
376
425
  /** Add a comment to an instance without approving or rejecting. */
377
426
  addComment(instanceId: string, raw: AddCommentOptions, auditCtx?: AuditContext): Promise<void>;
378
427
  /** Resubmit a rejected instance, creating a new linked instance from level 1. */
@@ -442,7 +491,36 @@ declare class ApprovalEngine {
442
491
  /** Compute a deadline `days` from `from`, honouring the business calendar if one is configured. */
443
492
  private deadlineFrom;
444
493
  private requireInstance;
494
+ /**
495
+ * Identity of the parallel branch group a level belongs to.
496
+ *
497
+ * An ungrouped level is its own group of one, keyed by its level number, so
498
+ * sequential and parallel levels can be handled by the same code paths. The
499
+ * `#` prefix keeps a synthetic key from ever colliding with a real group
500
+ * name a template author chose.
501
+ */
502
+ private static groupKeyOf;
503
+ /** Every level currently collecting decisions — more than one inside a parallel group. */
504
+ private pendingLevels;
505
+ /** All levels sharing a group with the given level, including it. */
506
+ private groupMembers;
507
+ /**
508
+ * Pick which pending level an actor is operating on.
509
+ *
510
+ * With sequential levels there is only ever one candidate. Inside a parallel
511
+ * group an approver may sit on several branches at once, and guessing which
512
+ * one they meant would silently record the decision against the wrong branch —
513
+ * so that case demands an explicit `level`.
514
+ */
515
+ private resolveActorLevel;
445
516
  private currentLevelInstance;
517
+ /**
518
+ * The next group of levels to activate: every level sharing the group of the
519
+ * lowest-numbered waiting level.
520
+ */
521
+ private findNextGroup;
522
+ /** Resolve approvers for a group, set its deadlines, and mark it pending. */
523
+ private activateGroup;
446
524
  private findNextLevel;
447
525
  private findPreviousLevel;
448
526
  private guardBulkSize;
@@ -453,4 +531,4 @@ declare class ApprovalEngine {
453
531
  private runExternalAudit;
454
532
  }
455
533
 
456
- export { type AddCommentOptions as A, type BulkResult as B, type CanApproveResult as C, type DelegateOptions as D, type EscalateOptions as E, type HealthResult as H, type IdGeneratorFn as I, type OrgProvider as O, type PreviewChainLevel as P, type ReassignOptions as R, type SubmitOptions as S, 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 BusinessCalendar as f, type CancelOptions as g, type ConditionOperatorFn as h, type CycleTimeStats as i, type IdempotencyKeyFn as j, type OverrideOptions as k, type PreviewResult as l, type RejectOptions as m, type ResubmitOptions as n, type RetryPolicy as o, defaultIdGenerator as p, toComparableNumber as t, validateConditionExpression as v, weekendCalendar as w };
534
+ export { type AddCommentOptions as A, type BulkResult as B, type CanApproveResult as C, type DelegateOptions as D, type EscalateOptions as E, type HealthResult as H, type IdGeneratorFn as I, type OrgProvider as O, type PreviewChainLevel as P, type ReassignOptions as R, type SubmitOptions as S, 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 BusinessCalendar as f, type CancelOptions as g, type ConditionOperatorFn as h, type CycleTimeStats as i, type IdempotencyKeyFn as j, type OverrideOptions as k, type PreviewResult as l, type RejectOptions as m, type ResubmitOptions as n, type RetryPolicy as o, defaultIdGenerator as p, toComparableNumber as t, validateConditionExpression as v, weekendCalendar as w };
@@ -1,4 +1,4 @@
1
- import { b as AuditEntry, a as ApprovalInstance } from './instance-CtkmEkYa.js';
1
+ import { b as AuditEntry, a as ApprovalInstance } from './instance-iaAIdMbs.js';
2
2
 
3
3
  interface IAuditAdapter {
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { b as AuditEntry, a as ApprovalInstance } from './instance-CtkmEkYa.cjs';
1
+ import { b as AuditEntry, a as ApprovalInstance } from './instance-iaAIdMbs.cjs';
2
2
 
3
3
  interface IAuditAdapter {
4
4
  /**
@@ -1,7 +1,7 @@
1
- import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-CtkmEkYa.js';
1
+ import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-iaAIdMbs.js';
2
2
 
3
3
  interface AuthorizationContext {
4
- operation: 'submit' | 'approve' | 'reject' | 'delegate' | 'reassign' | 'cancel' | 'escalate' | 'override' | 'resubmit' | 'addComment';
4
+ operation: 'submit' | 'approve' | 'reject' | 'delegate' | 'reassign' | 'cancel' | 'escalate' | 'override' | 'resubmit' | 'addComment' | 'updateData';
5
5
  actorId: string;
6
6
  instance: Readonly<ApprovalInstance>;
7
7
  level?: Readonly<ApprovalLevelInstance>;
@@ -1,7 +1,7 @@
1
- import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-CtkmEkYa.cjs';
1
+ import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-iaAIdMbs.cjs';
2
2
 
3
3
  interface AuthorizationContext {
4
- operation: 'submit' | 'approve' | 'reject' | 'delegate' | 'reassign' | 'cancel' | 'escalate' | 'override' | 'resubmit' | 'addComment';
4
+ operation: 'submit' | 'approve' | 'reject' | 'delegate' | 'reassign' | 'cancel' | 'escalate' | 'override' | 'resubmit' | 'addComment' | 'updateData';
5
5
  actorId: string;
6
6
  instance: Readonly<ApprovalInstance>;
7
7
  level?: Readonly<ApprovalLevelInstance>;
@@ -1,4 +1,4 @@
1
- type MetricName = 'approval.submitted' | 'approval.approved' | 'approval.rejected' | 'approval.cancelled' | 'approval.expired' | 'approval.sla_breached' | 'approval.escalated' | 'approval.reassigned' | 'approval.overridden' | 'approval.conflict_retry' | 'approval.operation_duration_ms';
1
+ type MetricName = 'approval.submitted' | 'approval.approved' | 'approval.rejected' | 'approval.cancelled' | 'approval.expired' | 'approval.sla_breached' | 'approval.escalated' | 'approval.reassigned' | 'approval.overridden' | 'approval.data_updated' | 'approval.conflict_retry' | 'approval.operation_duration_ms';
2
2
  interface IMetricsAdapter {
3
3
  /** Increment a counter. Synchronous — never awaited. */
4
4
  increment(metric: MetricName, labels?: Record<string, string>): void;
@@ -1,4 +1,4 @@
1
- type MetricName = 'approval.submitted' | 'approval.approved' | 'approval.rejected' | 'approval.cancelled' | 'approval.expired' | 'approval.sla_breached' | 'approval.escalated' | 'approval.reassigned' | 'approval.overridden' | 'approval.conflict_retry' | 'approval.operation_duration_ms';
1
+ type MetricName = 'approval.submitted' | 'approval.approved' | 'approval.rejected' | 'approval.cancelled' | 'approval.expired' | 'approval.sla_breached' | 'approval.escalated' | 'approval.reassigned' | 'approval.overridden' | 'approval.data_updated' | 'approval.conflict_retry' | 'approval.operation_duration_ms';
2
2
  interface IMetricsAdapter {
3
3
  /** Increment a counter. Synchronous — never awaited. */
4
4
  increment(metric: MetricName, labels?: Record<string, string>): void;
@@ -1,4 +1,4 @@
1
- import { a as ApprovalInstance, b as AuditEntry } from './instance-CtkmEkYa.js';
1
+ import { a as ApprovalInstance, b as AuditEntry } from './instance-iaAIdMbs.cjs';
2
2
 
3
3
  interface ApprovalEvent {
4
4
  instanceId: string;
@@ -62,6 +62,22 @@ interface ExpiredEvent extends ApprovalEvent {
62
62
  interface SlaBreachedEvent extends ApprovalEvent {
63
63
  slaDeadlineAt: Date;
64
64
  }
65
+ /**
66
+ * Emitted when an instance's document data is changed while it is still
67
+ * pending. {@link addedLevels} and {@link removedLevels} describe how the
68
+ * remaining approval chain was recomputed — both empty when the data change
69
+ * did not affect it.
70
+ */
71
+ interface DataUpdatedEvent extends ApprovalEvent {
72
+ updatedBy: string;
73
+ reason?: string;
74
+ /** Field paths whose values differ after the update. */
75
+ changedFields: string[];
76
+ /** Level numbers added to the future chain by re-evaluating conditions. */
77
+ addedLevels: number[];
78
+ /** Level numbers removed from the future chain by re-evaluating conditions. */
79
+ removedLevels: number[];
80
+ }
65
81
  interface ApprovalEventMap {
66
82
  'approval:submitted': SubmittedEvent;
67
83
  'approval:approved': ApprovedEvent;
@@ -76,6 +92,7 @@ interface ApprovalEventMap {
76
92
  'approval:overridden': OverriddenEvent;
77
93
  'approval:expired': ExpiredEvent;
78
94
  'approval:sla_breached': SlaBreachedEvent;
95
+ 'approval:data_updated': DataUpdatedEvent;
79
96
  }
80
97
  type ApprovalEventName = keyof ApprovalEventMap;
81
98
  interface HistoryEntry extends AuditEntry {
@@ -99,4 +116,4 @@ interface INotificationAdapter {
99
116
  notify(event: NotificationEvent): Promise<void>;
100
117
  }
101
118
 
102
- export type { ApprovalEvent as A, CancelledEvent as C, DelegatedEvent 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, ExpiredEvent as d, RejectedEvent as e, ResubmittedEvent as f, SubmittedEvent as g };
119
+ 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, DelegatedEvent as d, ExpiredEvent as e, RejectedEvent as f, ResubmittedEvent as g, SubmittedEvent as h };
@@ -1,4 +1,4 @@
1
- import { a as ApprovalInstance, b as AuditEntry } from './instance-CtkmEkYa.cjs';
1
+ import { a as ApprovalInstance, b as AuditEntry } from './instance-iaAIdMbs.js';
2
2
 
3
3
  interface ApprovalEvent {
4
4
  instanceId: string;
@@ -62,6 +62,22 @@ interface ExpiredEvent extends ApprovalEvent {
62
62
  interface SlaBreachedEvent extends ApprovalEvent {
63
63
  slaDeadlineAt: Date;
64
64
  }
65
+ /**
66
+ * Emitted when an instance's document data is changed while it is still
67
+ * pending. {@link addedLevels} and {@link removedLevels} describe how the
68
+ * remaining approval chain was recomputed — both empty when the data change
69
+ * did not affect it.
70
+ */
71
+ interface DataUpdatedEvent extends ApprovalEvent {
72
+ updatedBy: string;
73
+ reason?: string;
74
+ /** Field paths whose values differ after the update. */
75
+ changedFields: string[];
76
+ /** Level numbers added to the future chain by re-evaluating conditions. */
77
+ addedLevels: number[];
78
+ /** Level numbers removed from the future chain by re-evaluating conditions. */
79
+ removedLevels: number[];
80
+ }
65
81
  interface ApprovalEventMap {
66
82
  'approval:submitted': SubmittedEvent;
67
83
  'approval:approved': ApprovedEvent;
@@ -76,6 +92,7 @@ interface ApprovalEventMap {
76
92
  'approval:overridden': OverriddenEvent;
77
93
  'approval:expired': ExpiredEvent;
78
94
  'approval:sla_breached': SlaBreachedEvent;
95
+ 'approval:data_updated': DataUpdatedEvent;
79
96
  }
80
97
  type ApprovalEventName = keyof ApprovalEventMap;
81
98
  interface HistoryEntry extends AuditEntry {
@@ -99,4 +116,4 @@ interface INotificationAdapter {
99
116
  notify(event: NotificationEvent): Promise<void>;
100
117
  }
101
118
 
102
- export type { ApprovalEvent as A, CancelledEvent as C, DelegatedEvent 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, ExpiredEvent as d, RejectedEvent as e, ResubmittedEvent as f, SubmittedEvent as g };
119
+ 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, DelegatedEvent as d, ExpiredEvent as e, RejectedEvent as f, ResubmittedEvent as g, SubmittedEvent as h };
@@ -1,4 +1,4 @@
1
- import { a as ApprovalInstance } from './instance-CtkmEkYa.cjs';
1
+ import { a as ApprovalInstance } from './instance-iaAIdMbs.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-CtkmEkYa.js';
1
+ import { a as ApprovalInstance } from './instance-iaAIdMbs.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-CtkmEkYa.js';
1
+ import { A as ApprovalTemplate, a as ApprovalInstance, f as ApprovalStatus, b as AuditEntry } from './instance-iaAIdMbs.js';
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-CtkmEkYa.cjs';
1
+ import { A as ApprovalTemplate, a as ApprovalInstance, f as ApprovalStatus, b as AuditEntry } from './instance-iaAIdMbs.cjs';
2
2
 
3
3
  interface PaginationOpts {
4
4
  limit: number;
@@ -1,5 +1,5 @@
1
- import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from '../IStorageAdapter-egPQD6Ry.cjs';
2
- import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-CtkmEkYa.cjs';
1
+ import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from '../IStorageAdapter-D-yIRiET.cjs';
2
+ import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-iaAIdMbs.cjs';
3
3
 
4
4
  declare class MemoryAdapter implements IStorageAdapter {
5
5
  private templates;