hierarchical-approval 1.6.0 → 1.7.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 (61) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +34 -0
  3. package/dist/{ApprovalEngine-SI-IRUMY.d.cts → ApprovalEngine-D16llfJv.d.cts} +46 -8
  4. package/dist/{ApprovalEngine-CyZJDbjP.d.ts → ApprovalEngine-gQdFy2CO.d.ts} +46 -8
  5. package/dist/{IAuditAdapter-wfOcN3Zf.d.ts → IAuditAdapter-DDV4Rf9F.d.ts} +1 -1
  6. package/dist/{IAuditAdapter-Cr0dLoVP.d.cts → IAuditAdapter-UJNdJanq.d.cts} +1 -1
  7. package/dist/{IAuthorizationPolicy-BHybAsq5.d.cts → IAuthorizationPolicy-CFJ-gXKl.d.ts} +2 -2
  8. package/dist/{IAuthorizationPolicy-CdvoZsGC.d.ts → IAuthorizationPolicy-CN6LAaKg.d.cts} +2 -2
  9. package/dist/{IMetricsAdapter-JZVK3QUq.d.cts → IMetricsAdapter-DWq8IFaf.d.cts} +1 -1
  10. package/dist/{IMetricsAdapter-JZVK3QUq.d.ts → IMetricsAdapter-DWq8IFaf.d.ts} +1 -1
  11. package/dist/{INotificationAdapter-Cn-LNPtD.d.cts → INotificationAdapter-Dv_MuYwS.d.ts} +12 -2
  12. package/dist/{INotificationAdapter-Cyo0cvXU.d.ts → INotificationAdapter-Dy2d0JKy.d.cts} +12 -2
  13. package/dist/{IOperationMiddleware-DrJy9UtL.d.cts → IOperationMiddleware-DGG-guxK.d.cts} +1 -1
  14. package/dist/{IOperationMiddleware-rx7XAPrW.d.ts → IOperationMiddleware-DWYXVuJD.d.ts} +1 -1
  15. package/dist/{IStorageAdapter-Dz8jrIAK.d.cts → IStorageAdapter-CRSfnH8U.d.cts} +1 -1
  16. package/dist/{IStorageAdapter-CXRyeUOK.d.ts → IStorageAdapter-DMa5VNNz.d.ts} +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.cjs +13 -4
  20. package/dist/adapters/PostgresAdapter.cjs.map +1 -1
  21. package/dist/adapters/PostgresAdapter.d.cts +2 -2
  22. package/dist/adapters/PostgresAdapter.d.ts +2 -2
  23. package/dist/adapters/PostgresAdapter.js +13 -4
  24. package/dist/adapters/PostgresAdapter.js.map +1 -1
  25. package/dist/index.cjs +187 -0
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +9 -9
  28. package/dist/index.d.ts +9 -9
  29. package/dist/index.js +187 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/{instance-SA0BU-Gb.d.cts → instance-BE0uJmg3.d.cts} +27 -2
  32. package/dist/{instance-SA0BU-Gb.d.ts → instance-BE0uJmg3.d.ts} +27 -2
  33. package/dist/nestjs.cjs +187 -0
  34. package/dist/nestjs.cjs.map +1 -1
  35. package/dist/nestjs.d.cts +8 -8
  36. package/dist/nestjs.d.ts +8 -8
  37. package/dist/nestjs.js +187 -0
  38. package/dist/nestjs.js.map +1 -1
  39. package/dist/plugins/audit.d.cts +2 -2
  40. package/dist/plugins/audit.d.ts +2 -2
  41. package/dist/plugins/metrics.cjs +2 -0
  42. package/dist/plugins/metrics.cjs.map +1 -1
  43. package/dist/plugins/metrics.d.cts +1 -1
  44. package/dist/plugins/metrics.d.ts +1 -1
  45. package/dist/plugins/metrics.js +2 -0
  46. package/dist/plugins/metrics.js.map +1 -1
  47. package/dist/plugins/notify.d.cts +2 -2
  48. package/dist/plugins/notify.d.ts +2 -2
  49. package/dist/plugins/resilience.d.cts +3 -3
  50. package/dist/plugins/resilience.d.ts +3 -3
  51. package/dist/plugins/tracing.d.cts +2 -2
  52. package/dist/plugins/tracing.d.ts +2 -2
  53. package/dist/plugins/webhook.d.cts +2 -2
  54. package/dist/plugins/webhook.d.ts +2 -2
  55. package/dist/testing.cjs +187 -0
  56. package/dist/testing.cjs.map +1 -1
  57. package/dist/testing.d.cts +8 -8
  58. package/dist/testing.d.ts +8 -8
  59. package/dist/testing.js +187 -0
  60. package/dist/testing.js.map +1 -1
  61. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,45 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  _Nothing yet._
