hierarchical-approval 0.2.1 → 0.3.1

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 (53) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +112 -38
  3. package/dist/{ApprovalEngine-DICBgdIp.d.ts → ApprovalEngine-Bmg1bbrd.d.cts} +9 -190
  4. package/dist/{ApprovalEngine-CSxLWZ5h.d.cts → ApprovalEngine-DLyhejR-.d.ts} +9 -190
  5. package/dist/Clock-3FnOczFJ.d.cts +6 -0
  6. package/dist/Clock-3FnOczFJ.d.ts +6 -0
  7. package/dist/IAuditAdapter-B3vvU09m.d.ts +12 -0
  8. package/dist/IAuditAdapter-B_DhuPsU.d.cts +12 -0
  9. package/dist/IMetricsAdapter-D9PUz4tM.d.cts +9 -0
  10. package/dist/IMetricsAdapter-D9PUz4tM.d.ts +9 -0
  11. package/dist/INotificationAdapter-BdfVjYa8.d.ts +102 -0
  12. package/dist/INotificationAdapter-DVVmXU6a.d.cts +102 -0
  13. package/dist/IOperationMiddleware-CkRh93Wf.d.cts +62 -0
  14. package/dist/IOperationMiddleware-Dk_xOKxR.d.ts +62 -0
  15. package/dist/IStorageAdapter-DbeWhtCG.d.cts +47 -0
  16. package/dist/IStorageAdapter-EdwtErKD.d.ts +47 -0
  17. package/dist/Logger-CIzvalFc.d.cts +9 -0
  18. package/dist/Logger-CIzvalFc.d.ts +9 -0
  19. package/dist/adapters/MemoryAdapter.d.cts +2 -1
  20. package/dist/adapters/MemoryAdapter.d.ts +2 -1
  21. package/dist/adapters/PostgresAdapter.d.cts +2 -1
  22. package/dist/adapters/PostgresAdapter.d.ts +2 -1
  23. package/dist/index.d.cts +12 -4
  24. package/dist/index.d.ts +12 -4
  25. package/dist/{IStorageAdapter-B2xGuaKi.d.cts → instance-D8D7b07N.d.cts} +1 -45
  26. package/dist/{IStorageAdapter-B2xGuaKi.d.ts → instance-D8D7b07N.d.ts} +1 -45
  27. package/dist/plugins/audit.cjs +370 -0
  28. package/dist/plugins/audit.cjs.map +1 -0
  29. package/dist/plugins/audit.d.cts +292 -0
  30. package/dist/plugins/audit.d.ts +292 -0
  31. package/dist/plugins/audit.js +362 -0
  32. package/dist/plugins/audit.js.map +1 -0
  33. package/dist/plugins/metrics.cjs +406 -0
  34. package/dist/plugins/metrics.cjs.map +1 -0
  35. package/dist/plugins/metrics.d.cts +282 -0
  36. package/dist/plugins/metrics.d.ts +282 -0
  37. package/dist/plugins/metrics.js +399 -0
  38. package/dist/plugins/metrics.js.map +1 -0
  39. package/dist/plugins/notify.cjs +373 -0
  40. package/dist/plugins/notify.cjs.map +1 -0
  41. package/dist/plugins/notify.d.cts +349 -0
  42. package/dist/plugins/notify.d.ts +349 -0
  43. package/dist/plugins/notify.js +368 -0
  44. package/dist/plugins/notify.js.map +1 -0
  45. package/dist/plugins/resilience.cjs +328 -0
  46. package/dist/plugins/resilience.cjs.map +1 -0
  47. package/dist/plugins/resilience.d.cts +310 -0
  48. package/dist/plugins/resilience.d.ts +310 -0
  49. package/dist/plugins/resilience.js +321 -0
  50. package/dist/plugins/resilience.js.map +1 -0
  51. package/dist/testing.d.cts +9 -2
  52. package/dist/testing.d.ts +9 -2
  53. package/package.json +22 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,76 @@
