@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.
- package/README.md +40 -2
- package/dist/consumer.d.ts +19 -0
- package/dist/consumer.d.ts.map +1 -0
- package/dist/consumer.js +274 -0
- package/dist/consumer.js.map +1 -0
- package/dist/deferred-actions.d.ts +34 -0
- package/dist/deferred-actions.d.ts.map +1 -0
- package/dist/deferred-actions.js +126 -0
- package/dist/deferred-actions.js.map +1 -0
- package/dist/errors.d.ts +51 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +82 -0
- package/dist/errors.js.map +1 -0
- package/dist/handlers.d.ts +7 -0
- package/dist/handlers.d.ts.map +1 -0
- package/dist/handlers.js +20 -0
- package/dist/handlers.js.map +1 -0
- package/dist/index.d.ts +13 -2
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -1
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +21 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +139 -0
- package/dist/init.js.map +1 -0
- package/dist/kinds.d.ts +20 -0
- package/dist/kinds.d.ts.map +1 -0
- package/dist/kinds.js +72 -0
- package/dist/kinds.js.map +1 -0
- package/dist/migrations.d.ts +27 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +67 -0
- package/dist/migrations.js.map +1 -0
- package/dist/policies.d.ts +21 -0
- package/dist/policies.d.ts.map +1 -0
- package/dist/policies.js +99 -0
- package/dist/policies.js.map +1 -0
- package/dist/request.d.ts +3 -0
- package/dist/request.d.ts.map +1 -0
- package/dist/request.js +356 -0
- package/dist/request.js.map +1 -0
- package/dist/task-tracking-client.d.ts +39 -0
- package/dist/task-tracking-client.d.ts.map +1 -0
- package/dist/task-tracking-client.js +100 -0
- package/dist/task-tracking-client.js.map +1 -0
- package/dist/telemetry-shim.d.ts +35 -0
- package/dist/telemetry-shim.d.ts.map +1 -0
- package/dist/telemetry-shim.js +88 -0
- package/dist/telemetry-shim.js.map +1 -0
- package/dist/types.d.ts +279 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +5 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +40 -0
- package/dist/validation.js.map +1 -0
- package/package.json +44 -3
- package/src/migrations/001-approval-policies.sql +27 -0
- package/src/migrations/002-deferred-actions.sql +41 -0
package/README.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# @nodii/approval
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Approval library for the Nodii microservice stack. Implements the platform-wide
|
|
4
|
+
approval contract locked in `10-approval-doctrine.md`. Polyglot ship: TS + Python + Go
|
|
5
|
+
at v0.1.0.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
## Public surface (v0.1.0)
|
|
8
|
+
|
|
9
|
+
- `defineApprovalKinds(serviceName, defs)` — type-safe kind registry per spec § 5.3.
|
|
10
|
+
- `initApproval({...})` — boot-time wiring (pg, redis, telemetry, task-tracking).
|
|
11
|
+
- `bindApprovalHandlers({...})` — register `onApproved` / `onRejected` / `onExpired`.
|
|
12
|
+
- `requestApproval({...})` — the canonical 7-step request sequence per spec § 5.2.
|
|
13
|
+
- `startApprovalConsumer({...})` / `stopApprovalConsumer({...})` — BullMQ-leased
|
|
14
|
+
completion-event consumer per spec § 5.8.
|
|
15
|
+
- `getApprovalMigrationSQL(serviceName)` — emit per-service `<service>_approval_policies`
|
|
16
|
+
+ `<service>_deferred_actions` migration SQL per spec § 5.4 / § 5.6.
|
|
17
|
+
|
|
18
|
+
Spec: planning hub `feature_doc` `docKey=approval`. See
|
|
19
|
+
[`10-approval-doctrine.md`](../../../nodii-planning-hub/docs/planning/global/10-approval-doctrine.md)
|
|
20
|
+
for the canonical contract.
|
|
21
|
+
|
|
22
|
+
## Boot
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { initApproval, startApprovalConsumer } from "@nodii/approval";
|
|
26
|
+
import Redis from "ioredis";
|
|
27
|
+
import postgres from "postgres";
|
|
28
|
+
|
|
29
|
+
const pgPool = postgres(process.env.DATABASE_URL!);
|
|
30
|
+
const redis = new Redis(process.env.REDIS_URL!);
|
|
31
|
+
|
|
32
|
+
await initApproval({
|
|
33
|
+
serviceName: "billing",
|
|
34
|
+
pgPool,
|
|
35
|
+
redis,
|
|
36
|
+
taskServiceUrl: process.env.TASK_TRACKING_GRPC_URL!,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
await startApprovalConsumer({ serviceName: "billing" });
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
See `tests/integration.test.ts` for an end-to-end example that wires the
|
|
43
|
+
library against postgres + redis + an in-process task-tracking gRPC stub.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Worker } from "bullmq";
|
|
2
|
+
export interface StartApprovalConsumerOpts {
|
|
3
|
+
/** Defaults to `<serviceName>-approval-completions`. */
|
|
4
|
+
queueName?: string;
|
|
5
|
+
/** Defaults to `cfg.consumerMaxRetries`. */
|
|
6
|
+
maxAttempts?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ApprovalConsumerHandle {
|
|
9
|
+
worker: Worker;
|
|
10
|
+
close(timeoutMs?: number): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export declare function startApprovalConsumer(opts?: StartApprovalConsumerOpts): Promise<ApprovalConsumerHandle>;
|
|
13
|
+
export declare function stopApprovalConsumer(opts?: {
|
|
14
|
+
timeoutMs?: number;
|
|
15
|
+
}): Promise<void>;
|
|
16
|
+
export declare function defaultQueueName(serviceName: string): string;
|
|
17
|
+
/** Test-only reset — closes the active worker if any. */
|
|
18
|
+
export declare function _resetConsumerForTests(): void;
|
|
19
|
+
//# sourceMappingURL=consumer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consumer.d.ts","sourceRoot":"","sources":["../src/consumer.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,MAAM,EAAY,MAAM,QAAQ,CAAC;AAmB1C,MAAM,WAAW,yBAAyB;IACxC,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C;AAID,wBAAsB,qBAAqB,CACzC,IAAI,GAAE,yBAA8B,GACnC,OAAO,CAAC,sBAAsB,CAAC,CAyDjC;AAED,wBAAsB,oBAAoB,CACxC,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAChC,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE5D;AAkPD,yDAAyD;AACzD,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C"}
|
package/dist/consumer.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
// BullMQ-leased completion-event consumer per spec § 5.8.
|
|
2
|
+
//
|
|
3
|
+
// Subscribes to `tasks.task.completed.v1` and dispatches the registered
|
|
4
|
+
// handler for each approval task that targets this service. Apply-tx
|
|
5
|
+
// wraps status-update + handler invocation + audit emit + dedupe write
|
|
6
|
+
// in one transaction.
|
|
7
|
+
//
|
|
8
|
+
// v0.1.0 scope: the consumer is a BullMQ Worker. Adopting services
|
|
9
|
+
// publish completion events into the `tasks.task.completed.v1` queue
|
|
10
|
+
// (either via a fan-out from task-tracking's outbox drainer, or via a
|
|
11
|
+
// direct BullMQ producer in integration tests). We do NOT own the
|
|
12
|
+
// publisher side — task-tracking does.
|
|
13
|
+
import { Worker } from "bullmq";
|
|
14
|
+
import { applyDecision, findById, findByTaskId, markExecuted, markFailed, } from "./deferred-actions";
|
|
15
|
+
import { DeferredActionNotFound } from "./errors";
|
|
16
|
+
import { requireApproval, requireHandlers } from "./init";
|
|
17
|
+
import { emitAudit, inc, observe } from "./telemetry-shim";
|
|
18
|
+
let CURRENT = null;
|
|
19
|
+
export async function startApprovalConsumer(opts = {}) {
|
|
20
|
+
if (CURRENT) {
|
|
21
|
+
return CURRENT;
|
|
22
|
+
}
|
|
23
|
+
const cfg = requireApproval();
|
|
24
|
+
const queue = opts.queueName ?? defaultQueueName(cfg.serviceName);
|
|
25
|
+
const maxAttempts = opts.maxAttempts ?? cfg.consumerMaxRetries;
|
|
26
|
+
// BullMQ's Connection type is pinned to its own ioredis version; the
|
|
27
|
+
// workspace may resolve a different patch. Cast through `unknown` to
|
|
28
|
+
// accept the user-supplied Redis as long as it has the same structural
|
|
29
|
+
// surface BullMQ uses internally.
|
|
30
|
+
const worker = new Worker(queue, async (job) => {
|
|
31
|
+
await consumeCompletionEvent(cfg, job);
|
|
32
|
+
}, {
|
|
33
|
+
connection: cfg.redis,
|
|
34
|
+
concurrency: 1, // single-flight per service per spec § 5.8 lease semantics
|
|
35
|
+
lockDuration: cfg.consumerLeaseSeconds * 1000,
|
|
36
|
+
autorun: true,
|
|
37
|
+
removeOnComplete: { count: 1000 },
|
|
38
|
+
removeOnFail: { count: 1000 },
|
|
39
|
+
settings: {
|
|
40
|
+
backoffStrategy: (attempts) => Math.min(60_000, 2 ** attempts * 250),
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
worker.on("failed", (job, err) => {
|
|
44
|
+
cfg.logger.warn("approval.consumer.job_failed", {
|
|
45
|
+
job_id: job?.id,
|
|
46
|
+
attempts: job?.attemptsMade,
|
|
47
|
+
max_attempts: maxAttempts,
|
|
48
|
+
error: err.message,
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
CURRENT = {
|
|
52
|
+
worker,
|
|
53
|
+
async close(timeoutMs = 30_000) {
|
|
54
|
+
try {
|
|
55
|
+
await Promise.race([
|
|
56
|
+
worker.close(),
|
|
57
|
+
new Promise((resolve) => setTimeout(resolve, timeoutMs).unref()),
|
|
58
|
+
]);
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
CURRENT = null;
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
return CURRENT;
|
|
66
|
+
}
|
|
67
|
+
export async function stopApprovalConsumer(opts = {}) {
|
|
68
|
+
if (!CURRENT)
|
|
69
|
+
return;
|
|
70
|
+
await CURRENT.close(opts.timeoutMs);
|
|
71
|
+
}
|
|
72
|
+
export function defaultQueueName(serviceName) {
|
|
73
|
+
return `${serviceName}-approval-completions`;
|
|
74
|
+
}
|
|
75
|
+
async function consumeCompletionEvent(cfg, job) {
|
|
76
|
+
const event = job.data;
|
|
77
|
+
const startMs = Date.now();
|
|
78
|
+
// Dedupe via consumed_events.
|
|
79
|
+
const already = await isAlreadyConsumed(cfg, event.eventId, event.topic);
|
|
80
|
+
if (already) {
|
|
81
|
+
cfg.logger.info("approval.consumer.dedupe_skip", {
|
|
82
|
+
event_id: event.eventId,
|
|
83
|
+
});
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// Filter to approval kinds for this service only.
|
|
87
|
+
if (!event.kind.startsWith(`approval:${cfg.serviceName}.`)) {
|
|
88
|
+
await markConsumed(cfg, event.eventId, event.topic);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// Resolve the deferred-action row.
|
|
92
|
+
const pg = cfg.pgPool;
|
|
93
|
+
let row = await findByTaskId(cfg, pg, event.taskId);
|
|
94
|
+
if (!row) {
|
|
95
|
+
const fallbackId = typeof event.metadata?.deferredActionId === "string"
|
|
96
|
+
? event.metadata.deferredActionId
|
|
97
|
+
: null;
|
|
98
|
+
if (fallbackId) {
|
|
99
|
+
row = await findById(cfg, pg, fallbackId);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (!row) {
|
|
103
|
+
// No row found → no tenant id available. Pass null rather than the
|
|
104
|
+
// literal string "unknown" so downstream audit-chain queries
|
|
105
|
+
// filtering by tenant_id don't mistake this for a real tenant value.
|
|
106
|
+
emitAudit({
|
|
107
|
+
tenantId: null,
|
|
108
|
+
action: `${cfg.serviceName}.approval.completion_unknown`,
|
|
109
|
+
metadata: { task_id: event.taskId, event_id: event.eventId },
|
|
110
|
+
});
|
|
111
|
+
inc({
|
|
112
|
+
name: "approval_completion_unknown_total",
|
|
113
|
+
family: "async_worker",
|
|
114
|
+
labels: { service: cfg.serviceName },
|
|
115
|
+
});
|
|
116
|
+
await markConsumed(cfg, event.eventId, event.topic);
|
|
117
|
+
throw new DeferredActionNotFound(event.taskId);
|
|
118
|
+
}
|
|
119
|
+
// Apply tx: status update + handler dispatch + audit + dedupe insert.
|
|
120
|
+
// If no handler is registered for this kind, treat it as a terminal
|
|
121
|
+
// failure — mark the row failed, write the dedupe row to stop BullMQ
|
|
122
|
+
// from retrying forever, and emit the matching audit + metric.
|
|
123
|
+
const handlers = requireHandlers();
|
|
124
|
+
const handlerSet = handlers.get(row.kind);
|
|
125
|
+
if (!handlerSet) {
|
|
126
|
+
const reason = `HandlerNotRegistered: no handler for kind '${row.kind}'`;
|
|
127
|
+
await markFailed(cfg, pg, row.id, reason);
|
|
128
|
+
emitAudit({
|
|
129
|
+
tenantId: row.tenant_id,
|
|
130
|
+
action: `${cfg.serviceName}.approval.execution_failed`,
|
|
131
|
+
targetKind: "deferred_action",
|
|
132
|
+
targetId: row.id,
|
|
133
|
+
metadata: { kind: row.kind, error: reason },
|
|
134
|
+
});
|
|
135
|
+
inc({
|
|
136
|
+
name: "approval_handler_failures_total",
|
|
137
|
+
family: "async_worker",
|
|
138
|
+
labels: {
|
|
139
|
+
service: cfg.serviceName,
|
|
140
|
+
kind: row.kind,
|
|
141
|
+
decision: event.outcome,
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
await markConsumed(cfg, event.eventId, event.topic);
|
|
145
|
+
// Terminal — return so BullMQ stops retrying (matches the retry-
|
|
146
|
+
// exhaustion branch below). Operator recovery via RetryApproval RPC.
|
|
147
|
+
cfg.logger.error("approval.consumer.handler_not_registered", {
|
|
148
|
+
kind: row.kind,
|
|
149
|
+
row_id: row.id,
|
|
150
|
+
});
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const ctx = {
|
|
154
|
+
tenantId: row.tenant_id,
|
|
155
|
+
deferredActionId: row.id,
|
|
156
|
+
deferredActionPayload: row.deferred_action_payload,
|
|
157
|
+
requesterMembershipId: row.requester_membership_id,
|
|
158
|
+
approverMembershipId: event.completedByMembershipId,
|
|
159
|
+
approverNote: event.completionNote,
|
|
160
|
+
approverKind: "user",
|
|
161
|
+
kind: row.kind,
|
|
162
|
+
metadata: row.metadata,
|
|
163
|
+
};
|
|
164
|
+
try {
|
|
165
|
+
await pg.begin(async (tx) => {
|
|
166
|
+
const updated = await applyDecision(cfg, tx, row.id, event.outcome, event.completedByMembershipId, event.completionNote);
|
|
167
|
+
// Idempotency guard: if 0 rows updated, the row already moved past
|
|
168
|
+
// `pending` — a prior duplicate completion event already ran the
|
|
169
|
+
// handler. Skip rather than re-fire (consumed_events dedupe +
|
|
170
|
+
// status='pending' guard = exactly-once handler invocation).
|
|
171
|
+
if (updated === 0) {
|
|
172
|
+
cfg.logger.info("approval.consumer.duplicate_completion_skipped", {
|
|
173
|
+
row_id: row.id,
|
|
174
|
+
event_id: event.eventId,
|
|
175
|
+
task_id: event.taskId,
|
|
176
|
+
});
|
|
177
|
+
await tx.unsafe(`INSERT INTO consumed_events (event_id, topic) VALUES ($1, $2)
|
|
178
|
+
ON CONFLICT (event_id, topic) DO NOTHING`, [event.eventId, event.topic]);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (event.outcome === "approved") {
|
|
182
|
+
await handlerSet.onApproved(ctx);
|
|
183
|
+
}
|
|
184
|
+
else if (event.outcome === "rejected") {
|
|
185
|
+
await handlerSet.onRejected(ctx);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
await handlerSet.onExpired(ctx);
|
|
189
|
+
}
|
|
190
|
+
await markExecuted(cfg, tx, row.id);
|
|
191
|
+
const auditAction = event.outcome === "approved"
|
|
192
|
+
? `${cfg.serviceName}.approval.executed`
|
|
193
|
+
: `${cfg.serviceName}.approval.aborted`;
|
|
194
|
+
emitAudit({
|
|
195
|
+
tenantId: row.tenant_id,
|
|
196
|
+
action: auditAction,
|
|
197
|
+
targetKind: "deferred_action",
|
|
198
|
+
targetId: row.id,
|
|
199
|
+
// Event metadata first; lib-mandated fields last so they win.
|
|
200
|
+
metadata: {
|
|
201
|
+
...event.metadata,
|
|
202
|
+
kind: row.kind,
|
|
203
|
+
outcome: event.outcome,
|
|
204
|
+
approver_membership_id: event.completedByMembershipId,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
await tx.unsafe(`INSERT INTO consumed_events (event_id, topic) VALUES ($1, $2)
|
|
208
|
+
ON CONFLICT (event_id, topic) DO NOTHING`, [event.eventId, event.topic]);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
catch (handlerErr) {
|
|
212
|
+
// Apply tx rolled back here — status reverts to whatever was before
|
|
213
|
+
// applyDecision ran, consumed_events row was NOT written, and the
|
|
214
|
+
// handler failure needs separate handling. Enforce consumerMaxRetries
|
|
215
|
+
// at this layer (BullMQ's per-job `attempts` is producer-controlled,
|
|
216
|
+
// so we cap on attemptsMade ourselves).
|
|
217
|
+
const reason = handlerErr instanceof Error ? handlerErr.message : String(handlerErr);
|
|
218
|
+
const attemptsMade = job.attemptsMade ?? 0;
|
|
219
|
+
const exhausted = attemptsMade + 1 >= cfg.consumerMaxRetries;
|
|
220
|
+
if (exhausted) {
|
|
221
|
+
// Terminal failure — mark + dedupe + don't re-throw so BullMQ stops.
|
|
222
|
+
await markFailed(cfg, pg, row.id, reason);
|
|
223
|
+
await markConsumed(cfg, event.eventId, event.topic);
|
|
224
|
+
emitAudit({
|
|
225
|
+
tenantId: row.tenant_id,
|
|
226
|
+
action: `${cfg.serviceName}.approval.execution_failed`,
|
|
227
|
+
targetKind: "deferred_action",
|
|
228
|
+
targetId: row.id,
|
|
229
|
+
metadata: { kind: row.kind, error: reason, attempts: attemptsMade + 1 },
|
|
230
|
+
});
|
|
231
|
+
inc({
|
|
232
|
+
name: "approval_handler_failures_total",
|
|
233
|
+
family: "async_worker",
|
|
234
|
+
labels: {
|
|
235
|
+
service: cfg.serviceName,
|
|
236
|
+
kind: row.kind,
|
|
237
|
+
decision: event.outcome,
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
return; // do not re-throw — operator-recoverable via RetryApproval RPC
|
|
241
|
+
}
|
|
242
|
+
// Transient — re-throw so BullMQ retries with backoff. The status is
|
|
243
|
+
// already rolled back by the apply tx; next attempt re-enters cleanly.
|
|
244
|
+
throw handlerErr;
|
|
245
|
+
}
|
|
246
|
+
observe({
|
|
247
|
+
name: "approval_execution_duration_seconds",
|
|
248
|
+
family: "async_worker",
|
|
249
|
+
labels: { service: cfg.serviceName, kind: row.kind },
|
|
250
|
+
value: (Date.now() - startMs) / 1000,
|
|
251
|
+
});
|
|
252
|
+
if (event.publishedAt) {
|
|
253
|
+
const lag = (Date.now() - new Date(event.publishedAt).getTime()) / 1000;
|
|
254
|
+
observe({
|
|
255
|
+
name: "approval_consumer_lag_seconds",
|
|
256
|
+
family: "async_worker",
|
|
257
|
+
labels: { service: cfg.serviceName },
|
|
258
|
+
value: lag,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
async function isAlreadyConsumed(cfg, eventId, topic) {
|
|
263
|
+
const rows = await cfg.pgPool.unsafe("SELECT 1 FROM consumed_events WHERE event_id = $1 AND topic = $2 LIMIT 1", [eventId, topic]);
|
|
264
|
+
return Array.isArray(rows) && rows.length > 0;
|
|
265
|
+
}
|
|
266
|
+
async function markConsumed(cfg, eventId, topic) {
|
|
267
|
+
await cfg.pgPool.unsafe(`INSERT INTO consumed_events (event_id, topic) VALUES ($1, $2)
|
|
268
|
+
ON CONFLICT (event_id, topic) DO NOTHING`, [eventId, topic]);
|
|
269
|
+
}
|
|
270
|
+
/** Test-only reset — closes the active worker if any. */
|
|
271
|
+
export function _resetConsumerForTests() {
|
|
272
|
+
CURRENT = null;
|
|
273
|
+
}
|
|
274
|
+
//# sourceMappingURL=consumer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consumer.js","sourceRoot":"","sources":["../src/consumer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { DeferredActionRow, ResolvedApprovalConfig, SqlExecutor, SqlTxExecutor } from "./types";
|
|
2
|
+
export interface InsertDeferredActionInput {
|
|
3
|
+
tenantId: string;
|
|
4
|
+
kind: string;
|
|
5
|
+
requesterMembershipId: string;
|
|
6
|
+
deferredActionPayload: Record<string, unknown>;
|
|
7
|
+
idempotencyKey: string;
|
|
8
|
+
metadata: Record<string, unknown>;
|
|
9
|
+
auditCorrelationId: string | null;
|
|
10
|
+
}
|
|
11
|
+
/** Insert a fresh `pending` deferred-action row. Returns the inserted row.
|
|
12
|
+
* Used by `requestApproval` step [5]. */
|
|
13
|
+
export declare function insertDeferredAction(cfg: ResolvedApprovalConfig, tx: SqlTxExecutor, input: InsertDeferredActionInput): Promise<DeferredActionRow>;
|
|
14
|
+
/** Update the row's `task_id` after task-tracking returns. */
|
|
15
|
+
export declare function setTaskId(cfg: ResolvedApprovalConfig, tx: SqlTxExecutor, rowId: string, taskId: string): Promise<void>;
|
|
16
|
+
/** Look up a deferred-action row by task_id. Returns null if not found. */
|
|
17
|
+
export declare function findByTaskId(cfg: ResolvedApprovalConfig, pg: SqlExecutor, taskId: string): Promise<DeferredActionRow | null>;
|
|
18
|
+
/** Look up a deferred-action row by id. */
|
|
19
|
+
export declare function findById(cfg: ResolvedApprovalConfig, pg: SqlExecutor, id: string): Promise<DeferredActionRow | null>;
|
|
20
|
+
/** Atomic transition to `approved` | `rejected` | `expired` with decision
|
|
21
|
+
* metadata. Used by the completion-event consumer.
|
|
22
|
+
*
|
|
23
|
+
* Returns the number of rows updated. The caller short-circuits when 0,
|
|
24
|
+
* which happens if a duplicate completion event with a different event_id
|
|
25
|
+
* but the same task_id is delivered — the first event already moved the
|
|
26
|
+
* row past `pending`, and the handler must NOT re-fire. (Per
|
|
27
|
+
* consumed_events dedupe + this WHERE guard, the handler runs exactly
|
|
28
|
+
* once even under producer-side double-publish.) */
|
|
29
|
+
export declare function applyDecision(cfg: ResolvedApprovalConfig, tx: SqlTxExecutor, rowId: string, newStatus: "approved" | "rejected" | "expired", approverMembershipId: string | null, approverNote: string | null): Promise<number>;
|
|
30
|
+
/** Final transition to `executed` (handler ran clean). */
|
|
31
|
+
export declare function markExecuted(cfg: ResolvedApprovalConfig, tx: SqlTxExecutor, rowId: string): Promise<void>;
|
|
32
|
+
/** Final transition to `failed` (handler threw post-retries). */
|
|
33
|
+
export declare function markFailed(cfg: ResolvedApprovalConfig, tx: SqlTxExecutor, rowId: string, reason: string): Promise<void>;
|
|
34
|
+
//# sourceMappingURL=deferred-actions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deferred-actions.d.ts","sourceRoot":"","sources":["../src/deferred-actions.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,iBAAiB,EACjB,sBAAsB,EACtB,WAAW,EACX,aAAa,EACd,MAAM,SAAS,CAAC;AAMjB,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,qBAAqB,EAAE,MAAM,CAAC;IAC9B,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/C,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED;0CAC0C;AAC1C,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,sBAAsB,EAC3B,EAAE,EAAE,aAAa,EACjB,KAAK,EAAE,yBAAyB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAmB5B;AAED,8DAA8D;AAC9D,wBAAsB,SAAS,CAC7B,GAAG,EAAE,sBAAsB,EAC3B,EAAE,EAAE,aAAa,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,2EAA2E;AAC3E,wBAAsB,YAAY,CAChC,GAAG,EAAE,sBAAsB,EAC3B,EAAE,EAAE,WAAW,EACf,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAQnC;AAED,2CAA2C;AAC3C,wBAAsB,QAAQ,CAC5B,GAAG,EAAE,sBAAsB,EAC3B,EAAE,EAAE,WAAW,EACf,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAOnC;AAED;;;;;;;;qDAQqD;AACrD,wBAAsB,aAAa,CACjC,GAAG,EAAE,sBAAsB,EAC3B,EAAE,EAAE,aAAa,EACjB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,UAAU,GAAG,UAAU,GAAG,SAAS,EAC9C,oBAAoB,EAAE,MAAM,GAAG,IAAI,EACnC,YAAY,EAAE,MAAM,GAAG,IAAI,GAC1B,OAAO,CAAC,MAAM,CAAC,CAiBjB;AAED,0DAA0D;AAC1D,wBAAsB,YAAY,CAChC,GAAG,EAAE,sBAAsB,EAC3B,EAAE,EAAE,aAAa,EACjB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,iEAAiE;AACjE,wBAAsB,UAAU,CAC9B,GAAG,EAAE,sBAAsB,EAC3B,EAAE,EAAE,aAAa,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAMf"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// DB CRUD for `<service>_deferred_actions` per spec § 5.6.
|
|
2
|
+
//
|
|
3
|
+
// All queries go through the postgres.js handle (raw mode) OR the db-rls
|
|
4
|
+
// `withSystemContext` wrapper (RLS-aware mode). The library defers tenant-
|
|
5
|
+
// scoping to db-rls when wired; in raw mode, callers must ensure tenant
|
|
6
|
+
// isolation themselves (only acceptable for platform-tenant scope per
|
|
7
|
+
// 08-rls-doctrine § 4).
|
|
8
|
+
function tableName(serviceName) {
|
|
9
|
+
return `${serviceName}_deferred_actions`;
|
|
10
|
+
}
|
|
11
|
+
/** Insert a fresh `pending` deferred-action row. Returns the inserted row.
|
|
12
|
+
* Used by `requestApproval` step [5]. */
|
|
13
|
+
export async function insertDeferredAction(cfg, tx, input) {
|
|
14
|
+
const tbl = tableName(cfg.serviceName);
|
|
15
|
+
const rows = await tx.unsafe(`INSERT INTO ${tbl}
|
|
16
|
+
(tenant_id, kind, requester_membership_id, deferred_action_payload,
|
|
17
|
+
status, idempotency_key, metadata, audit_correlation_id)
|
|
18
|
+
VALUES ($1, $2, $3, $4, 'pending', $5, $6, $7)
|
|
19
|
+
RETURNING *`, [
|
|
20
|
+
input.tenantId,
|
|
21
|
+
input.kind,
|
|
22
|
+
input.requesterMembershipId,
|
|
23
|
+
JSON.stringify(input.deferredActionPayload),
|
|
24
|
+
input.idempotencyKey,
|
|
25
|
+
JSON.stringify(input.metadata),
|
|
26
|
+
input.auditCorrelationId,
|
|
27
|
+
]);
|
|
28
|
+
return rowFromSql(rows[0]);
|
|
29
|
+
}
|
|
30
|
+
/** Update the row's `task_id` after task-tracking returns. */
|
|
31
|
+
export async function setTaskId(cfg, tx, rowId, taskId) {
|
|
32
|
+
const tbl = tableName(cfg.serviceName);
|
|
33
|
+
await tx.unsafe(`UPDATE ${tbl} SET task_id = $1 WHERE id = $2`, [
|
|
34
|
+
taskId,
|
|
35
|
+
rowId,
|
|
36
|
+
]);
|
|
37
|
+
}
|
|
38
|
+
/** Look up a deferred-action row by task_id. Returns null if not found. */
|
|
39
|
+
export async function findByTaskId(cfg, pg, taskId) {
|
|
40
|
+
const tbl = tableName(cfg.serviceName);
|
|
41
|
+
const rows = await pg.unsafe(`SELECT * FROM ${tbl} WHERE task_id = $1 LIMIT 1`, [taskId]);
|
|
42
|
+
if (!Array.isArray(rows) || rows.length === 0)
|
|
43
|
+
return null;
|
|
44
|
+
return rowFromSql(rows[0]);
|
|
45
|
+
}
|
|
46
|
+
/** Look up a deferred-action row by id. */
|
|
47
|
+
export async function findById(cfg, pg, id) {
|
|
48
|
+
const tbl = tableName(cfg.serviceName);
|
|
49
|
+
const rows = await pg.unsafe(`SELECT * FROM ${tbl} WHERE id = $1 LIMIT 1`, [
|
|
50
|
+
id,
|
|
51
|
+
]);
|
|
52
|
+
if (!Array.isArray(rows) || rows.length === 0)
|
|
53
|
+
return null;
|
|
54
|
+
return rowFromSql(rows[0]);
|
|
55
|
+
}
|
|
56
|
+
/** Atomic transition to `approved` | `rejected` | `expired` with decision
|
|
57
|
+
* metadata. Used by the completion-event consumer.
|
|
58
|
+
*
|
|
59
|
+
* Returns the number of rows updated. The caller short-circuits when 0,
|
|
60
|
+
* which happens if a duplicate completion event with a different event_id
|
|
61
|
+
* but the same task_id is delivered — the first event already moved the
|
|
62
|
+
* row past `pending`, and the handler must NOT re-fire. (Per
|
|
63
|
+
* consumed_events dedupe + this WHERE guard, the handler runs exactly
|
|
64
|
+
* once even under producer-side double-publish.) */
|
|
65
|
+
export async function applyDecision(cfg, tx, rowId, newStatus, approverMembershipId, approverNote) {
|
|
66
|
+
const tbl = tableName(cfg.serviceName);
|
|
67
|
+
const result = await tx.unsafe(`UPDATE ${tbl}
|
|
68
|
+
SET status = $1, approver_membership_id = $2, approver_note = $3, decided_at = now()
|
|
69
|
+
WHERE id = $4 AND status = 'pending'`, [newStatus, approverMembershipId, approverNote, rowId]);
|
|
70
|
+
// postgres.js returns an array-shaped result with a `.count` property
|
|
71
|
+
// for non-returning UPDATEs.
|
|
72
|
+
const count = typeof result.count === "number"
|
|
73
|
+
? result.count
|
|
74
|
+
: Array.isArray(result)
|
|
75
|
+
? result.length
|
|
76
|
+
: 0;
|
|
77
|
+
return count;
|
|
78
|
+
}
|
|
79
|
+
/** Final transition to `executed` (handler ran clean). */
|
|
80
|
+
export async function markExecuted(cfg, tx, rowId) {
|
|
81
|
+
const tbl = tableName(cfg.serviceName);
|
|
82
|
+
await tx.unsafe(`UPDATE ${tbl} SET status = 'executed', executed_at = now() WHERE id = $1`, [rowId]);
|
|
83
|
+
}
|
|
84
|
+
/** Final transition to `failed` (handler threw post-retries). */
|
|
85
|
+
export async function markFailed(cfg, tx, rowId, reason) {
|
|
86
|
+
const tbl = tableName(cfg.serviceName);
|
|
87
|
+
await tx.unsafe(`UPDATE ${tbl} SET status = 'failed', failed_reason = $1, executed_at = now() WHERE id = $2`, [reason, rowId]);
|
|
88
|
+
}
|
|
89
|
+
function rowFromSql(r) {
|
|
90
|
+
return {
|
|
91
|
+
id: String(r.id),
|
|
92
|
+
tenant_id: String(r.tenant_id),
|
|
93
|
+
kind: String(r.kind),
|
|
94
|
+
requester_membership_id: String(r.requester_membership_id),
|
|
95
|
+
deferred_action_payload: parseJsonb(r.deferred_action_payload),
|
|
96
|
+
status: r.status,
|
|
97
|
+
task_id: r.task_id == null ? null : String(r.task_id),
|
|
98
|
+
approver_membership_id: r.approver_membership_id == null
|
|
99
|
+
? null
|
|
100
|
+
: String(r.approver_membership_id),
|
|
101
|
+
approver_note: r.approver_note == null ? null : String(r.approver_note),
|
|
102
|
+
requested_at: String(r.requested_at ?? ""),
|
|
103
|
+
decided_at: r.decided_at == null ? null : String(r.decided_at),
|
|
104
|
+
executed_at: r.executed_at == null ? null : String(r.executed_at),
|
|
105
|
+
failed_reason: r.failed_reason == null ? null : String(r.failed_reason),
|
|
106
|
+
idempotency_key: String(r.idempotency_key),
|
|
107
|
+
audit_correlation_id: r.audit_correlation_id == null ? null : String(r.audit_correlation_id),
|
|
108
|
+
metadata: parseJsonb(r.metadata) ?? {},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function parseJsonb(v) {
|
|
112
|
+
if (v == null)
|
|
113
|
+
return {};
|
|
114
|
+
if (typeof v === "string") {
|
|
115
|
+
try {
|
|
116
|
+
return JSON.parse(v);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return {};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (typeof v === "object")
|
|
123
|
+
return v;
|
|
124
|
+
return {};
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=deferred-actions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deferred-actions.js","sourceRoot":"","sources":["../src/deferred-actions.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export declare class ApprovalError extends Error {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
}
|
|
4
|
+
export declare class ApprovalNotInitialized extends ApprovalError {
|
|
5
|
+
readonly name = "ApprovalNotInitialized";
|
|
6
|
+
constructor();
|
|
7
|
+
}
|
|
8
|
+
export declare class UnknownApprovalKind extends ApprovalError {
|
|
9
|
+
readonly name = "UnknownApprovalKind";
|
|
10
|
+
constructor(kind: string, registered: readonly string[]);
|
|
11
|
+
}
|
|
12
|
+
export declare class HandlerForUnregisteredKind extends ApprovalError {
|
|
13
|
+
readonly name = "HandlerForUnregisteredKind";
|
|
14
|
+
constructor(kind: string, registered: readonly string[]);
|
|
15
|
+
}
|
|
16
|
+
export declare class HandlerNotRegistered extends ApprovalError {
|
|
17
|
+
readonly name = "HandlerNotRegistered";
|
|
18
|
+
constructor(kind: string);
|
|
19
|
+
}
|
|
20
|
+
export declare class IdempotencyKeyRequired extends ApprovalError {
|
|
21
|
+
readonly name = "IdempotencyKeyRequired";
|
|
22
|
+
constructor();
|
|
23
|
+
}
|
|
24
|
+
export declare class ReservedServiceNameViolation extends ApprovalError {
|
|
25
|
+
readonly name = "ReservedServiceNameViolation";
|
|
26
|
+
constructor(serviceName: string, reserved: readonly string[]);
|
|
27
|
+
}
|
|
28
|
+
export declare class InvalidApprovalSegment extends ApprovalError {
|
|
29
|
+
readonly which: "resource" | "verb";
|
|
30
|
+
readonly value: string;
|
|
31
|
+
readonly name = "InvalidApprovalSegment";
|
|
32
|
+
constructor(which: "resource" | "verb", value: string, msg: string);
|
|
33
|
+
}
|
|
34
|
+
export declare class ApproverRoleNotInCatalog extends ApprovalError {
|
|
35
|
+
readonly name = "ApproverRoleNotInCatalog";
|
|
36
|
+
constructor(approverRole: string, kindName: string, knownRoles: readonly string[]);
|
|
37
|
+
}
|
|
38
|
+
export declare class TaskCreationFailed extends ApprovalError {
|
|
39
|
+
readonly cause: unknown;
|
|
40
|
+
readonly name = "TaskCreationFailed";
|
|
41
|
+
constructor(cause: unknown, msg: string);
|
|
42
|
+
}
|
|
43
|
+
export declare class DeferredActionNotFound extends ApprovalError {
|
|
44
|
+
readonly name = "DeferredActionNotFound";
|
|
45
|
+
constructor(taskId: string);
|
|
46
|
+
}
|
|
47
|
+
export declare class TenantPolicyKindUnknown extends ApprovalError {
|
|
48
|
+
readonly name = "TenantPolicyKindUnknown";
|
|
49
|
+
constructor(kind: string);
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAMA,qBAAa,aAAc,SAAQ,KAAK;IACtC,SAAkB,IAAI,EAAE,MAAM,CAAmB;CAClD;AAED,qBAAa,sBAAuB,SAAQ,aAAa;IACvD,SAAkB,IAAI,4BAA4B;;CAOnD;AAED,qBAAa,mBAAoB,SAAQ,aAAa;IACpD,SAAkB,IAAI,yBAAyB;gBACnC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE;CAKxD;AAED,qBAAa,0BAA2B,SAAQ,aAAa;IAC3D,SAAkB,IAAI,gCAAgC;gBAC1C,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE;CAKxD;AAED,qBAAa,oBAAqB,SAAQ,aAAa;IACrD,SAAkB,IAAI,0BAA0B;gBACpC,IAAI,EAAE,MAAM;CAKzB;AAED,qBAAa,sBAAuB,SAAQ,aAAa;IACvD,SAAkB,IAAI,4BAA4B;;CAMnD;AAED,qBAAa,4BAA6B,SAAQ,aAAa;IAC7D,SAAkB,IAAI,kCAAkC;gBAC5C,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE;CAK7D;AAED,qBAAa,sBAAuB,SAAQ,aAAa;aAGrC,KAAK,EAAE,UAAU,GAAG,MAAM;aAC1B,KAAK,EAAE,MAAM;IAH/B,SAAkB,IAAI,4BAA4B;gBAEhC,KAAK,EAAE,UAAU,GAAG,MAAM,EAC1B,KAAK,EAAE,MAAM,EAC7B,GAAG,EAAE,MAAM;CAId;AAED,qBAAa,wBAAyB,SAAQ,aAAa;IACzD,SAAkB,IAAI,8BAA8B;gBAElD,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,SAAS,MAAM,EAAE;CAMhC;AAED,qBAAa,kBAAmB,SAAQ,aAAa;aAGjC,KAAK,EAAE,OAAO;IAFhC,SAAkB,IAAI,wBAAwB;gBAE5B,KAAK,EAAE,OAAO,EAC9B,GAAG,EAAE,MAAM;CAId;AAED,qBAAa,sBAAuB,SAAQ,aAAa;IACvD,SAAkB,IAAI,4BAA4B;gBACtC,MAAM,EAAE,MAAM;CAG3B;AAED,qBAAa,uBAAwB,SAAQ,aAAa;IACxD,SAAkB,IAAI,6BAA6B;gBACvC,IAAI,EAAE,MAAM;CAKzB"}
|