9
9
 
10
+ ## [1.7.0] - 2026-09-04
11
+
12
+ ### Added — attachment references
13
+
14
+ - **`addAttachment()` / `removeAttachment()` attach supporting evidence to an
15
+ approval** — a quote PDF, a signed contract, a screenshot of a system of
16
+ record. Approvers had nowhere to put the document their decision rested on, so
17
+ it lived in an email thread the audit trail never saw.
18
+
19
+ ```ts
20
+ await engine.addAttachment(id, {
21
+ actorId: 'buyer-1',
22
+ name: 'quote.pdf',
23
+ uri: 's3://procurement/quotes/q-1.pdf',
24
+ contentType: 'application/pdf',
25
+ sizeBytes: 48_120,
26
+ });
27
+ ```
28
+
29
+ - **References only, never bytes.** Approval documents belong in the object
30
+ store or DMS the organisation already runs, which handles retention, virus
31
+ scanning and access control far better than an approval table could; copying
32
+ them here would make the audit database the largest and least governed copy of
33
+ them. Removing detaches the reference and never deletes from the underlying
34
+ store.
35
+
36
+ - **The audit trail keeps what was removed** — name and URI are recorded on the
37
+ `attachment_removed` entry, so the record still shows an approver saw evidence
38
+ that is no longer listed. Dropping that would let the trail imply a decision
39
+ was made on less than it was.
40
+
41
+ - Persisted across all four PostgreSQL sites (schema, `migrate()`,
42
+ insert, update, and read-back), with `addedAt` revived as a `Date`. An
43
+ instance with no attachments reads back as `undefined` rather than `[]`,
44
+ keeping the round-trip shape identical to `MemoryAdapter`.
45
+
46
+ New exports: `Attachment`, `AttachmentEvent`, `AddAttachmentOptions`,
47
+ `RemoveAttachmentOptions`. `ApprovalInstance` gains `attachments`.
48
+
10
49
  ## [1.6.0] - 2026-09-04
11
50
 
12
51
  ### Fixed — PostgresAdapter silently dropped most instance updates
package/README.md CHANGED
@@ -399,6 +399,40 @@ engine.registerConditionOperator('between', (actual, expected) => {
399
399
  });