1
+ # Changelog
2
+
3
+ All notable changes to `hierarchical-approval` are documented here. This project
4
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
+
6
+ ## [0.3.1] - 2026-06-26
7
+
8
+ ### Docs
9
+
10
+ - Replace the README's Mermaid code blocks with pre-rendered PNG diagrams
11
+ referenced by absolute URL, so the status lifecycle, approval flow, and
12
+ architecture diagrams render on npmjs.com (which does not render Mermaid) as
13
+ well as on GitHub.
14
+
15
+ ## [0.3.0] - 2026-06-26
16
+
17
+ ### Added — enterprise plug-in layer
18
+
19
+ Production-grade implementations of the existing engine extension points, each
20
+ published on its own tree-shakeable import subpath with **zero runtime
21
+ dependencies** (Node.js built-ins only). The core engine is unchanged.
22
+
23
+ - **`hierarchical-approval/plugins/audit`**
24
+ - `HashChainAuditAdapter` — SHA-256 hash-chained, tamper-evident audit log.
25
+ `verify()` detects content tampering, deletion, reordering, **and tail
26
+ truncation** (via an in-process high-water mark or an explicit
27
+ `expectedLength` anchor). Pluggable writer/reader; in-memory by default.
28
+ - `RedactingAuditAdapter` — redacts configured PII field paths and free-text
29
+ fields before forwarding to a wrapped adapter; original entries are never
30
+ mutated.
31
+ - `CompositeAuditAdapter` — fan out to multiple audit sinks with per-child
32
+ fault isolation. Never throws.
33
+ - **`hierarchical-approval/plugins/metrics`**
34
+ - `PrometheusMetricsAdapter` — accumulates counters/histograms and renders
35
+ `scrape()` in Prometheus text exposition format.
36
+ - `InMemoryMetricsAdapter` — `snapshot()` with count/sum/min/max/avg/p50/p95;
37
+ O(1) ring-buffer sample retention.
38
+ - `CompositeMetricsAdapter` — fan out to multiple metrics backends.
39
+ - **`hierarchical-approval/plugins/resilience`**
40
+ - `RbacAuthorizationPolicy` — per-operation role rules with a default-deny
41
+ posture and a pluggable role provider.
42
+ - `CompositeAuthorizationPolicy` — combine policies with AND/OR semantics.
43
+ - `RateLimitMiddleware` — token-bucket throttling keyed per actor + operation,
44
+ with an injectable clock.
45
+ - `LoggingMiddleware` — structured before/after/onError logging with timings.
46
+ - **`hierarchical-approval/plugins/notify`**
47
+ - `OutboxNotificationAdapter` — transactional outbox with retry, exponential
48
+ backoff, and a dead-letter queue for at-least-once delivery. Exposes
49
+ `drain()`, `pending()`, and `deadLettered()`.
50
+ - `TemplatedNotificationAdapter` — render a human-readable message per event
51
+ type via a configurable template map.
52
+ - `CompositeNotificationAdapter` — multi-channel fan-out with fault isolation.
53
+
54
+ ### Changed
55
+
56
+ - `package.json` `exports` and the build now expose the four `plugins/*`
57
+ subpaths in ESM, CJS, and `.d.ts`.
58
+
59
+ ### Tests
60
+
61
+ - Test suite grew from 196 to 380 passing tests covering the new plug-ins
62
+ (tamper detection, backoff/dead-letter, rate-limit math, RBAC, PII redaction,
63
+ Prometheus exposition, and the public-export surface).
64
+
65
+ ## [0.2.1] - 2026-06-23
66
+
67
+ - Documentation and tooling fixes; audit-log de-duplication in `MemoryAdapter`.
68
+
69
+ ## [0.2.0] - 2026-06-23
70
+
71
+ - Advanced decision modes (`quorum`, `weighted`), `reassign()`, `getStatistics()`,
72
+ and an injectable `BusinessCalendar`.
73
+
74
+ ## [0.1.1] - 2026-06-21
75
+
76
+ - Initial published release line.
package/README.md CHANGED
@@ -97,19 +97,9 @@ Approval workflows are deceptively simple until they aren't. Most teams start wi
97
97
 
98
98
  ### The status lifecycle
99
99
 
