@contractspec/integration.runtime 2.10.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) 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 +1463 -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 +23 -0
  12. package/dist/channel/policy.js +119 -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 +9 -0
  22. package/dist/channel/replay-fixtures.js +42 -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 +319 -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 +115 -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 +2 -0
  43. package/dist/index.js +1621 -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 +1462 -0
  47. package/dist/node/channel/memory-store.js +222 -0
  48. package/dist/node/channel/policy.js +118 -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 +41 -0
  53. package/dist/node/channel/service.js +318 -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 +1621 -1
  61. package/dist/node/transport/auth-resolver.js +51 -0
  62. package/dist/node/transport/index.js +162 -0
  63. package/dist/node/transport/transport-factory.js +77 -0
  64. package/dist/node/transport/version-negotiator.js +36 -0
  65. package/dist/runtime.d.ts +16 -0
  66. package/dist/runtime.health.test.d.ts +1 -0
  67. package/dist/transport/auth-resolver.d.ts +20 -0
  68. package/dist/transport/auth-resolver.js +52 -0
  69. package/dist/transport/index.d.ts +3 -0
  70. package/dist/transport/index.js +163 -0
  71. package/dist/transport/transport-factory.d.ts +31 -0
  72. package/dist/transport/transport-factory.js +78 -0
  73. package/dist/transport/version-negotiator.d.ts +14 -0
  74. package/dist/transport/version-negotiator.js +37 -0
  75. package/package.json +273 -6
