@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
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
declare const METRIC_FAMILY_REQUEST: "request_handler";
|
|
2
|
+
declare const METRIC_FAMILY_WORKER: "async_worker";
|
|
3
|
+
interface AuditEmitArgs {
|
|
4
|
+
/** Pass null when no tenant context is available (e.g. completion event
|
|
5
|
+
* for an unknown task_id). Downstream audit-chain queries filtering by
|
|
6
|
+
* tenant_id treat null correctly; literal strings like "unknown" would
|
|
7
|
+
* mingle with real tenant values. */
|
|
8
|
+
tenantId: string | null;
|
|
9
|
+
action: string;
|
|
10
|
+
targetKind?: string;
|
|
11
|
+
targetId?: string;
|
|
12
|
+
metadata?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
/** Emit a single audit row via @nodii/telemetry. Best-effort: errors do
|
|
15
|
+
* not propagate to the caller — but the FIRST failure per process
|
|
16
|
+
* warn-logs so operators see the wiring gap. */
|
|
17
|
+
export declare function emitAudit(args: AuditEmitArgs): void;
|
|
18
|
+
interface MetricInc {
|
|
19
|
+
name: string;
|
|
20
|
+
family: typeof METRIC_FAMILY_REQUEST | typeof METRIC_FAMILY_WORKER;
|
|
21
|
+
labels: Record<string, string | number>;
|
|
22
|
+
by?: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function inc(args: MetricInc): void;
|
|
25
|
+
interface HistObs {
|
|
26
|
+
name: string;
|
|
27
|
+
family: typeof METRIC_FAMILY_REQUEST | typeof METRIC_FAMILY_WORKER;
|
|
28
|
+
labels: Record<string, string | number>;
|
|
29
|
+
value: number;
|
|
30
|
+
}
|
|
31
|
+
export declare function observe(args: HistObs): void;
|
|
32
|
+
/** Test-only reset for the warn ledger so each test starts clean. */
|
|
33
|
+
export declare function _resetWarnLedgerForTests(): void;
|
|
34
|
+
export { METRIC_FAMILY_REQUEST, METRIC_FAMILY_WORKER };
|
|
35
|
+
//# sourceMappingURL=telemetry-shim.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry-shim.d.ts","sourceRoot":"","sources":["../src/telemetry-shim.ts"],"names":[],"mappings":"AAoBA,QAAA,MAAM,qBAAqB,EAAG,iBAA0B,CAAC;AACzD,QAAA,MAAM,oBAAoB,EAAG,cAAuB,CAAC;AAgBrD,UAAU,aAAa;IACrB;;;0CAGsC;IACtC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;iDAEiD;AACjD,wBAAgB,SAAS,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAuBnD;AAED,UAAU,SAAS;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,qBAAqB,GAAG,OAAO,oBAAoB,CAAC;IACnE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;IACxC,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,GAAG,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAUzC;AAED,UAAU,OAAO;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,qBAAqB,GAAG,OAAO,oBAAoB,CAAC;IACnE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;IACxC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAU3C;AAED,qEAAqE;AACrE,wBAAgB,wBAAwB,IAAI,IAAI,CAG/C;AAED,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Telemetry + audit emission shim per spec § 5.15.
|
|
2
|
+
//
|
|
3
|
+
// v0.1.0 posture:
|
|
4
|
+
// - audit.emit goes through @nodii/telemetry's `audit.emit({action,
|
|
5
|
+
// target_kind, target_id, payload})` per the audit.ts surface.
|
|
6
|
+
// Tenant_id flows in via `payload.tenant_id`; request_id, intent_id
|
|
7
|
+
// flow from the telemetry NodiiContext so we do NOT pass them inline.
|
|
8
|
+
// - Metrics are wired against @nodii/telemetry's vocabulary registry.
|
|
9
|
+
// Adopting services register the metric names from spec § 5.15 in
|
|
10
|
+
// their telemetry init's `vocabularyRegistry`. When unregistered,
|
|
11
|
+
// `MetricVocabularyError` is thrown — we catch at this boundary so
|
|
12
|
+
// a missing-vocab registration on the consumer side does not break
|
|
13
|
+
// the request path. The first swallow per metric name emits a
|
|
14
|
+
// warn-log via console.warn so adopters DO see the gap (the lib
|
|
15
|
+
// does NOT silently no-op forever).
|
|
16
|
+
//
|
|
17
|
+
// No Noop default. Real audit emission goes through the wired writer.
|
|
18
|
+
import { audit, metrics } from "@nodii/telemetry";
|
|
19
|
+
const METRIC_FAMILY_REQUEST = "request_handler";
|
|
20
|
+
const METRIC_FAMILY_WORKER = "async_worker";
|
|
21
|
+
/** Per-process one-shot warn ledger. Each metric name warn-logs once. */
|
|
22
|
+
const WARNED_METRICS = new Set();
|
|
23
|
+
let AUDIT_EMIT_FAILED_WARNED = false;
|
|
24
|
+
function warnOnce(metricName, err) {
|
|
25
|
+
if (WARNED_METRICS.has(metricName))
|
|
26
|
+
return;
|
|
27
|
+
WARNED_METRICS.add(metricName);
|
|
28
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
29
|
+
// biome-ignore lint/suspicious/noConsole: best-effort warn for missing vocab registration
|
|
30
|
+
console.warn(`[@nodii/approval] metric '${metricName}' swallowed: ${msg}. Register the approval metric vocabulary per spec § 5.15 in your initTelemetry({ vocabularyRegistry }) call. Further swallows of this metric will be silent.`);
|
|
31
|
+
}
|
|
32
|
+
/** Emit a single audit row via @nodii/telemetry. Best-effort: errors do
|
|
33
|
+
* not propagate to the caller — but the FIRST failure per process
|
|
34
|
+
* warn-logs so operators see the wiring gap. */
|
|
35
|
+
export function emitAudit(args) {
|
|
36
|
+
// Fire-and-forget. `audit.emit` returns a Promise; we intentionally
|
|
37
|
+
// do not await it so the request path stays low-latency. The writer
|
|
38
|
+
// wired by the adopting service is responsible for buffering + retry.
|
|
39
|
+
void (async () => {
|
|
40
|
+
try {
|
|
41
|
+
await audit.emit({
|
|
42
|
+
action: args.action,
|
|
43
|
+
target_kind: args.targetKind ?? "",
|
|
44
|
+
target_id: args.targetId ?? null,
|
|
45
|
+
payload: { tenant_id: args.tenantId, ...(args.metadata ?? {}) },
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
if (!AUDIT_EMIT_FAILED_WARNED) {
|
|
50
|
+
AUDIT_EMIT_FAILED_WARNED = true;
|
|
51
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
52
|
+
// biome-ignore lint/suspicious/noConsole: best-effort warn for audit-writer wiring gap
|
|
53
|
+
console.warn(`[@nodii/approval] audit.emit threw: ${msg}. Further audit failures will be silent. Wire an audit writer via initTelemetry({ auditWriter }) or @nodii/audit-chain's createChainAdapter.`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
})();
|
|
57
|
+
}
|
|
58
|
+
export function inc(args) {
|
|
59
|
+
try {
|
|
60
|
+
const handle = metrics.counter(args.name, {
|
|
61
|
+
family: args.family,
|
|
62
|
+
description: `@nodii/approval counter ${args.name}`,
|
|
63
|
+
});
|
|
64
|
+
handle.inc(args.by ?? 1, args.labels);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
warnOnce(args.name, err);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export function observe(args) {
|
|
71
|
+
try {
|
|
72
|
+
const handle = metrics.histogram(args.name, {
|
|
73
|
+
family: args.family,
|
|
74
|
+
description: `@nodii/approval histogram ${args.name}`,
|
|
75
|
+
});
|
|
76
|
+
handle.record(args.value, args.labels);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
warnOnce(args.name, err);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Test-only reset for the warn ledger so each test starts clean. */
|
|
83
|
+
export function _resetWarnLedgerForTests() {
|
|
84
|
+
WARNED_METRICS.clear();
|
|
85
|
+
AUDIT_EMIT_FAILED_WARNED = false;
|
|
86
|
+
}
|
|
87
|
+
export { METRIC_FAMILY_REQUEST, METRIC_FAMILY_WORKER };
|
|
88
|
+
//# sourceMappingURL=telemetry-shim.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry-shim.js","sourceRoot":"","sources":["../src/telemetry-shim.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import type { Redis as IoRedis } from "ioredis";
|
|
2
|
+
/** Generic SQL client surface — postgres.js-shaped, broad enough to cover
|
|
3
|
+
* both the pool handle and the in-transaction handle.
|
|
4
|
+
* biome-ignore lint/suspicious/noExplicitAny: porsager/postgres template-string overloads are wide. */
|
|
5
|
+
export type SqlExecArgs = readonly unknown[];
|
|
6
|
+
export type SqlExecResult = any;
|
|
7
|
+
export interface SqlExecutor {
|
|
8
|
+
unsafe(sql: string, args?: SqlExecArgs): Promise<SqlExecResult>;
|
|
9
|
+
}
|
|
10
|
+
export type SqlTxExecutor = SqlExecutor;
|
|
11
|
+
export interface SqlPoolExecutor extends SqlExecutor {
|
|
12
|
+
/** Run `fn` inside a single transaction (BEGIN/COMMIT/ROLLBACK). */
|
|
13
|
+
begin<T>(fn: (tx: SqlTxExecutor) => Promise<T>): Promise<T>;
|
|
14
|
+
}
|
|
15
|
+
/** Reserved approval-service-name namespaces, mirrored from role-catalog. */
|
|
16
|
+
export declare const RESERVED_SERVICE_NAMES: readonly ["platform", "system", "pii", "audit", "dsar"];
|
|
17
|
+
export type ReservedServiceName = (typeof RESERVED_SERVICE_NAMES)[number];
|
|
18
|
+
/** Input shape for one entry in `defineApprovalKinds({...})`. */
|
|
19
|
+
export interface ApprovalKindDefInput {
|
|
20
|
+
/** Lowercase + underscores; no dots; matches role-catalog's permission-segment rule. */
|
|
21
|
+
resource: string;
|
|
22
|
+
/** Lowercase + underscores; no dots. */
|
|
23
|
+
verb: string;
|
|
24
|
+
/** Human-readable description; surfaced into task-tracking. */
|
|
25
|
+
description?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Role declared in the module's role-catalog. Library cross-checks at
|
|
28
|
+
* `initApproval` time — `ApproverRoleNotInCatalog` on mismatch.
|
|
29
|
+
*/
|
|
30
|
+
approverRole: string;
|
|
31
|
+
/** Default true. ON => task created when no tenant policy row. */
|
|
32
|
+
optInDefault?: boolean;
|
|
33
|
+
/** Default false. When true + requester is in approver_role => short-circuit. */
|
|
34
|
+
selfActionAllowed?: boolean;
|
|
35
|
+
/** Override the global default expiry. */
|
|
36
|
+
expirySeconds?: number;
|
|
37
|
+
/** Optional override for the task-tracking action-button label. */
|
|
38
|
+
actionButtonLabel?: string;
|
|
39
|
+
}
|
|
40
|
+
/** Fully-resolved kind def stored in the runtime registry. */
|
|
41
|
+
export interface ApprovalKindDef {
|
|
42
|
+
/** Short kind without the service prefix: `<resource>.<verb>`. */
|
|
43
|
+
shortKind: string;
|
|
44
|
+
/** Fully-qualified kind: `<service>.<resource>.<verb>`. */
|
|
45
|
+
kind: string;
|
|
46
|
+
resource: string;
|
|
47
|
+
verb: string;
|
|
48
|
+
description: string;
|
|
49
|
+
approverRole: string;
|
|
50
|
+
optInDefault: boolean;
|
|
51
|
+
selfActionAllowed: boolean;
|
|
52
|
+
expirySeconds: number | undefined;
|
|
53
|
+
actionButtonLabel: string | undefined;
|
|
54
|
+
}
|
|
55
|
+
/** Result of `defineApprovalKinds<K, T>(serviceName, defs)`. */
|
|
56
|
+
export type DefinedKindMap<S extends string, T extends Record<string, ApprovalKindDefInput>> = {
|
|
57
|
+
[Name in keyof T]: ApprovalKindDef & {
|
|
58
|
+
kind: `${S}.${T[Name]["resource"]}.${T[Name]["verb"]}`;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
/** Handler triple registered per kind. */
|
|
62
|
+
export interface HandlerSet {
|
|
63
|
+
onApproved: (ctx: HandlerContext) => Promise<void>;
|
|
64
|
+
onRejected: (ctx: HandlerContext) => Promise<void>;
|
|
65
|
+
onExpired: (ctx: HandlerContext) => Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
/** Sentinel used when the lib short-circuits the request flow. */
|
|
68
|
+
export type ApproverKind = "user" | "audit_trail_only" | "self_action";
|
|
69
|
+
/** Context passed to every handler invocation. */
|
|
70
|
+
export interface HandlerContext {
|
|
71
|
+
tenantId: string;
|
|
72
|
+
deferredActionId: string;
|
|
73
|
+
deferredActionPayload: Record<string, unknown>;
|
|
74
|
+
requesterMembershipId: string;
|
|
75
|
+
approverMembershipId: string | null;
|
|
76
|
+
approverNote: string | null;
|
|
77
|
+
approverKind: ApproverKind;
|
|
78
|
+
kind: string;
|
|
79
|
+
metadata: Record<string, unknown>;
|
|
80
|
+
}
|
|
81
|
+
/** `requestApproval` input. */
|
|
82
|
+
export interface RequestApprovalOpts {
|
|
83
|
+
kind: string;
|
|
84
|
+
tenantId: string;
|
|
85
|
+
requesterMembershipId: string;
|
|
86
|
+
idempotencyKey: string;
|
|
87
|
+
/** Arbitrary JSON — library serialises + persists. */
|
|
88
|
+
deferredActionPayload: Record<string, unknown>;
|
|
89
|
+
/** Surfaced into task-tracking's `task.metadata`. */
|
|
90
|
+
metadata?: Record<string, unknown>;
|
|
91
|
+
/** Override per-call (rare — prefer kind-level default). */
|
|
92
|
+
expirySeconds?: number;
|
|
93
|
+
/**
|
|
94
|
+
* Saga id (optional). If omitted, library generates a deterministic
|
|
95
|
+
* synthetic saga id from `(kind, idempotencyKey)`.
|
|
96
|
+
*/
|
|
97
|
+
sagaId?: string;
|
|
98
|
+
/** Optional task-tracking action-button override. */
|
|
99
|
+
actionButton?: {
|
|
100
|
+
label: string;
|
|
101
|
+
moduleKey: string;
|
|
102
|
+
actionKey: string;
|
|
103
|
+
params: Record<string, unknown>;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/** `requestApproval` return value. */
|
|
107
|
+
export type RequestApprovalResultStatus = "task_created" | "self_action_executed" | "opt_in_disabled_executed" | "duplicate";
|
|
108
|
+
export interface RequestApprovalResult {
|
|
109
|
+
approvalId: string;
|
|
110
|
+
taskId: string | null;
|
|
111
|
+
status: RequestApprovalResultStatus;
|
|
112
|
+
}
|
|
113
|
+
/** Membership lookup function — service provides per its own RBAC store. */
|
|
114
|
+
export type MembershipRolesLookup = (tenantId: string, membershipId: string) => Promise<readonly string[]>;
|
|
115
|
+
/**
|
|
116
|
+
* gRPC client surface for `nodii-task-tracking.TaskService.CreateTask`.
|
|
117
|
+
* Library accepts an injectable client so integration tests can swap in
|
|
118
|
+
* an in-process implementation without burning proto codegen cycles.
|
|
119
|
+
* Production wiring: provide `taskServiceUrl` + library constructs a real
|
|
120
|
+
* grpc-js client wrapped with `@nodii/grpc-auth`'s S2S interceptor.
|
|
121
|
+
*/
|
|
122
|
+
export interface TaskServiceClient {
|
|
123
|
+
createTask(req: CreateTaskRequest): Promise<CreateTaskResponse>;
|
|
124
|
+
/** Optional — for graceful shutdown. */
|
|
125
|
+
close?: () => Promise<void>;
|
|
126
|
+
}
|
|
127
|
+
export interface CreateTaskRequest {
|
|
128
|
+
tenantId: string;
|
|
129
|
+
kind: string;
|
|
130
|
+
title: string;
|
|
131
|
+
description: string;
|
|
132
|
+
assigneeRole: string;
|
|
133
|
+
metadata: Record<string, unknown>;
|
|
134
|
+
/** ISO8601 timestamp. */
|
|
135
|
+
expiresAt: string;
|
|
136
|
+
idempotencyKey: string;
|
|
137
|
+
sourceModule: string;
|
|
138
|
+
actionButton: {
|
|
139
|
+
label: string;
|
|
140
|
+
moduleKey: string;
|
|
141
|
+
actionKey: string;
|
|
142
|
+
params: Record<string, unknown>;
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
export interface CreateTaskResponse {
|
|
146
|
+
taskId: string;
|
|
147
|
+
}
|
|
148
|
+
/** Inbound completion event shape. Library is transport-agnostic — the
|
|
149
|
+
* consumer is responsible for decoding the BullMQ job payload into this
|
|
150
|
+
* type. */
|
|
151
|
+
export interface TaskCompletedEvent {
|
|
152
|
+
/** Unique event id used for dedupe via `consumed_events`. */
|
|
153
|
+
eventId: string;
|
|
154
|
+
/** Source topic — always `tasks.task.completed.v1` in v0.1.0. */
|
|
155
|
+
topic: string;
|
|
156
|
+
/** Source task id. */
|
|
157
|
+
taskId: string;
|
|
158
|
+
/** approved | rejected | expired. */
|
|
159
|
+
outcome: "approved" | "rejected" | "expired";
|
|
160
|
+
/** Kind of the original task — should start with `approval:<service>.`. */
|
|
161
|
+
kind: string;
|
|
162
|
+
/** Task-tracking-resolved completion membership. NULL on `expired`. */
|
|
163
|
+
completedByMembershipId: string | null;
|
|
164
|
+
/** Free-form note. */
|
|
165
|
+
completionNote: string | null;
|
|
166
|
+
/** Task-tracking payload metadata (carries `approvalId`, `deferredActionId`). */
|
|
167
|
+
metadata: Record<string, unknown>;
|
|
168
|
+
/** ISO8601 — when task-tracking published. */
|
|
169
|
+
publishedAt?: string;
|
|
170
|
+
}
|
|
171
|
+
/** Resolved tenant-policy for one kind. */
|
|
172
|
+
export interface ResolvedPolicy {
|
|
173
|
+
enabled: boolean;
|
|
174
|
+
approverRole: string;
|
|
175
|
+
expirySeconds: number;
|
|
176
|
+
thresholdParams: Record<string, unknown> | null;
|
|
177
|
+
/** True when no per-tenant row exists; library used kind defaults. */
|
|
178
|
+
fallthrough: boolean;
|
|
179
|
+
}
|
|
180
|
+
/** `initApproval` config (spec § 5.1). */
|
|
181
|
+
export interface InitApprovalConfig {
|
|
182
|
+
serviceName: string;
|
|
183
|
+
/**
|
|
184
|
+
* Either supply a postgres.js-shaped pool handle (raw mode) OR a db-rls
|
|
185
|
+
* instance (RLS-aware mode). At least one must be provided.
|
|
186
|
+
*/
|
|
187
|
+
pgPool?: SqlPoolExecutor;
|
|
188
|
+
/** Optional db-rls handle (when wired). Both can coexist; rls wins. */
|
|
189
|
+
dbRls?: DbRlsLike;
|
|
190
|
+
redis: IoRedis;
|
|
191
|
+
/** Task-tracking gRPC URL. Ignored if `taskServiceClient` is provided. */
|
|
192
|
+
taskServiceUrl?: string;
|
|
193
|
+
/** Test-only override — supersedes `taskServiceUrl`. */
|
|
194
|
+
taskServiceClient?: TaskServiceClient;
|
|
195
|
+
/** Production knob: pass through to the internally-constructed
|
|
196
|
+
* `TaskTrackingGrpcClient` when `taskServiceUrl` is set. This is how
|
|
197
|
+
* adopting services layer in `@nodii/grpc-auth`'s S2S interceptor +
|
|
198
|
+
* TLS credentials + per-RPC deadline without bypassing initApproval. */
|
|
199
|
+
taskClientOptions?: {
|
|
200
|
+
interceptors?: ReadonlyArray<import("@grpc/grpc-js").Interceptor>;
|
|
201
|
+
credentials?: import("@grpc/grpc-js").ChannelCredentials;
|
|
202
|
+
deadlineMs?: number;
|
|
203
|
+
};
|
|
204
|
+
/** Service that publishes the kind-set this lib enforces. */
|
|
205
|
+
membershipRolesLookup: MembershipRolesLookup;
|
|
206
|
+
/** Default expiry across kinds without their own override. */
|
|
207
|
+
defaultExpirySeconds?: number;
|
|
208
|
+
/** BullMQ lease TTL for the completion consumer. */
|
|
209
|
+
consumerLeaseSeconds?: number;
|
|
210
|
+
/** Retry budget for completion-event handler failures. */
|
|
211
|
+
consumerMaxRetries?: number;
|
|
212
|
+
/** Module's permission catalog (used for approverRole cross-check). */
|
|
213
|
+
knownRoles?: readonly string[];
|
|
214
|
+
/** Optional structured logger. */
|
|
215
|
+
logger?: {
|
|
216
|
+
warn: (msg: string, fields?: Record<string, unknown>) => void;
|
|
217
|
+
info: (msg: string, fields?: Record<string, unknown>) => void;
|
|
218
|
+
error: (msg: string, fields?: Record<string, unknown>) => void;
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
/** Minimal db-rls surface used by the lib. The real `@nodii/db-rls` exposes
|
|
222
|
+
* more — we only depend on `withSystemContext({tenantId}, fn)`. */
|
|
223
|
+
export interface DbRlsLike {
|
|
224
|
+
withSystemContext<T>(args: {
|
|
225
|
+
tenantId: string;
|
|
226
|
+
}, fn: (tx: SqlPoolExecutor) => Promise<T>): Promise<T>;
|
|
227
|
+
pool: SqlPoolExecutor;
|
|
228
|
+
}
|
|
229
|
+
/** Process-global resolved config. */
|
|
230
|
+
export interface ResolvedApprovalConfig {
|
|
231
|
+
serviceName: string;
|
|
232
|
+
pgPool: SqlPoolExecutor;
|
|
233
|
+
dbRls: DbRlsLike | undefined;
|
|
234
|
+
redis: IoRedis;
|
|
235
|
+
taskServiceClient: TaskServiceClient;
|
|
236
|
+
membershipRolesLookup: MembershipRolesLookup;
|
|
237
|
+
defaultExpirySeconds: number;
|
|
238
|
+
consumerLeaseSeconds: number;
|
|
239
|
+
consumerMaxRetries: number;
|
|
240
|
+
knownRoles: ReadonlySet<string>;
|
|
241
|
+
logger: NonNullable<InitApprovalConfig["logger"]>;
|
|
242
|
+
}
|
|
243
|
+
/** Deferred-action row shape, mirroring `<service>_deferred_actions`. */
|
|
244
|
+
export interface DeferredActionRow {
|
|
245
|
+
id: string;
|
|
246
|
+
tenant_id: string;
|
|
247
|
+
kind: string;
|
|
248
|
+
requester_membership_id: string;
|
|
249
|
+
deferred_action_payload: Record<string, unknown>;
|
|
250
|
+
status: "pending" | "approved" | "rejected" | "expired" | "executed" | "failed";
|
|
251
|
+
task_id: string | null;
|
|
252
|
+
approver_membership_id: string | null;
|
|
253
|
+
approver_note: string | null;
|
|
254
|
+
requested_at: string;
|
|
255
|
+
decided_at: string | null;
|
|
256
|
+
executed_at: string | null;
|
|
257
|
+
failed_reason: string | null;
|
|
258
|
+
idempotency_key: string;
|
|
259
|
+
audit_correlation_id: string | null;
|
|
260
|
+
metadata: Record<string, unknown>;
|
|
261
|
+
}
|
|
262
|
+
/** Approval-policy row shape, mirroring `<service>_approval_policies`. */
|
|
263
|
+
export interface ApprovalPolicyRow {
|
|
264
|
+
id: string;
|
|
265
|
+
tenant_id: string;
|
|
266
|
+
kind: string;
|
|
267
|
+
enabled: boolean;
|
|
268
|
+
approver_role: string;
|
|
269
|
+
threshold_params: Record<string, unknown> | null;
|
|
270
|
+
expiry_seconds: number | null;
|
|
271
|
+
created_at: string;
|
|
272
|
+
updated_at: string;
|
|
273
|
+
}
|
|
274
|
+
/** Constants used by the consumer + retry budget. */
|
|
275
|
+
export declare const DEFAULT_EXPIRY_SECONDS: number;
|
|
276
|
+
export declare const DEFAULT_CONSUMER_LEASE_SECONDS = 30;
|
|
277
|
+
export declare const DEFAULT_CONSUMER_MAX_RETRIES = 5;
|
|
278
|
+
export declare const DEFAULT_IDEMPOTENCY_TTL_SECONDS: number;
|
|
279
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Public type surface for @nodii/approval per spec § 5.1 / § 5.16.
|
|
2
|
+
//
|
|
3
|
+
// Structural shapes match the spec verbatim. Polyglot peers (Python +
|
|
4
|
+
// Go) mirror these names in their language idioms.
|
|
5
|
+
/** Reserved approval-service-name namespaces, mirrored from role-catalog. */
|
|
6
|
+
export const RESERVED_SERVICE_NAMES = [
|
|
7
|
+
"platform",
|
|
8
|
+
"system",
|
|
9
|
+
"pii",
|
|
10
|
+
"audit",
|
|
11
|
+
"dsar",
|
|
12
|
+
];
|
|
13
|
+
/** Constants used by the consumer + retry budget. */
|
|
14
|
+
export const DEFAULT_EXPIRY_SECONDS = 7 * 24 * 3600; // 7 days
|
|
15
|
+
export const DEFAULT_CONSUMER_LEASE_SECONDS = 30;
|
|
16
|
+
export const DEFAULT_CONSUMER_MAX_RETRIES = 5;
|
|
17
|
+
export const DEFAULT_IDEMPOTENCY_TTL_SECONDS = 24 * 3600; // 24h
|
|
18
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,EAAE;AACF,sEAAsE;AACtE,mDAAmD;AAsBnD,6EAA6E;AAC7E,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,UAAU;IACV,QAAQ;IACR,KAAK;IACL,OAAO;IACP,MAAM;CACE,CAAC;AAwSX,qDAAqD;AACrD,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,SAAS;AAC9D,MAAM,CAAC,MAAM,8BAA8B,GAAG,EAAE,CAAC;AACjD,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,CAAC;AAC9C,MAAM,CAAC,MAAM,+BAA+B,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,MAAM"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function validateServiceName(serviceName: string, opts?: {
|
|
2
|
+
bypassReservedNamespaceCheck?: boolean;
|
|
3
|
+
}): void;
|
|
4
|
+
export declare function validatePermissionSegment(value: string, which: "resource" | "verb"): void;
|
|
5
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAgBA,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE;IAAE,4BAA4B,CAAC,EAAE,OAAO,CAAA;CAAO,GACpD,IAAI,CA+BN;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,UAAU,GAAG,MAAM,GACzB,IAAI,CAsBN"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Validators for service name + resource + verb segments per spec § 5.3.
|
|
2
|
+
// Reuses role-catalog's segment shape regex so the two surfaces stay in
|
|
3
|
+
// lockstep.
|
|
4
|
+
import { InvalidApprovalSegment, ReservedServiceNameViolation } from "./errors";
|
|
5
|
+
import { RESERVED_SERVICE_NAMES } from "./types";
|
|
6
|
+
/** Segment regex: lowercase, first char alpha, alnum + underscore only.
|
|
7
|
+
* Kept identical to the SERVICE_NAME_RE because the service name is
|
|
8
|
+
* interpolated unquoted into Postgres table identifiers
|
|
9
|
+
* (`<service>_deferred_actions` etc.) — hyphens would yield invalid
|
|
10
|
+
* SQL identifiers like `task-tracking_deferred_actions`. */
|
|
11
|
+
const SEGMENT_RE = /^[a-z][a-z0-9_]*$/;
|
|
12
|
+
const SERVICE_NAME_RE = /^[a-z][a-z0-9_]*$/;
|
|
13
|
+
const MAX_LEN = 50;
|
|
14
|
+
export function validateServiceName(serviceName, opts = {}) {
|
|
15
|
+
if (!serviceName || typeof serviceName !== "string") {
|
|
16
|
+
throw new InvalidApprovalSegment("resource", String(serviceName), "serviceName must be a non-empty string");
|
|
17
|
+
}
|
|
18
|
+
if (serviceName.length > MAX_LEN) {
|
|
19
|
+
throw new InvalidApprovalSegment("resource", serviceName, `serviceName '${serviceName}' exceeds max length ${MAX_LEN}`);
|
|
20
|
+
}
|
|
21
|
+
if (!SERVICE_NAME_RE.test(serviceName)) {
|
|
22
|
+
throw new InvalidApprovalSegment("resource", serviceName, `serviceName '${serviceName}' must match ${SERVICE_NAME_RE.source}`);
|
|
23
|
+
}
|
|
24
|
+
if (!opts.bypassReservedNamespaceCheck &&
|
|
25
|
+
RESERVED_SERVICE_NAMES.includes(serviceName)) {
|
|
26
|
+
throw new ReservedServiceNameViolation(serviceName, RESERVED_SERVICE_NAMES);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function validatePermissionSegment(value, which) {
|
|
30
|
+
if (!value || typeof value !== "string" || value.length === 0) {
|
|
31
|
+
throw new InvalidApprovalSegment(which, String(value), `${which} must be non-empty`);
|
|
32
|
+
}
|
|
33
|
+
if (value.length > MAX_LEN) {
|
|
34
|
+
throw new InvalidApprovalSegment(which, value, `${which} '${value}' exceeds max length ${MAX_LEN}`);
|
|
35
|
+
}
|
|
36
|
+
if (!SEGMENT_RE.test(value)) {
|
|
37
|
+
throw new InvalidApprovalSegment(which, value, `${which} '${value}' must match ${SEGMENT_RE.source} (lowercase, start with letter, alnum + underscore only)`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,wEAAwE;AACxE,YAAY;AAEZ,OAAO,EAAE,sBAAsB,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAEjD;;;;6DAI6D;AAC7D,MAAM,UAAU,GAAG,mBAAmB,CAAC;AACvC,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAC5C,MAAM,OAAO,GAAG,EAAE,CAAC;AAEnB,MAAM,UAAU,mBAAmB,CACjC,WAAmB,EACnB,OAAmD,EAAE;IAErD,IAAI,CAAC,WAAW,IAAI,OAAO,WAAW,KAAK,QAAQ,EAAE,CAAC;QACpD,MAAM,IAAI,sBAAsB,CAC9B,UAAU,EACV,MAAM,CAAC,WAAW,CAAC,EACnB,wCAAwC,CACzC,CAAC;IACJ,CAAC;IACD,IAAI,WAAW,CAAC,MAAM,GAAG,OAAO,EAAE,CAAC;QACjC,MAAM,IAAI,sBAAsB,CAC9B,UAAU,EACV,WAAW,EACX,gBAAgB,WAAW,wBAAwB,OAAO,EAAE,CAC7D,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,sBAAsB,CAC9B,UAAU,EACV,WAAW,EACX,gBAAgB,WAAW,gBAAgB,eAAe,CAAC,MAAM,EAAE,CACpE,CAAC;IACJ,CAAC;IACD,IACE,CAAC,IAAI,CAAC,4BAA4B;QACjC,sBAA4C,CAAC,QAAQ,CAAC,WAAW,CAAC,EACnE,CAAC;QACD,MAAM,IAAI,4BAA4B,CACpC,WAAW,EACX,sBAA2C,CAC5C,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,KAAa,EACb,KAA0B;IAE1B,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9D,MAAM,IAAI,sBAAsB,CAC9B,KAAK,EACL,MAAM,CAAC,KAAK,CAAC,EACb,GAAG,KAAK,oBAAoB,CAC7B,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,OAAO,EAAE,CAAC;QAC3B,MAAM,IAAI,sBAAsB,CAC9B,KAAK,EACL,KAAK,EACL,GAAG,KAAK,KAAK,KAAK,wBAAwB,OAAO,EAAE,CACpD,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,sBAAsB,CAC9B,KAAK,EACL,KAAK,EACL,GAAG,KAAK,KAAK,KAAK,gBAAgB,UAAU,CAAC,MAAM,0DAA0D,CAC9G,CAAC;IACJ,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nodii/approval",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Approval library for the Nodii microservice stack — defineApprovalKinds + requestApproval + bindApprovalHandlers + per-tenant policies + completion-event consumer + migrate-gen. Polyglot ship: TS + Python + Go in parity. Spec: planning hub docKey=approval.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -12,13 +12,54 @@
|
|
|
12
12
|
"default": "./dist/index.js"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
|
-
"files": [
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"src/migrations/001-approval-policies.sql",
|
|
19
|
+
"src/migrations/002-deferred-actions.sql"
|
|
20
|
+
],
|
|
16
21
|
"publishConfig": {
|
|
17
22
|
"access": "public"
|
|
18
23
|
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"test": "bun test"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@grpc/grpc-js": "^1.13.0",
|
|
31
|
+
"bullmq": "^5.20.0",
|
|
32
|
+
"ioredis": "^5.4.0",
|
|
33
|
+
"postgres": "^3.4.0"
|
|
34
|
+
},
|
|
35
|
+
"peerDependenciesMeta": {
|
|
36
|
+
"postgres": {
|
|
37
|
+
"optional": true
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@nodii/audit-chain": "0.2.5",
|
|
42
|
+
"@nodii/grpc-auth": "0.2.0",
|
|
43
|
+
"@nodii/idempotency": "0.2.5",
|
|
44
|
+
"@nodii/role-catalog": "0.1.3",
|
|
45
|
+
"@nodii/telemetry": "0.3.0",
|
|
46
|
+
"@grpc/grpc-js": "^1.13.0",
|
|
47
|
+
"@types/bun": "^1.3.13",
|
|
48
|
+
"bullmq": "^5.20.0",
|
|
49
|
+
"ioredis": "^5.4.0",
|
|
50
|
+
"postgres": "^3.4.0",
|
|
51
|
+
"typescript": "^5.9.3"
|
|
52
|
+
},
|
|
19
53
|
"repository": {
|
|
20
54
|
"type": "git",
|
|
21
55
|
"url": "git+https://github.com/cognion-nucleus/nodii-libs.git",
|
|
22
56
|
"directory": "ts/approval"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@nodii/audit-chain": "0.2.5",
|
|
60
|
+
"@nodii/grpc-auth": "0.2.0",
|
|
61
|
+
"@nodii/idempotency": "0.2.5",
|
|
62
|
+
"@nodii/role-catalog": "0.1.3",
|
|
63
|
+
"@nodii/telemetry": "0.3.0"
|
|
23
64
|
}
|
|
24
65
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
-- @nodii/approval migration 001
|
|
2
|
+
-- Generates the per-service `<service>_approval_policies` table per spec § 5.4.
|
|
3
|
+
-- Substitute `__SERVICE__` with the adopting service's name before running.
|
|
4
|
+
|
|
5
|
+
CREATE TABLE IF NOT EXISTS __SERVICE___approval_policies (
|
|
6
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
7
|
+
tenant_id UUID NOT NULL,
|
|
8
|
+
kind TEXT NOT NULL,
|
|
9
|
+
enabled BOOLEAN NOT NULL DEFAULT true,
|
|
10
|
+
approver_role TEXT NOT NULL,
|
|
11
|
+
threshold_params JSONB,
|
|
12
|
+
expiry_seconds INTEGER,
|
|
13
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
14
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
15
|
+
UNIQUE (tenant_id, kind)
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
CREATE INDEX IF NOT EXISTS __SERVICE___approval_policies_by_tenant
|
|
19
|
+
ON __SERVICE___approval_policies (tenant_id);
|
|
20
|
+
|
|
21
|
+
-- NOTE: RLS is the adopter's responsibility — this migration does NOT emit
|
|
22
|
+
-- ALTER TABLE ... ENABLE ROW LEVEL SECURITY. Services with @nodii/db-rls
|
|
23
|
+
-- ENABLE + FORCE RLS and add the standard 3-policy pattern (per
|
|
24
|
+
-- 08-rls-doctrine § 4) via their own migration tooling — the policy
|
|
25
|
+
-- predicates need the service's tenant-context GUC, which the lib does
|
|
26
|
+
-- not own. Services in raw-pg mode (platform-tenant scope) leave RLS off
|
|
27
|
+
-- by design.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
-- @nodii/approval migration 002
|
|
2
|
+
-- Generates the per-service `<service>_deferred_actions` table per spec § 5.6.
|
|
3
|
+
-- Substitute `__SERVICE__` with the adopting service's name before running.
|
|
4
|
+
|
|
5
|
+
CREATE TABLE IF NOT EXISTS __SERVICE___deferred_actions (
|
|
6
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
7
|
+
tenant_id UUID NOT NULL,
|
|
8
|
+
kind TEXT NOT NULL,
|
|
9
|
+
requester_membership_id UUID NOT NULL,
|
|
10
|
+
deferred_action_payload JSONB NOT NULL,
|
|
11
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
12
|
+
task_id UUID,
|
|
13
|
+
approver_membership_id UUID,
|
|
14
|
+
approver_note TEXT,
|
|
15
|
+
requested_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
16
|
+
decided_at TIMESTAMPTZ,
|
|
17
|
+
executed_at TIMESTAMPTZ,
|
|
18
|
+
failed_reason TEXT,
|
|
19
|
+
idempotency_key TEXT NOT NULL,
|
|
20
|
+
audit_correlation_id TEXT,
|
|
21
|
+
metadata JSONB DEFAULT '{}'::jsonb
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
CREATE INDEX IF NOT EXISTS __SERVICE___deferred_actions_by_task
|
|
25
|
+
ON __SERVICE___deferred_actions (task_id) WHERE task_id IS NOT NULL;
|
|
26
|
+
|
|
27
|
+
CREATE INDEX IF NOT EXISTS __SERVICE___deferred_actions_pending
|
|
28
|
+
ON __SERVICE___deferred_actions (tenant_id, kind) WHERE status = 'pending';
|
|
29
|
+
|
|
30
|
+
CREATE INDEX IF NOT EXISTS __SERVICE___deferred_actions_by_idempotency
|
|
31
|
+
ON __SERVICE___deferred_actions (tenant_id, idempotency_key);
|
|
32
|
+
|
|
33
|
+
-- consumed_events table — shared dedupe table per replica-consumer pattern.
|
|
34
|
+
-- Idempotent CREATE allows multiple nodii-libs adoptions to coexist without
|
|
35
|
+
-- conflict.
|
|
36
|
+
CREATE TABLE IF NOT EXISTS consumed_events (
|
|
37
|
+
event_id TEXT NOT NULL,
|
|
38
|
+
topic TEXT NOT NULL,
|
|
39
|
+
consumed_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
40
|
+
PRIMARY KEY (event_id, topic)
|
|
41
|
+
);
|