@contractspec/integration.runtime 2.10.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/channel/dispatcher.d.ts +37 -0
  2. package/dist/channel/dispatcher.js +130 -0
  3. package/dist/channel/dispatcher.test.d.ts +1 -0
  4. package/dist/channel/github.d.ts +47 -0
  5. package/dist/channel/github.js +58 -0
  6. package/dist/channel/github.test.d.ts +1 -0
  7. package/dist/channel/index.d.ts +14 -0
  8. package/dist/channel/index.js +1420 -0
  9. package/dist/channel/memory-store.d.ts +28 -0
  10. package/dist/channel/memory-store.js +223 -0
  11. package/dist/channel/policy.d.ts +19 -0
  12. package/dist/channel/policy.js +110 -0
  13. package/dist/channel/policy.test.d.ts +1 -0
  14. package/dist/channel/postgres-queries.d.ts +11 -0
  15. package/dist/channel/postgres-queries.js +222 -0
  16. package/dist/channel/postgres-schema.d.ts +1 -0
  17. package/dist/channel/postgres-schema.js +94 -0
  18. package/dist/channel/postgres-store.d.ts +21 -0
  19. package/dist/channel/postgres-store.js +498 -0
  20. package/dist/channel/postgres-store.test.d.ts +1 -0
  21. package/dist/channel/replay-fixtures.d.ts +8 -0
  22. package/dist/channel/replay-fixtures.js +31 -0
  23. package/dist/channel/replay.test.d.ts +1 -0
  24. package/dist/channel/service.d.ts +26 -0
  25. package/dist/channel/service.js +287 -0
  26. package/dist/channel/service.test.d.ts +1 -0
  27. package/dist/channel/slack.d.ts +42 -0
  28. package/dist/channel/slack.js +82 -0
  29. package/dist/channel/slack.test.d.ts +1 -0
  30. package/dist/channel/store.d.ts +83 -0
  31. package/dist/channel/store.js +1 -0
  32. package/dist/channel/telemetry.d.ts +17 -0
  33. package/dist/channel/telemetry.js +1 -0
  34. package/dist/channel/types.d.ts +111 -0
  35. package/dist/channel/types.js +1 -0
  36. package/dist/channel/whatsapp-meta.d.ts +55 -0
  37. package/dist/channel/whatsapp-meta.js +66 -0
  38. package/dist/channel/whatsapp-meta.test.d.ts +1 -0
  39. package/dist/channel/whatsapp-twilio.d.ts +20 -0
  40. package/dist/channel/whatsapp-twilio.js +61 -0
  41. package/dist/channel/whatsapp-twilio.test.d.ts +1 -0
  42. package/dist/index.d.ts +1 -0
  43. package/dist/index.js +1418 -1
  44. package/dist/node/channel/dispatcher.js +129 -0
  45. package/dist/node/channel/github.js +57 -0
  46. package/dist/node/channel/index.js +1419 -0
  47. package/dist/node/channel/memory-store.js +222 -0
  48. package/dist/node/channel/policy.js +109 -0
  49. package/dist/node/channel/postgres-queries.js +221 -0
  50. package/dist/node/channel/postgres-schema.js +93 -0
  51. package/dist/node/channel/postgres-store.js +497 -0
  52. package/dist/node/channel/replay-fixtures.js +30 -0
  53. package/dist/node/channel/service.js +286 -0
  54. package/dist/node/channel/slack.js +81 -0
  55. package/dist/node/channel/store.js +0 -0
  56. package/dist/node/channel/telemetry.js +0 -0
  57. package/dist/node/channel/types.js +0 -0
  58. package/dist/node/channel/whatsapp-meta.js +65 -0
  59. package/dist/node/channel/whatsapp-twilio.js +60 -0
  60. package/dist/node/index.js +1418 -1
  61. package/dist/runtime.health.test.d.ts +1 -0
  62. package/package.json +213 -6