@@ -0,0 +1,319 @@
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
+ policyRef: {
34
+ key: "channel.messaging-policy",
35
+ version: "1.0.0"
36
+ }
37
+ };
38
+
39
+ class MessagingPolicyEngine {
40
+ config;
41
+ constructor(config) {
42
+ this.config = {
43
+ ...DEFAULT_MESSAGING_POLICY_CONFIG,
44
+ ...config ?? {}
45
+ };
46
+ }
47
+ evaluate(input) {
48
+ const text = (input.event.message?.text ?? "").toLowerCase();
49
+ if (containsAny(text, this.config.blockedSignals)) {
50
+ return {
51
+ confidence: 0.2,
52
+ riskTier: "blocked",
53
+ verdict: "blocked",
54
+ reasons: ["blocked_signal_detected"],
55
+ responseText: this.config.safeAckTemplate,
56
+ requiresApproval: true,
57
+ policyRef: this.config.policyRef
58
+ };
59
+ }
60
+ if (containsAny(text, this.config.highRiskSignals)) {
61
+ return {
62
+ confidence: 0.55,
63
+ riskTier: "high",
64
+ verdict: "assist",
65
+ reasons: ["high_risk_topic_detected"],
66
+ responseText: this.config.safeAckTemplate,
67
+ requiresApproval: true,
68
+ policyRef: this.config.policyRef
69
+ };
70
+ }
71
+ const mediumRiskDetected = containsAny(text, this.config.mediumRiskSignals);
72
+ const confidence = mediumRiskDetected ? 0.74 : 0.92;
73
+ const riskTier = mediumRiskDetected ? "medium" : "low";
74
+ if (confidence >= this.config.autoResolveMinConfidence && riskTier === "low") {
75
+ return {
76
+ confidence,
77
+ riskTier,
78
+ verdict: "autonomous",
79
+ reasons: ["low_risk_high_confidence"],
80
+ responseText: this.defaultResponseText(input.event),
81
+ requiresApproval: false,
82
+ policyRef: this.config.policyRef
83
+ };
84
+ }
85
+ if (confidence >= this.config.assistMinConfidence) {
86
+ return {
87
+ confidence,
88
+ riskTier,
89
+ verdict: "assist",
90
+ reasons: ["needs_human_review"],
91
+ responseText: this.config.safeAckTemplate,
92
+ requiresApproval: true,
93
+ policyRef: this.config.policyRef
94
+ };
95
+ }
96
+ return {
97
+ confidence,
98
+ riskTier: "blocked",
99
+ verdict: "blocked",
100
+ reasons: ["low_confidence"],
101
+ responseText: this.config.safeAckTemplate,
102
+ requiresApproval: true,
103
+ policyRef: this.config.policyRef
104
+ };
105
+ }
106
+ defaultResponseText(event) {
107
+ if (!event.message?.text) {
108
+ return this.config.safeAckTemplate;
109
+ }
110
+ return `Acknowledged: ${event.message.text.slice(0, 240)}`;
111
+ }
112
+ }
113
+ function containsAny(text, candidates) {
114
+ return candidates.some((candidate) => text.includes(candidate));
115
+ }
116
+
117
+ // src/channel/service.ts
118
+ import { createHash, randomUUID } from "crypto";
119
+ class ChannelRuntimeService {
120
+ store;
121
+ policy;
122
+ asyncProcessing;
123
+ processInBackground;
124
+ modelName;
125
+ promptVersion;
126
+ policyVersion;
127
+ telemetry;
128
+ constructor(store, options = {}) {
129
+ this.store = store;
130
+ this.policy = options.policy ?? new MessagingPolicyEngine;
131
+ this.asyncProcessing = options.asyncProcessing ?? true;
132
+ this.processInBackground = options.processInBackground ?? ((task) => {
133
+ setTimeout(() => {
134
+ task();
135
+ }, 0);
136
+ });
137
+ this.modelName = options.modelName ?? "policy-heuristics-v1";
138
+ this.promptVersion = options.promptVersion ?? "channel-runtime.v1";
139
+ this.policyVersion = options.policyVersion ?? "messaging-policy.v1";
140
+ this.telemetry = options.telemetry;
141
+ }
142
+ async ingest(event) {
143
+ const startedAtMs = Date.now();
144
+ const claim = await this.store.claimEventReceipt({
145
+ workspaceId: event.workspaceId,
146
+ providerKey: event.providerKey,
147
+ externalEventId: event.externalEventId,
148
+ eventType: event.eventType,
149
+ signatureValid: event.signatureValid,
150
+ payloadHash: event.rawPayload ? sha256(event.rawPayload) : undefined,
151
+ traceId: event.traceId
152
+ });
153
+ if (claim.duplicate) {
154
+ this.telemetry?.record({
155
+ stage: "ingest",
156
+ status: "duplicate",
157
+ workspaceId: event.workspaceId,
158
+ providerKey: event.providerKey,
159
+ receiptId: claim.receiptId,
160
+ traceId: event.traceId,
161
+ latencyMs: Date.now() - startedAtMs
162
+ });
163
+ return {
164
+ status: "duplicate",
165
+ receiptId: claim.receiptId
166
+ };
167
+ }
168
+ this.telemetry?.record({
169
+ stage: "ingest",
170
+ status: "accepted",
171
+ workspaceId: event.workspaceId,
172
+ providerKey: event.providerKey,
173
+ receiptId: claim.receiptId,
174
+ traceId: event.traceId,
175
+ latencyMs: Date.now() - startedAtMs
176
+ });
177
+ if (!event.signatureValid) {
178
+ await this.store.updateReceiptStatus(claim.receiptId, "rejected", {
179
+ code: "INVALID_SIGNATURE",
180
+ message: "Inbound event signature is invalid."
181
+ });
182
+ this.telemetry?.record({
183
+ stage: "ingest",
184
+ status: "rejected",
185
+ workspaceId: event.workspaceId,
186
+ providerKey: event.providerKey,
187
+ receiptId: claim.receiptId,
188
+ traceId: event.traceId,
189
+ latencyMs: Date.now() - startedAtMs,
190
+ metadata: {
191
+ errorCode: "INVALID_SIGNATURE"
192
+ }
193
+ });
194
+ return {
195
+ status: "rejected",
196
+ receiptId: claim.receiptId
197
+ };
198
+ }
199
+ const task = async () => {
200
+ await this.processAcceptedEvent(claim.receiptId, event);
201
+ };
202
+ if (this.asyncProcessing) {
203
+ this.processInBackground(task);
204
+ } else {
205
+ await task();
206
+ }
207
+ return {
208
+ status: "accepted",
209
+ receiptId: claim.receiptId
210
+ };
211
+ }
212
+ async processAcceptedEvent(receiptId, event) {
213
+ try {
214
+ await this.store.updateReceiptStatus(receiptId, "processing");
215
+ const thread = await this.store.upsertThread({
216
+ workspaceId: event.workspaceId,
217
+ providerKey: event.providerKey,
218
+ externalThreadId: event.thread.externalThreadId,
219
+ externalChannelId: event.thread.externalChannelId,
220
+ externalUserId: event.thread.externalUserId,
221
+ occurredAt: event.occurredAt
222
+ });
223
+ const policyDecision = this.policy.evaluate({ event });
224
+ this.telemetry?.record({
225
+ stage: "decision",
226
+ status: "processed",
227
+ workspaceId: event.workspaceId,
228
+ providerKey: event.providerKey,
229
+ receiptId,
230
+ traceId: event.traceId,
231
+ metadata: {
232
+ verdict: policyDecision.verdict,
233
+ riskTier: policyDecision.riskTier,
234
+ confidence: policyDecision.confidence
235
+ }
236
+ });
237
+ const decision = await this.store.saveDecision({
238
+ receiptId,
239
+ threadId: thread.id,
240
+ policyMode: policyDecision.verdict === "autonomous" ? "autonomous" : "assist",
241
+ riskTier: policyDecision.riskTier,
242
+ confidence: policyDecision.confidence,
243
+ modelName: this.modelName,
244
+ promptVersion: this.promptVersion,
245
+ policyVersion: this.policyVersion,
246
+ actionPlan: {
247
+ verdict: policyDecision.verdict,
248
+ reasons: policyDecision.reasons,
249
+ policyRef: policyDecision.policyRef
250
+ },
251
+ requiresApproval: policyDecision.requiresApproval
252
+ });
253
+ if (policyDecision.verdict === "autonomous") {
254
+ await this.store.enqueueOutboxAction({
255
+ workspaceId: event.workspaceId,
256
+ providerKey: event.providerKey,
257
+ decisionId: decision.id,
258
+ threadId: thread.id,
259
+ actionType: "reply",
260
+ idempotencyKey: buildOutboxIdempotencyKey(event, policyDecision.responseText),
261
+ target: {
262
+ externalThreadId: event.thread.externalThreadId,
263
+ externalChannelId: event.thread.externalChannelId,
264
+ externalUserId: event.thread.externalUserId
265
+ },
266
+ payload: {
267
+ id: randomUUID(),
268
+ text: policyDecision.responseText
269
+ }
270
+ });
271
+ this.telemetry?.record({
272
+ stage: "outbox",
273
+ status: "accepted",
274
+ workspaceId: event.workspaceId,
275
+ providerKey: event.providerKey,
276
+ receiptId,
277
+ traceId: event.traceId,
278
+ metadata: {
279
+ actionType: "reply"
280
+ }
281
+ });
282
+ }
283
+ await this.store.updateReceiptStatus(receiptId, "processed");
284
+ this.telemetry?.record({
285
+ stage: "ingest",
286
+ status: "processed",
287
+ workspaceId: event.workspaceId,
288
+ providerKey: event.providerKey,
289
+ receiptId,
290
+ traceId: event.traceId
291
+ });
292
+ } catch (error) {
293
+ await this.store.updateReceiptStatus(receiptId, "failed", {
294
+ code: "PROCESSING_FAILED",
295
+ message: error instanceof Error ? error.message : String(error)
296
+ });
297
+ this.telemetry?.record({
298
+ stage: "ingest",
299
+ status: "failed",
300
+ workspaceId: event.workspaceId,
301
+ providerKey: event.providerKey,
302
+ receiptId,
303
+ traceId: event.traceId,
304
+ metadata: {
305
+ errorCode: "PROCESSING_FAILED"
306
+ }
307
+ });
308
+ }
309
+ }
310
+ }
311
+ function buildOutboxIdempotencyKey(event, responseText) {
312
+ return sha256(`${event.workspaceId}:${event.providerKey}:${event.externalEventId}:reply:${responseText}`);
313
+ }
314
+ function sha256(value) {
315
+ return createHash("sha256").update(value).digest("hex");
316
+ }
317
+ export {
318
+ ChannelRuntimeService
319
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,42 @@
1
+ import type { ChannelInboundEvent } from './types';
2
+ export interface SlackWebhookEnvelope {
3
+ type: string;
4
+ team_id?: string;
5
+ api_app_id?: string;
6
+ event_id?: string;
7
+ event_time?: number;
8
+ challenge?: string;
9
+ event?: {
10
+ type?: string;
11
+ user?: string;
12
+ text?: string;
13
+ ts?: string;
14
+ thread_ts?: string;
15
+ channel?: string;
16
+ subtype?: string;
17
+ bot_id?: string;
18
+ };
19
+ }
20
+ export interface SlackSignatureVerificationInput {
21
+ signingSecret: string;
22
+ requestTimestamp: string | null;
23
+ requestSignature: string | null;
24
+ rawBody: string;
25
+ nowMs?: number;
26
+ toleranceSeconds?: number;
27
+ }
28
+ export interface SignatureVerificationResult {
29
+ valid: boolean;
30
+ reason?: string;
31
+ }
32
+ export declare function verifySlackSignature(input: SlackSignatureVerificationInput): SignatureVerificationResult;
33
+ export declare function parseSlackWebhookPayload(rawBody: string): SlackWebhookEnvelope;
34
+ export declare function isSlackUrlVerificationPayload(payload: SlackWebhookEnvelope): boolean;
35
+ export interface NormalizeSlackEventInput {
36
+ workspaceId: string;
37
+ payload: SlackWebhookEnvelope;
38
+ signatureValid: boolean;
39
+ traceId?: string;
40
+ rawBody?: string;
41
+ }
42
+ export declare function normalizeSlackInboundEvent(input: NormalizeSlackEventInput): ChannelInboundEvent | null;
@@ -0,0 +1,82 @@
1
+ // @bun
2
+ // src/channel/slack.ts
3
+ import { createHmac, timingSafeEqual } from "crypto";
4
+ function verifySlackSignature(input) {
5
+ if (!input.requestTimestamp || !input.requestSignature) {
6
+ return { valid: false, reason: "missing_signature_headers" };
7
+ }
8
+ const timestamp = Number.parseInt(input.requestTimestamp, 10);
9
+ if (!Number.isFinite(timestamp)) {
10
+ return { valid: false, reason: "invalid_timestamp" };
11
+ }
12
+ const nowMs = input.nowMs ?? Date.now();
13
+ const toleranceMs = (input.toleranceSeconds ?? 300) * 1000;
14
+ if (Math.abs(nowMs - timestamp * 1000) > toleranceMs) {
15
+ return { valid: false, reason: "timestamp_out_of_range" };
16
+ }
17
+ const base = `v0:${input.requestTimestamp}:${input.rawBody}`;
18
+ const expected = `v0=${createHmac("sha256", input.signingSecret).update(base).digest("hex")}`;
19
+ const receivedBuffer = Buffer.from(input.requestSignature, "utf8");
20
+ const expectedBuffer = Buffer.from(expected, "utf8");
21
+ if (receivedBuffer.length !== expectedBuffer.length) {
22
+ return { valid: false, reason: "signature_length_mismatch" };
23
+ }
24
+ const valid = timingSafeEqual(receivedBuffer, expectedBuffer);
25
+ return valid ? { valid: true } : { valid: false, reason: "signature_mismatch" };
26
+ }
27
+ function parseSlackWebhookPayload(rawBody) {
28
+ const parsed = JSON.parse(rawBody);
29
+ return parsed;
30
+ }
31
+ function isSlackUrlVerificationPayload(payload) {
32
+ return payload.type === "url_verification";
33
+ }
34
+ function normalizeSlackInboundEvent(input) {
35
+ if (input.payload.type !== "event_callback") {
36
+ return null;
37
+ }
38
+ const event = input.payload.event;
39
+ if (!event?.type) {
40
+ return null;
41
+ }
42
+ if (event.subtype === "bot_message" || event.bot_id) {
43
+ return null;
44
+ }
45
+ const externalEventId = input.payload.event_id ?? event.ts;
46
+ if (!externalEventId) {
47
+ return null;
48
+ }
49
+ const threadId = event.thread_ts ?? event.ts ?? externalEventId;
50
+ if (!threadId) {
51
+ return null;
52
+ }
53
+ return {
54
+ workspaceId: input.workspaceId,
55
+ providerKey: "messaging.slack",
56
+ externalEventId,
57
+ eventType: `slack.${event.type}`,
58
+ occurredAt: input.payload.event_time ? new Date(input.payload.event_time * 1000) : new Date,
59
+ signatureValid: input.signatureValid,
60
+ traceId: input.traceId,
61
+ rawPayload: input.rawBody,
62
+ thread: {
63
+ externalThreadId: threadId,
64
+ externalChannelId: event.channel,
65
+ externalUserId: event.user
66
+ },
67
+ message: event.text ? {
68
+ text: event.text,
69
+ externalMessageId: event.ts
70
+ } : undefined,
71
+ metadata: {
72
+ slackEventType: event.type,
73
+ slackTeamId: input.payload.team_id ?? input.workspaceId
74
+ }
75
+ };
76
+ }
77
+ export {
78
+ verifySlackSignature,
79
+ parseSlackWebhookPayload,
80
+ normalizeSlackInboundEvent,
81
+ isSlackUrlVerificationPayload
82
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,83 @@
1
+ import type { ChannelDecisionRecord, ChannelDeliveryAttemptRecord, ChannelEventReceiptRecord, ChannelOutboxActionRecord, ChannelThreadRecord } from './types';
2
+ export interface ClaimEventReceiptInput {
3
+ workspaceId: string;
4
+ providerKey: string;
5
+ externalEventId: string;
6
+ eventType: string;
7
+ signatureValid: boolean;
8
+ payloadHash?: string;
9
+ traceId?: string;
10
+ }
11
+ export interface ClaimEventReceiptResult {
12
+ receiptId: string;
13
+ duplicate: boolean;
14
+ }
15
+ export interface UpsertThreadInput {
16
+ workspaceId: string;
17
+ providerKey: string;
18
+ externalThreadId: string;
19
+ externalChannelId?: string;
20
+ externalUserId?: string;
21
+ state?: Record<string, unknown>;
22
+ occurredAt?: Date;
23
+ }
24
+ export interface SaveDecisionInput {
25
+ receiptId: string;
26
+ threadId: string;
27
+ policyMode: 'suggest' | 'assist' | 'autonomous';
28
+ riskTier: 'low' | 'medium' | 'high' | 'blocked';
29
+ confidence: number;
30
+ modelName: string;
31
+ promptVersion: string;
32
+ policyVersion: string;
33
+ toolTrace?: Record<string, unknown>[];
34
+ actionPlan: Record<string, unknown>;
35
+ requiresApproval: boolean;
36
+ }
37
+ export interface EnqueueOutboxActionInput {
38
+ workspaceId: string;
39
+ providerKey: string;
40
+ decisionId: string;
41
+ threadId: string;
42
+ actionType: string;
43
+ idempotencyKey: string;
44
+ target: Record<string, unknown>;
45
+ payload: Record<string, unknown>;
46
+ }
47
+ export interface EnqueueOutboxActionResult {
48
+ actionId: string;
49
+ duplicate: boolean;
50
+ }
51
+ export interface RecordDeliveryAttemptInput {
52
+ actionId: string;
53
+ attempt: number;
54
+ responseStatus?: number;
55
+ responseBody?: string;
56
+ latencyMs?: number;
57
+ }
58
+ export interface MarkOutboxRetryInput {
59
+ actionId: string;
60
+ nextAttemptAt: Date;
61
+ lastErrorCode: string;
62
+ lastErrorMessage: string;
63
+ }
64
+ export interface MarkOutboxDeadLetterInput {
65
+ actionId: string;
66
+ lastErrorCode: string;
67
+ lastErrorMessage: string;
68
+ }
69
+ export interface ChannelRuntimeStore {
70
+ claimEventReceipt(input: ClaimEventReceiptInput): Promise<ClaimEventReceiptResult>;
71
+ updateReceiptStatus(receiptId: string, status: ChannelEventReceiptRecord['status'], error?: {
72
+ code: string;
73
+ message: string;
74
+ }): Promise<void>;
75
+ upsertThread(input: UpsertThreadInput): Promise<ChannelThreadRecord>;
76
+ saveDecision(input: SaveDecisionInput): Promise<ChannelDecisionRecord>;
77
+ enqueueOutboxAction(input: EnqueueOutboxActionInput): Promise<EnqueueOutboxActionResult>;
78
+ claimPendingOutboxActions(limit: number, now?: Date): Promise<ChannelOutboxActionRecord[]>;
79
+ recordDeliveryAttempt(input: RecordDeliveryAttemptInput): Promise<ChannelDeliveryAttemptRecord>;
80
+ markOutboxSent(actionId: string, providerMessageId?: string): Promise<void>;
81
+ markOutboxRetry(input: MarkOutboxRetryInput): Promise<void>;
82
+ markOutboxDeadLetter(input: MarkOutboxDeadLetterInput): Promise<void>;
83
+ }
@@ -0,0 +1 @@
1
+ // @bun
@@ -0,0 +1,17 @@
1
+ export type ChannelTelemetryStage = 'ingest' | 'decision' | 'outbox' | 'dispatch';
2
+ export type ChannelTelemetryStatus = 'accepted' | 'duplicate' | 'rejected' | 'processed' | 'failed' | 'sent' | 'retry' | 'dead_letter';
3
+ export interface ChannelTelemetryEvent {
4
+ stage: ChannelTelemetryStage;
5
+ status: ChannelTelemetryStatus;
6
+ workspaceId?: string;
7
+ providerKey?: string;
8
+ receiptId?: string;
9
+ actionId?: string;
10
+ traceId?: string;
11
+ latencyMs?: number;
12
+ attempt?: number;
13
+ metadata?: Record<string, string | number | boolean>;
14
+ }
15
+ export interface ChannelTelemetryEmitter {
16
+ record(event: ChannelTelemetryEvent): Promise<void> | void;
17
+ }
@@ -0,0 +1 @@
1
+ // @bun
@@ -0,0 +1,115 @@
1
+ export type ChannelProviderKey = 'messaging.slack' | 'messaging.github' | 'messaging.whatsapp.meta' | 'messaging.whatsapp.twilio' | (string & {});
2
+ export interface ChannelThreadRef {
3
+ externalThreadId: string;
4
+ externalChannelId?: string;
5
+ externalUserId?: string;
6
+ }
7
+ export interface InboundMessage {
8
+ text: string;
9
+ externalMessageId?: string;
10
+ }
11
+ export interface ChannelInboundEvent {
12
+ workspaceId: string;
13
+ providerKey: ChannelProviderKey;
14
+ externalEventId: string;
15
+ eventType: string;
16
+ occurredAt: Date;
17
+ signatureValid: boolean;
18
+ traceId?: string;
19
+ thread: ChannelThreadRef;
20
+ message?: InboundMessage;
21
+ metadata?: Record<string, string>;
22
+ rawPayload?: string;
23
+ }
24
+ export type ChannelRiskTier = 'low' | 'medium' | 'high' | 'blocked';
25
+ export type ChannelPolicyVerdict = 'autonomous' | 'assist' | 'blocked';
26
+ export interface ChannelPolicyDecision {
27
+ confidence: number;
28
+ riskTier: ChannelRiskTier;
29
+ verdict: ChannelPolicyVerdict;
30
+ reasons: string[];
31
+ responseText: string;
32
+ requiresApproval: boolean;
33
+ policyRef?: {
34
+ key: string;
35
+ version: string;
36
+ };
37
+ }
38
+ export interface ChannelEventReceiptRecord {
39
+ id: string;
40
+ workspaceId: string;
41
+ providerKey: string;
42
+ externalEventId: string;
43
+ eventType: string;
44
+ status: 'accepted' | 'processing' | 'processed' | 'duplicate' | 'failed' | 'rejected';
45
+ signatureValid: boolean;
46
+ payloadHash?: string;
47
+ traceId?: string;
48
+ firstSeenAt: Date;
49
+ lastSeenAt: Date;
50
+ processedAt?: Date;
51
+ errorCode?: string;
52
+ errorMessage?: string;
53
+ }
54
+ export interface ChannelThreadRecord {
55
+ id: string;
56
+ workspaceId: string;
57
+ providerKey: string;
58
+ externalThreadId: string;
59
+ externalChannelId?: string;
60
+ externalUserId?: string;
61
+ state: Record<string, unknown>;
62
+ lastProviderEventAt?: Date;
63
+ createdAt: Date;
64
+ updatedAt: Date;
65
+ }
66
+ export interface ChannelDecisionRecord {
67
+ id: string;
68
+ receiptId: string;
69
+ threadId: string;
70
+ policyMode: 'suggest' | 'assist' | 'autonomous';
71
+ riskTier: ChannelRiskTier;
72
+ confidence: number;
73
+ modelName: string;
74
+ promptVersion: string;
75
+ policyVersion: string;
76
+ toolTrace: Record<string, unknown>[];
77
+ actionPlan: Record<string, unknown>;
78
+ requiresApproval: boolean;
79
+ approvedBy?: string;
80
+ approvedAt?: Date;
81
+ createdAt: Date;
82
+ }
83
+ export interface ChannelOutboxActionRecord {
84
+ id: string;
85
+ workspaceId: string;
86
+ providerKey: string;
87
+ decisionId: string;
88
+ threadId: string;
89
+ actionType: string;
90
+ idempotencyKey: string;
91
+ target: Record<string, unknown>;
92
+ payload: Record<string, unknown>;
93
+ status: 'pending' | 'sending' | 'sent' | 'retryable' | 'failed' | 'dead_letter' | 'cancelled';
94
+ attemptCount: number;
95
+ nextAttemptAt: Date;
96
+ providerMessageId?: string;
97
+ lastErrorCode?: string;
98
+ lastErrorMessage?: string;
99
+ createdAt: Date;
100
+ updatedAt: Date;
101
+ sentAt?: Date;
102
+ }
103
+ export interface ChannelDeliveryAttemptRecord {
104
+ id: number;
105
+ actionId: string;
106
+ attempt: number;
107
+ responseStatus?: number;
108
+ responseBody?: string;
109
+ latencyMs?: number;
110
+ createdAt: Date;
111
+ }
112
+ export interface ChannelIngestResult {
113
+ status: 'accepted' | 'duplicate' | 'rejected';
114
+ receiptId: string;
115
+ }
@@ -0,0 +1 @@
1
+ // @bun