400
400
  ```
401
401
 
402
+ ### Attachments
403
+
404
+ Attach supporting evidence — a quote PDF, a signed contract, a screenshot of a
405
+ system of record:
406
+
407
+ ```ts
408
+ const withQuote = await engine.addAttachment(instance.id, {
409
+ actorId: 'buyer-1',
410
+ name: 'quote.pdf',
411
+ uri: 's3://procurement/quotes/q-1.pdf', // a reference, not bytes
412
+ contentType: 'application/pdf',
413
+ sizeBytes: 48_120,
414
+ });
415
+
416
+ await engine.removeAttachment(instance.id, {
417
+ actorId: 'buyer-1',
418
+ attachmentId: withQuote.attachments[0].id,
419
+ reason: 'superseded by revised quote',
420
+ });
421
+ ```
422
+
423
+ **The engine stores references, never bytes.** Approval documents belong in the
424
+ object store or DMS you already run, which handles retention, virus scanning and
425
+ access control far better than an approval table could — copying them here would
426
+ make the audit database the largest and least governed copy of them.
427
+
428
+ Removing detaches the reference; it never deletes from the underlying store,
429
+ which is the DMS's call. The audit entry keeps the name and URI of what was
430
+ removed, so the trail still shows an approver saw evidence that is no longer
431
+ listed.
432
+
433
+ Emits `approval:attachment_added` / `approval:attachment_removed`. Allowed on
434
+ any non-terminal instance, subject to your authorization policy.
435
+
402
436
  ### Asking the submitter a question
403
437
 
404
438
  Approvers routinely need one fact before they can decide. `requestInfo()` puts
@@ -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-Dz8jrIAK.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-SA0BU-Gb.cjs';
3
- import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-Cn-LNPtD.cjs';
1
+ import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-CRSfnH8U.cjs';
2
+ import { l as ConditionExpression, p as ResolverFn, g as ApprovalTemplateConfig, A as ApprovalTemplate, k as AuditContext, a as ApprovalInstance, e as ApprovalMode, b as AuditEntry } from './instance-BE0uJmg3.cjs';
3
+ import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-Dy2d0JKy.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-DrJy9UtL.cjs';
8
- import { I as IAuditAdapter } from './IAuditAdapter-Cr0dLoVP.cjs';
9
- import { I as IMetricsAdapter } from './IMetricsAdapter-JZVK3QUq.cjs';
7
+ import { I as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-DGG-guxK.cjs';
8
+ import { I as IAuditAdapter } from './IAuditAdapter-UJNdJanq.cjs';
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-BHybAsq5.cjs';
11
+ import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-CN6LAaKg.cjs';
12
12
 
13
13
  declare const SubmitOptionsSchema: z.ZodObject<{
14
14
  templateName: z.ZodString;
@@ -78,6 +78,18 @@ declare const ProvideInfoOptionsSchema: z.ZodObject<{
78
78
  respondedBy: z.ZodString;
79
79
  response: z.ZodString;
80
80
  }, z.core.$strip>;
81
+ declare const AddAttachmentOptionsSchema: z.ZodObject<{
82
+ actorId: z.ZodString;
83
+ name: z.ZodString;
84
+ uri: z.ZodString;
85
+ contentType: z.ZodOptional<z.ZodString>;
86
+ sizeBytes: z.ZodOptional<z.ZodNumber>;
87
+ }, z.core.$strip>;
88
+ declare const RemoveAttachmentOptionsSchema: z.ZodObject<{
89
+ actorId: z.ZodString;
90
+ attachmentId: z.ZodString;
91
+ reason: z.ZodOptional<z.ZodString>;
92
+ }, z.core.$strip>;
81
93
  declare const UpdateDataOptionsSchema: z.ZodObject<{
82
94
  updatedBy: z.ZodString;
83
95
  data: z.ZodRecord<z.ZodString, z.ZodUnknown>;
@@ -101,6 +113,8 @@ type OverrideOptions = z.infer<typeof OverrideOptionsSchema>;
101
113
  type UpdateDataOptions = z.infer<typeof UpdateDataOptionsSchema>;
102
114
  type RequestInfoOptions = z.infer<typeof RequestInfoOptionsSchema>;
103
115
  type ProvideInfoOptions = z.infer<typeof ProvideInfoOptionsSchema>;
116
+ type AddAttachmentOptions = z.infer<typeof AddAttachmentOptionsSchema>;
117
+ type RemoveAttachmentOptions = z.infer<typeof RemoveAttachmentOptionsSchema>;
104
118
 
105
119
  /**
106
120
  * Computes deadline dates from a number of days. The default engine behaviour
@@ -132,7 +146,7 @@ interface WeekendCalendarOptions {
132
146
  */
133
147
  declare function weekendCalendar(options?: WeekendCalendarOptions): BusinessCalendar;
134
148
 
135
- type IdGeneratorPrefix = 'inst' | 'tpl';
149
+ type IdGeneratorPrefix = 'inst' | 'tpl' | 'att';
136
150
  type IdGeneratorFn = (prefix: IdGeneratorPrefix) => string;
137
151
  declare const defaultIdGenerator: IdGeneratorFn;
138
152
 
@@ -490,6 +504,30 @@ declare class ApprovalEngine {
490
504
  * by being held.
491
505
  */
492
506
  private extendDeadlinesBy;
507
+ /**
508
+ * Attach supporting evidence to an approval — a quote PDF, a signed contract,
509
+ * a screenshot of a system of record.
510
+ *
511
+ * Stores a **reference**, never bytes. Approval documents belong in the object
512
+ * store or DMS the organisation already runs, which handles retention, virus
513
+ * scanning and access control far better than an approval table could;
514
+ * copying them here would make the audit database the largest and least
515
+ * governed copy of them.
516
+ *
517
+ * Allowed on any non-terminal instance, by anyone the authorization policy
518
+ * permits: the submitter adding a missing quote and an approver adding the
519
+ * note that justifies their decision are both ordinary.
520
+ */
521
+ addAttachment(instanceId: string, raw: AddAttachmentOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
522
+ /**
523
+ * Detach a reference from an approval.
524
+ *
525
+ * The audit entry keeps the name and URI of what was removed, so the record
526
+ * still shows an approver saw evidence that is no longer listed — dropping
527
+ * that would let the trail imply a decision was made on less than it was.
528
+ * Nothing is deleted from the underlying store; that is the DMS's call.
529
+ */
530
+ removeAttachment(instanceId: string, raw: RemoveAttachmentOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
493
531
  /** Add a comment to an instance without approving or rejecting. */
494
532
  addComment(instanceId: string, raw: AddCommentOptions, auditCtx?: AuditContext): Promise<void>;
495
533
  /** Resubmit a rejected instance, creating a new linked instance from level 1. */
@@ -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-CXRyeUOK.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-SA0BU-Gb.js';
3
- import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-Cyo0cvXU.js';
1
+ import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-DMa5VNNz.js';
2
+ import { l as ConditionExpression, p as ResolverFn, g as ApprovalTemplateConfig, A as ApprovalTemplate, k as AuditContext, a as ApprovalInstance, e as ApprovalMode, b as AuditEntry } from './instance-BE0uJmg3.js';
3
+ import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-Dv_MuYwS.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-rx7XAPrW.js';
8
- import { I as IAuditAdapter } from './IAuditAdapter-wfOcN3Zf.js';
9
- import { I as IMetricsAdapter } from './IMetricsAdapter-JZVK3QUq.js';
7
+ import { I as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-DWYXVuJD.js';
8
+ import { I as IAuditAdapter } from './IAuditAdapter-DDV4Rf9F.js';
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-CdvoZsGC.js';
11
+ import { I as IAuthorizationPolicy } from './IAuthorizationPolicy-CFJ-gXKl.js';
12
12
 
13
13
  declare const SubmitOptionsSchema: z.ZodObject<{
14
14
  templateName: z.ZodString;
@@ -78,6 +78,18 @@ declare const ProvideInfoOptionsSchema: z.ZodObject<{
78
78
  respondedBy: z.ZodString;
79
79
  response: z.ZodString;
80
80
  }, z.core.$strip>;
81
+ declare const AddAttachmentOptionsSchema: z.ZodObject<{
82
+ actorId: z.ZodString;
83
+ name: z.ZodString;
84
+ uri: z.ZodString;
85
+ contentType: z.ZodOptional<z.ZodString>;
86
+ sizeBytes: z.ZodOptional<z.ZodNumber>;
87
+ }, z.core.$strip>;
88
+ declare const RemoveAttachmentOptionsSchema: z.ZodObject<{
89
+ actorId: z.ZodString;
90
+ attachmentId: z.ZodString;
91
+ reason: z.ZodOptional<z.ZodString>;
92
+ }, z.core.$strip>;
81
93
  declare const UpdateDataOptionsSchema: z.ZodObject<{
82
94
  updatedBy: z.ZodString;
83
95
  data: z.ZodRecord<z.ZodString, z.ZodUnknown>;
@@ -101,6 +113,8 @@ type OverrideOptions = z.infer<typeof OverrideOptionsSchema>;
101
113
  type UpdateDataOptions = z.infer<typeof UpdateDataOptionsSchema>;
102
114
  type RequestInfoOptions = z.infer<typeof RequestInfoOptionsSchema>;
103
115
  type ProvideInfoOptions = z.infer<typeof ProvideInfoOptionsSchema>;
116
+ type AddAttachmentOptions = z.infer<typeof AddAttachmentOptionsSchema>;
117
+ type RemoveAttachmentOptions = z.infer<typeof RemoveAttachmentOptionsSchema>;
104
118
 
105
119
  /**
106
120
  * Computes deadline dates from a number of days. The default engine behaviour
@@ -132,7 +146,7 @@ interface WeekendCalendarOptions {
132
146
  */
133
147
  declare function weekendCalendar(options?: WeekendCalendarOptions): BusinessCalendar;
134
148
 
135
- type IdGeneratorPrefix = 'inst' | 'tpl';
149
+ type IdGeneratorPrefix = 'inst' | 'tpl' | 'att';
136
150
  type IdGeneratorFn = (prefix: IdGeneratorPrefix) => string;
137
151
  declare const defaultIdGenerator: IdGeneratorFn;
138
152
 
@@ -490,6 +504,30 @@ declare class ApprovalEngine {
490
504
  * by being held.
491
505
  */
492
506
  private extendDeadlinesBy;
507
+ /**
508
+ * Attach supporting evidence to an approval — a quote PDF, a signed contract,
509
+ * a screenshot of a system of record.
510
+ *
511
+ * Stores a **reference**, never bytes. Approval documents belong in the object
512
+ * store or DMS the organisation already runs, which handles retention, virus
513
+ * scanning and access control far better than an approval table could;
514
+ * copying them here would make the audit database the largest and least
515
+ * governed copy of them.
516
+ *
517
+ * Allowed on any non-terminal instance, by anyone the authorization policy
518
+ * permits: the submitter adding a missing quote and an approver adding the
519
+ * note that justifies their decision are both ordinary.
520
+ */
521
+ addAttachment(instanceId: string, raw: AddAttachmentOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
522
+ /**
523
+ * Detach a reference from an approval.
524
+ *
525
+ * The audit entry keeps the name and URI of what was removed, so the record
526
+ * still shows an approver saw evidence that is no longer listed — dropping
527
+ * that would let the trail imply a decision was made on less than it was.
528
+ * Nothing is deleted from the underlying store; that is the DMS's call.
529
+ */
530
+ removeAttachment(instanceId: string, raw: RemoveAttachmentOptions, auditCtx?: AuditContext): Promise<ApprovalInstance>;
493
531
  /** Add a comment to an instance without approving or rejecting. */
494
532
  addComment(instanceId: string, raw: AddCommentOptions, auditCtx?: AuditContext): Promise<void>;
495
533
  /** Resubmit a rejected instance, creating a new linked instance from level 1. */
@@ -1,4 +1,4 @@
1
- import { b as AuditEntry, a as ApprovalInstance } from './instance-SA0BU-Gb.js';
1
+ import { b as AuditEntry, a as ApprovalInstance } from './instance-BE0uJmg3.js';
2
2
 
3
3
  interface IAuditAdapter {
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { b as AuditEntry, a as ApprovalInstance } from './instance-SA0BU-Gb.cjs';
1
+ import { b as AuditEntry, a as ApprovalInstance } from './instance-BE0uJmg3.cjs';
2
2
 
3
3
  interface IAuditAdapter {
4
4
  /**
@@ -1,7 +1,7 @@
1
- import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-SA0BU-Gb.cjs';
1
+ import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-BE0uJmg3.js';
2
2
 
3
3
  interface AuthorizationContext {
4
- operation: 'submit' | 'approve' | 'reject' | 'delegate' | 'reassign' | 'cancel' | 'escalate' | 'override' | 'resubmit' | 'addComment' | 'updateData' | 'requestInfo' | 'provideInfo';
4
+ operation: 'submit' | 'approve' | 'reject' | 'delegate' | 'reassign' | 'cancel' | 'escalate' | 'override' | 'resubmit' | 'addComment' | 'updateData' | 'requestInfo' | 'provideInfo' | 'addAttachment' | 'removeAttachment';
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-SA0BU-Gb.js';
1
+ import { a as ApprovalInstance, d as ApprovalLevelInstance } from './instance-BE0uJmg3.cjs';
2
2
 
3
3
  interface AuthorizationContext {
4
- operation: 'submit' | 'approve' | 'reject' | 'delegate' | 'reassign' | 'cancel' | 'escalate' | 'override' | 'resubmit' | 'addComment' | 'updateData' | 'requestInfo' | 'provideInfo';
4
+ operation: 'submit' | 'approve' | 'reject' | 'delegate' | 'reassign' | 'cancel' | 'escalate' | 'override' | 'resubmit' | 'addComment' | 'updateData' | 'requestInfo' | 'provideInfo' | 'addAttachment' | 'removeAttachment';
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.data_updated' | 'approval.reminded' | 'approval.info_requested' | 'approval.info_provided' | '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.reminded' | 'approval.info_requested' | 'approval.info_provided' | 'approval.attachment_added' | 'approval.attachment_removed' | '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.data_updated' | 'approval.reminded' | 'approval.info_requested' | 'approval.info_provided' | '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.reminded' | 'approval.info_requested' | 'approval.info_provided' | 'approval.attachment_added' | 'approval.attachment_removed' | '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-SA0BU-Gb.cjs';
1
+ import { a as ApprovalInstance, b as AuditEntry } from './instance-BE0uJmg3.js';
2
2
 
3
3
  interface ApprovalEvent {
4
4
  instanceId: string;
@@ -104,6 +104,14 @@ interface InfoProvidedEvent extends ApprovalEvent {
104
104
  /** The approvers waiting again now that the question is answered. */
105
105
  recipients: string[];
106
106
  }
107
+ /** Emitted when supporting evidence is attached to or removed from an approval. */
108
+ interface AttachmentEvent extends ApprovalEvent {
109
+ actorId: string;
110
+ attachmentId: string;
111
+ name: string;
112
+ uri: string;
113
+ level?: number;
114
+ }
107
115
  interface ApprovalEventMap {
108
116
  'approval:submitted': SubmittedEvent;
109
117
  'approval:approved': ApprovedEvent;
@@ -122,6 +130,8 @@ interface ApprovalEventMap {
122
130
  'approval:reminder': ReminderEvent;
123
131
  'approval:info_requested': InfoRequestedEvent;
124
132
  'approval:info_provided': InfoProvidedEvent;
133
+ 'approval:attachment_added': AttachmentEvent;
134
+ 'approval:attachment_removed': AttachmentEvent;
125
135
  }
126
136
  type ApprovalEventName = keyof ApprovalEventMap;
127
137
  interface HistoryEntry extends AuditEntry {
@@ -145,4 +155,4 @@ interface INotificationAdapter {
145
155
  notify(event: NotificationEvent): Promise<void>;
146
156
  }
147
157
 
148
- 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, InfoProvidedEvent as f, InfoRequestedEvent as g, RejectedEvent as h, ReminderEvent as i, ResubmittedEvent as j, SubmittedEvent as k };
158
+ 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, SubmittedEvent as l };
@@ -1,4 +1,4 @@
1
- import { a as ApprovalInstance, b as AuditEntry } from './instance-SA0BU-Gb.js';
1
+ import { a as ApprovalInstance, b as AuditEntry } from './instance-BE0uJmg3.cjs';
2
2
 
3
3
  interface ApprovalEvent {
4
4
  instanceId: string;
@@ -104,6 +104,14 @@ interface InfoProvidedEvent extends ApprovalEvent {
104
104
  /** The approvers waiting again now that the question is answered. */
105
105
  recipients: string[];
106
106
  }
107
+ /** Emitted when supporting evidence is attached to or removed from an approval. */
108
+ interface AttachmentEvent extends ApprovalEvent {
109
+ actorId: string;
110
+ attachmentId: string;
111
+ name: string;
112
+ uri: string;
113
+ level?: number;
114
+ }
107
115
  interface ApprovalEventMap {
108
116
  'approval:submitted': SubmittedEvent;
109
117
  'approval:approved': ApprovedEvent;
@@ -122,6 +130,8 @@ interface ApprovalEventMap {
122
130
  'approval:reminder': ReminderEvent;
123
131
  'approval:info_requested': InfoRequestedEvent;
124
132
  'approval:info_provided': InfoProvidedEvent;
133
+ 'approval:attachment_added': AttachmentEvent;
134
+ 'approval:attachment_removed': AttachmentEvent;
125
135
  }
126
136
  type ApprovalEventName = keyof ApprovalEventMap;
127
137
  interface HistoryEntry extends AuditEntry {
@@ -145,4 +155,4 @@ interface INotificationAdapter {
145
155
  notify(event: NotificationEvent): Promise<void>;
146
156
  }
147
157
 
148
- 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, InfoProvidedEvent as f, InfoRequestedEvent as g, RejectedEvent as h, ReminderEvent as i, ResubmittedEvent as j, SubmittedEvent as k };
158
+ 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, SubmittedEvent as l };
@@ -1,4 +1,4 @@
1
- import { a as ApprovalInstance } from './instance-SA0BU-Gb.cjs';
1
+ import { a as ApprovalInstance } from './instance-BE0uJmg3.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-SA0BU-Gb.js';
1
+ import { a as ApprovalInstance } from './instance-BE0uJmg3.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-SA0BU-Gb.cjs';
1
+ import { A as ApprovalTemplate, a as ApprovalInstance, f as ApprovalStatus, b as AuditEntry } from './instance-BE0uJmg3.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-SA0BU-Gb.js';
1
+ import { A as ApprovalTemplate, a as ApprovalInstance, f as ApprovalStatus, b as AuditEntry } from './instance-BE0uJmg3.js';
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-Dz8jrIAK.cjs';
2
- import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-SA0BU-Gb.cjs';
1
+ import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from '../IStorageAdapter-CRSfnH8U.cjs';
2
+ import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-BE0uJmg3.cjs';
3
3
 
4
4
  declare class MemoryAdapter implements IStorageAdapter {
5
5
  private templates;
@@ -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-CXRyeUOK.js';
2
- import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-SA0BU-Gb.js';
1
+ import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from '../IStorageAdapter-DMa5VNNz.js';
2
+ import { A as ApprovalTemplate, a as ApprovalInstance, b as AuditEntry } from '../instance-BE0uJmg3.js';
3
3
 
4
4
  declare class MemoryAdapter implements IStorageAdapter {
5
5
  private templates;
@@ -116,6 +116,7 @@ var PostgresAdapter = class {
116
116
  sla_breached_at TIMESTAMPTZ,
117
117
  template_snapshot JSONB,
118
118
  info_request JSONB,
119
+ attachments JSONB NOT NULL DEFAULT '[]',
119
120
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
120
121
  updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
121
122
  PRIMARY KEY (tenant_id, id),
@@ -159,6 +160,7 @@ var PostgresAdapter = class {
159
160
  ALTER TABLE IF EXISTS ${this.p}_instances ADD COLUMN IF NOT EXISTS sla_breached_at TIMESTAMPTZ;
160
161
  ALTER TABLE IF EXISTS ${this.p}_instances ADD COLUMN IF NOT EXISTS template_snapshot JSONB;
161
162
  ALTER TABLE IF EXISTS ${this.p}_instances ADD COLUMN IF NOT EXISTS info_request JSONB;
163
+ ALTER TABLE IF EXISTS ${this.p}_instances ADD COLUMN IF NOT EXISTS attachments JSONB NOT NULL DEFAULT '[]';
162
164
  `);