@@ -0,0 +1,28 @@
1
+ import type { ChannelDecisionRecord, ChannelDeliveryAttemptRecord, ChannelEventReceiptRecord, ChannelOutboxActionRecord, ChannelThreadRecord } from './types';
2
+ import type { ChannelRuntimeStore, ClaimEventReceiptInput, ClaimEventReceiptResult, EnqueueOutboxActionInput, EnqueueOutboxActionResult, MarkOutboxDeadLetterInput, MarkOutboxRetryInput, RecordDeliveryAttemptInput, SaveDecisionInput, UpsertThreadInput } from './store';
3
+ export declare class InMemoryChannelRuntimeStore implements ChannelRuntimeStore {
4
+ readonly receipts: Map<string, ChannelEventReceiptRecord>;
5
+ readonly threads: Map<string, ChannelThreadRecord>;
6
+ readonly decisions: Map<string, ChannelDecisionRecord>;
7
+ readonly outbox: Map<string, ChannelOutboxActionRecord>;
8
+ readonly deliveryAttempts: Map<string, ChannelDeliveryAttemptRecord>;
9
+ private readonly receiptKeyToId;
10
+ private readonly threadKeyToId;
11
+ private readonly outboxKeyToId;
12
+ private deliveryAttemptSequence;
13
+ claimEventReceipt(input: ClaimEventReceiptInput): Promise<ClaimEventReceiptResult>;
14
+ updateReceiptStatus(receiptId: string, status: ChannelEventReceiptRecord['status'], error?: {
15
+ code: string;
16
+ message: string;
17
+ }): Promise<void>;
18
+ upsertThread(input: UpsertThreadInput): Promise<ChannelThreadRecord>;
19
+ saveDecision(input: SaveDecisionInput): Promise<ChannelDecisionRecord>;
20
+ enqueueOutboxAction(input: EnqueueOutboxActionInput): Promise<EnqueueOutboxActionResult>;
21
+ claimPendingOutboxActions(limit: number, now?: Date): Promise<ChannelOutboxActionRecord[]>;
22
+ recordDeliveryAttempt(input: RecordDeliveryAttemptInput): Promise<ChannelDeliveryAttemptRecord>;
23
+ markOutboxSent(actionId: string, providerMessageId?: string): Promise<void>;
24
+ markOutboxRetry(input: MarkOutboxRetryInput): Promise<void>;
25
+ markOutboxDeadLetter(input: MarkOutboxDeadLetterInput): Promise<void>;
26
+ private receiptKey;
27
+ private threadKey;
28
+ }
@@ -0,0 +1,223 @@
1
+ // @bun
2
+ // src/channel/memory-store.ts
3
+ import { randomUUID } from "crypto";
4
+
5
+ class InMemoryChannelRuntimeStore {
6
+ receipts = new Map;
7
+ threads = new Map;
8
+ decisions = new Map;
9
+ outbox = new Map;
10
+ deliveryAttempts = new Map;
11
+ receiptKeyToId = new Map;
12
+ threadKeyToId = new Map;
13
+ outboxKeyToId = new Map;
14
+ deliveryAttemptSequence = 0;
15
+ async claimEventReceipt(input) {
16
+ const key = this.receiptKey(input);
17
+ const existingId = this.receiptKeyToId.get(key);
18
+ if (existingId) {
19
+ const existing = this.receipts.get(existingId);
20
+ if (existing) {
21
+ existing.lastSeenAt = new Date;
22
+ this.receipts.set(existing.id, existing);
23
+ }
24
+ return {
25
+ receiptId: existingId,
26
+ duplicate: true
27
+ };
28
+ }
29
+ const id = randomUUID();
30
+ const now = new Date;
31
+ this.receipts.set(id, {
32
+ id,
33
+ workspaceId: input.workspaceId,
34
+ providerKey: input.providerKey,
35
+ externalEventId: input.externalEventId,
36
+ eventType: input.eventType,
37
+ status: "accepted",
38
+ signatureValid: input.signatureValid,
39
+ payloadHash: input.payloadHash,
40
+ traceId: input.traceId,
41
+ firstSeenAt: now,
42
+ lastSeenAt: now
43
+ });
44
+ this.receiptKeyToId.set(key, id);
45
+ return { receiptId: id, duplicate: false };
46
+ }
47
+ async updateReceiptStatus(receiptId, status, error) {
48
+ const receipt = this.receipts.get(receiptId);
49
+ if (!receipt) {
50
+ return;
51
+ }
52
+ receipt.status = status;
53
+ receipt.lastSeenAt = new Date;
54
+ if (status === "processed") {
55
+ receipt.processedAt = new Date;
56
+ }
57
+ receipt.errorCode = error?.code;
58
+ receipt.errorMessage = error?.message;
59
+ this.receipts.set(receiptId, receipt);
60
+ }
61
+ async upsertThread(input) {
62
+ const key = this.threadKey(input);
63
+ const existingId = this.threadKeyToId.get(key);
64
+ if (existingId) {
65
+ const existing = this.threads.get(existingId);
66
+ if (!existing) {
67
+ throw new Error("Corrupted thread state");
68
+ }
69
+ existing.externalChannelId = input.externalChannelId ?? existing.externalChannelId;
70
+ existing.externalUserId = input.externalUserId ?? existing.externalUserId;
71
+ existing.lastProviderEventAt = input.occurredAt ?? existing.lastProviderEventAt;
72
+ existing.updatedAt = new Date;
73
+ if (input.state) {
74
+ existing.state = {
75
+ ...existing.state,
76
+ ...input.state
77
+ };
78
+ }
79
+ this.threads.set(existing.id, existing);
80
+ return existing;
81
+ }
82
+ const id = randomUUID();
83
+ const now = new Date;
84
+ const record = {
85
+ id,
86
+ workspaceId: input.workspaceId,
87
+ providerKey: input.providerKey,
88
+ externalThreadId: input.externalThreadId,
89
+ externalChannelId: input.externalChannelId,
90
+ externalUserId: input.externalUserId,
91
+ state: input.state ?? {},
92
+ lastProviderEventAt: input.occurredAt,
93
+ createdAt: now,
94
+ updatedAt: now
95
+ };
96
+ this.threads.set(id, record);
97
+ this.threadKeyToId.set(key, id);
98
+ return record;
99
+ }
100
+ async saveDecision(input) {
101
+ const id = randomUUID();
102
+ const record = {
103
+ id,
104
+ receiptId: input.receiptId,
105
+ threadId: input.threadId,
106
+ policyMode: input.policyMode,
107
+ riskTier: input.riskTier,
108
+ confidence: input.confidence,
109
+ modelName: input.modelName,
110
+ promptVersion: input.promptVersion,
111
+ policyVersion: input.policyVersion,
112
+ toolTrace: input.toolTrace ?? [],
113
+ actionPlan: input.actionPlan,
114
+ requiresApproval: input.requiresApproval,
115
+ createdAt: new Date
116
+ };
117
+ this.decisions.set(id, record);
118
+ return record;
119
+ }
120
+ async enqueueOutboxAction(input) {
121
+ const existingId = this.outboxKeyToId.get(input.idempotencyKey);
122
+ if (existingId) {
123
+ return {
124
+ actionId: existingId,
125
+ duplicate: true
126
+ };
127
+ }
128
+ const id = randomUUID();
129
+ const now = new Date;
130
+ this.outbox.set(id, {
131
+ id,
132
+ workspaceId: input.workspaceId,
133
+ providerKey: input.providerKey,
134
+ decisionId: input.decisionId,
135
+ threadId: input.threadId,
136
+ actionType: input.actionType,
137
+ idempotencyKey: input.idempotencyKey,
138
+ target: input.target,
139
+ payload: input.payload,
140
+ status: "pending",
141
+ attemptCount: 0,
142
+ nextAttemptAt: now,
143
+ createdAt: now,
144
+ updatedAt: now
145
+ });
146
+ this.outboxKeyToId.set(input.idempotencyKey, id);
147
+ return {
148
+ actionId: id,
149
+ duplicate: false
150
+ };
151
+ }
152
+ async claimPendingOutboxActions(limit, now = new Date) {
153
+ const items = Array.from(this.outbox.values()).filter((item) => (item.status === "pending" || item.status === "retryable") && item.nextAttemptAt.getTime() <= now.getTime()).sort((a, b) => a.nextAttemptAt.getTime() - b.nextAttemptAt.getTime()).slice(0, Math.max(1, limit));
154
+ const claimed = [];
155
+ for (const item of items) {
156
+ const updated = {
157
+ ...item,
158
+ status: "sending",
159
+ attemptCount: item.attemptCount + 1,
160
+ updatedAt: new Date
161
+ };
162
+ this.outbox.set(updated.id, updated);
163
+ claimed.push(updated);
164
+ }
165
+ return claimed;
166
+ }
167
+ async recordDeliveryAttempt(input) {
168
+ this.deliveryAttemptSequence += 1;
169
+ const record = {
170
+ id: this.deliveryAttemptSequence,
171
+ actionId: input.actionId,
172
+ attempt: input.attempt,
173
+ responseStatus: input.responseStatus,
174
+ responseBody: input.responseBody,
175
+ latencyMs: input.latencyMs,
176
+ createdAt: new Date
177
+ };
178
+ this.deliveryAttempts.set(`${input.actionId}:${input.attempt}`, record);
179
+ return record;
180
+ }
181
+ async markOutboxSent(actionId, providerMessageId) {
182
+ const item = this.outbox.get(actionId);
183
+ if (!item)
184
+ return;
185
+ item.status = "sent";
186
+ item.providerMessageId = providerMessageId;
187
+ item.sentAt = new Date;
188
+ item.lastErrorCode = undefined;
189
+ item.lastErrorMessage = undefined;
190
+ item.updatedAt = new Date;
191
+ this.outbox.set(actionId, item);
192
+ }
193
+ async markOutboxRetry(input) {
194
+ const item = this.outbox.get(input.actionId);
195
+ if (!item)
196
+ return;
197
+ item.status = "retryable";
198
+ item.nextAttemptAt = input.nextAttemptAt;
199
+ item.lastErrorCode = input.lastErrorCode;
200
+ item.lastErrorMessage = input.lastErrorMessage;
201
+ item.updatedAt = new Date;
202
+ this.outbox.set(input.actionId, item);
203
+ }
204
+ async markOutboxDeadLetter(input) {
205
+ const item = this.outbox.get(input.actionId);
206
+ if (!item)
207
+ return;
208
+ item.status = "dead_letter";
209
+ item.lastErrorCode = input.lastErrorCode;
210
+ item.lastErrorMessage = input.lastErrorMessage;
211
+ item.updatedAt = new Date;
212
+ this.outbox.set(input.actionId, item);
213
+ }
214
+ receiptKey(input) {
215
+ return `${input.workspaceId}:${input.providerKey}:${input.externalEventId}`;
216
+ }
217
+ threadKey(input) {
218
+ return `${input.workspaceId}:${input.providerKey}:${input.externalThreadId}`;
219
+ }
220
+ }
221
+ export {
222
+ InMemoryChannelRuntimeStore
223
+ };
@@ -0,0 +1,19 @@
1
+ import type { ChannelInboundEvent, ChannelPolicyDecision } from './types';
2
+ export interface MessagingPolicyConfig {
3
+ autoResolveMinConfidence: number;
4
+ assistMinConfidence: number;
5
+ blockedSignals: string[];
6
+ highRiskSignals: string[];
7
+ mediumRiskSignals: string[];
8
+ safeAckTemplate: string;
9
+ }
10
+ export declare const DEFAULT_MESSAGING_POLICY_CONFIG: MessagingPolicyConfig;
11
+ export interface PolicyEvaluationInput {
12
+ event: ChannelInboundEvent;
13
+ }
14
+ export declare class MessagingPolicyEngine {
15
+ private readonly config;
16
+ constructor(config?: Partial<MessagingPolicyConfig>);
17
+ evaluate(input: PolicyEvaluationInput): ChannelPolicyDecision;
18
+ private defaultResponseText;
19
+ }
@@ -0,0 +1,110 @@
1
+ // @bun
2
+ // src/channel/policy.ts
3
+ var DEFAULT_MESSAGING_POLICY_CONFIG = {
4
+ autoResolveMinConfidence: 0.85,
5
+ assistMinConfidence: 0.65,
6
+ blockedSignals: [
7
+ "ignore previous instructions",
8
+ "reveal secret",
9
+ "api key",
10
+ "password",
11
+ "token",
12
+ "drop table",
13
+ "delete repository"
14
+ ],
15
+ highRiskSignals: [
16
+ "refund",
17
+ "delete account",
18
+ "cancel subscription",
19
+ "permission",
20
+ "admin access",
21
+ "wire transfer",
22
+ "bank account"
23
+ ],
24
+ mediumRiskSignals: [
25
+ "urgent",
26
+ "legal",
27
+ "compliance",
28
+ "frustrated",
29
+ "escalate",
30
+ "outage"
31
+ ],
32
+ safeAckTemplate: "Thanks for your message. We received it and are preparing the next step."
33
+ };
34
+
35
+ class MessagingPolicyEngine {
36
+ config;
37
+ constructor(config) {
38
+ this.config = {
39
+ ...DEFAULT_MESSAGING_POLICY_CONFIG,
40
+ ...config ?? {}
41
+ };
42
+ }
43
+ evaluate(input) {
44
+ const text = (input.event.message?.text ?? "").toLowerCase();
45
+ if (containsAny(text, this.config.blockedSignals)) {
46
+ return {
47
+ confidence: 0.2,
48
+ riskTier: "blocked",
49
+ verdict: "blocked",
50
+ reasons: ["blocked_signal_detected"],
51
+ responseText: this.config.safeAckTemplate,
52
+ requiresApproval: true
53
+ };
54
+ }
55
+ if (containsAny(text, this.config.highRiskSignals)) {
56
+ return {
57
+ confidence: 0.55,
58
+ riskTier: "high",
59
+ verdict: "assist",
60
+ reasons: ["high_risk_topic_detected"],
61
+ responseText: this.config.safeAckTemplate,
62
+ requiresApproval: true
63
+ };
64
+ }
65
+ const mediumRiskDetected = containsAny(text, this.config.mediumRiskSignals);
66
+ const confidence = mediumRiskDetected ? 0.74 : 0.92;
67
+ const riskTier = mediumRiskDetected ? "medium" : "low";
68
+ if (confidence >= this.config.autoResolveMinConfidence && riskTier === "low") {
69
+ return {
70
+ confidence,
71
+ riskTier,
72
+ verdict: "autonomous",
73
+ reasons: ["low_risk_high_confidence"],
74
+ responseText: this.defaultResponseText(input.event),
75
+ requiresApproval: false
76
+ };
77
+ }
78
+ if (confidence >= this.config.assistMinConfidence) {
79
+ return {
80
+ confidence,
81
+ riskTier,
82
+ verdict: "assist",
83
+ reasons: ["needs_human_review"],
84
+ responseText: this.config.safeAckTemplate,
85
+ requiresApproval: true
86
+ };
87
+ }
88
+ return {
89
+ confidence,
90
+ riskTier: "blocked",
91
+ verdict: "blocked",
92
+ reasons: ["low_confidence"],
93
+ responseText: this.config.safeAckTemplate,
94
+ requiresApproval: true
95
+ };
96
+ }
97
+ defaultResponseText(event) {
98
+ if (!event.message?.text) {
99
+ return this.config.safeAckTemplate;
100
+ }
101
+ return `Acknowledged: ${event.message.text.slice(0, 240)}`;
102
+ }
103
+ }
104
+ function containsAny(text, candidates) {
105
+ return candidates.some((candidate) => text.includes(candidate));
106
+ }
107
+ export {
108
+ MessagingPolicyEngine,
109
+ DEFAULT_MESSAGING_POLICY_CONFIG
110
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,11 @@
1
+ export declare const CLAIM_EVENT_RECEIPT_SQL = "\nwith inserted as (\n insert into channel_event_receipts (\n id,\n workspace_id,\n provider_key,\n external_event_id,\n event_type,\n status,\n signature_valid,\n payload_hash,\n trace_id\n )\n values ($1, $2, $3, $4, $5, 'accepted', $6, $7, $8)\n on conflict (workspace_id, provider_key, external_event_id)\n do nothing\n returning id\n)\nselect id, true as inserted from inserted\nunion all\nselect id, false as inserted\nfrom channel_event_receipts\nwhere workspace_id = $2\n and provider_key = $3\n and external_event_id = $4\nlimit 1\n";
2
+ export declare const MARK_RECEIPT_DUPLICATE_SQL = "\nupdate channel_event_receipts\nset last_seen_at = now(), status = 'duplicate'\nwhere id = $1\n";
3
+ export declare const UPDATE_RECEIPT_STATUS_SQL = "\nupdate channel_event_receipts\nset\n status = $2,\n error_code = $3,\n error_message = $4,\n last_seen_at = now(),\n processed_at = case when $2 = 'processed' then now() else processed_at end\nwhere id = $1\n";
4
+ export declare const UPSERT_THREAD_SQL = "\ninsert into channel_threads (\n id,\n workspace_id,\n provider_key,\n external_thread_id,\n external_channel_id,\n external_user_id,\n state,\n last_provider_event_ts\n)\nvalues ($1, $2, $3, $4, $5, $6, $7::jsonb, $8)\non conflict (workspace_id, provider_key, external_thread_id)\ndo update set\n external_channel_id = coalesce(excluded.external_channel_id, channel_threads.external_channel_id),\n external_user_id = coalesce(excluded.external_user_id, channel_threads.external_user_id),\n state = channel_threads.state || excluded.state,\n last_provider_event_ts = coalesce(excluded.last_provider_event_ts, channel_threads.last_provider_event_ts),\n updated_at = now()\nreturning\n id,\n workspace_id,\n provider_key,\n external_thread_id,\n external_channel_id,\n external_user_id,\n state,\n last_provider_event_ts,\n created_at,\n updated_at\n";
5
+ export declare const INSERT_DECISION_SQL = "\ninsert into channel_ai_decisions (\n id,\n receipt_id,\n thread_id,\n policy_mode,\n risk_tier,\n confidence,\n model_name,\n prompt_version,\n policy_version,\n tool_trace,\n action_plan,\n requires_approval\n)\nvalues ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10::jsonb, $11::jsonb, $12)\n";
6
+ export declare const ENQUEUE_OUTBOX_SQL = "\nwith inserted as (\n insert into channel_outbox_actions (\n id,\n workspace_id,\n provider_key,\n decision_id,\n thread_id,\n action_type,\n idempotency_key,\n target,\n payload,\n status\n )\n values ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9::jsonb, 'pending')\n on conflict (idempotency_key)\n do nothing\n returning id\n)\nselect id, true as inserted from inserted\nunion all\nselect id, false as inserted\nfrom channel_outbox_actions\nwhere idempotency_key = $7\nlimit 1\n";
7
+ export declare const CLAIM_PENDING_OUTBOX_SQL = "\nwith candidates as (\n select id\n from channel_outbox_actions\n where status in ('pending', 'retryable')\n and next_attempt_at <= $2\n order by next_attempt_at asc\n limit $1\n for update skip locked\n)\nupdate channel_outbox_actions as actions\nset\n status = 'sending',\n attempt_count = actions.attempt_count + 1,\n updated_at = now()\nfrom candidates\nwhere actions.id = candidates.id\nreturning\n actions.id,\n actions.workspace_id,\n actions.provider_key,\n actions.decision_id,\n actions.thread_id,\n actions.action_type,\n actions.idempotency_key,\n actions.target,\n actions.payload,\n actions.status,\n actions.attempt_count,\n actions.next_attempt_at,\n actions.provider_message_id,\n actions.last_error_code,\n actions.last_error_message,\n actions.created_at,\n actions.updated_at,\n actions.sent_at\n";
8
+ export declare const INSERT_DELIVERY_ATTEMPT_SQL = "\ninsert into channel_delivery_attempts (\n action_id,\n attempt,\n response_status,\n response_body,\n latency_ms\n)\nvalues ($1, $2, $3, $4, $5)\non conflict (action_id, attempt)\ndo update set\n response_status = excluded.response_status,\n response_body = excluded.response_body,\n latency_ms = excluded.latency_ms,\n created_at = now()\nreturning\n id,\n action_id,\n attempt,\n response_status,\n response_body,\n latency_ms,\n created_at\n";
9
+ export declare const MARK_OUTBOX_SENT_SQL = "\nupdate channel_outbox_actions\nset\n status = 'sent',\n provider_message_id = $2,\n sent_at = now(),\n updated_at = now(),\n last_error_code = null,\n last_error_message = null\nwhere id = $1\n";
10
+ export declare const MARK_OUTBOX_RETRY_SQL = "\nupdate channel_outbox_actions\nset\n status = 'retryable',\n next_attempt_at = $2,\n last_error_code = $3,\n last_error_message = $4,\n updated_at = now()\nwhere id = $1\n";
11
+ export declare const MARK_OUTBOX_DEAD_LETTER_SQL = "\nupdate channel_outbox_actions\nset\n status = 'dead_letter',\n last_error_code = $2,\n last_error_message = $3,\n updated_at = now()\nwhere id = $1\n";
@@ -0,0 +1,222 @@
1
+ // @bun
2
+ // src/channel/postgres-queries.ts
3
+ var CLAIM_EVENT_RECEIPT_SQL = `
4
+ with inserted as (
5
+ insert into channel_event_receipts (
6
+ id,
7
+ workspace_id,
8
+ provider_key,
9
+ external_event_id,
10
+ event_type,
11
+ status,
12
+ signature_valid,
13
+ payload_hash,
14
+ trace_id
15
+ )
16
+ values ($1, $2, $3, $4, $5, 'accepted', $6, $7, $8)
17
+ on conflict (workspace_id, provider_key, external_event_id)
18
+ do nothing
19
+ returning id
20
+ )
21
+ select id, true as inserted from inserted
22
+ union all
23
+ select id, false as inserted
24
+ from channel_event_receipts
25
+ where workspace_id = $2
26
+ and provider_key = $3
27
+ and external_event_id = $4
28
+ limit 1
29
+ `;
30
+ var MARK_RECEIPT_DUPLICATE_SQL = `
31
+ update channel_event_receipts
32
+ set last_seen_at = now(), status = 'duplicate'
33
+ where id = $1
34
+ `;
35
+ var UPDATE_RECEIPT_STATUS_SQL = `
36
+ update channel_event_receipts
37
+ set
38
+ status = $2,
39
+ error_code = $3,
40
+ error_message = $4,
41
+ last_seen_at = now(),
42
+ processed_at = case when $2 = 'processed' then now() else processed_at end
43
+ where id = $1
44
+ `;
45
+ var UPSERT_THREAD_SQL = `
46
+ insert into channel_threads (
47
+ id,
48
+ workspace_id,
49
+ provider_key,
50
+ external_thread_id,
51
+ external_channel_id,
52
+ external_user_id,
53
+ state,
54
+ last_provider_event_ts
55
+ )
56
+ values ($1, $2, $3, $4, $5, $6, $7::jsonb, $8)
57
+ on conflict (workspace_id, provider_key, external_thread_id)
58
+ do update set
59
+ external_channel_id = coalesce(excluded.external_channel_id, channel_threads.external_channel_id),
60
+ external_user_id = coalesce(excluded.external_user_id, channel_threads.external_user_id),
61
+ state = channel_threads.state || excluded.state,
62
+ last_provider_event_ts = coalesce(excluded.last_provider_event_ts, channel_threads.last_provider_event_ts),
63
+ updated_at = now()
64
+ returning
65
+ id,
66
+ workspace_id,
67
+ provider_key,
68
+ external_thread_id,
69
+ external_channel_id,
70
+ external_user_id,
71
+ state,
72
+ last_provider_event_ts,
73
+ created_at,
74
+ updated_at
75
+ `;
76
+ var INSERT_DECISION_SQL = `
77
+ insert into channel_ai_decisions (
78
+ id,
79
+ receipt_id,
80
+ thread_id,
81
+ policy_mode,
82
+ risk_tier,
83
+ confidence,
84
+ model_name,
85
+ prompt_version,
86
+ policy_version,
87
+ tool_trace,
88
+ action_plan,
89
+ requires_approval
90
+ )
91
+ values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10::jsonb, $11::jsonb, $12)
92
+ `;
93
+ var ENQUEUE_OUTBOX_SQL = `
94
+ with inserted as (
95
+ insert into channel_outbox_actions (
96
+ id,
97
+ workspace_id,
98
+ provider_key,
99
+ decision_id,
100
+ thread_id,
101
+ action_type,
102
+ idempotency_key,
103
+ target,
104
+ payload,
105
+ status
106
+ )
107
+ values ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9::jsonb, 'pending')
108
+ on conflict (idempotency_key)
109
+ do nothing
110
+ returning id
111
+ )
112
+ select id, true as inserted from inserted
113
+ union all
114
+ select id, false as inserted
115
+ from channel_outbox_actions
116
+ where idempotency_key = $7
117
+ limit 1
118
+ `;
119
+ var CLAIM_PENDING_OUTBOX_SQL = `
120
+ with candidates as (
121
+ select id
122
+ from channel_outbox_actions
123
+ where status in ('pending', 'retryable')
124
+ and next_attempt_at <= $2
125
+ order by next_attempt_at asc
126
+ limit $1
127
+ for update skip locked
128
+ )
129
+ update channel_outbox_actions as actions
130
+ set
131
+ status = 'sending',
132
+ attempt_count = actions.attempt_count + 1,
133
+ updated_at = now()
134
+ from candidates
135
+ where actions.id = candidates.id
136
+ returning
137
+ actions.id,
138
+ actions.workspace_id,
139
+ actions.provider_key,
140
+ actions.decision_id,
141
+ actions.thread_id,
142
+ actions.action_type,
143
+ actions.idempotency_key,
144
+ actions.target,
145
+ actions.payload,
146
+ actions.status,
147
+ actions.attempt_count,
148
+ actions.next_attempt_at,
149
+ actions.provider_message_id,
150
+ actions.last_error_code,
151
+ actions.last_error_message,
152
+ actions.created_at,
153
+ actions.updated_at,
154
+ actions.sent_at
155
+ `;
156
+ var INSERT_DELIVERY_ATTEMPT_SQL = `
157
+ insert into channel_delivery_attempts (
158
+ action_id,
159
+ attempt,
160
+ response_status,
161
+ response_body,
162
+ latency_ms
163
+ )
164
+ values ($1, $2, $3, $4, $5)
165
+ on conflict (action_id, attempt)
166
+ do update set
167
+ response_status = excluded.response_status,
168
+ response_body = excluded.response_body,
169
+ latency_ms = excluded.latency_ms,
170
+ created_at = now()
171
+ returning
172
+ id,
173
+ action_id,
174
+ attempt,
175
+ response_status,
176
+ response_body,
177
+ latency_ms,
178
+ created_at
179
+ `;
180
+ var MARK_OUTBOX_SENT_SQL = `
181
+ update channel_outbox_actions
182
+ set
183
+ status = 'sent',
184
+ provider_message_id = $2,
185
+ sent_at = now(),
186
+ updated_at = now(),
187
+ last_error_code = null,
188
+ last_error_message = null
189
+ where id = $1
190
+ `;
191
+ var MARK_OUTBOX_RETRY_SQL = `
192
+ update channel_outbox_actions
193
+ set
194
+ status = 'retryable',
195
+ next_attempt_at = $2,
196
+ last_error_code = $3,
197
+ last_error_message = $4,
198
+ updated_at = now()
199
+ where id = $1
200
+ `;
201
+ var MARK_OUTBOX_DEAD_LETTER_SQL = `
202
+ update channel_outbox_actions
203
+ set
204
+ status = 'dead_letter',
205
+ last_error_code = $2,
206
+ last_error_message = $3,
207
+ updated_at = now()
208
+ where id = $1
209
+ `;
210
+ export {
211
+ UPSERT_THREAD_SQL,
212
+ UPDATE_RECEIPT_STATUS_SQL,
213
+ MARK_RECEIPT_DUPLICATE_SQL,
214
+ MARK_OUTBOX_SENT_SQL,
215
+ MARK_OUTBOX_RETRY_SQL,
216
+ MARK_OUTBOX_DEAD_LETTER_SQL,
217
+ INSERT_DELIVERY_ATTEMPT_SQL,
218
+ INSERT_DECISION_SQL,
219
+ ENQUEUE_OUTBOX_SQL,
220
+ CLAIM_PENDING_OUTBOX_SQL,
221
+ CLAIM_EVENT_RECEIPT_SQL
222
+ };
@@ -0,0 +1 @@
1
+ export declare const CHANNEL_RUNTIME_SCHEMA_STATEMENTS: readonly string[];