@nodii/approval 0.0.1 → 0.1.4

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 (60) hide show
  1. package/README.md +40 -2
  2. package/dist/consumer.d.ts +19 -0
  3. package/dist/consumer.d.ts.map +1 -0
  4. package/dist/consumer.js +274 -0
  5. package/dist/consumer.js.map +1 -0
  6. package/dist/deferred-actions.d.ts +34 -0
  7. package/dist/deferred-actions.d.ts.map +1 -0
  8. package/dist/deferred-actions.js +126 -0
  9. package/dist/deferred-actions.js.map +1 -0
  10. package/dist/errors.d.ts +51 -0
  11. package/dist/errors.d.ts.map +1 -0
  12. package/dist/errors.js +82 -0
  13. package/dist/errors.js.map +1 -0
  14. package/dist/handlers.d.ts +7 -0
  15. package/dist/handlers.d.ts.map +1 -0
  16. package/dist/handlers.js +20 -0
  17. package/dist/handlers.js.map +1 -0
  18. package/dist/index.d.ts +13 -2
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +15 -1
  21. package/dist/index.js.map +1 -0
  22. package/dist/init.d.ts +21 -0
  23. package/dist/init.d.ts.map +1 -0
  24. package/dist/init.js +139 -0
  25. package/dist/init.js.map +1 -0
  26. package/dist/kinds.d.ts +20 -0
  27. package/dist/kinds.d.ts.map +1 -0
  28. package/dist/kinds.js +72 -0
  29. package/dist/kinds.js.map +1 -0
  30. package/dist/migrations.d.ts +27 -0
  31. package/dist/migrations.d.ts.map +1 -0
  32. package/dist/migrations.js +67 -0
  33. package/dist/migrations.js.map +1 -0
  34. package/dist/policies.d.ts +21 -0
  35. package/dist/policies.d.ts.map +1 -0
  36. package/dist/policies.js +99 -0
  37. package/dist/policies.js.map +1 -0
  38. package/dist/request.d.ts +3 -0
  39. package/dist/request.d.ts.map +1 -0
  40. package/dist/request.js +356 -0
  41. package/dist/request.js.map +1 -0
  42. package/dist/task-tracking-client.d.ts +39 -0
  43. package/dist/task-tracking-client.d.ts.map +1 -0
  44. package/dist/task-tracking-client.js +100 -0
  45. package/dist/task-tracking-client.js.map +1 -0
  46. package/dist/telemetry-shim.d.ts +35 -0
  47. package/dist/telemetry-shim.d.ts.map +1 -0
  48. package/dist/telemetry-shim.js +88 -0
  49. package/dist/telemetry-shim.js.map +1 -0
  50. package/dist/types.d.ts +279 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +18 -0
  53. package/dist/types.js.map +1 -0
  54. package/dist/validation.d.ts +5 -0
  55. package/dist/validation.d.ts.map +1 -0
  56. package/dist/validation.js +40 -0
  57. package/dist/validation.js.map +1 -0
  58. package/package.json +44 -3
  59. package/src/migrations/001-approval-policies.sql +27 -0
  60. package/src/migrations/002-deferred-actions.sql +41 -0