163
165
  }
164
166
  /** Enable PostgreSQL Row Level Security for the instances and audit_log tables. */
@@ -218,9 +220,9 @@ var PostgresAdapter = class {
218
220
  status, current_level, version, idempotency_key,
219
221
  data, metadata, levels,
220
222
  parent_instance_id, expires_at, deadline_action,
221
- sla_deadline_at, sla_breached_at, template_snapshot, info_request,
223
+ sla_deadline_at, sla_breached_at, template_snapshot, info_request, attachments,
222
224
  created_at, updated_at)
223
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23)
225
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24)
224
226
  ON CONFLICT (tenant_id, id) DO NOTHING`,
225
227
  [
226
228
  instance.id,
@@ -244,6 +246,7 @@ var PostgresAdapter = class {
244
246
  instance.slaBreachedAt?.toISOString() ?? null,
245
247
  instance.templateSnapshot ? JSON.stringify(instance.templateSnapshot) : null,
246
248
  instance.infoRequest ? JSON.stringify(instance.infoRequest) : null,
249
+ JSON.stringify(instance.attachments ?? []),
247
250
  instance.createdAt.toISOString(),
248
251
  instance.updatedAt.toISOString()
249
252
  ]
@@ -269,7 +272,8 @@ var PostgresAdapter = class {
269
272
  deadline_action = $13,
270
273
  sla_deadline_at = $14,
271
274
  template_snapshot = $15,
272
- info_request = $16
275
+ info_request = $16,
276
+ attachments = $17
273
277
  WHERE tenant_id = $1 AND id = $2 AND version = $3
274
278
  RETURNING id`,