100
- ```mermaid
101
- stateDiagram-v2
102
- [*] --> pending
103
- pending --> pending: approve advances a level
104
- pending --> approved: final level or override
105
- pending --> rejected: reject
106
- pending --> cancelled: cancel
107
- pending --> expired: deadline reached
108
- approved --> [*]
109
- rejected --> [*]
110
- cancelled --> [*]
111
- expired --> [*]
112
- ```
100
+ <p align="center">
101
+ <img src="https://raw.githubusercontent.com/matthews-wong/hierarchical-approval/main/assets/diagrams/lifecycle.png" alt="Status lifecycle: pending advances on approve, and resolves to approved, rejected, cancelled, or expired." width="640">
102
+ </p>
113
103
 
114
104
  > `submit()` creates the instance in `pending`; `resubmit()` on a rejected instance spawns a new linked instance starting again at level 1.
115
105
 
@@ -117,20 +107,9 @@ stateDiagram-v2
117
107
 
118
108
  A level is satisfied according to its mode (`any`, `all`, `majority`, `quorum`, or `weighted`):
119
109
 
120
- ```mermaid
121
- flowchart TD
122
- S[Submit] --> R[Resolve approvers for current level]
123
- R --> ACT{An approver acts}
124
- ACT -->|approve| MET{Level threshold met}
125
- ACT -->|reject| REJ{Level rejected}
126
- MET -->|not yet| ACT
127
- MET -->|yes| MORE{More levels}
128
- MORE -->|yes| ADV[Advance to next level]
129
- ADV --> R
130
- MORE -->|no| DONE[Status approved]
131
- REJ -->|yes| OUT[Status rejected]
132
- REJ -->|no| ACT
133
- ```
110
+ <p align="center">
111
+ <img src="https://raw.githubusercontent.com/matthews-wong/hierarchical-approval/main/assets/diagrams/flow.png" alt="Approval flow: submit, resolve approvers, approvers act; when the level threshold is met advance to the next level or complete; rejection ends the instance." width="380">
112
+ </p>
134
113
 
135
114
  ### Architecture — a small core with pluggable ports
136
115
 
@@ -138,17 +117,9 @@ The engine never talks to your database, queue, or notification service directly
138
117
 
139
118
  Solid arrow = required (storage). Dotted arrows = optional ports you can plug in:
140
119
 
141
- ```mermaid
142
- flowchart LR
143
- APP[Your application] --> ENG[ApprovalEngine]
144
- ENG --> ST[IStorageAdapter - Memory, Postgres or custom]
145
- ENG -.-> NO[INotificationAdapter]
146
- ENG -.-> AU[IAuditAdapter]
147
- ENG -.-> ME[IMetricsAdapter]
148
- ENG -.-> SC[ISchedulerAdapter]
149
- ENG -.-> AZ[IAuthorizationPolicy]
150
- ENG -.-> MW[IOperationMiddleware]
151
- ```
120
+ <p align="center">
121
+ <img src="https://raw.githubusercontent.com/matthews-wong/hierarchical-approval/main/assets/diagrams/architecture.png" alt="Architecture: your application calls ApprovalEngine, which requires an IStorageAdapter (Memory, Postgres, or custom) and optionally plugs into notification, audit, metrics, scheduler, authorization, and middleware ports." width="560">
122
+ </p>
152
123
 
153
124
  ---
154
125
 
@@ -932,6 +903,109 @@ Middleware errors are caught and logged — they never propagate to callers.
932
903
 
933
904
  ---
934
905
 