package/dist/errors.js ADDED
@@ -0,0 +1,82 @@
1
+ // Typed error hierarchy for @nodii/approval per spec § 5.14.
2
+ //
3
+ // Every error has a stable code (the class name) and a message. The
4
+ // downstream gRPC interceptor / Hono error middleware map these to
5
+ // stable wire codes.
6
+ export class ApprovalError extends Error {
7
+ name = "ApprovalError";
8
+ }
9
+ export class ApprovalNotInitialized extends ApprovalError {
10
+ name = "ApprovalNotInitialized";
11
+ constructor() {
12
+ super("@nodii/approval not initialised. Call `initApproval({...})` before any " +
13
+ "library API. See spec § 5.1.");
14
+ }
15
+ }
16
+ export class UnknownApprovalKind extends ApprovalError {
17
+ name = "UnknownApprovalKind";
18
+ constructor(kind, registered) {
19
+ super(`Approval kind '${kind}' is not registered. Service has registered: [${registered.join(", ")}]. See 10-approval-doctrine § 3.`);
20
+ }
21
+ }
22
+ export class HandlerForUnregisteredKind extends ApprovalError {
23
+ name = "HandlerForUnregisteredKind";
24
+ constructor(kind, registered) {
25
+ super(`bindApprovalHandlers: kind '${kind}' is not registered via defineApprovalKinds. Registered kinds: [${registered.join(", ")}]`);
26
+ }
27
+ }
28
+ export class HandlerNotRegistered extends ApprovalError {
29
+ name = "HandlerNotRegistered";
30
+ constructor(kind) {
31
+ super(`No handler registered for kind '${kind}'. Call bindApprovalHandlers({...}) at boot.`);
32
+ }
33
+ }
34
+ export class IdempotencyKeyRequired extends ApprovalError {
35
+ name = "IdempotencyKeyRequired";
36
+ constructor() {
37
+ super("requestApproval requires idempotencyKey. The library refuses to compute or persist anything without one. See 10-approval-doctrine § 5 + idempotency.md § 5.9.");
38
+ }
39
+ }
40
+ export class ReservedServiceNameViolation extends ApprovalError {
41
+ name = "ReservedServiceNameViolation";
42
+ constructor(serviceName, reserved) {
43
+ super(`defineApprovalKinds: serviceName '${serviceName}' is in the reserved namespace set [${reserved.join(", ")}]. Pass { bypassReservedNamespaceCheck: true } only if you are nodii-tenant-service.`);
44
+ }
45
+ }
46
+ export class InvalidApprovalSegment extends ApprovalError {
47
+ which;
48
+ value;
49
+ name = "InvalidApprovalSegment";
50
+ constructor(which, value, msg) {
51
+ super(msg);
52
+ this.which = which;
53
+ this.value = value;
54
+ }
55
+ }
56
+ export class ApproverRoleNotInCatalog extends ApprovalError {
57
+ name = "ApproverRoleNotInCatalog";
58
+ constructor(approverRole, kindName, knownRoles) {
59
+ super(`Kind '${kindName}' references approverRole '${approverRole}', but it is not declared in the module's role-catalog (known: [${knownRoles.join(", ")}]). See role-catalog.md § 5.1.`);
60
+ }
61
+ }
62
+ export class TaskCreationFailed extends ApprovalError {
63
+ cause;
64
+ name = "TaskCreationFailed";
65
+ constructor(cause, msg) {
66
+ super(msg);
67
+ this.cause = cause;
68
+ }
69
+ }
70
+ export class DeferredActionNotFound extends ApprovalError {
71
+ name = "DeferredActionNotFound";
72
+ constructor(taskId) {
73
+ super(`No deferred-action row found for task_id '${taskId}'.`);
74
+ }
75
+ }
76
+ export class TenantPolicyKindUnknown extends ApprovalError {
77
+ name = "TenantPolicyKindUnknown";
78
+ constructor(kind) {
79
+ super(`Tenant policy row exists for kind '${kind}', but the kind is not registered in the running service. Falling through to kind defaults; the policy row is stale and should be cleaned up.`);
80
+ }
81
+ }
82
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,EAAE;AACF,oEAAoE;AACpE,mEAAmE;AACnE,qBAAqB;AAErB,MAAM,OAAO,aAAc,SAAQ,KAAK;IACpB,IAAI,GAAW,eAAe,CAAC;CAClD;AAED,MAAM,OAAO,sBAAuB,SAAQ,aAAa;IACrC,IAAI,GAAG,wBAAwB,CAAC;IAClD;QACE,KAAK,CACH,yEAAyE;YACvE,8BAA8B,CACjC,CAAC;IACJ,CAAC;CACF;AAED,MAAM,OAAO,mBAAoB,SAAQ,aAAa;IAClC,IAAI,GAAG,qBAAqB,CAAC;IAC/C,YAAY,IAAY,EAAE,UAA6B;QACrD,KAAK,CACH,kBAAkB,IAAI,iDAAiD,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,kCAAkC,CAC/H,CAAC;IACJ,CAAC;CACF;AAED,MAAM,OAAO,0BAA2B,SAAQ,aAAa;IACzC,IAAI,GAAG,4BAA4B,CAAC;IACtD,YAAY,IAAY,EAAE,UAA6B;QACrD,KAAK,CACH,+BAA+B,IAAI,mEAAmE,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAC/H,CAAC;IACJ,CAAC;CACF;AAED,MAAM,OAAO,oBAAqB,SAAQ,aAAa;IACnC,IAAI,GAAG,sBAAsB,CAAC;IAChD,YAAY,IAAY;QACtB,KAAK,CACH,mCAAmC,IAAI,8CAA8C,CACtF,CAAC;IACJ,CAAC;CACF;AAED,MAAM,OAAO,sBAAuB,SAAQ,aAAa;IACrC,IAAI,GAAG,wBAAwB,CAAC;IAClD;QACE,KAAK,CACH,+JAA+J,CAChK,CAAC;IACJ,CAAC;CACF;AAED,MAAM,OAAO,4BAA6B,SAAQ,aAAa;IAC3C,IAAI,GAAG,8BAA8B,CAAC;IACxD,YAAY,WAAmB,EAAE,QAA2B;QAC1D,KAAK,CACH,qCAAqC,WAAW,uCAAuC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,sFAAsF,CACjM,CAAC;IACJ,CAAC;CACF;AAED,MAAM,OAAO,sBAAuB,SAAQ,aAAa;IAGrC;IACA;IAHA,IAAI,GAAG,wBAAwB,CAAC;IAClD,YACkB,KAA0B,EAC1B,KAAa,EAC7B,GAAW;QAEX,KAAK,CAAC,GAAG,CAAC,CAAC;QAJK,UAAK,GAAL,KAAK,CAAqB;QAC1B,UAAK,GAAL,KAAK,CAAQ;IAI/B,CAAC;CACF;AAED,MAAM,OAAO,wBAAyB,SAAQ,aAAa;IACvC,IAAI,GAAG,0BAA0B,CAAC;IACpD,YACE,YAAoB,EACpB,QAAgB,EAChB,UAA6B;QAE7B,KAAK,CACH,SAAS,QAAQ,8BAA8B,YAAY,mEAAmE,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,gCAAgC,CACpL,CAAC;IACJ,CAAC;CACF;AAED,MAAM,OAAO,kBAAmB,SAAQ,aAAa;IAGjC;IAFA,IAAI,GAAG,oBAAoB,CAAC;IAC9C,YACkB,KAAc,EAC9B,GAAW;QAEX,KAAK,CAAC,GAAG,CAAC,CAAC;QAHK,UAAK,GAAL,KAAK,CAAS;IAIhC,CAAC;CACF;AAED,MAAM,OAAO,sBAAuB,SAAQ,aAAa;IACrC,IAAI,GAAG,wBAAwB,CAAC;IAClD,YAAY,MAAc;QACxB,KAAK,CAAC,6CAA6C,MAAM,IAAI,CAAC,CAAC;IACjE,CAAC;CACF;AAED,MAAM,OAAO,uBAAwB,SAAQ,aAAa;IACtC,IAAI,GAAG,yBAAyB,CAAC;IACnD,YAAY,IAAY;QACtB,KAAK,CACH,sCAAsC,IAAI,+IAA+I,CAC1L,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,7 @@
1
+ import type { HandlerSet } from "./types";
2
+ export interface BindApprovalHandlersOpts {
3
+ serviceName: string;
4
+ handlers: Record<string, HandlerSet>;
5
+ }
6
+ export declare function bindApprovalHandlers(opts: BindApprovalHandlersOpts): void;
7
+ //# sourceMappingURL=handlers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACtC;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,wBAAwB,GAAG,IAAI,CAkBzE"}
@@ -0,0 +1,20 @@
1
+ // bindApprovalHandlers — register `onApproved`/`onRejected`/`onExpired`
2
+ // triples per kind. Per spec § 5.1 + § 5.14 (`HandlerForUnregisteredKind`).
3
+ import { HandlerForUnregisteredKind } from "./errors";
4
+ import { requireHandlers, requireKindRegistry } from "./init";
5
+ export function bindApprovalHandlers(opts) {
6
+ const reg = requireKindRegistry();
7
+ const handlers = requireHandlers();
8
+ for (const [kind, set] of Object.entries(opts.handlers)) {
9
+ if (!reg.has(kind)) {
10
+ throw new HandlerForUnregisteredKind(kind, reg.keys());
11
+ }
12
+ if (typeof set.onApproved !== "function" ||
13
+ typeof set.onRejected !== "function" ||
14
+ typeof set.onExpired !== "function") {
15
+ throw new TypeError(`bindApprovalHandlers: kind '${kind}' must declare onApproved / onRejected / onExpired functions`);
16
+ }
17
+ handlers.set(kind, set);
18
+ }
19
+ }
20
+ //# sourceMappingURL=handlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handlers.js","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,4EAA4E;AAE5E,OAAO,EAAE,0BAA0B,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAQ9D,MAAM,UAAU,oBAAoB,CAAC,IAA8B;IACjE,MAAM,GAAG,GAAG,mBAAmB,EAAE,CAAC;IAClC,MAAM,QAAQ,GAAG,eAAe,EAAE,CAAC;IACnC,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACnB,MAAM,IAAI,0BAA0B,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QACzD,CAAC;QACD,IACE,OAAO,GAAG,CAAC,UAAU,KAAK,UAAU;YACpC,OAAO,GAAG,CAAC,UAAU,KAAK,UAAU;YACpC,OAAO,GAAG,CAAC,SAAS,KAAK,UAAU,EACnC,CAAC;YACD,MAAM,IAAI,SAAS,CACjB,+BAA+B,IAAI,8DAA8D,CAClG,CAAC;QACJ,CAAC;QACD,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC1B,CAAC;AACH,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,13 @@
1
- export declare const LIB_NAME: "approval";
2
- export declare const VERSION: "0.0.1";
1
+ export declare const LIB_NAME = "approval";
2
+ export declare const VERSION = "0.1.0";
3
+ export { defineApprovalKinds, KindRegistry, type DefineApprovalKindsOpts, } from "./kinds";
4
+ export { initApproval, requireApproval, getApprovalOrNull, requireKindRegistry, requireHandlers, registerKinds, _resetForTests, } from "./init";
5
+ export { bindApprovalHandlers, type BindApprovalHandlersOpts, } from "./handlers";
6
+ export { requestApproval } from "./request";
7
+ export { startApprovalConsumer, stopApprovalConsumer, defaultQueueName, _resetConsumerForTests, type StartApprovalConsumerOpts, type ApprovalConsumerHandle, } from "./consumer";
8
+ export { getApprovalMigrationSQL, splitStatements, type GetMigrationSQLOpts, } from "./migrations";
9
+ export { TaskTrackingGrpcClient, TASK_SERVICE_DESCRIPTOR, type TaskTrackingGrpcClientOpts, } from "./task-tracking-client";
10
+ export { ApprovalError, ApprovalNotInitialized, ApproverRoleNotInCatalog, DeferredActionNotFound, HandlerForUnregisteredKind, HandlerNotRegistered, IdempotencyKeyRequired, InvalidApprovalSegment, ReservedServiceNameViolation, TaskCreationFailed, TenantPolicyKindUnknown, UnknownApprovalKind, } from "./errors";
11
+ export type { ApprovalKindDef, ApprovalKindDefInput, ApprovalPolicyRow, ApproverKind, CreateTaskRequest, CreateTaskResponse, DbRlsLike, DeferredActionRow, DefinedKindMap, HandlerContext, HandlerSet, InitApprovalConfig, MembershipRolesLookup, RequestApprovalOpts, RequestApprovalResult, RequestApprovalResultStatus, ResolvedApprovalConfig, ResolvedPolicy, TaskCompletedEvent, TaskServiceClient, } from "./types";
12
+ export { DEFAULT_CONSUMER_LEASE_SECONDS, DEFAULT_CONSUMER_MAX_RETRIES, DEFAULT_EXPIRY_SECONDS, DEFAULT_IDEMPOTENCY_TTL_SECONDS, RESERVED_SERVICE_NAMES, } from "./types";
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,QAAQ,aAAa,CAAC;AACnC,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B,OAAO,EACL,mBAAmB,EACnB,YAAY,EACZ,KAAK,uBAAuB,GAC7B,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,aAAa,EACb,cAAc,GACf,MAAM,QAAQ,CAAC;AAEhB,OAAO,EACL,oBAAoB,EACpB,KAAK,wBAAwB,GAC9B,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAE5C,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,EAChB,sBAAsB,EACtB,KAAK,yBAAyB,EAC9B,KAAK,sBAAsB,GAC5B,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,uBAAuB,EACvB,eAAe,EACf,KAAK,mBAAmB,GACzB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,KAAK,0BAA0B,GAChC,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,aAAa,EACb,sBAAsB,EACtB,wBAAwB,EACxB,sBAAsB,EACtB,0BAA0B,EAC1B,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,EACtB,4BAA4B,EAC5B,kBAAkB,EAClB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,UAAU,CAAC;AAElB,YAAY,EACV,eAAe,EACf,oBAAoB,EACpB,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,SAAS,EACT,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,UAAU,EACV,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,qBAAqB,EACrB,2BAA2B,EAC3B,sBAAsB,EACtB,cAAc,EACd,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,8BAA8B,EAC9B,4BAA4B,EAC5B,sBAAsB,EACtB,+BAA+B,EAC/B,sBAAsB,GACvB,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,16 @@
1
+ // @nodii/approval — v0.1.0 public barrel.
2
+ //
3
+ // Spec: planning hub feature_doc serviceId=nodii-libs docKey=approval.
4
+ // Polyglot ship: TS + Python + Go in parity. This is the TypeScript impl.
1
5
  export const LIB_NAME = "approval";