275
279
  [
@@ -288,7 +292,8 @@ var PostgresAdapter = class {
288
292
  instance.deadlineAction ?? null,
289
293
  instance.slaDeadlineAt?.toISOString() ?? null,
290
294
  instance.templateSnapshot ? JSON.stringify(instance.templateSnapshot) : null,
291
- instance.infoRequest ? JSON.stringify(instance.infoRequest) : null
295
+ instance.infoRequest ? JSON.stringify(instance.infoRequest) : null,
296
+ JSON.stringify(instance.attachments ?? [])
292
297
  ]
293
298
  );
294
299
  if (result.rowCount === 0) throw new ApprovalConflictError(instance.id);
@@ -537,6 +542,10 @@ var PostgresAdapter = class {
537
542
  slaDeadlineAt: row["sla_deadline_at"] ? new Date(row["sla_deadline_at"]) : void 0,
538
543
  slaBreachedAt: row["sla_breached_at"] ? new Date(row["sla_breached_at"]) : void 0,
539
544
  templateSnapshot: row["template_snapshot"] ?? void 0,
545
+ // Absent attachments map to undefined, not [], so a Postgres round trip
546
+ // returns the same shape MemoryAdapter does for an instance that never
547
+ // had any — the parity the round-trip test pins down.
548
+ attachments: (row["attachments"] ?? []).length ? row["attachments"].map((a) => ({ ...a, addedAt: new Date(a.addedAt) })) : void 0,
540
549
  infoRequest: row["info_request"] ? {
541
550
  ...row["info_request"],
542
551
  askedAt: new Date(row["info_request"].askedAt)