906
+ ## Built-in enterprise plug-ins
907
+
908
+ The interfaces above are ports. The library also ships production-grade implementations of them, each on its own import subpath so you only pull in what you use (fully tree-shakeable, zero extra dependencies — `node:crypto` only).
909
+
910
+ ### `plugins/audit` — tamper-evident & PII-safe audit
911
+
912
+ ```ts
913
+ import {
914
+ HashChainAuditAdapter,
915
+ RedactingAuditAdapter,
916
+ CompositeAuditAdapter,
917
+ } from 'hierarchical-approval/plugins/audit';
918
+
919
+ // Each entry is SHA-256-chained to the previous one (per tenant+instance).
920
+ const chain = new HashChainAuditAdapter();
921
+
922
+ // Redact PII before it reaches external sinks (originals are never mutated).
923
+ const redacted = new RedactingAuditAdapter({
924
+ inner: chain,
925
+ fieldPaths: ['newValue.applicant.ssn', 'newValue.card.*'],
926
+ freeTextFields: ['comment', 'reason'],
927
+ });
928
+
929
+ const engine = new ApprovalEngine({ adapter, auditAdapter: redacted });
930
+
931
+ // Later — prove the log was not altered, deleted, or reordered:
932
+ const result = chain.verify(tenantId, instanceId);
933
+ // { ok: true } | { ok: false, brokenAt: <seq> }
934
+ ```
935
+
936
+ `verify()` detects content tampering, deletion, reordering, **and tail truncation** (via an in-process high-water mark, or an explicit `expectedLength` anchor that survives restarts). Use `CompositeAuditAdapter` to fan out to several sinks (Kafka, S3, CloudTrail) with per-child fault isolation.
937
+
938
+ ### `plugins/metrics` — Prometheus & dashboards
939
+
940
+ ```ts
941
+ import {
942
+ PrometheusMetricsAdapter,
943
+ InMemoryMetricsAdapter,
944
+ } from 'hierarchical-approval/plugins/metrics';
945
+
946
+ const metrics = new PrometheusMetricsAdapter();
947
+ const engine = new ApprovalEngine({ adapter, metricsAdapter: metrics });
948
+
949
+ // Expose on /metrics:
950
+ res.type('text/plain').send(metrics.scrape()); // valid Prometheus exposition format
951
+ ```
952
+
953
+ `InMemoryMetricsAdapter.snapshot()` returns counters and timing stats (count/sum/min/max/avg/p50/p95) for dashboards and tests; `CompositeMetricsAdapter` feeds both at once.
954
+
955
+ ### `plugins/resilience` — RBAC, rate limiting, structured logging
956
+
957
+ ```ts
958
+ import {
959
+ RbacAuthorizationPolicy,
960
+ CompositeAuthorizationPolicy,
961
+ RateLimitMiddleware,
962
+ LoggingMiddleware,
963
+ } from 'hierarchical-approval/plugins/resilience';
964
+
965
+ const engine = new ApprovalEngine({
966
+ adapter,
967
+ authorizationPolicy: new RbacAuthorizationPolicy({
968
+ defaultMode: 'deny', // closed by default — unlisted operations are denied
969
+ rules: {
970
+ approve: { roles: ['manager', 'director'] }, // match: 'any' by default
971
+ override: { roles: ['admin'] },
972
+ },
973
+ roleProvider: (actorId, tenantId) => roleStore.rolesFor(actorId, tenantId),
974
+ }),
975
+ middleware: [
976
+ new RateLimitMiddleware({ capacity: 20, refillTokensPerSecond: 5 }), // token bucket per actor+op
977
+ new LoggingMiddleware({ logger }),
978
+ ],
979
+ });
980
+ ```
981
+
982
+ `CompositeAuthorizationPolicy` combines policies with AND (first denial wins) or OR (any allow) semantics.
983
+
984
+ ### `plugins/notify` — reliable, multi-channel delivery
985
+
986
+ ```ts
987
+ import {
988
+ OutboxNotificationAdapter,
989
+ TemplatedNotificationAdapter,
990
+ CompositeNotificationAdapter,
991
+ } from 'hierarchical-approval/plugins/notify';
992
+
993
+ // At-least-once delivery: transactional outbox + exponential backoff + dead-letter.
994
+ const outbox = new OutboxNotificationAdapter({
995
+ transport: async (event) => emailGateway.send(event),
996
+ maxAttempts: 5,
997
+ });
998
+
999
+ const engine = new ApprovalEngine({ adapter, notificationAdapter: outbox });
1000
+
1001
+ await outbox.drain(); // deliver due items (call from a worker/cron)
1002
+ await outbox.deadLettered(); // inspect permanently-failed events
1003
+ ```
1004
+
1005
+ `TemplatedNotificationAdapter` renders a human-readable message per event type; `CompositeNotificationAdapter` fans out to multiple channels with fault isolation.
1006
+
1007
+ ---
1008
+
935
1009
  ## Custom storage adapter
936
1010
 
937
1011
  Implement `IStorageAdapter` to use any database:
@@ -1,87 +1,12 @@
1
- import { a as ApprovalInstance, d as AuditEntry, g as ApprovalLevelInstance, I as IStorageAdapter, q as ResolverFn, j as ApprovalTemplateConfig, A as ApprovalTemplate, m as AuditContext, h as ApprovalMode, P as PaginationOpts, b as PaginatedResult, c as InstanceFilter, C as CursorPaginationOpts, e as CursorPaginatedResult } from './IStorageAdapter-B2xGuaKi.js';
1
+ import { I as IStorageAdapter, P as PaginationOpts, a as PaginatedResult, b as InstanceFilter, C as CursorPaginationOpts, c as CursorPaginatedResult } from './IStorageAdapter-DbeWhtCG.cjs';
2
+ import { m as ResolverFn, g as ApprovalTemplateConfig, A as ApprovalTemplate, j as AuditContext, a as ApprovalInstance, e as ApprovalMode, b as AuditEntry } from './instance-D8D7b07N.cjs';
3
+ import { I as INotificationAdapter, b as ApprovalEventName, a as ApprovalEventMap } from './INotificationAdapter-DVVmXU6a.cjs';
2
4
  import { z } from 'zod';
3
-
4
- interface ApprovalEvent {
5
- instanceId: string;
6
- documentId: string;
7
- documentType: string;
8
- timestamp: Date;
9
- }
10
- interface SubmittedEvent extends ApprovalEvent {
11
- submittedBy: string;
12
- currentApprovers: string[];
13
- }
14
- interface ApprovedEvent extends ApprovalEvent {
15
- approverId: string;
16
- level: number;
17
- comment?: string;
18
- isFinal: boolean;
19
- }
20
- interface RejectedEvent extends ApprovalEvent {
21
- approverId: string;
22
- level: number;
23
- reason: string;
24
- returnTo: 'originator' | 'previous' | null;
25
- }
26
- interface DelegatedEvent extends ApprovalEvent {
27
- fromApprover: string;
28
- toApprover: string;
29
- level: number;
30
- reason: string;
31
- }
32
- interface ReassignedEvent extends ApprovalEvent {
33
- reassignedBy: string;
34
- fromApprover: string;
35
- toApprover: string;
36
- level: number;
37
- reason: string;
38
- }
39
- interface EscalatedEvent extends ApprovalEvent {
40
- level: number;
41
- escalatedTo: string;
42
- }
43
- interface CancelledEvent extends ApprovalEvent {
44
- cancelledBy: string;
45
- reason: string;
46
- }
47
- interface LevelAdvancedEvent extends ApprovalEvent {
48
- fromLevel: number;
49
- toLevel: number;
50
- newApprovers: string[];
51
- }
52
- interface ResubmittedEvent extends ApprovalEvent {
53
- resubmittedBy: string;
54
- originalInstanceId: string;
55
- }
56
- interface OverriddenEvent extends ApprovalEvent {
57
- overriddenBy: string;
58
- justification: string;
59
- }
60
- interface ExpiredEvent extends ApprovalEvent {
61
- deadlineAction: 'cancel' | 'reject';
62
- }
63
- interface SlaBreachedEvent extends ApprovalEvent {
64
- slaDeadlineAt: Date;
65
- }
66
- interface ApprovalEventMap {
67
- 'approval:submitted': SubmittedEvent;
68
- 'approval:approved': ApprovedEvent;
69
- 'approval:rejected': RejectedEvent;
70
- 'approval:delegated': DelegatedEvent;
71
- 'approval:reassigned': ReassignedEvent;
72
- 'approval:escalated': EscalatedEvent;
73
- 'approval:cancelled': CancelledEvent;
74
- 'approval:completed': ApprovalInstance;
75
- 'approval:level_advanced': LevelAdvancedEvent;
76
- 'approval:resubmitted': ResubmittedEvent;
77
- 'approval:overridden': OverriddenEvent;
78
- 'approval:expired': ExpiredEvent;
79
- 'approval:sla_breached': SlaBreachedEvent;
80
- }
81
- type ApprovalEventName = keyof ApprovalEventMap;
82
- interface HistoryEntry extends AuditEntry {
83
- instanceId: string;
84
- }
5
+ import { L as Logger } from './Logger-CIzvalFc.cjs';
6
+ import { C as Clock } from './Clock-3FnOczFJ.cjs';
7
+ import { I as IAuthorizationPolicy, g as IOperationMiddleware, a as ApprovalError } from './IOperationMiddleware-CkRh93Wf.cjs';
8
+ import { I as IAuditAdapter } from './IAuditAdapter-B_DhuPsU.cjs';
9
+ import { I as IMetricsAdapter } from './IMetricsAdapter-D9PUz4tM.cjs';
85
10
 