2
- export const VERSION = "0.0.1";
6
+ export const VERSION = "0.1.0";
7
+ export { defineApprovalKinds, KindRegistry, } from "./kinds";
8
+ export { initApproval, requireApproval, getApprovalOrNull, requireKindRegistry, requireHandlers, registerKinds, _resetForTests, } from "./init";
9
+ export { bindApprovalHandlers, } from "./handlers";
10
+ export { requestApproval } from "./request";
11
+ export { startApprovalConsumer, stopApprovalConsumer, defaultQueueName, _resetConsumerForTests, } from "./consumer";
12
+ export { getApprovalMigrationSQL, splitStatements, } from "./migrations";
13
+ export { TaskTrackingGrpcClient, TASK_SERVICE_DESCRIPTOR, } from "./task-tracking-client";
14
+ export { ApprovalError, ApprovalNotInitialized, ApproverRoleNotInCatalog, DeferredActionNotFound, HandlerForUnregisteredKind, HandlerNotRegistered, IdempotencyKeyRequired, InvalidApprovalSegment, ReservedServiceNameViolation, TaskCreationFailed, TenantPolicyKindUnknown, UnknownApprovalKind, } from "./errors";
15
+ export { DEFAULT_CONSUMER_LEASE_SECONDS, DEFAULT_CONSUMER_MAX_RETRIES, DEFAULT_EXPIRY_SECONDS, DEFAULT_IDEMPOTENCY_TTL_SECONDS, RESERVED_SERVICE_NAMES, } from "./types";
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,EAAE;AACF,uEAAuE;AACvE,0EAA0E;AAE1E,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAC;AACnC,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAE/B,OAAO,EACL,mBAAmB,EACnB,YAAY,GAEb,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,aAAa,EACb,cAAc,GACf,MAAM,QAAQ,CAAC;AAEhB,OAAO,EACL,oBAAoB,GAErB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAE5C,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,EAChB,sBAAsB,GAGvB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,uBAAuB,EACvB,eAAe,GAEhB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,sBAAsB,EACtB,uBAAuB,GAExB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,aAAa,EACb,sBAAsB,EACtB,wBAAwB,EACxB,sBAAsB,EACtB,0BAA0B,EAC1B,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,EACtB,4BAA4B,EAC5B,kBAAkB,EAClB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,UAAU,CAAC;AAyBlB,OAAO,EACL,8BAA8B,EAC9B,4BAA4B,EAC5B,sBAAsB,EACtB,+BAA+B,EAC/B,sBAAsB,GACvB,MAAM,SAAS,CAAC"}
package/dist/init.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { KindRegistry } from "./kinds";
2
+ import type { ApprovalKindDef, HandlerSet, InitApprovalConfig, ResolvedApprovalConfig } from "./types";
3
+ /**
4
+ * Initialise the approval library. Idempotent within a process — subsequent
5
+ * calls warn + no-op. Pass `kinds` so the library can cross-check approver
6
+ * roles against the role-catalog at boot (spec § 5.3).
7
+ */
8
+ export declare function initApproval(cfg: InitApprovalConfig): void;
9
+ export declare function requireApproval(): ResolvedApprovalConfig;
10
+ export declare function getApprovalOrNull(): ResolvedApprovalConfig | null;
11
+ export declare function requireKindRegistry(): KindRegistry;
12
+ export declare function requireHandlers(): Map<string, HandlerSet>;
13
+ /**
14
+ * Register a kind map produced by `defineApprovalKinds`. Cross-checks every
15
+ * kind's `approverRole` against the registered `knownRoles` set (spec § 5.3,
16
+ * "Approver-role cross-check at boot").
17
+ */
18
+ export declare function registerKinds(defs: Record<string, ApprovalKindDef>): void;
19
+ /** Test-only reset. Exposed via the public barrel for synthetic consumers. */
20
+ export declare function _resetForTests(): void;
21
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,KAAK,EACV,eAAe,EACf,UAAU,EACV,kBAAkB,EAClB,sBAAsB,EACvB,MAAM,SAAS,CAAC;AA2BjB;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,kBAAkB,GAAG,IAAI,CAsE1D;AAED,wBAAgB,eAAe,IAAI,sBAAsB,CAGxD;AAED,wBAAgB,iBAAiB,IAAI,sBAAsB,GAAG,IAAI,CAEjE;AAED,wBAAgB,mBAAmB,IAAI,YAAY,CAGlD;AAED,wBAAgB,eAAe,IAAI,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAGzD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,IAAI,CAezE;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,IAAI,IAAI,CAIrC"}
package/dist/init.js ADDED
@@ -0,0 +1,139 @@
1
+ // Process-global init for @nodii/approval per spec § 5.1.
2
+ //
3
+ // `initApproval({...})` resolves the config + registers the kind registry
4
+ // + handler registry. Calling twice in the same process is an error.
5
+ //
6
+ // Production-default wiring:
7
+ // - pgPool (or db-rls) — REAL postgres pool / @nodii/db-rls instance
8
+ // - redis — REAL ioredis client
9
+ // - taskServiceClient — REAL grpc-js client constructed from taskServiceUrl,
10
+ // OR caller-supplied (test wiring)
11
+ //
12
+ // No Noop default. No InMemory default. Per R1.
13
+ import { ApprovalNotInitialized, ApproverRoleNotInCatalog } from "./errors";
14
+ import { KindRegistry } from "./kinds";
15
+ import { validateServiceName } from "./validation";
16
+ import { DEFAULT_CONSUMER_LEASE_SECONDS, DEFAULT_CONSUMER_MAX_RETRIES, DEFAULT_EXPIRY_SECONDS, } from "./types";
17
+ import { TaskTrackingGrpcClient } from "./task-tracking-client";
18
+ let RESOLVED = null;
19
+ let REGISTRY = null;
20
+ let HANDLERS = null;
21
+ const CONSOLE_LOGGER = {
22
+ info(msg, fields) {
23
+ // biome-ignore lint/suspicious/noConsole: default logger
24
+ console.info(`[@nodii/approval] ${msg}`, fields ?? {});
25
+ },
26
+ warn(msg, fields) {
27
+ // biome-ignore lint/suspicious/noConsole: default logger
28
+ console.warn(`[@nodii/approval] ${msg}`, fields ?? {});
29
+ },
30
+ error(msg, fields) {
31
+ // biome-ignore lint/suspicious/noConsole: default logger
32
+ console.error(`[@nodii/approval] ${msg}`, fields ?? {});
33
+ },
34
+ };
35
+ /**
36
+ * Initialise the approval library. Idempotent within a process — subsequent
37
+ * calls warn + no-op. Pass `kinds` so the library can cross-check approver
38
+ * roles against the role-catalog at boot (spec § 5.3).
39
+ */
40
+ export function initApproval(cfg) {
41
+ if (RESOLVED) {
42
+ CONSOLE_LOGGER.warn("initApproval() called more than once; ignoring subsequent call.");
43
+ return;
44
+ }
45
+ if (!cfg.serviceName || cfg.serviceName.trim() === "") {
46
+ throw new TypeError("initApproval: serviceName is required");
47
+ }
48
+ // serviceName is interpolated unquoted into Postgres identifiers
49
+ // throughout the lib (`<service>_deferred_actions` etc.) — re-validate
50
+ // here (defineApprovalKinds + getApprovalMigrationSQL also validate;
51
+ // initApproval was the gap).
52
+ validateServiceName(cfg.serviceName);
53
+ if (!cfg.redis) {
54
+ throw new TypeError("initApproval: redis is required");
55
+ }
56
+ if (!cfg.pgPool && !cfg.dbRls) {
57
+ throw new TypeError("initApproval: either pgPool or dbRls must be provided");
58
+ }
59
+ if (!cfg.taskServiceClient &&
60
+ (!cfg.taskServiceUrl || cfg.taskServiceUrl.trim() === "")) {
61
+ throw new TypeError("initApproval: either taskServiceUrl (real grpc) or taskServiceClient (test override) must be provided");
62
+ }
63
+ if (!cfg.membershipRolesLookup) {
64
+ throw new TypeError("initApproval: membershipRolesLookup is required (used for self-action exception evaluation)");
65
+ }
66
+ const pgPool = cfg.dbRls ? cfg.dbRls.pool : cfg.pgPool;
67
+ if (!pgPool)
68
+ throw new TypeError("initApproval: pgPool resolution failed");
69
+ const taskServiceClient = cfg.taskServiceClient
70
+ ? cfg.taskServiceClient
71
+ : new TaskTrackingGrpcClient({
72
+ url: cfg.taskServiceUrl,
73
+ sourceModule: cfg.serviceName,
74
+ // Production wiring — adopters layer grpc-auth S2S envelope +
75
+ // TLS credentials + deadline via taskClientOptions per § 5.7.
76
+ interceptors: cfg.taskClientOptions?.interceptors
77
+ ? Array.from(cfg.taskClientOptions.interceptors)
78
+ : undefined,
79
+ credentials: cfg.taskClientOptions?.credentials,
80
+ deadlineMs: cfg.taskClientOptions?.deadlineMs,
81
+ });
82
+ RESOLVED = {
83
+ serviceName: cfg.serviceName,
84
+ pgPool,
85
+ dbRls: cfg.dbRls,
86
+ redis: cfg.redis,
87
+ taskServiceClient,
88
+ membershipRolesLookup: cfg.membershipRolesLookup,
89
+ defaultExpirySeconds: cfg.defaultExpirySeconds ?? DEFAULT_EXPIRY_SECONDS,
90
+ consumerLeaseSeconds: cfg.consumerLeaseSeconds ?? DEFAULT_CONSUMER_LEASE_SECONDS,
91
+ consumerMaxRetries: cfg.consumerMaxRetries ?? DEFAULT_CONSUMER_MAX_RETRIES,
92
+ knownRoles: new Set(cfg.knownRoles ?? []),
93
+ logger: cfg.logger ?? CONSOLE_LOGGER,
94
+ };
95
+ REGISTRY = new KindRegistry(cfg.serviceName);
96
+ HANDLERS = new Map();
97
+ }
98
+ export function requireApproval() {
99
+ if (!RESOLVED)
100
+ throw new ApprovalNotInitialized();
101
+ return RESOLVED;
102
+ }
103
+ export function getApprovalOrNull() {
104
+ return RESOLVED;
105
+ }
106
+ export function requireKindRegistry() {
107
+ if (!REGISTRY)
108
+ throw new ApprovalNotInitialized();
109
+ return REGISTRY;
110
+ }
111
+ export function requireHandlers() {
112
+ if (!HANDLERS)
113
+ throw new ApprovalNotInitialized();
114
+ return HANDLERS;
115
+ }
116
+ /**
117
+ * Register a kind map produced by `defineApprovalKinds`. Cross-checks every
118
+ * kind's `approverRole` against the registered `knownRoles` set (spec § 5.3,
119
+ * "Approver-role cross-check at boot").
120
+ */
121
+ export function registerKinds(defs) {
122
+ const cfg = requireApproval();
123
+ const reg = requireKindRegistry();
124
+ if (cfg.knownRoles.size > 0) {
125
+ for (const [name, def] of Object.entries(defs)) {
126
+ if (!cfg.knownRoles.has(def.approverRole)) {
127
+ throw new ApproverRoleNotInCatalog(def.approverRole, name, Array.from(cfg.knownRoles));
128
+ }
129
+ }
130
+ }
131
+ reg.register(defs);
132
+ }
133
+ /** Test-only reset. Exposed via the public barrel for synthetic consumers. */
134
+ export function _resetForTests() {
135
+ RESOLVED = null;
136
+ REGISTRY = null;
137
+ HANDLERS = null;
138
+ }
139
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { ApprovalKindDef, ApprovalKindDefInput, DefinedKindMap } from "./types";
2
+ export interface DefineApprovalKindsOpts {
3
+ /** Only used by nodii-tenant-service for `platform_ops.*` kinds. */
4
+ bypassReservedNamespaceCheck?: boolean;
5
+ }
6
+ export declare function defineApprovalKinds<S extends string, const T extends Record<string, ApprovalKindDefInput>>(serviceName: S, defs: T, opts?: DefineApprovalKindsOpts): DefinedKindMap<S, T>;
7
+ /** Process-local registry of all kinds for the running service. */
8
+ export declare class KindRegistry {
9
+ readonly serviceName: string;
10
+ private readonly map;
11
+ constructor(serviceName: string);
12
+ /** Register a previously-defined kind map. */
13
+ register(defs: Record<string, ApprovalKindDef>): void;
14
+ get(kind: string): ApprovalKindDef | undefined;
15
+ has(kind: string): boolean;
16
+ keys(): readonly string[];
17
+ values(): readonly ApprovalKindDef[];
18
+ size(): number;
19
+ }
20
+ //# sourceMappingURL=kinds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kinds.d.ts","sourceRoot":"","sources":["../src/kinds.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,eAAe,EACf,oBAAoB,EACpB,cAAc,EACf,MAAM,SAAS,CAAC;AAGjB,MAAM,WAAW,uBAAuB;IACtC,oEAAoE;IACpE,4BAA4B,CAAC,EAAE,OAAO,CAAC;CACxC;AAED,wBAAgB,mBAAmB,CACjC,CAAC,SAAS,MAAM,EAChB,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,EAEpD,WAAW,EAAE,CAAC,EACd,IAAI,EAAE,CAAC,EACP,IAAI,GAAE,uBAA4B,GACjC,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,CAmCtB;AAED,mEAAmE;AACnE,qBAAa,YAAY;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAsC;gBAE9C,WAAW,EAAE,MAAM;IAI/B,8CAA8C;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,IAAI;IAWrD,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAI9C,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B,IAAI,IAAI,SAAS,MAAM,EAAE;IAIzB,MAAM,IAAI,SAAS,eAAe,EAAE;IAIpC,IAAI,IAAI,MAAM;CAGf"}
package/dist/kinds.js ADDED
@@ -0,0 +1,72 @@
1
+ // defineApprovalKinds — type-safe kind registry per spec § 5.3.
2
+ //
3
+ // Compile-time: TypeScript template literal types narrow `kind` to the
4
+ // exact string `${service}.${resource}.${verb}`.
5
+ // Runtime: validates serviceName + resource + verb segments via
6
+ // `validation.ts`. Rejects reserved namespaces unless explicitly bypassed.
7
+ import { validatePermissionSegment, validateServiceName } from "./validation";
8
+ export function defineApprovalKinds(serviceName, defs, opts = {}) {
9
+ validateServiceName(serviceName, opts);
10
+ const out = {};
11
+ const seen = new Set();
12
+ for (const [name, raw] of Object.entries(defs)) {
13
+ if (raw === null || typeof raw !== "object") {
14
+ throw new TypeError(`defineApprovalKinds: entry '${name}' must be an object, got ${typeof raw}`);
15
+ }
16
+ const def = raw;
17
+ validatePermissionSegment(def.resource, "resource");
18
+ validatePermissionSegment(def.verb, "verb");
19
+ const shortKind = `${def.resource}.${def.verb}`;
20
+ const kind = `${serviceName}.${shortKind}`;
21
+ if (seen.has(kind)) {
22
+ throw new TypeError(`defineApprovalKinds: duplicate kind '${kind}' in service '${serviceName}'`);
23
+ }
24
+ seen.add(kind);
25
+ out[name] = {
26
+ shortKind,
27
+ kind,
28
+ resource: def.resource,
29
+ verb: def.verb,
30
+ description: def.description ?? "",
31
+ approverRole: def.approverRole,
32
+ optInDefault: def.optInDefault ?? true,
33
+ selfActionAllowed: def.selfActionAllowed ?? false,
34
+ expirySeconds: def.expirySeconds,
35
+ actionButtonLabel: def.actionButtonLabel,
36
+ };
37
+ }
38
+ return out;
39
+ }
40
+ /** Process-local registry of all kinds for the running service. */
41
+ export class KindRegistry {
42
+ serviceName;
43
+ map = new Map();
44
+ constructor(serviceName) {
45
+ this.serviceName = serviceName;
46
+ }
47
+ /** Register a previously-defined kind map. */
48
+ register(defs) {
49
+ for (const def of Object.values(defs)) {
50
+ if (this.map.has(def.kind)) {
51
+ throw new TypeError(`KindRegistry: kind '${def.kind}' already registered`);
52
+ }
53
+ this.map.set(def.kind, def);
54
+ }
55
+ }
56
+ get(kind) {
57
+ return this.map.get(kind);
58
+ }
59
+ has(kind) {
60
+ return this.map.has(kind);
61
+ }
62
+ keys() {
63
+ return Array.from(this.map.keys());
64
+ }
65
+ values() {
66
+ return Array.from(this.map.values());
67
+ }
68
+ size() {
69
+ return this.map.size;
70
+ }
71
+ }
72
+ //# sourceMappingURL=kinds.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kinds.js","sourceRoot":"","sources":["../src/kinds.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ export interface GetMigrationSQLOpts {
2
+ /** Default false — set true if the adopting service is in the reserved
3
+ * namespace (e.g. nodii-tenant-service for `platform` kinds). */
4
+ bypassReservedNamespaceCheck?: boolean;
5
+ }
6
+ /**
7
+ * Return the SQL string ready to apply against the adopting service's
8
+ * postgres. Substitutes every `__SERVICE__` occurrence with `serviceName`.
9
+ *
10
+ * The returned string contains all three statements (policies table,
11
+ * deferred-actions table, consumed_events table) concatenated; callers
12
+ * either run them as one batch or split on `;` if their pg client
13
+ * requires per-statement execution.
14
+ */
15
+ export declare function getApprovalMigrationSQL(serviceName: string, opts?: GetMigrationSQLOpts): string;
16
+ /** Convenience: split a SQL blob into per-statement executions, skipping
17
+ * comment-only lines + empty statements.
18
+ *
19
+ * CONSTRAINT: this splitter is intentionally naive — `/;\s*(?:\n|$)/`
20
+ * cuts at every top-level semicolon. It will mis-cut SQL containing
21
+ * embedded semicolons inside string literals or `DO $$ ... ; ... $$`
22
+ * blocks. The 001/002 migrations shipped today have neither, so it's
23
+ * safe. If a future migration grows DO/FUNCTION bodies, replace this
24
+ * with a real SQL tokenizer (e.g. pg-query-emscripten) — do NOT just
25
+ * bolt on more regex. */
26
+ export declare function splitStatements(sql: string): string[];
27
+ //# sourceMappingURL=migrations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AAoCA,MAAM,WAAW,mBAAmB;IAClC;sEACkE;IAClE,4BAA4B,CAAC,EAAE,OAAO,CAAC;CACxC;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE,mBAAwB,GAC7B,MAAM,CAKR;AAED;;;;;;;;;0BAS0B;AAC1B,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAWrD"}
@@ -0,0 +1,67 @@
1
+ // Per-service migration SQL emit helper per spec § 5.12.
2
+ //
3
+ // `getApprovalMigrationSQL("billing")` → returns the SQL ready to apply
4
+ // against a `billing` service's postgres (i.e. with `__SERVICE__`
5
+ // substituted). Adopting services run this output through their normal
6
+ // migration tooling (postgres.js raw exec, sqlc, alembic, etc.).
7
+ import { readFileSync } from "node:fs";
8
+ import { dirname, join } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import { validateServiceName } from "./validation";
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = dirname(__filename);
13
+ /** Resolve a migration file from the published package layout.
14
+ *
15
+ * Published consumers receive both `dist/` (compiled JS+d.ts) and the
16
+ * source SQL files at `src/migrations/*.sql` per `package.json#files`.
17
+ * At runtime in `node_modules/@nodii/approval/dist/migrations.js`,
18
+ * `__dirname` is `dist/`, so we resolve to `../src/migrations/<file>`.
19
+ * In source-mode (running directly via bun from `src/migrations.ts`),
20
+ * `__dirname` IS the src directory, so we resolve to `migrations/<file>`. */
21
+ function loadMigrationFile(filename) {
22
+ // Source-mode path: src/migrations/ relative to src/migrations.ts.
23
+ const srcPath = join(__dirname, "migrations", filename);
24
+ // Published-package path: dist/migrations.js → ../src/migrations/.
25
+ const publishedSrcPath = join(__dirname, "..", "src", "migrations", filename);
26
+ try {
27
+ return readFileSync(srcPath, "utf8");
28
+ }
29
+ catch {
30
+ return readFileSync(publishedSrcPath, "utf8");
31
+ }
32
+ }
33
+ /**
34
+ * Return the SQL string ready to apply against the adopting service's
35
+ * postgres. Substitutes every `__SERVICE__` occurrence with `serviceName`.
36
+ *
37
+ * The returned string contains all three statements (policies table,
38
+ * deferred-actions table, consumed_events table) concatenated; callers
39
+ * either run them as one batch or split on `;` if their pg client
40
+ * requires per-statement execution.
41
+ */
42
+ export function getApprovalMigrationSQL(serviceName, opts = {}) {
43
+ validateServiceName(serviceName, opts);
44
+ const a = loadMigrationFile("001-approval-policies.sql");
45
+ const b = loadMigrationFile("002-deferred-actions.sql");
46
+ return [a, b].map((s) => s.replaceAll("__SERVICE__", serviceName)).join("\n");
47
+ }
48
+ /** Convenience: split a SQL blob into per-statement executions, skipping
49
+ * comment-only lines + empty statements.
50
+ *
51
+ * CONSTRAINT: this splitter is intentionally naive — `/;\s*(?:\n|$)/`
52
+ * cuts at every top-level semicolon. It will mis-cut SQL containing
53
+ * embedded semicolons inside string literals or `DO $$ ... ; ... $$`
54
+ * blocks. The 001/002 migrations shipped today have neither, so it's
55
+ * safe. If a future migration grows DO/FUNCTION bodies, replace this
56
+ * with a real SQL tokenizer (e.g. pg-query-emscripten) — do NOT just
57
+ * bolt on more regex. */
58
+ export function splitStatements(sql) {
59
+ return sql
60
+ .split(/;\s*(?:\n|$)/)
61
+ .map((s) => s.trim())
62
+ .filter((s) => s.length > 0 &&
63
+ !s
64
+ .split("\n")
65
+ .every((l) => l.trim().startsWith("--") || l.trim() === ""));
66
+ }
67
+ //# sourceMappingURL=migrations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrations.js","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,EAAE;AACF,wEAAwE;AACxE,kEAAkE;AAClE,uEAAuE;AACvE,iEAAiE;AAEjE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC;;;;;;;8EAO8E;AAC9E,SAAS,iBAAiB,CAAC,QAAgB;IACzC,mEAAmE;IACnE,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC;IACxD,mEAAmE;IACnE,MAAM,gBAAgB,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC;IAC9E,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,YAAY,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;IAChD,CAAC;AACH,CAAC;AAQD;;;;;;;;GAQG;AACH,MAAM,UAAU,uBAAuB,CACrC,WAAmB,EACnB,OAA4B,EAAE;IAE9B,mBAAmB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IACvC,MAAM,CAAC,GAAG,iBAAiB,CAAC,2BAA2B,CAAC,CAAC;IACzD,MAAM,CAAC,GAAG,iBAAiB,CAAC,0BAA0B,CAAC,CAAC;IACxD,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAChF,CAAC;AAED;;;;;;;;;0BAS0B;AAC1B,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,OAAO,GAAG;SACP,KAAK,CAAC,cAAc,CAAC;SACrB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CACL,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,MAAM,GAAG,CAAC;QACZ,CAAC,CAAC;aACC,KAAK,CAAC,IAAI,CAAC;aACX,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAChE,CAAC;AACN,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { ApprovalKindDef, ApprovalPolicyRow, ResolvedApprovalConfig, ResolvedPolicy, SqlExecutor } from "./types";
2
+ export declare function findPolicy(cfg: ResolvedApprovalConfig, pg: SqlExecutor, tenantId: string, kind: string): Promise<ApprovalPolicyRow | null>;
3
+ /**
4
+ * Resolve the effective policy for a (tenant, kind) — uses the tenant row
5
+ * if present, otherwise falls through to kind defaults.
6
+ *
7
+ * Returns `{enabled: false, fallthrough: true}` when no row exists AND
8
+ * the kind's `optInDefault` is false (the spec § 5.10 opt-in-disabled
9
+ * short-circuit case).
10
+ */
11
+ export declare function resolvePolicy(cfg: ResolvedApprovalConfig, pg: SqlExecutor, tenantId: string, kindDef: ApprovalKindDef): Promise<ResolvedPolicy>;
12
+ /** Upsert a policy row. Used by the per-service policy gRPC service. */
13
+ export declare function upsertPolicy(cfg: ResolvedApprovalConfig, pg: SqlExecutor, args: {
14
+ tenantId: string;
15
+ kind: string;
16
+ enabled: boolean;
17
+ approverRole: string;
18
+ thresholdParams: Record<string, unknown> | null;
19
+ expirySeconds: number | null;
20
+ }): Promise<void>;
21
+ //# sourceMappingURL=policies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policies.d.ts","sourceRoot":"","sources":["../src/policies.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EACjB,sBAAsB,EACtB,cAAc,EACd,WAAW,EACZ,MAAM,SAAS,CAAC;AAMjB,wBAAsB,UAAU,CAC9B,GAAG,EAAE,sBAAsB,EAC3B,EAAE,EAAE,WAAW,EACf,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAmBnC;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,sBAAsB,EAC3B,EAAE,EAAE,WAAW,EACf,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,cAAc,CAAC,CA4BzB;AAED,wEAAwE;AACxE,wBAAsB,YAAY,CAChC,GAAG,EAAE,sBAAsB,EAC3B,EAAE,EAAE,WAAW,EACf,IAAI,EAAE;IACJ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAChD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,GACA,OAAO,CAAC,IAAI,CAAC,CAuBf"}