@opentag/store 0.3.0 → 0.3.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.
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/repository.ts
2
+ import { createHash } from "crypto";
2
3
  import {
3
4
  ApprovalDecisionSchema,
4
5
  ApplyIntentOutcomeSchema,
@@ -13,6 +14,7 @@ import {
13
14
  PolicyRuleSchema,
14
15
  ProposalLineageSchema,
15
16
  preflightMutationIntent,
17
+ formatProjectTargetRef,
16
18
  projectTargetRefFromEvent,
17
19
  protocolRunFieldsFromEvent,
18
20
  RunAdmissionDecisionSchema,
@@ -20,7 +22,7 @@ import {
20
22
  RunEventVisibilitySchema,
21
23
  SuggestedChangesSnapshotSchema
22
24
  } from "@opentag/core";
23
- import { and, asc, eq, inArray } from "drizzle-orm";
25
+ import { and, asc, desc, eq, inArray, lt } from "drizzle-orm";
24
26
 
25
27
  // src/schema.ts
26
28
  import { index, integer, primaryKey, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
@@ -93,6 +95,35 @@ var runEvents = sqliteTable(
93
95
  runIdx: index("run_events_run_idx").on(table.runId)
94
96
  })
95
97
  );
98
+ var sourceDeliveries = sqliteTable(
99
+ "source_deliveries",
100
+ {
101
+ source: text("source").notNull(),
102
+ deliveryId: text("delivery_id").notNull(),
103
+ runId: text("run_id").notNull(),
104
+ eventId: text("event_id").notNull(),
105
+ createdAt: text("created_at").notNull()
106
+ },
107
+ (table) => ({
108
+ pk: primaryKey({ columns: [table.source, table.deliveryId] }),
109
+ runIdx: index("source_deliveries_run_idx").on(table.runId)
110
+ })
111
+ );
112
+ var controlPlaneEvents = sqliteTable(
113
+ "control_plane_events",
114
+ {
115
+ id: integer("id").primaryKey({ autoIncrement: true }),
116
+ type: text("type").notNull(),
117
+ severity: text("severity").notNull(),
118
+ subject: text("subject"),
119
+ payloadJson: text("payload_json").notNull(),
120
+ createdAt: text("created_at").notNull()
121
+ },
122
+ (table) => ({
123
+ typeIdx: index("control_plane_events_type_idx").on(table.type),
124
+ severityIdx: index("control_plane_events_severity_idx").on(table.severity)
125
+ })
126
+ );
96
127
  var suggestedChanges = sqliteTable("suggested_changes", {
97
128
  proposalId: text("proposal_id").primaryKey(),
98
129
  runId: text("run_id").notNull(),
@@ -194,6 +225,7 @@ var callbackDeliveries = sqliteTable(
194
225
  uri: text("uri").notNull(),
195
226
  body: text("body").notNull(),
196
227
  threadKey: text("thread_key"),
228
+ idempotencyKey: text("idempotency_key"),
197
229
  metadataJson: text("metadata_json"),
198
230
  status: text("status").notNull(),
199
231
  attempts: integer("attempts").notNull().default(0),
@@ -204,7 +236,8 @@ var callbackDeliveries = sqliteTable(
204
236
  },
205
237
  (table) => ({
206
238
  callbackRunIdx: index("callback_deliveries_run_idx").on(table.runId),
207
- callbackStatusIdx: index("callback_deliveries_status_idx").on(table.status)
239
+ callbackStatusIdx: index("callback_deliveries_status_idx").on(table.status),
240
+ callbackIdempotencyIdx: uniqueIndex("callback_deliveries_idempotency_key_idx").on(table.idempotencyKey)
208
241
  })
209
242
  );
210
243
  function migrateSchema(sqlite) {
@@ -247,6 +280,28 @@ function migrateSchema(sqlite) {
247
280
  created_at TEXT NOT NULL
248
281
  );
249
282
  CREATE INDEX IF NOT EXISTS run_events_run_idx ON run_events(run_id);
283
+ CREATE TABLE IF NOT EXISTS source_deliveries (
284
+ source TEXT NOT NULL,
285
+ delivery_id TEXT NOT NULL,
286
+ run_id TEXT NOT NULL,
287
+ event_id TEXT NOT NULL,
288
+ created_at TEXT NOT NULL,
289
+ PRIMARY KEY (source, delivery_id)
290
+ );
291
+ CREATE INDEX IF NOT EXISTS source_deliveries_run_idx
292
+ ON source_deliveries(run_id);
293
+ CREATE TABLE IF NOT EXISTS control_plane_events (
294
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
295
+ type TEXT NOT NULL,
296
+ severity TEXT NOT NULL,
297
+ subject TEXT,
298
+ payload_json TEXT NOT NULL,
299
+ created_at TEXT NOT NULL
300
+ );
301
+ CREATE INDEX IF NOT EXISTS control_plane_events_type_idx
302
+ ON control_plane_events(type);
303
+ CREATE INDEX IF NOT EXISTS control_plane_events_severity_idx
304
+ ON control_plane_events(severity);
250
305
  CREATE TABLE IF NOT EXISTS suggested_changes (
251
306
  proposal_id TEXT PRIMARY KEY,
252
307
  run_id TEXT NOT NULL,
@@ -328,6 +383,7 @@ function migrateSchema(sqlite) {
328
383
  uri TEXT NOT NULL,
329
384
  body TEXT NOT NULL,
330
385
  thread_key TEXT,
386
+ idempotency_key TEXT,
331
387
  metadata_json TEXT,
332
388
  status TEXT NOT NULL,
333
389
  attempts INTEGER NOT NULL DEFAULT 0,
@@ -340,6 +396,8 @@ function migrateSchema(sqlite) {
340
396
  ON callback_deliveries(run_id);
341
397
  CREATE INDEX IF NOT EXISTS callback_deliveries_status_idx
342
398
  ON callback_deliveries(status);
399
+ CREATE UNIQUE INDEX IF NOT EXISTS callback_deliveries_idempotency_key_idx
400
+ ON callback_deliveries(idempotency_key);
343
401
  CREATE TABLE IF NOT EXISTS follow_up_requests (
344
402
  id TEXT PRIMARY KEY,
345
403
  source_event_id TEXT NOT NULL,
@@ -446,6 +504,29 @@ function migrateSchema(sqlite) {
446
504
  sqlite.exec("ALTER TABLE run_events ADD COLUMN message TEXT");
447
505
  }
448
506
  sqlite.exec("CREATE INDEX IF NOT EXISTS run_events_run_idx ON run_events(run_id)");
507
+ sqlite.exec(`
508
+ CREATE TABLE IF NOT EXISTS source_deliveries (
509
+ source TEXT NOT NULL,
510
+ delivery_id TEXT NOT NULL,
511
+ run_id TEXT NOT NULL,
512
+ event_id TEXT NOT NULL,
513
+ created_at TEXT NOT NULL,
514
+ PRIMARY KEY (source, delivery_id)
515
+ );
516
+ `);
517
+ sqlite.exec("CREATE INDEX IF NOT EXISTS source_deliveries_run_idx ON source_deliveries(run_id)");
518
+ sqlite.exec(`
519
+ CREATE TABLE IF NOT EXISTS control_plane_events (
520
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
521
+ type TEXT NOT NULL,
522
+ severity TEXT NOT NULL,
523
+ subject TEXT,
524
+ payload_json TEXT NOT NULL,
525
+ created_at TEXT NOT NULL
526
+ );
527
+ `);
528
+ sqlite.exec("CREATE INDEX IF NOT EXISTS control_plane_events_type_idx ON control_plane_events(type)");
529
+ sqlite.exec("CREATE INDEX IF NOT EXISTS control_plane_events_severity_idx ON control_plane_events(severity)");
449
530
  sqlite.exec("CREATE UNIQUE INDEX IF NOT EXISTS repo_policy_rules_repo_id_idx ON repo_policy_rules(provider, owner, repo, id)");
450
531
  sqlite.exec("CREATE UNIQUE INDEX IF NOT EXISTS repo_mutation_mappings_repo_id_idx ON repo_mutation_mappings(provider, owner, repo, id)");
451
532
  const callbackColumns = sqlite.prepare("PRAGMA table_info(callback_deliveries)").all();
@@ -456,6 +537,10 @@ function migrateSchema(sqlite) {
456
537
  if (!callbackColumnNames.has("metadata_json")) {
457
538
  sqlite.exec("ALTER TABLE callback_deliveries ADD COLUMN metadata_json TEXT");
458
539
  }
540
+ if (!callbackColumnNames.has("idempotency_key")) {
541
+ sqlite.exec("ALTER TABLE callback_deliveries ADD COLUMN idempotency_key TEXT");
542
+ }
543
+ sqlite.exec("CREATE UNIQUE INDEX IF NOT EXISTS callback_deliveries_idempotency_key_idx ON callback_deliveries(idempotency_key)");
459
544
  const legacySlackTable = sqlite.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'slack_channel_bindings'").get();
460
545
  if (legacySlackTable) {
461
546
  sqlite.exec(`
@@ -512,8 +597,18 @@ function runFromRow(row) {
512
597
  ...result ? { result } : {}
513
598
  };
514
599
  }
600
+ function terminalRunStatus(status) {
601
+ return status === "succeeded" || status === "failed" || status === "cancelled" || status === "interrupted" || status === "timed_out";
602
+ }
603
+ function sourceContainerMetadataMatches(input) {
604
+ if (input.event.source !== input.source) return false;
605
+ return Object.entries(input.metadata).every(([key, value]) => input.event.metadata[key] === value);
606
+ }
607
+ function callbackDeliveryMetadataFromJson(metadataJson) {
608
+ return metadataJson && typeof metadataJson === "string" ? JSON.parse(metadataJson) : void 0;
609
+ }
515
610
  function callbackDeliveryFromRow(row) {
516
- const metadata = row.metadataJson && typeof row.metadataJson === "string" ? JSON.parse(row.metadataJson) : void 0;
611
+ const metadata = callbackDeliveryMetadataFromJson(row.metadataJson);
517
612
  return {
518
613
  id: row.id,
519
614
  runId: row.runId,
@@ -522,9 +617,12 @@ function callbackDeliveryFromRow(row) {
522
617
  uri: row.uri,
523
618
  body: row.body,
524
619
  ...row.threadKey ? { threadKey: row.threadKey } : {},
620
+ ...row.idempotencyKey ? { idempotencyKey: row.idempotencyKey } : {},
525
621
  ...metadata?.agentId ? { agentId: metadata.agentId } : {},
526
622
  ...metadata?.statusMessageKey ? { statusMessageKey: metadata.statusMessageKey } : {},
623
+ ...metadata?.externalMessageId ? { externalMessageId: metadata.externalMessageId } : {},
527
624
  ...metadata?.blocks ? { blocks: metadata.blocks } : {},
625
+ ...metadata && "rich" in metadata ? { rich: metadata.rich } : {},
528
626
  status: row.status,
529
627
  attempts: row.attempts,
530
628
  ...row.lastError ? { lastError: row.lastError } : {},
@@ -533,6 +631,19 @@ function callbackDeliveryFromRow(row) {
533
631
  updatedAt: row.updatedAt
534
632
  };
535
633
  }
634
+ function callbackBodyHash(input) {
635
+ return createHash("sha256").update(JSON.stringify({ body: input.body, blocks: input.blocks ?? [], rich: input.rich ?? null })).digest("hex");
636
+ }
637
+ function callbackIdempotencyKey(input) {
638
+ return [
639
+ input.runId,
640
+ input.provider,
641
+ input.threadKey ?? input.uri,
642
+ input.kind,
643
+ input.statusMessageKey ?? "",
644
+ callbackBodyHash({ body: input.body, ...input.blocks ? { blocks: input.blocks } : {}, ...input.rich !== void 0 ? { rich: input.rich } : {} })
645
+ ].join("|");
646
+ }
536
647
  function followUpRequestFromRow(row) {
537
648
  return {
538
649
  id: row.id,
@@ -564,6 +675,69 @@ function recordFromJson(value) {
564
675
  return void 0;
565
676
  }
566
677
  }
678
+ function metadataString(metadata, keys) {
679
+ for (const key of keys) {
680
+ const value = metadata[key];
681
+ if (typeof value !== "string") continue;
682
+ const trimmed = value.trim();
683
+ if (trimmed.length > 0) return trimmed;
684
+ }
685
+ return null;
686
+ }
687
+ function metadataBoolean(metadata, keys) {
688
+ for (const key of keys) {
689
+ const value = metadata[key];
690
+ if (typeof value === "boolean") return value;
691
+ }
692
+ return null;
693
+ }
694
+ function signatureStateFromEvent(event) {
695
+ const explicitState = metadataString(event.metadata, ["signatureState", "webhookSignatureState"]);
696
+ if (explicitState === "verified" || explicitState === "unverified" || explicitState === "unknown") return explicitState;
697
+ const verified = metadataBoolean(event.metadata, [
698
+ "signatureVerified",
699
+ "verifiedSignature",
700
+ "webhookSignatureVerified",
701
+ "githubSignatureVerified"
702
+ ]);
703
+ if (verified === true) return "verified";
704
+ if (verified === false) return "unverified";
705
+ return "unknown";
706
+ }
707
+ function sourceDeliveryIdFromEvent(event) {
708
+ return metadataString(event.metadata, [
709
+ "sourceDeliveryId",
710
+ "webhookDeliveryId",
711
+ "deliveryId",
712
+ "githubDeliveryId",
713
+ "githubDeliveryGuid",
714
+ "slackEventId",
715
+ "larkEventId"
716
+ ]);
717
+ }
718
+ function projectTargetProvenance(ref) {
719
+ if (!ref) return null;
720
+ return {
721
+ ref: formatProjectTargetRef(ref),
722
+ ...ref
723
+ };
724
+ }
725
+ function runProvenance(input) {
726
+ return {
727
+ source: input.event.source,
728
+ sourceEventId: input.event.sourceEventId,
729
+ sourceDeliveryId: sourceDeliveryIdFromEvent(input.event),
730
+ signatureState: signatureStateFromEvent(input.event),
731
+ projectTarget: projectTargetProvenance(input.projectTarget),
732
+ admissionDecision: {
733
+ action: input.admissionDecision.action,
734
+ reasonCode: input.admissionDecision.reasonCode,
735
+ ...input.admissionDecision.eventId ? { eventId: input.admissionDecision.eventId } : {},
736
+ ...input.admissionDecision.activeRunId ? { activeRunId: input.admissionDecision.activeRunId } : {}
737
+ },
738
+ expectedRunnerId: input.expectedRunnerId
739
+ };
740
+ }
567
741
  function channelBindingFromRow(row) {
568
742
  const metadata = recordFromJson(row.metadataJson);
569
743
  return {
@@ -664,6 +838,111 @@ function emptyApplyOutcomeCounts() {
664
838
  function recordFromUnknown(value) {
665
839
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
666
840
  }
841
+ function payloadString(payload, path) {
842
+ let current = payload;
843
+ for (const segment of path) {
844
+ const record = recordFromUnknown(current);
845
+ if (!record) return null;
846
+ current = record[segment];
847
+ }
848
+ return typeof current === "string" && current.trim().length > 0 ? current : null;
849
+ }
850
+ function controlPlaneAlertSubject(event) {
851
+ if (event.type === "run.claimed") {
852
+ return payloadString(event.payload, ["runnerId"]) ?? event.subject ?? "unknown-runner";
853
+ }
854
+ if (event.type === "security.auth_failed") {
855
+ return payloadString(event.payload, ["tokenFingerprint"]) ?? event.subject ?? "unknown-token";
856
+ }
857
+ if (event.type === "security.token_misuse") {
858
+ const provider = payloadString(event.payload, ["provider"]);
859
+ const tokenKind = payloadString(event.payload, ["tokenKind"]);
860
+ if (provider && tokenKind) return `${provider}:${tokenKind}`;
861
+ return event.subject ?? "unknown-token";
862
+ }
863
+ if (event.type === "security.signature_failed") {
864
+ const provider = payloadString(event.payload, ["provider"]);
865
+ const endpoint = payloadString(event.payload, ["endpoint"]);
866
+ if (provider && endpoint) return `${provider}:${endpoint}`;
867
+ return event.subject ?? "unknown-signature-source";
868
+ }
869
+ if (event.type === "security.request_body_rejected") {
870
+ return payloadString(event.payload, ["endpoint"]) ?? event.subject ?? "unknown-endpoint";
871
+ }
872
+ if (event.type === "admission.needs_human_decision") {
873
+ const reasonCode = payloadString(event.payload, ["decision", "reasonCode"]) ?? payloadString(event.payload, ["reasonCode"]);
874
+ if (reasonCode === "repo_not_bound" || reasonCode === "repo_context_missing") {
875
+ return payloadString(event.payload, ["projectTarget"]) ?? reasonCode;
876
+ }
877
+ }
878
+ return event.subject ?? event.type;
879
+ }
880
+ function controlPlaneAlertKind(event) {
881
+ if (event.type === "run.claimed") return "abnormal_runner_claim_rate";
882
+ if (event.type === "security.auth_failed") return "repeated_auth_failures";
883
+ if (event.type === "security.token_misuse") return "token_misuse";
884
+ if (event.type === "security.signature_failed") return "repeated_signature_failures";
885
+ if (event.type === "security.request_body_rejected") {
886
+ return payloadString(event.payload, ["reason"]) === "request_body_too_large" ? "repeated_large_payload_rejections" : "repeated_invalid_request_body";
887
+ }
888
+ if (event.type === "admission.needs_human_decision") {
889
+ const reasonCode = payloadString(event.payload, ["decision", "reasonCode"]) ?? payloadString(event.payload, ["reasonCode"]);
890
+ if (reasonCode === "repo_not_bound" || reasonCode === "repo_context_missing") return "repeated_unknown_project_targets";
891
+ }
892
+ return null;
893
+ }
894
+ function controlPlaneAlertMetadata(kind) {
895
+ if (kind === "repeated_auth_failures") {
896
+ return {
897
+ severity: "warn",
898
+ reason: "Repeated dispatcher authorization failures were observed.",
899
+ nextAction: "Check for token misuse, stale runner configuration, or a leaked/rotated pairing token."
900
+ };
901
+ }
902
+ if (kind === "token_misuse") {
903
+ return {
904
+ severity: "warn",
905
+ reason: "A platform or relay token failed with a terminal authentication or configuration error.",
906
+ nextAction: "Rotate or replace the affected token, then restart or re-pair the ingress or runner that owns it."
907
+ };
908
+ }
909
+ if (kind === "repeated_large_payload_rejections") {
910
+ return {
911
+ severity: "warn",
912
+ reason: "Repeated oversized dispatcher request bodies were rejected.",
913
+ nextAction: "Check source ingress payload size, request body limits, and whether a client is retrying an invalid payload."
914
+ };
915
+ }
916
+ if (kind === "repeated_invalid_request_body") {
917
+ return {
918
+ severity: "warn",
919
+ reason: "Repeated malformed or schema-invalid request bodies were rejected.",
920
+ nextAction: "Check source webhook payload shape, client versions, and whether unsigned or incompatible traffic is hitting the endpoint."
921
+ };
922
+ }
923
+ if (kind === "repeated_signature_failures") {
924
+ return {
925
+ severity: "warn",
926
+ reason: "Repeated source webhook signature verification failures were observed.",
927
+ nextAction: "Check the source webhook secret, signing configuration, endpoint URL, and whether unsigned traffic is hitting the ingress."
928
+ };
929
+ }
930
+ if (kind === "abnormal_runner_claim_rate") {
931
+ return {
932
+ severity: "warn",
933
+ reason: "Runner claim volume exceeded the local alert threshold.",
934
+ nextAction: "Check for runaway runner loops, token misuse, or an unexpected burst of queued runs for this runner."
935
+ };
936
+ }
937
+ return {
938
+ severity: "warn",
939
+ reason: "Repeated source events resolved to missing or unbound Project Targets.",
940
+ nextAction: "Verify source metadata, Project Target bindings, and runner allowlists before retrying."
941
+ };
942
+ }
943
+ function controlPlaneAlertThreshold(kind, thresholds) {
944
+ return thresholds?.[kind] ?? (kind === "token_misuse" ? 1 : kind === "repeated_auth_failures" || kind === "repeated_signature_failures" ? 3 : kind === "abnormal_runner_claim_rate" ? 10 : 2);
945
+ }
667
946
  function metricsFromEvents(runId, events) {
668
947
  const latestApplyPlans = /* @__PURE__ */ new Map();
669
948
  for (const event of events) {
@@ -731,6 +1010,17 @@ function aggregateMetrics(input) {
731
1010
  };
732
1011
  }
733
1012
  function createOpenTagRepository(db) {
1013
+ async function repoBindingRunnerId(projectTarget) {
1014
+ if (!projectTarget) return null;
1015
+ const row = await db.select().from(repoBindings).where(
1016
+ and(
1017
+ eq(repoBindings.provider, projectTarget.provider),
1018
+ eq(repoBindings.owner, projectTarget.owner),
1019
+ eq(repoBindings.repo, projectTarget.repo)
1020
+ )
1021
+ ).limit(1).get();
1022
+ return row?.runnerId ?? null;
1023
+ }
734
1024
  async function appendRunEvent(input) {
735
1025
  await db.insert(runEvents).values({
736
1026
  runId: input.runId,
@@ -742,6 +1032,51 @@ function createOpenTagRepository(db) {
742
1032
  createdAt: input.createdAt ?? nowIso()
743
1033
  });
744
1034
  }
1035
+ async function recordCreateRunReplay(input) {
1036
+ const reason = input.replayKind === "source_delivery" ? "Source delivery already created a run." : "Source event already created a run.";
1037
+ const reasonCode = input.replayKind === "source_delivery" ? "duplicate_source_delivery" : "duplicate_source_event";
1038
+ const replayDecision = RunAdmissionDecisionSchema.parse({
1039
+ action: "drop_duplicate",
1040
+ reason,
1041
+ reasonCode,
1042
+ decidedAt: input.createdAt,
1043
+ activeRunId: input.runRow.id,
1044
+ eventId: input.event.id
1045
+ });
1046
+ await appendRunEvent({
1047
+ runId: input.runRow.id,
1048
+ type: "admission.decided",
1049
+ payload: replayDecision,
1050
+ visibility: "audit",
1051
+ importance: "normal",
1052
+ message: replayDecision.reason,
1053
+ createdAt: input.createdAt
1054
+ });
1055
+ await appendRunEvent({
1056
+ runId: input.runRow.id,
1057
+ type: "run.create_idempotent_replay",
1058
+ payload: {
1059
+ requestedRunId: input.requestedRunId,
1060
+ eventId: input.event.id,
1061
+ replayKey: input.replayKind === "source_delivery" ? { kind: "source_delivery", source: input.event.source, deliveryId: input.sourceDeliveryId } : { kind: "source_event", eventId: input.event.id },
1062
+ provenance: runProvenance({
1063
+ event: input.event,
1064
+ projectTarget: input.projectTarget,
1065
+ admissionDecision: replayDecision,
1066
+ expectedRunnerId: input.expectedRunnerId
1067
+ })
1068
+ },
1069
+ visibility: "audit",
1070
+ importance: "low",
1071
+ createdAt: input.createdAt
1072
+ });
1073
+ return {
1074
+ run: runFromRow(input.runRow),
1075
+ created: false,
1076
+ replayKind: input.replayKind,
1077
+ replayDecision
1078
+ };
1079
+ }
745
1080
  async function buildApplyPlan(input) {
746
1081
  const storedProposalRow = await db.select().from(suggestedChanges).where(eq(suggestedChanges.proposalId, input.proposalId)).limit(1).get();
747
1082
  const decisionRow = await db.select().from(approvalDecisions).where(eq(approvalDecisions.id, input.approvalDecisionId)).limit(1).get();
@@ -860,6 +1195,74 @@ function createOpenTagRepository(db) {
860
1195
  event: OpenTagEventSchema.parse(JSON.parse(row.eventJson))
861
1196
  };
862
1197
  },
1198
+ async findCancelableRunForSourceContainer(input) {
1199
+ const rows = await db.select().from(runs).where(
1200
+ and(
1201
+ eq(runs.repoProvider, input.repoProvider),
1202
+ eq(runs.repoOwner, input.owner),
1203
+ eq(runs.repoName, input.repo),
1204
+ inArray(runs.status, ["queued", "assigned", "running", "needs_approval"])
1205
+ )
1206
+ ).orderBy(asc(runs.createdAt));
1207
+ for (const row of rows) {
1208
+ const event = OpenTagEventSchema.parse(JSON.parse(row.eventJson));
1209
+ if (sourceContainerMetadataMatches({ event, source: input.source, metadata: input.metadata })) {
1210
+ return { run: runFromRow(row), event };
1211
+ }
1212
+ }
1213
+ return null;
1214
+ },
1215
+ async cancelRun(input) {
1216
+ const row = await db.select().from(runs).where(eq(runs.id, input.runId)).limit(1).get();
1217
+ if (!row) return { outcome: "not_found" };
1218
+ const event = OpenTagEventSchema.parse(JSON.parse(row.eventJson));
1219
+ const existingRun = runFromRow(row);
1220
+ if (terminalRunStatus(row.status)) {
1221
+ return { outcome: "already_terminal", run: existingRun, event };
1222
+ }
1223
+ const updatedAt = nowIso();
1224
+ const result = {
1225
+ conclusion: "cancelled",
1226
+ summary: input.reason ?? "Cancellation was requested by a human.",
1227
+ nextAction: "OpenTag will not treat this stop request as a successful completion."
1228
+ };
1229
+ await db.update(runs).set({
1230
+ status: "cancelled",
1231
+ resultJson: JSON.stringify(result),
1232
+ assignedRunnerId: null,
1233
+ leasedAt: null,
1234
+ leaseExpiresAt: null,
1235
+ heartbeatAt: null,
1236
+ updatedAt
1237
+ }).where(eq(runs.id, input.runId));
1238
+ await appendRunEvent({
1239
+ runId: input.runId,
1240
+ type: "run.cancel_requested",
1241
+ payload: {
1242
+ previousStatus: row.status,
1243
+ previousRunnerId: row.assignedRunnerId,
1244
+ terminalReason: "cancelled_by_user",
1245
+ terminalSemantics: "A human stop request is not a successful completion and does not auto-promote queued follow-ups.",
1246
+ ...input.requestedBy ? { requestedBy: input.requestedBy } : {},
1247
+ reason: result.summary
1248
+ },
1249
+ visibility: "audit",
1250
+ importance: "high",
1251
+ message: result.summary,
1252
+ createdAt: updatedAt
1253
+ });
1254
+ return {
1255
+ outcome: "cancelled",
1256
+ run: {
1257
+ ...existingRun,
1258
+ status: "cancelled",
1259
+ assignedRunnerId: void 0,
1260
+ updatedAt,
1261
+ result
1262
+ },
1263
+ event
1264
+ };
1265
+ },
863
1266
  async createFollowUpRequest(input) {
864
1267
  const event = OpenTagEventSchema.parse(input.event);
865
1268
  const decision = RunAdmissionDecisionSchema.parse(input.decision);
@@ -894,6 +1297,10 @@ function createOpenTagRepository(db) {
894
1297
  const row = await db.select().from(followUpRequests).where(eq(followUpRequests.id, input.id)).limit(1).get();
895
1298
  return row ? followUpRequestFromRow(row) : null;
896
1299
  },
1300
+ async listQueuedFollowUpsForActiveRun(input) {
1301
+ const rows = await db.select().from(followUpRequests).where(and(eq(followUpRequests.activeRunId, input.activeRunId), eq(followUpRequests.status, "queued"))).orderBy(asc(followUpRequests.createdAt));
1302
+ return rows.map(followUpRequestFromRow);
1303
+ },
897
1304
  async createRunFromFollowUpRequest(input) {
898
1305
  const row = await db.select().from(followUpRequests).where(eq(followUpRequests.id, input.followUpRequestId)).limit(1).get();
899
1306
  if (!row) {
@@ -950,7 +1357,13 @@ function createOpenTagRepository(db) {
950
1357
  },
951
1358
  async registerRunner(input) {
952
1359
  const createdAt = nowIso();
953
- await db.insert(runners).values({ runnerId: input.runnerId, name: input.name, createdAt }).onConflictDoNothing();
1360
+ await db.insert(runners).values({ runnerId: input.runnerId, name: input.name, createdAt, heartbeatAt: createdAt }).onConflictDoUpdate({
1361
+ target: runners.runnerId,
1362
+ set: {
1363
+ name: input.name,
1364
+ heartbeatAt: createdAt
1365
+ }
1366
+ });
954
1367
  },
955
1368
  async getRunner(input) {
956
1369
  const row = await db.select().from(runners).where(eq(runners.runnerId, input.runnerId)).limit(1).get();
@@ -1039,6 +1452,24 @@ function createOpenTagRepository(db) {
1039
1452
  }
1040
1453
  });
1041
1454
  },
1455
+ async deleteChannelBinding(input) {
1456
+ const existing = await db.select().from(channelBindings).where(
1457
+ and(
1458
+ eq(channelBindings.provider, input.provider),
1459
+ eq(channelBindings.accountId, input.accountId),
1460
+ eq(channelBindings.conversationId, input.conversationId)
1461
+ )
1462
+ ).limit(1).get();
1463
+ if (!existing) return false;
1464
+ await db.delete(channelBindings).where(
1465
+ and(
1466
+ eq(channelBindings.provider, input.provider),
1467
+ eq(channelBindings.accountId, input.accountId),
1468
+ eq(channelBindings.conversationId, input.conversationId)
1469
+ )
1470
+ );
1471
+ return true;
1472
+ },
1042
1473
  async createSlackChannelBinding(input) {
1043
1474
  const repoProvider = input.repoProvider ?? "github";
1044
1475
  await db.insert(channelBindings).values({
@@ -1065,6 +1496,26 @@ function createOpenTagRepository(db) {
1065
1496
  const createdAt = nowIso();
1066
1497
  const protocolFields = protocolRunFieldsFromEvent(event, createdAt);
1067
1498
  const repoKey = projectTargetRefFromEvent(event);
1499
+ const expectedRunnerId = await repoBindingRunnerId(repoKey);
1500
+ const sourceDeliveryId = sourceDeliveryIdFromEvent(event);
1501
+ if (sourceDeliveryId) {
1502
+ const existingDelivery = await db.select().from(sourceDeliveries).where(and(eq(sourceDeliveries.source, event.source), eq(sourceDeliveries.deliveryId, sourceDeliveryId))).limit(1).get();
1503
+ if (existingDelivery) {
1504
+ const existingByDelivery = await db.select().from(runs).where(eq(runs.id, existingDelivery.runId)).limit(1).get();
1505
+ if (existingByDelivery) {
1506
+ return recordCreateRunReplay({
1507
+ runRow: existingByDelivery,
1508
+ requestedRunId: input.id,
1509
+ event,
1510
+ projectTarget: repoKey,
1511
+ expectedRunnerId,
1512
+ replayKind: "source_delivery",
1513
+ sourceDeliveryId,
1514
+ createdAt
1515
+ });
1516
+ }
1517
+ }
1518
+ }
1068
1519
  const insertResult = await db.insert(runs).values({
1069
1520
  id: input.id,
1070
1521
  eventId: event.id,
@@ -1088,32 +1539,16 @@ function createOpenTagRepository(db) {
1088
1539
  if (!existingBySourceEvent) {
1089
1540
  throw new Error(`Run already exists for event ${event.id}, but it could not be loaded`);
1090
1541
  }
1091
- const replayDecision = RunAdmissionDecisionSchema.parse({
1092
- action: "drop_duplicate",
1093
- reason: "Source event already created a run.",
1094
- reasonCode: "duplicate_source_event",
1095
- decidedAt: createdAt,
1096
- activeRunId: existingBySourceEvent.id,
1097
- eventId: event.id
1098
- });
1099
- await appendRunEvent({
1100
- runId: existingBySourceEvent.id,
1101
- type: "admission.decided",
1102
- payload: replayDecision,
1103
- visibility: "audit",
1104
- importance: "normal",
1105
- message: replayDecision.reason,
1106
- createdAt
1107
- });
1108
- await appendRunEvent({
1109
- runId: existingBySourceEvent.id,
1110
- type: "run.create_idempotent_replay",
1111
- payload: { requestedRunId: input.id, eventId: event.id },
1112
- visibility: "audit",
1113
- importance: "low",
1542
+ return recordCreateRunReplay({
1543
+ runRow: existingBySourceEvent,
1544
+ requestedRunId: input.id,
1545
+ event,
1546
+ projectTarget: repoKey,
1547
+ expectedRunnerId,
1548
+ replayKind: "source_event",
1549
+ sourceDeliveryId,
1114
1550
  createdAt
1115
1551
  });
1116
- return { run: runFromRow(existingBySourceEvent), created: false };
1117
1552
  }
1118
1553
  const createDecision = RunAdmissionDecisionSchema.parse({
1119
1554
  action: "start",
@@ -1122,6 +1557,15 @@ function createOpenTagRepository(db) {
1122
1557
  decidedAt: createdAt,
1123
1558
  eventId: event.id
1124
1559
  });
1560
+ if (sourceDeliveryId) {
1561
+ await db.insert(sourceDeliveries).values({
1562
+ source: event.source,
1563
+ deliveryId: sourceDeliveryId,
1564
+ runId: input.id,
1565
+ eventId: event.id,
1566
+ createdAt
1567
+ }).onConflictDoNothing({ target: [sourceDeliveries.source, sourceDeliveries.deliveryId] });
1568
+ }
1125
1569
  await appendRunEvent({
1126
1570
  runId: input.id,
1127
1571
  type: "admission.decided",
@@ -1134,7 +1578,15 @@ function createOpenTagRepository(db) {
1134
1578
  await appendRunEvent({
1135
1579
  runId: input.id,
1136
1580
  type: "run.created",
1137
- payload: { eventId: event.id },
1581
+ payload: {
1582
+ eventId: event.id,
1583
+ provenance: runProvenance({
1584
+ event,
1585
+ projectTarget: repoKey,
1586
+ admissionDecision: createDecision,
1587
+ expectedRunnerId
1588
+ })
1589
+ },
1138
1590
  visibility: "audit",
1139
1591
  importance: "low",
1140
1592
  createdAt
@@ -1184,8 +1636,35 @@ function createOpenTagRepository(db) {
1184
1636
  created: true
1185
1637
  };
1186
1638
  },
1639
+ async pruneSourceDeliveries(input) {
1640
+ const cutoff = new Date(input.olderThan);
1641
+ if (!Number.isFinite(cutoff.getTime())) {
1642
+ throw new Error("olderThan must be a valid timestamp.");
1643
+ }
1644
+ const requestedLimit = input.limit ?? 1e3;
1645
+ const limit = Number.isFinite(requestedLimit) ? Math.max(1, Math.floor(requestedLimit)) : 1e3;
1646
+ const rows = await db.select().from(sourceDeliveries).where(lt(sourceDeliveries.createdAt, cutoff.toISOString())).orderBy(asc(sourceDeliveries.createdAt)).limit(limit);
1647
+ let pruned = 0;
1648
+ let retainedActive = 0;
1649
+ for (const row of rows) {
1650
+ const runRow = await db.select({ status: runs.status }).from(runs).where(eq(runs.id, row.runId)).limit(1).get();
1651
+ if (runRow && !terminalRunStatus(runRow.status)) {
1652
+ retainedActive += 1;
1653
+ continue;
1654
+ }
1655
+ const result = await db.delete(sourceDeliveries).where(and(eq(sourceDeliveries.source, row.source), eq(sourceDeliveries.deliveryId, row.deliveryId)));
1656
+ pruned += result.changes;
1657
+ }
1658
+ return {
1659
+ scanned: rows.length,
1660
+ pruned,
1661
+ retainedActive
1662
+ };
1663
+ },
1187
1664
  async claimNextRun(input) {
1188
1665
  const now = /* @__PURE__ */ new Date();
1666
+ const runnerHeartbeatAt = nowIso();
1667
+ await db.update(runners).set({ heartbeatAt: runnerHeartbeatAt }).where(eq(runners.runnerId, input.runnerId));
1189
1668
  const activeRows = await db.select().from(runs).where(inArray(runs.status, ["assigned", "running"])).orderBy(asc(runs.createdAt));
1190
1669
  for (const activeRow of activeRows) {
1191
1670
  if (!isIsoExpired(activeRow.leaseExpiresAt, now)) continue;
@@ -1309,6 +1788,7 @@ function createOpenTagRepository(db) {
1309
1788
  if (!row) return false;
1310
1789
  const leaseSeconds = input.leaseSeconds ?? 60;
1311
1790
  const leaseExpiresAt = new Date(Date.now() + leaseSeconds * 1e3).toISOString();
1791
+ await db.update(runners).set({ heartbeatAt: updatedAt }).where(eq(runners.runnerId, input.runnerId));
1312
1792
  await db.update(runs).set({ heartbeatAt: updatedAt, leaseExpiresAt, updatedAt }).where(eq(runs.id, input.runId));
1313
1793
  await appendRunEvent({
1314
1794
  runId: input.runId,
@@ -1326,31 +1806,55 @@ function createOpenTagRepository(db) {
1326
1806
  if (input.runnerId) {
1327
1807
  conditions.push(eq(runs.assignedRunnerId, input.runnerId));
1328
1808
  }
1809
+ if (input.idempotencyKey) {
1810
+ const existing = await db.select().from(runEvents).where(eq(runEvents.runId, input.runId)).orderBy(desc(runEvents.id)).limit(250);
1811
+ for (const event of existing) {
1812
+ if (event.type !== "run.running") continue;
1813
+ const payload = recordFromJson(event.payloadJson);
1814
+ if (payload?.["idempotencyKey"] === input.idempotencyKey) return "duplicate";
1815
+ }
1816
+ }
1329
1817
  const updateResult = await db.update(runs).set({ status: "running", executor: input.executor, updatedAt }).where(and(...conditions));
1330
1818
  if (updateResult.changes === 0) {
1331
- return false;
1819
+ return "not_found";
1332
1820
  }
1333
1821
  await appendRunEvent({
1334
1822
  runId: input.runId,
1335
1823
  type: "run.running",
1336
- payload: input.runnerId ? { runnerId: input.runnerId, executor: input.executor } : { executor: input.executor },
1824
+ payload: {
1825
+ ...input.runnerId ? { runnerId: input.runnerId } : {},
1826
+ ...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {},
1827
+ executor: input.executor,
1828
+ ...input.runTimeoutMs ? { runTimeoutMs: input.runTimeoutMs } : {}
1829
+ },
1337
1830
  visibility: "audit",
1338
1831
  importance: "normal",
1339
1832
  createdAt: updatedAt
1340
1833
  });
1341
- return true;
1834
+ return "running";
1342
1835
  },
1343
1836
  async completeRun(input) {
1344
1837
  const result = OpenTagRunResultSchema.parse(input.result);
1345
1838
  const updatedAt = nowIso();
1346
- const status = result.conclusion === "success" ? "succeeded" : result.conclusion === "cancelled" ? "cancelled" : result.conclusion === "needs_human" ? "needs_approval" : "failed";
1839
+ const status = result.conclusion === "success" ? "succeeded" : result.conclusion === "cancelled" ? "cancelled" : result.conclusion === "interrupted" ? "interrupted" : result.conclusion === "timed_out" ? "timed_out" : result.conclusion === "needs_human" ? "needs_approval" : "failed";
1347
1840
  const runRow = await db.select().from(runs).where(eq(runs.id, input.runId)).limit(1).get();
1348
1841
  if (!runRow) {
1349
- if (input.runnerId) return false;
1842
+ if (input.runnerId) return "not_found";
1350
1843
  throw new Error(`Run not found: ${input.runId}`);
1351
1844
  }
1845
+ if (input.idempotencyKey) {
1846
+ const existing = await db.select().from(runEvents).where(eq(runEvents.runId, input.runId)).orderBy(desc(runEvents.id)).limit(250);
1847
+ for (const event of existing) {
1848
+ if (event.type !== "run.completed") continue;
1849
+ const payload = recordFromJson(event.payloadJson);
1850
+ if (payload?.["idempotencyKey"] === input.idempotencyKey) return "duplicate";
1851
+ }
1852
+ }
1853
+ if (terminalRunStatus(runRow.status)) {
1854
+ return "not_found";
1855
+ }
1352
1856
  if (input.runnerId && runRow.assignedRunnerId !== input.runnerId) {
1353
- return false;
1857
+ return "not_found";
1354
1858
  }
1355
1859
  const runThread = runRow ? protocolRunFieldsFromEvent(OpenTagEventSchema.parse(JSON.parse(runRow.eventJson)), runRow.createdAt).thread : void 0;
1356
1860
  await db.update(runs).set({
@@ -1394,7 +1898,10 @@ function createOpenTagRepository(db) {
1394
1898
  await appendRunEvent({
1395
1899
  runId: input.runId,
1396
1900
  type: "run.completed",
1397
- payload: result,
1901
+ payload: {
1902
+ ...result,
1903
+ ...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}
1904
+ },
1398
1905
  visibility: "audit",
1399
1906
  importance: "high",
1400
1907
  message: result.summary,
@@ -1414,7 +1921,7 @@ function createOpenTagRepository(db) {
1414
1921
  createdAt: updatedAt
1415
1922
  });
1416
1923
  }
1417
- return true;
1924
+ return "completed";
1418
1925
  },
1419
1926
  async getSuggestedChanges(input) {
1420
1927
  const row = await db.select().from(suggestedChanges).where(eq(suggestedChanges.proposalId, input.proposalId)).limit(1).get();
@@ -1602,13 +2109,22 @@ function createOpenTagRepository(db) {
1602
2109
  async recordProgress(input) {
1603
2110
  if (input.runnerId) {
1604
2111
  const row = await db.select().from(runs).where(and(eq(runs.id, input.runId), eq(runs.assignedRunnerId, input.runnerId))).limit(1).get();
1605
- if (!row) return false;
2112
+ if (!row) return "not_found";
2113
+ }
2114
+ if (input.idempotencyKey) {
2115
+ const existing = await db.select().from(runEvents).where(eq(runEvents.runId, input.runId)).orderBy(desc(runEvents.id)).limit(250);
2116
+ for (const event of existing) {
2117
+ if (event.type !== "run.progress") continue;
2118
+ const payload = recordFromJson(event.payloadJson);
2119
+ if (payload?.["idempotencyKey"] === input.idempotencyKey) return "duplicate";
2120
+ }
1606
2121
  }
1607
2122
  await appendRunEvent({
1608
2123
  runId: input.runId,
1609
2124
  type: "run.progress",
1610
2125
  payload: {
1611
2126
  ...input.runnerId ? { runnerId: input.runnerId } : {},
2127
+ ...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {},
1612
2128
  type: input.type ?? "progress",
1613
2129
  message: input.message,
1614
2130
  at: input.at ?? nowIso()
@@ -1618,7 +2134,7 @@ function createOpenTagRepository(db) {
1618
2134
  message: input.message,
1619
2135
  createdAt: input.at ?? nowIso()
1620
2136
  });
1621
- return true;
2137
+ return "recorded";
1622
2138
  },
1623
2139
  async getRun(input) {
1624
2140
  const row = await db.select().from(runs).where(eq(runs.id, input.runId)).limit(1).get();
@@ -1641,8 +2157,91 @@ function createOpenTagRepository(db) {
1641
2157
  createdAt: row.createdAt
1642
2158
  }));
1643
2159
  },
2160
+ async appendControlPlaneEvent(input) {
2161
+ await db.insert(controlPlaneEvents).values({
2162
+ type: input.type,
2163
+ severity: input.severity ?? "info",
2164
+ subject: input.subject ?? null,
2165
+ payloadJson: JSON.stringify(input.payload ?? {}),
2166
+ createdAt: input.createdAt ?? nowIso()
2167
+ });
2168
+ },
2169
+ async listControlPlaneEvents(input = {}) {
2170
+ const conditions = [
2171
+ ...input.type ? [eq(controlPlaneEvents.type, input.type)] : [],
2172
+ ...input.severity ? [eq(controlPlaneEvents.severity, input.severity)] : []
2173
+ ];
2174
+ const rows = await db.select().from(controlPlaneEvents).where(conditions.length > 0 ? and(...conditions) : void 0).orderBy(asc(controlPlaneEvents.id)).limit(input.limit ?? 100);
2175
+ return rows.map((row) => ({
2176
+ id: row.id,
2177
+ type: row.type,
2178
+ severity: row.severity,
2179
+ ...row.subject ? { subject: row.subject } : {},
2180
+ payload: JSON.parse(row.payloadJson),
2181
+ createdAt: row.createdAt
2182
+ }));
2183
+ },
2184
+ async summarizeControlPlaneAlerts(input = {}) {
2185
+ const limit = input.limit ?? 5e3;
2186
+ const rows = await db.select().from(controlPlaneEvents).orderBy(desc(controlPlaneEvents.id)).limit(limit);
2187
+ const claimRows = await db.select().from(runEvents).where(eq(runEvents.type, "run.claimed")).orderBy(desc(runEvents.id)).limit(limit);
2188
+ const groups = /* @__PURE__ */ new Map();
2189
+ function addEvent(event) {
2190
+ if (input.since && event.createdAt < input.since) return;
2191
+ const kind = controlPlaneAlertKind(event);
2192
+ if (!kind) return;
2193
+ const subject = controlPlaneAlertSubject(event);
2194
+ const key = `${kind}|${event.type}|${subject}`;
2195
+ const group = groups.get(key) ?? { kind, eventType: event.type, subject, events: [] };
2196
+ group.events.push(event);
2197
+ groups.set(key, group);
2198
+ }
2199
+ for (const row of rows.reverse()) {
2200
+ addEvent({
2201
+ id: row.id,
2202
+ type: row.type,
2203
+ severity: row.severity,
2204
+ ...row.subject ? { subject: row.subject } : {},
2205
+ payload: JSON.parse(row.payloadJson),
2206
+ createdAt: row.createdAt
2207
+ });
2208
+ }
2209
+ for (const row of claimRows.reverse()) {
2210
+ addEvent({
2211
+ id: row.id,
2212
+ type: row.type,
2213
+ severity: "info",
2214
+ subject: row.runId,
2215
+ payload: JSON.parse(row.payloadJson),
2216
+ createdAt: row.createdAt
2217
+ });
2218
+ }
2219
+ return [...groups.values()].flatMap((group) => {
2220
+ const threshold = controlPlaneAlertThreshold(group.kind, input.thresholds);
2221
+ if (group.events.length < threshold) return [];
2222
+ const metadata = controlPlaneAlertMetadata(group.kind);
2223
+ const first = group.events[0];
2224
+ const last = group.events.at(-1);
2225
+ return [
2226
+ {
2227
+ id: `${group.kind}:${group.eventType}:${group.subject}`,
2228
+ type: group.kind,
2229
+ severity: metadata.severity,
2230
+ eventType: group.eventType,
2231
+ count: group.events.length,
2232
+ threshold,
2233
+ firstSeenAt: first.createdAt,
2234
+ lastSeenAt: last.createdAt,
2235
+ subject: group.subject,
2236
+ reason: metadata.reason,
2237
+ nextAction: metadata.nextAction
2238
+ }
2239
+ ];
2240
+ }).sort((left, right) => right.count - left.count || left.id.localeCompare(right.id));
2241
+ },
1644
2242
  async enqueueCallbackDelivery(input) {
1645
2243
  const createdAt = nowIso();
2244
+ const idempotencyKey = callbackIdempotencyKey(input);
1646
2245
  const rows = await db.insert(callbackDeliveries).values({
1647
2246
  runId: input.runId,
1648
2247
  kind: input.kind,
@@ -1650,17 +2249,32 @@ function createOpenTagRepository(db) {
1650
2249
  uri: input.uri,
1651
2250
  body: input.body,
1652
2251
  threadKey: input.threadKey ?? null,
2252
+ idempotencyKey,
1653
2253
  metadataJson: JSON.stringify({
1654
2254
  ...input.agentId ? { agentId: input.agentId } : {},
1655
2255
  ...input.statusMessageKey ? { statusMessageKey: input.statusMessageKey } : {},
1656
- ...input.blocks ? { blocks: input.blocks } : {}
2256
+ ...input.blocks ? { blocks: input.blocks } : {},
2257
+ ...input.rich !== void 0 ? { rich: input.rich } : {}
1657
2258
  }),
1658
2259
  status: "pending",
1659
2260
  createdAt,
1660
2261
  updatedAt: createdAt
1661
- }).returning();
2262
+ }).onConflictDoNothing({ target: callbackDeliveries.idempotencyKey }).returning();
1662
2263
  const row = rows[0];
1663
- if (!row) throw new Error("callback delivery was not created");
2264
+ if (!row) {
2265
+ const existing = await db.select().from(callbackDeliveries).where(eq(callbackDeliveries.idempotencyKey, idempotencyKey)).limit(1).get();
2266
+ if (!existing) throw new Error("callback delivery was not created");
2267
+ await appendRunEvent({
2268
+ runId: input.runId,
2269
+ type: `callback.${input.kind}.duplicate`,
2270
+ payload: callbackDeliveryFromRow(existing),
2271
+ visibility: "audit",
2272
+ importance: "normal",
2273
+ message: "Duplicate callback delivery suppressed.",
2274
+ createdAt
2275
+ });
2276
+ return callbackDeliveryFromRow(existing);
2277
+ }
1664
2278
  await appendRunEvent({
1665
2279
  runId: input.runId,
1666
2280
  type: `callback.${input.kind}.queued`,
@@ -1675,29 +2289,46 @@ function createOpenTagRepository(db) {
1675
2289
  const updatedAt = nowIso();
1676
2290
  const row = await db.select().from(callbackDeliveries).where(eq(callbackDeliveries.id, input.deliveryId)).limit(1).get();
1677
2291
  if (!row) return;
1678
- await db.update(callbackDeliveries).set({ status: "delivered", attempts: row.attempts + 1, lastError: null, nextAttemptAt: null, updatedAt }).where(eq(callbackDeliveries.id, input.deliveryId));
2292
+ const metadata = callbackDeliveryMetadataFromJson(row.metadataJson) ?? {};
2293
+ const metadataJson = JSON.stringify({
2294
+ ...metadata,
2295
+ ...input.externalMessageId ? { externalMessageId: input.externalMessageId } : {}
2296
+ });
2297
+ await db.update(callbackDeliveries).set({ status: "delivered", attempts: row.attempts + 1, lastError: null, nextAttemptAt: null, metadataJson, updatedAt }).where(eq(callbackDeliveries.id, input.deliveryId));
2298
+ const deliveredRow = { ...row, metadataJson };
1679
2299
  await appendRunEvent({
1680
2300
  runId: row.runId,
1681
2301
  type: `callback.${row.kind}.delivered`,
1682
- payload: { ...callbackDeliveryFromRow(row), status: "delivered", attempts: row.attempts + 1, updatedAt },
2302
+ payload: { ...callbackDeliveryFromRow(deliveredRow), status: "delivered", attempts: row.attempts + 1, updatedAt },
1683
2303
  visibility: "human",
1684
2304
  importance: row.kind === "final" ? "high" : "normal",
1685
2305
  message: row.body,
1686
2306
  createdAt: updatedAt
1687
2307
  });
1688
2308
  },
2309
+ async findCallbackExternalMessageId(input) {
2310
+ const rows = await db.select().from(callbackDeliveries).where(and(eq(callbackDeliveries.runId, input.runId), eq(callbackDeliveries.provider, input.provider), eq(callbackDeliveries.status, "delivered"))).orderBy(desc(callbackDeliveries.updatedAt), desc(callbackDeliveries.id));
2311
+ for (const row of rows) {
2312
+ const delivery = callbackDeliveryFromRow(row);
2313
+ if (delivery.statusMessageKey !== input.statusMessageKey) continue;
2314
+ if ((delivery.threadKey ?? void 0) !== input.threadKey) continue;
2315
+ if (delivery.externalMessageId) return delivery.externalMessageId;
2316
+ }
2317
+ return void 0;
2318
+ },
1689
2319
  async markCallbackFailed(input) {
1690
2320
  const updatedAt = nowIso();
1691
2321
  const row = await db.select().from(callbackDeliveries).where(eq(callbackDeliveries.id, input.deliveryId)).limit(1).get();
1692
2322
  if (!row) return;
1693
- await db.update(callbackDeliveries).set({ status: "failed", attempts: row.attempts + 1, lastError: input.error, nextAttemptAt: input.nextAttemptAt ?? null, updatedAt }).where(eq(callbackDeliveries.id, input.deliveryId));
2323
+ const attempts = row.attempts + 1;
2324
+ await db.update(callbackDeliveries).set({ status: "failed", attempts, lastError: input.error, nextAttemptAt: input.nextAttemptAt ?? null, updatedAt }).where(eq(callbackDeliveries.id, input.deliveryId));
1694
2325
  await appendRunEvent({
1695
2326
  runId: row.runId,
1696
2327
  type: `callback.${row.kind}.failed`,
1697
2328
  payload: {
1698
2329
  ...callbackDeliveryFromRow(row),
1699
2330
  status: "failed",
1700
- attempts: row.attempts + 1,
2331
+ attempts,
1701
2332
  lastError: input.error,
1702
2333
  ...input.nextAttemptAt ? { nextAttemptAt: input.nextAttemptAt } : {},
1703
2334
  updatedAt
@@ -1706,6 +2337,24 @@ function createOpenTagRepository(db) {
1706
2337
  importance: "normal",
1707
2338
  createdAt: updatedAt
1708
2339
  });
2340
+ if (input.maxAttempts !== void 0 && attempts >= input.maxAttempts && !input.nextAttemptAt) {
2341
+ await appendRunEvent({
2342
+ runId: row.runId,
2343
+ type: `callback.${row.kind}.suppressed`,
2344
+ payload: {
2345
+ ...callbackDeliveryFromRow(row),
2346
+ status: "failed",
2347
+ attempts,
2348
+ maxAttempts: input.maxAttempts,
2349
+ lastError: input.error,
2350
+ updatedAt
2351
+ },
2352
+ visibility: "audit",
2353
+ importance: "high",
2354
+ message: "Callback delivery retry budget exhausted; further delivery attempts are suppressed to avoid duplicate storms.",
2355
+ createdAt: updatedAt
2356
+ });
2357
+ }
1709
2358
  },
1710
2359
  async listPendingCallbackDeliveries(input) {
1711
2360
  const now = input.now ?? /* @__PURE__ */ new Date();
@@ -1815,6 +2464,7 @@ export {
1815
2464
  approvalDecisions,
1816
2465
  callbackDeliveries,
1817
2466
  channelBindings,
2467
+ controlPlaneEvents,
1818
2468
  createOpenTagRepository,
1819
2469
  followUpRequests,
1820
2470
  migrateSchema,
@@ -1824,6 +2474,7 @@ export {
1824
2474
  runEvents,
1825
2475
  runners,
1826
2476
  runs,
2477
+ sourceDeliveries,
1827
2478
  suggestedChanges
1828
2479
  };
1829
2480
  //# sourceMappingURL=index.js.map