86
11
  declare const SubmitOptionsSchema: z.ZodObject<{
87
12
  templateName: z.ZodString;
@@ -151,19 +76,6 @@ type ResubmitOptions = z.infer<typeof ResubmitOptionsSchema>;
151
76
  type AddCommentOptions = z.infer<typeof AddCommentOptionsSchema>;
152
77
  type OverrideOptions = z.infer<typeof OverrideOptionsSchema>;
153
78
 
154
- interface Logger {
155
- info(msg: string, context?: Record<string, unknown>): void;
156
- warn(msg: string, context?: Record<string, unknown>): void;
157
- error(msg: string, err?: unknown, context?: Record<string, unknown>): void;
158
- debug(msg: string, context?: Record<string, unknown>): void;
159
- }
160
- declare const noopLogger: Logger;
161
-
162
- interface Clock {
163
- now(): Date;
164
- }
165
- declare const systemClock: Clock;
166
-
167
79
  /**
168
80
  * Computes deadline dates from a number of days. The default engine behaviour
169
81
  * treats day offsets (escalationAfterDays, slaDeadlineDays) as plain calendar
@@ -216,67 +128,6 @@ type ApproverResolverFn = (config: Record<string, unknown>, ctx: {
216
128
 
217
129
  type ConditionOperatorFn = (actual: unknown, expected: unknown) => boolean;
218
130
 
219
- declare class ApprovalError extends Error {
220
- readonly code: string;
221
- constructor(message: string, code: string);
222
- toJSON(): {
223
- code: string;
224
- message: string;
225
- name: string;
226
- };
227
- toHttpStatus(): number;
228
- }
229
- declare class ApprovalNotFoundError extends ApprovalError {
230
- constructor(resource: string, id: string);
231
- }
232
- declare class ApprovalConflictError extends ApprovalError {
233
- constructor(instanceId: string);
234
- }
235
- declare class ApprovalForbiddenError extends ApprovalError {
236
- constructor(message: string);
237
- }
238
- declare class ApprovalValidationError extends ApprovalError {
239
- readonly cause?: unknown | undefined;
240
- constructor(message: string, cause?: unknown | undefined);
241
- }
242
- declare class ApprovalTemplateNotFoundError extends ApprovalError {
243
- constructor(name: string);
244
- }
245
-
246
- interface NotificationEvent {
247
- type: ApprovalEventName;
248
- instanceId: string;
249
- documentId: string;
250
- documentType: string;
251
- timestamp: Date;
252
- /** Current-level approver IDs; empty for non-level events (cancelled, expired, etc.). */
253
- recipients: string[];
254
- templateName: string;
255
- tenantId: string;
256
- payload: ApprovalEventMap[ApprovalEventName];
257
- }
258
- interface INotificationAdapter {
259
- /** Called after every emitted approval event. Must not throw — errors are logged and swallowed. */
260
- notify(event: NotificationEvent): Promise<void>;
261
- }
262
-
263
- interface IAuditAdapter {
264
- /**
265
- * Called after every state-mutating operation, in addition to the primary storage adapter.
266
- * Intended for write-once sinks: Kafka, S3, CloudTrail, WORM stores.
267
- * Must not throw — errors are logged and swallowed.
268
- */
269
- append(tenantId: string, instanceId: string, entry: AuditEntry, instance: Readonly<ApprovalInstance>): Promise<void>;
270
- }
271
-
272
- 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';
273
- interface IMetricsAdapter {
274
- /** Increment a counter. Synchronous — never awaited. */
275
- increment(metric: MetricName, labels?: Record<string, string>): void;
276
- /** Record a timing measurement in milliseconds. Synchronous — never awaited. */
277
- timing(metric: MetricName, durationMs: number, labels?: Record<string, string>): void;
278
- }
279
-
280
131
  interface ISchedulerAdapter {
281
132
  /**
282
133
  * Schedule a one-shot callback to run at the given date.
@@ -289,38 +140,6 @@ interface ISchedulerAdapter {
289
140
  shutdown(): Promise<void>;
290
141
  }
291
142
 
292
- interface AuthorizationContext {
293
- operation: 'submit' | 'approve' | 'reject' | 'delegate' | 'reassign' | 'cancel' | 'escalate' | 'override' | 'resubmit' | 'addComment';
294
- actorId: string;
295
- instance: Readonly<ApprovalInstance>;
296
- level?: Readonly<ApprovalLevelInstance>;
297
- opts: Record<string, unknown>;
298
- }
299
- interface IAuthorizationPolicy {
300
- /**
301
- * Return undefined to allow the operation.
302
- * Return a non-empty string to deny — the engine throws ApprovalForbiddenError(message).
303
- * Throwing ApprovalForbiddenError directly is also permitted.
304
- */
305
- authorize(ctx: AuthorizationContext): Promise<string | undefined> | string | undefined;
306
- }
307
-
308
- interface OperationContext<T = unknown> {
309
- operation: string;
310
- instanceId?: string;
311
- actorId?: string;
312
- tenantId: string;
313
- input: T;
314
- }
315
- interface IOperationMiddleware {
316
- /** Runs after authorization and input validation, before state mutations. */
317
- before?(ctx: OperationContext): Promise<void> | void;
318
- /** Runs after successful completion of the operation. */
319
- after?(ctx: OperationContext, result: ApprovalInstance | void): Promise<void> | void;
320
- /** Runs when an ApprovalError is thrown. Does not suppress the error. */
321
- onError?(ctx: OperationContext, error: ApprovalError): Promise<void> | void;
322
- }
323
-
324
143
  interface ValidationResult {
325
144
  valid: boolean;
326
145
  errors: Array<{
@@ -501,4 +320,4 @@ declare class ApprovalEngine {
501
320
  private runExternalAudit;
502
321
  }
503
322
 
504
- export { type ResubmitOptions as $, type AddCommentOptions as A, type BulkResult as B, type Clock as C, type DelegateOptions as D, type EscalateOptions as E, type IMetricsAdapter as F, type INotificationAdapter as G, type HealthResult as H, type IAuditAdapter as I, type IOperationMiddleware as J, type ISchedulerAdapter as K, type Logger as L, type IdGeneratorFn as M, type IdempotencyKeyFn as N, type LevelAdvancedEvent as O, type MetricName as P, type NotificationEvent as Q, type OperationContext as R, type OrgProvider as S, type OverriddenEvent as T, type OverrideOptions as U, type PreviewChainLevel as V, type PreviewResult as W, type ReassignOptions as X, type ReassignedEvent as Y, type RejectOptions as Z, type RejectedEvent as _, ApprovalConflictError as a, type ResubmittedEvent as a0, type RetryPolicy as a1, type SlaBreachedEvent as a2, type SubmitOptions as a3, type SubmittedEvent as a4, type ValidationResult as a5, type WeekendCalendarOptions as a6, defaultIdGenerator as a7, noopLogger as a8, systemClock as a9, weekendCalendar as aa, ApprovalEngine as b, type ApprovalEngineOptions as c, ApprovalError as d, type ApprovalEvent as e, type ApprovalEventMap as f, type ApprovalEventName as g, ApprovalForbiddenError as h, ApprovalNotFoundError as i, type ApprovalStatistics as j, ApprovalTemplateNotFoundError as k, ApprovalValidationError as l, type ApproveOptions as m, type ApprovedEvent as n, type ApproverResolverFn as o, type AuthorizationContext as p, type BusinessCalendar as q, type CanApproveResult as r, type CancelOptions as s, type CancelledEvent as t, type ConditionOperatorFn as u, type DelegatedEvent as v, type EscalatedEvent as w, type ExpiredEvent as x, type HistoryEntry as y, type IAuthorizationPolicy as z };
323
+ 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 ISchedulerAdapter 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 IdGeneratorFn 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, weekendCalendar as w };