@byok-sdk/cloud-dataplane 0.11.0 → 0.13.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.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import pg from 'pg';
2
- import { DEDUP_RING_CAPACITY, NONCE_TTL_MS, validateActivityAppend, projectTimelineEvents, ByokCloudError, validateApprovalTimelineAppend, ApprovalObservationSchema, AllowAllRateLimiter, TruthCommitError, TruthCommitResponseSchema, truthRecordMetadata, TRUTH_REQUEST_ID_MAX_LENGTH, parseTimelineEvents, activityCursor, parseApprovalObservations, approvalTimelineCursor } from '@byok-sdk/cloud';
2
+ import { DEDUP_RING_CAPACITY, NONCE_TTL_MS, assertTaskAttemptListLimit, validateActivityAppend, projectTimelineEvents, activitySourceBatchState, ByokCloudError, validateApprovalTimelineAppend, ApprovalObservationSchema, validateApprovalTimelineResolve, AllowAllRateLimiter, TruthCommitError, TruthCommitResponseSchema, truthRecordMetadata, TRUTH_REQUEST_ID_MAX_LENGTH, parseTimelineEvents, activityCursor, parseApprovalObservations, approvalTimelineCursor } from '@byok-sdk/cloud';
3
3
  import { contentHash, ByokCoreError, assertCanonicalTimestamp, isContentHash, tenantObjectKey, objectKeyPrefix, CoreConflictError, isLegalBoardTransition, checkSkillPackManifest, checkSkillPackEntry, SKILL_PACK_ENTRY_PATH, tenantId, SKILL_PACK_MANIFEST_SCHEMA_ID } from '@byok-sdk/core';
4
4
  import { AwsClient } from 'aws4fetch';
5
5
  import { XMLParser } from 'fast-xml-parser';
@@ -826,6 +826,57 @@ async function rollback(client) {
826
826
  } catch {
827
827
  }
828
828
  }
829
+ async function registerDeviceOnClient(client, tenant, input) {
830
+ if (input.machineId !== void 0) {
831
+ const superseded = await client.query(
832
+ `DELETE FROM device
833
+ WHERE tenant_id = $1 AND product_id = $2 AND machine_id = $3 AND device_id <> $4
834
+ RETURNING device_id`,
835
+ [tenant, input.productId, input.machineId, input.deviceId]
836
+ );
837
+ await deleteDeviceState(
838
+ client,
839
+ tenant,
840
+ superseded.rows.map((row) => row.device_id)
841
+ );
842
+ }
843
+ const result = await client.query(
844
+ `INSERT INTO device (
845
+ tenant_id, device_id, product_id, device_name, device_public_key,
846
+ proof_key_id, proof_key_epoch, revoked, machine_id
847
+ )
848
+ VALUES ($1, $2, $3, $4, $5, $6, $7, false, $8)
849
+ ON CONFLICT (tenant_id, device_id) DO UPDATE
850
+ SET product_id = EXCLUDED.product_id,
851
+ device_name = EXCLUDED.device_name,
852
+ device_public_key = EXCLUDED.device_public_key,
853
+ proof_key_id = EXCLUDED.proof_key_id,
854
+ proof_key_epoch = EXCLUDED.proof_key_epoch,
855
+ revoked = false,
856
+ machine_id = EXCLUDED.machine_id,
857
+ capabilities = NULL
858
+ RETURNING ${SELECT_COLUMNS}`,
859
+ [
860
+ tenant,
861
+ input.deviceId,
862
+ input.productId,
863
+ input.deviceName,
864
+ input.devicePublicKey,
865
+ input.proofKeyId,
866
+ input.proofKeyEpoch,
867
+ input.machineId ?? null
868
+ ]
869
+ );
870
+ return toRecord(result.rows[0]);
871
+ }
872
+ async function getDeviceOnClient(client, tenant, deviceId) {
873
+ const result = await client.query(
874
+ `SELECT ${SELECT_COLUMNS} FROM device WHERE tenant_id = $1 AND device_id = $2`,
875
+ [tenant, deviceId]
876
+ );
877
+ const row = result.rows[0];
878
+ return row === void 0 ? void 0 : toRecord(row);
879
+ }
829
880
  var PostgresDeviceDirectory = class {
830
881
  #pool;
831
882
  #clock;
@@ -837,48 +888,9 @@ var PostgresDeviceDirectory = class {
837
888
  const client = await this.#pool.connect();
838
889
  try {
839
890
  await client.query("BEGIN");
840
- if (input.machineId !== void 0) {
841
- const superseded = await client.query(
842
- `DELETE FROM device
843
- WHERE tenant_id = $1 AND product_id = $2 AND machine_id = $3 AND device_id <> $4
844
- RETURNING device_id`,
845
- [tenant, input.productId, input.machineId, input.deviceId]
846
- );
847
- await deleteDeviceState(
848
- client,
849
- tenant,
850
- superseded.rows.map((row) => row.device_id)
851
- );
852
- }
853
- const result = await client.query(
854
- `INSERT INTO device (
855
- tenant_id, device_id, product_id, device_name, device_public_key,
856
- proof_key_id, proof_key_epoch, revoked, machine_id
857
- )
858
- VALUES ($1, $2, $3, $4, $5, $6, $7, false, $8)
859
- ON CONFLICT (tenant_id, device_id) DO UPDATE
860
- SET product_id = EXCLUDED.product_id,
861
- device_name = EXCLUDED.device_name,
862
- device_public_key = EXCLUDED.device_public_key,
863
- proof_key_id = EXCLUDED.proof_key_id,
864
- proof_key_epoch = EXCLUDED.proof_key_epoch,
865
- revoked = false,
866
- machine_id = EXCLUDED.machine_id,
867
- capabilities = NULL
868
- RETURNING ${SELECT_COLUMNS}`,
869
- [
870
- tenant,
871
- input.deviceId,
872
- input.productId,
873
- input.deviceName,
874
- input.devicePublicKey,
875
- input.proofKeyId,
876
- input.proofKeyEpoch,
877
- input.machineId ?? null
878
- ]
879
- );
891
+ const record = await registerDeviceOnClient(client, tenant, input);
880
892
  await client.query("COMMIT");
881
- return toRecord(result.rows[0]);
893
+ return record;
882
894
  } catch (cause) {
883
895
  await rollback(client);
884
896
  throw cause;
@@ -887,12 +899,12 @@ var PostgresDeviceDirectory = class {
887
899
  }
888
900
  }
889
901
  async get(tenant, deviceId) {
890
- const result = await this.#pool.query(
891
- `SELECT ${SELECT_COLUMNS} FROM device WHERE tenant_id = $1 AND device_id = $2`,
892
- [tenant, deviceId]
893
- );
894
- const row = result.rows[0];
895
- return row === void 0 ? void 0 : toRecord(row);
902
+ const client = await this.#pool.connect();
903
+ try {
904
+ return await getDeviceOnClient(client, tenant, deviceId);
905
+ } finally {
906
+ client.release();
907
+ }
896
908
  }
897
909
  /**
898
910
  * Revocation removes the registration and the device-scoped state that only
@@ -1026,23 +1038,31 @@ var PostgresInboundDedupStore = class {
1026
1038
  this.#capacity = capacity;
1027
1039
  }
1028
1040
  async checkAndRecord(tenant, deviceId, envelopeId) {
1041
+ return this.#checkAndRecord(tenant, deviceId, "", "", envelopeId);
1042
+ }
1043
+ async checkAndRecordAgent(tenant, deviceId, agentRef, envelopeId) {
1044
+ return this.#checkAndRecord(tenant, deviceId, agentRef.agentId, agentRef.profileRevision, envelopeId);
1045
+ }
1046
+ async #checkAndRecord(tenant, deviceId, agentId, agentProfileRevision, envelopeId) {
1029
1047
  const inserted = await this.#pool.query(
1030
- `INSERT INTO inbound_dedup (tenant_id, device_id, envelope_id)
1031
- VALUES ($1, $2, $3)
1032
- ON CONFLICT (tenant_id, device_id, envelope_id) DO NOTHING
1048
+ `INSERT INTO inbound_dedup (tenant_id, device_id, agent_id, agent_profile_revision, envelope_id)
1049
+ VALUES ($1, $2, $3, $4, $5)
1050
+ ON CONFLICT (tenant_id, device_id, agent_id, agent_profile_revision, envelope_id) DO NOTHING
1033
1051
  RETURNING 1`,
1034
- [tenant, deviceId, envelopeId]
1052
+ [tenant, deviceId, agentId, agentProfileRevision, envelopeId]
1035
1053
  );
1036
1054
  if (inserted.rowCount === 0) return true;
1037
1055
  await this.#pool.query(
1038
1056
  `DELETE FROM inbound_dedup
1039
1057
  WHERE tenant_id = $1 AND device_id = $2
1058
+ AND agent_id = $3 AND agent_profile_revision = $4
1040
1059
  AND recorded_seq <= (
1041
1060
  SELECT recorded_seq FROM inbound_dedup
1042
1061
  WHERE tenant_id = $1 AND device_id = $2
1062
+ AND agent_id = $3 AND agent_profile_revision = $4
1043
1063
  ORDER BY recorded_seq DESC
1044
- OFFSET $3 LIMIT 1)`,
1045
- [tenant, deviceId, this.#capacity]
1064
+ OFFSET $5 LIMIT 1)`,
1065
+ [tenant, deviceId, agentId, agentProfileRevision, this.#capacity]
1046
1066
  );
1047
1067
  return false;
1048
1068
  }
@@ -1074,24 +1094,58 @@ var PostgresNonceStore = class {
1074
1094
  );
1075
1095
  return nonce;
1076
1096
  }
1077
- async validate(tenant, deviceId, nonce) {
1097
+ async consumeIfValid(tenant, deviceId, nonce) {
1078
1098
  const result = await this.#pool.query(
1079
- `SELECT 1 FROM auth_nonce
1080
- WHERE tenant_id = $1 AND device_id = $2 AND nonce = $3
1081
- AND used = false AND expires_at >= $4`,
1099
+ `UPDATE auth_nonce
1100
+ SET used = true
1101
+ WHERE tenant_id = $1 AND device_id = $2 AND nonce = $3
1102
+ AND used = false AND expires_at >= $4
1103
+ RETURNING nonce`,
1082
1104
  [tenant, deviceId, nonce, this.#clock.now().toISOString()]
1083
1105
  );
1084
1106
  return result.rowCount === 1;
1085
1107
  }
1086
- async markUsed(tenant, nonce) {
1087
- await this.#pool.query("UPDATE auth_nonce SET used = true WHERE tenant_id = $1 AND nonce = $2", [
1088
- tenant,
1089
- nonce
1090
- ]);
1091
- }
1092
1108
  };
1093
1109
 
1094
1110
  // src/stores/pairing-codes.ts
1111
+ function bindingOf(input) {
1112
+ return {
1113
+ deviceName: input.deviceName,
1114
+ devicePublicKey: input.devicePublicKey,
1115
+ proofKeyId: input.proofKeyId,
1116
+ proofKeyEpoch: input.proofKeyEpoch,
1117
+ ...input.machineId === void 0 ? {} : { machineId: input.machineId }
1118
+ };
1119
+ }
1120
+ async function completionKey(pairingCode) {
1121
+ const digest = new Uint8Array(await globalThis.crypto.subtle.digest(
1122
+ "SHA-256",
1123
+ new TextEncoder().encode(`byok-pairing-completion-v1\0${pairingCode}`)
1124
+ ));
1125
+ const hex = [...digest].map((byte) => byte.toString(16).padStart(2, "0")).join("");
1126
+ return `pairing-completion:v1:sha256:${hex}`;
1127
+ }
1128
+ function sameBinding(left, right) {
1129
+ return left.deviceName === right.deviceName && left.devicePublicKey === right.devicePublicKey && left.proofKeyId === right.proofKeyId && left.proofKeyEpoch === right.proofKeyEpoch && left.machineId === right.machineId;
1130
+ }
1131
+ function sameDevice(device, claims, binding) {
1132
+ return device.tenantId === claims.tenant_id && device.productId === claims.product_id && device.deviceName === binding.deviceName && device.devicePublicKey === binding.devicePublicKey && device.proofKeyId === binding.proofKeyId && device.proofKeyEpoch === binding.proofKeyEpoch && device.machineId === binding.machineId;
1133
+ }
1134
+ function parseCompletion(body) {
1135
+ try {
1136
+ const parsed = JSON.parse(body);
1137
+ if (typeof parsed.deviceId !== "string" || parsed.binding === void 0 || typeof parsed.binding.deviceName !== "string" || typeof parsed.binding.devicePublicKey !== "string" || typeof parsed.binding.proofKeyId !== "string" || typeof parsed.binding.proofKeyEpoch !== "number" || parsed.binding.machineId !== void 0 && typeof parsed.binding.machineId !== "string") return void 0;
1138
+ return parsed;
1139
+ } catch {
1140
+ return void 0;
1141
+ }
1142
+ }
1143
+ async function rollback2(client) {
1144
+ try {
1145
+ await client.query("ROLLBACK");
1146
+ } catch {
1147
+ }
1148
+ }
1095
1149
  var PostgresPairingCodeStore = class {
1096
1150
  #pool;
1097
1151
  #clock;
@@ -1100,30 +1154,102 @@ var PostgresPairingCodeStore = class {
1100
1154
  this.#clock = clock;
1101
1155
  }
1102
1156
  async issue(tenant, input) {
1103
- await this.#pool.query(
1104
- `INSERT INTO pairing_code (code, tenant_id, product_id, expires_at, redeemed_at)
1105
- VALUES ($1, $2, $3, $4, NULL)
1106
- ON CONFLICT (code) DO UPDATE
1107
- SET tenant_id = EXCLUDED.tenant_id,
1108
- product_id = EXCLUDED.product_id,
1109
- expires_at = EXCLUDED.expires_at,
1110
- redeemed_at = NULL`,
1111
- [input.code, tenant, input.productId, input.expiresAt]
1112
- );
1157
+ const client = await this.#pool.connect();
1158
+ try {
1159
+ await client.query("BEGIN");
1160
+ const previous = await client.query(
1161
+ "SELECT tenant_id FROM pairing_code WHERE code = $1 FOR UPDATE",
1162
+ [input.code]
1163
+ );
1164
+ const key = await completionKey(input.code);
1165
+ const previousTenant = previous.rows[0]?.tenant_id;
1166
+ if (previousTenant !== void 0) {
1167
+ await client.query(
1168
+ "DELETE FROM device_request_receipts WHERE tenant_id = $1 AND key = $2",
1169
+ [previousTenant, key]
1170
+ );
1171
+ }
1172
+ await client.query(
1173
+ `INSERT INTO pairing_code (code, tenant_id, product_id, expires_at, redeemed_at)
1174
+ VALUES ($1, $2, $3, $4, NULL)
1175
+ ON CONFLICT (code) DO UPDATE
1176
+ SET tenant_id = EXCLUDED.tenant_id,
1177
+ product_id = EXCLUDED.product_id,
1178
+ expires_at = EXCLUDED.expires_at,
1179
+ redeemed_at = NULL`,
1180
+ [input.code, tenant, input.productId, input.expiresAt]
1181
+ );
1182
+ await client.query("COMMIT");
1183
+ } catch (cause) {
1184
+ await rollback2(client);
1185
+ throw cause;
1186
+ } finally {
1187
+ client.release();
1188
+ }
1113
1189
  return { code: input.code, expiresAt: input.expiresAt };
1114
1190
  }
1115
- async redeem(code) {
1116
- const now = this.#clock.now().toISOString();
1117
- const result = await this.#pool.query(
1118
- `UPDATE pairing_code
1119
- SET redeemed_at = $2
1120
- WHERE code = $1 AND redeemed_at IS NULL AND expires_at >= $2
1121
- RETURNING tenant_id, product_id`,
1122
- [code, now]
1123
- );
1124
- const row = result.rows[0];
1125
- if (row === void 0) return void 0;
1126
- return { tenantId: row.tenant_id, productId: row.product_id };
1191
+ async redeemAndRegister(input) {
1192
+ const now = this.#clock.now();
1193
+ const client = await this.#pool.connect();
1194
+ try {
1195
+ await client.query("BEGIN");
1196
+ const selected = await client.query(
1197
+ `SELECT tenant_id, product_id, expires_at, redeemed_at
1198
+ FROM pairing_code WHERE code = $1 FOR UPDATE`,
1199
+ [input.pairingCode]
1200
+ );
1201
+ const claims = selected.rows[0];
1202
+ if (claims === void 0) {
1203
+ await client.query("COMMIT");
1204
+ return void 0;
1205
+ }
1206
+ const tenant = claims.tenant_id;
1207
+ const binding = bindingOf(input);
1208
+ if (claims.redeemed_at !== null) {
1209
+ const stored = await client.query(
1210
+ "SELECT body FROM device_request_receipts WHERE tenant_id = $1 AND key = $2",
1211
+ [tenant, await completionKey(input.pairingCode)]
1212
+ );
1213
+ const completion2 = stored.rows[0] === void 0 ? void 0 : parseCompletion(stored.rows[0].body);
1214
+ if (completion2 === void 0 || !sameBinding(completion2.binding, binding)) {
1215
+ await client.query("COMMIT");
1216
+ return void 0;
1217
+ }
1218
+ const device2 = await getDeviceOnClient(client, tenant, completion2.deviceId);
1219
+ await client.query("COMMIT");
1220
+ return device2 === void 0 || !sameDevice(device2, claims, binding) ? void 0 : device2;
1221
+ }
1222
+ if (claims.expires_at.getTime() < now.getTime()) {
1223
+ await client.query("COMMIT");
1224
+ return void 0;
1225
+ }
1226
+ await client.query(
1227
+ "UPDATE pairing_code SET redeemed_at = $2 WHERE code = $1 AND redeemed_at IS NULL",
1228
+ [input.pairingCode, now.toISOString()]
1229
+ );
1230
+ const device = await registerDeviceOnClient(client, tenant, {
1231
+ productId: claims.product_id,
1232
+ deviceId: input.deviceId,
1233
+ deviceName: input.deviceName,
1234
+ devicePublicKey: input.devicePublicKey,
1235
+ proofKeyId: input.proofKeyId,
1236
+ proofKeyEpoch: input.proofKeyEpoch,
1237
+ ...input.machineId === void 0 ? {} : { machineId: input.machineId }
1238
+ });
1239
+ const completion = { deviceId: device.deviceId, binding };
1240
+ await client.query(
1241
+ `INSERT INTO device_request_receipts (tenant_id, key, body, recorded_at)
1242
+ VALUES ($1, $2, $3, $4)`,
1243
+ [tenant, await completionKey(input.pairingCode), JSON.stringify(completion), now.toISOString()]
1244
+ );
1245
+ await client.query("COMMIT");
1246
+ return device;
1247
+ } catch (cause) {
1248
+ await rollback2(client);
1249
+ throw cause;
1250
+ } finally {
1251
+ client.release();
1252
+ }
1127
1253
  }
1128
1254
  };
1129
1255
 
@@ -1230,9 +1356,15 @@ var PostgresProofRequestReceiptStore = class {
1230
1356
  return row === void 0 ? void 0 : toReceipt2(row);
1231
1357
  }
1232
1358
  };
1233
-
1234
- // src/stores/task-attempts.ts
1235
- var TASK_SELECT_COLUMNS = "tenant_id, task_id, device_id, agent_id, agent_profile_revision, owner_device_id, status, terminal_cause, cancel_requested_at, cancel_reason, cancel_message_id, updated_at";
1359
+ var TASK_SELECT_COLUMNS = "tenant_id, task_id, device_id, agent_id, agent_profile_revision, owner_device_id, status, terminal_cause, cancel_requested_at, cancel_reason, cancel_message_id, claimed_runtime, claimed_runtime_capabilities, updated_at";
1360
+ var AGENT_MESSAGE_ADMISSION_SELECT_COLUMNS = "message_id, payload_body, terminal_body";
1361
+ function admissionRowToRecord(row) {
1362
+ return {
1363
+ messageId: row.message_id,
1364
+ payloadBody: row.payload_body,
1365
+ ...row.terminal_body === null ? {} : { terminalBody: row.terminal_body }
1366
+ };
1367
+ }
1236
1368
  function taskRowToAttempt(row) {
1237
1369
  return {
1238
1370
  tenantId: row.tenant_id,
@@ -1250,6 +1382,10 @@ function taskRowToAttempt(row) {
1250
1382
  // former.
1251
1383
  ...row.owner_device_id === null ? {} : { ownerDeviceId: row.owner_device_id },
1252
1384
  status: row.status,
1385
+ // `claimed_runtime` is written only by the guarded claim UPDATE below, so a
1386
+ // non-null value here is always the winning claim's own self-report.
1387
+ ...row.claimed_runtime === null ? {} : { claimedRuntime: row.claimed_runtime },
1388
+ ...row.claimed_runtime_capabilities === null ? {} : { claimedRuntimeCapabilities: row.claimed_runtime_capabilities },
1253
1389
  ...row.terminal_cause == null ? {} : { terminalCause: row.terminal_cause },
1254
1390
  ...row.cancel_requested_at === null ? {} : {
1255
1391
  cancellation: {
@@ -1308,6 +1444,80 @@ var PostgresTaskAttemptStore = class {
1308
1444
  if (existing === void 0) throw new Error(`task ${input.taskId} vanished during Agent offer reservation`);
1309
1445
  return { attempt: existing, created: false };
1310
1446
  }
1447
+ async reserveAgentMessage(tenant, input) {
1448
+ const client = await this.#pool.connect();
1449
+ try {
1450
+ await client.query("BEGIN");
1451
+ const selected = await client.query(
1452
+ `SELECT ${TASK_SELECT_COLUMNS} FROM task
1453
+ WHERE tenant_id = $1 AND task_id = $2 FOR UPDATE`,
1454
+ [tenant, input.taskId]
1455
+ );
1456
+ const attempt = selected.rows[0];
1457
+ const existing = await client.query(
1458
+ `SELECT message_id, payload_body FROM agent_message_admission
1459
+ WHERE tenant_id = $1 AND device_id = $2 AND task_id = $3`,
1460
+ [tenant, input.deviceId, input.taskId]
1461
+ );
1462
+ const admission = existing.rows[0];
1463
+ if (admission !== void 0) {
1464
+ await client.query("COMMIT");
1465
+ return admission.message_id === input.messageId && admission.payload_body === input.payloadBody ? "pending" : "rejected";
1466
+ }
1467
+ if (attempt === void 0 || attempt.device_id !== input.deviceId || attempt.cancel_requested_at !== null || !["offered", "claimed", "running"].includes(attempt.status)) {
1468
+ await client.query("COMMIT");
1469
+ return "rejected";
1470
+ }
1471
+ const inserted = await client.query(
1472
+ `INSERT INTO agent_message_admission (
1473
+ tenant_id, device_id, task_id, message_id, payload_body
1474
+ ) VALUES ($1, $2, $3, $4, $5)
1475
+ ON CONFLICT (tenant_id, device_id, task_id) DO NOTHING
1476
+ RETURNING payload_body`,
1477
+ [tenant, input.deviceId, input.taskId, input.messageId, input.payloadBody]
1478
+ );
1479
+ if (inserted.rows[0] !== void 0) {
1480
+ await client.query("COMMIT");
1481
+ return "reserved";
1482
+ }
1483
+ const raced = await client.query(
1484
+ `SELECT message_id, payload_body FROM agent_message_admission
1485
+ WHERE tenant_id = $1 AND device_id = $2 AND task_id = $3`,
1486
+ [tenant, input.deviceId, input.taskId]
1487
+ );
1488
+ const racedAdmission = raced.rows[0];
1489
+ await client.query("COMMIT");
1490
+ return racedAdmission?.message_id === input.messageId && racedAdmission.payload_body === input.payloadBody ? "pending" : "rejected";
1491
+ } catch (cause) {
1492
+ await client.query("ROLLBACK").catch(() => {
1493
+ });
1494
+ throw cause;
1495
+ } finally {
1496
+ client.release();
1497
+ }
1498
+ }
1499
+ async readAgentMessage(tenant, input) {
1500
+ const result = await this.#pool.query(
1501
+ `SELECT ${AGENT_MESSAGE_ADMISSION_SELECT_COLUMNS}
1502
+ FROM agent_message_admission
1503
+ WHERE tenant_id = $1 AND device_id = $2 AND task_id = $3`,
1504
+ [tenant, input.deviceId, input.taskId]
1505
+ );
1506
+ const row = result.rows[0];
1507
+ return row !== void 0 && row.message_id === input.messageId && row.payload_body === input.payloadBody ? admissionRowToRecord(row) : void 0;
1508
+ }
1509
+ async finalizeAgentMessage(tenant, input) {
1510
+ const terminalized = await this.#pool.query(
1511
+ `UPDATE agent_message_admission
1512
+ SET terminal_body = $6
1513
+ WHERE tenant_id = $1 AND device_id = $2 AND task_id = $3
1514
+ AND message_id = $4 AND payload_body = $5 AND terminal_body IS NULL
1515
+ RETURNING ${AGENT_MESSAGE_ADMISSION_SELECT_COLUMNS}`,
1516
+ [tenant, input.deviceId, input.taskId, input.messageId, input.payloadBody, input.terminalBody]
1517
+ );
1518
+ const row = terminalized.rows[0];
1519
+ return row === void 0 ? this.readAgentMessage(tenant, input) : admissionRowToRecord(row);
1520
+ }
1311
1521
  async get(tenant, taskId) {
1312
1522
  const result = await this.#pool.query(
1313
1523
  `SELECT ${TASK_SELECT_COLUMNS} FROM task WHERE tenant_id = $1 AND task_id = $2`,
@@ -1324,14 +1534,44 @@ var PostgresTaskAttemptStore = class {
1324
1534
  );
1325
1535
  return result.rows.map(taskRowToAttempt);
1326
1536
  }
1537
+ async list(tenant, query) {
1538
+ assertTaskAttemptListLimit(query.limit);
1539
+ const result = await this.#pool.query(
1540
+ `SELECT ${TASK_SELECT_COLUMNS}
1541
+ FROM task
1542
+ WHERE tenant_id = $1 AND ($2::text IS NULL OR task_id > $2)
1543
+ ORDER BY task_id
1544
+ LIMIT $3`,
1545
+ [tenant, query.cursor ?? null, query.limit + 1]
1546
+ );
1547
+ const rows = result.rows.slice(0, query.limit);
1548
+ const attempts = rows.map(taskRowToAttempt);
1549
+ const hasMore = result.rows.length > rows.length;
1550
+ const last = attempts.at(-1);
1551
+ return {
1552
+ attempts,
1553
+ ...hasMore && last !== void 0 ? { nextCursor: last.taskId } : {}
1554
+ };
1555
+ }
1327
1556
  async claim(tenant, input) {
1328
1557
  const claimed = await this.#pool.query(
1329
1558
  `UPDATE task
1330
- SET owner_device_id = $3, status = 'claimed', updated_at = $4
1559
+ SET owner_device_id = $3,
1560
+ status = 'claimed',
1561
+ claimed_runtime = $5,
1562
+ claimed_runtime_capabilities = $6::jsonb,
1563
+ updated_at = $4
1331
1564
  WHERE tenant_id = $1 AND task_id = $2 AND owner_device_id IS NULL
1332
1565
  AND cancel_requested_at IS NULL AND status = 'offered'
1333
1566
  RETURNING ${TASK_SELECT_COLUMNS}`,
1334
- [tenant, input.taskId, input.deviceId, this.#now()]
1567
+ [
1568
+ tenant,
1569
+ input.taskId,
1570
+ input.deviceId,
1571
+ this.#now(),
1572
+ input.runtime ?? null,
1573
+ input.capabilities === void 0 ? null : JSON.stringify(input.capabilities)
1574
+ ]
1335
1575
  );
1336
1576
  const won = claimed.rows[0];
1337
1577
  if (won !== void 0) return taskRowToAttempt(won);
@@ -1496,20 +1736,63 @@ var PostgresMailboxStore = class {
1496
1736
  // Nothing above was mutated, so an identical call replays the same page.
1497
1737
  // The returned position is a READ cursor and moves no ack.
1498
1738
  nextSeq: page.at(-1)?.seq ?? query.afterSeq,
1499
- hasMore: result.rows.length > page.length
1739
+ hasMore: result.rows.length > page.length,
1740
+ recoverableFrom: await this.#recoverableFrom(tenant, query.deviceId)
1500
1741
  };
1501
1742
  }
1743
+ /**
1744
+ * One past the highest row this device lost, which is exactly the highest
1745
+ * `expired` seq — `1` when it has lost nothing.
1746
+ *
1747
+ * No column and no migration, because `collectRetired` never deletes an
1748
+ * unacked row: §12.7.5 keeps the dead letter, so the loss is already durable
1749
+ * in `outbox` and a stored floor would be a second authority free to drift
1750
+ * from the sweep that moved it. Whatever eventually reaps dead letters (S4B,
1751
+ * O-009) is the change that has to carry this floor forward instead.
1752
+ *
1753
+ * `acked` rows are not counted: a consumed row is not a lost one.
1754
+ *
1755
+ * Read AFTER the page on purpose. A sweep landing between the two reads can
1756
+ * only report a floor HIGHER than the page it accompanies, which makes the
1757
+ * caller fail closed over a page that may already be missing rows; reading
1758
+ * the floor first would report one too low and serve that gap silently.
1759
+ */
1760
+ async #recoverableFrom(tenant, deviceId) {
1761
+ const result = await this.#pool.query(
1762
+ `SELECT (COALESCE(MAX(seq), 0) + 1)::bigint AS recoverable_from
1763
+ FROM outbox
1764
+ WHERE tenant_id = $1 AND device_id = $2 AND state = 'expired'`,
1765
+ [tenant, deviceId]
1766
+ );
1767
+ return Number(result.rows[0].recoverable_from);
1768
+ }
1502
1769
  async advanceCursor(tenant, input) {
1503
1770
  this.#requireDeviceId(input.deviceId);
1504
1771
  const now = this.#now();
1505
1772
  const moved = await this.#pool.query(
1506
- `WITH moved AS (
1507
- INSERT INTO device_stream (tenant_id, device_id, next_seq, acked_seq, acked_at)
1508
- VALUES ($1, $2, 1, $3::bigint, $4)
1773
+ `WITH moved_zero AS (
1774
+ INSERT INTO device_stream (
1775
+ tenant_id, device_id, next_seq, acked_seq, acked_at, delivered_seq
1776
+ )
1777
+ SELECT $1, $2, 1, 0, $4, 0
1778
+ WHERE $3::bigint = 0
1509
1779
  ON CONFLICT (tenant_id, device_id) DO UPDATE
1510
- SET acked_seq = EXCLUDED.acked_seq, acked_at = EXCLUDED.acked_at
1511
- WHERE device_stream.acked_seq <= EXCLUDED.acked_seq
1512
- RETURNING acked_seq, acked_at
1780
+ SET acked_seq = 0, acked_at = EXCLUDED.acked_at
1781
+ WHERE device_stream.acked_seq <= 0
1782
+ AND device_stream.delivered_seq >= 0
1783
+ RETURNING acked_seq, acked_at, delivered_seq
1784
+ ), moved_positive AS (
1785
+ UPDATE device_stream
1786
+ SET acked_seq = $3::bigint, acked_at = $4
1787
+ WHERE tenant_id = $1 AND device_id = $2
1788
+ AND $3::bigint > 0
1789
+ AND acked_seq <= $3::bigint
1790
+ AND delivered_seq >= $3::bigint
1791
+ RETURNING acked_seq, acked_at, delivered_seq
1792
+ ), moved AS (
1793
+ SELECT acked_seq, acked_at, delivered_seq FROM moved_zero
1794
+ UNION ALL
1795
+ SELECT acked_seq, acked_at, delivered_seq FROM moved_positive
1513
1796
  ), marked AS (
1514
1797
  UPDATE outbox
1515
1798
  SET state = 'acked'
@@ -1517,7 +1800,7 @@ var PostgresMailboxStore = class {
1517
1800
  AND seq <= (SELECT acked_seq FROM moved)
1518
1801
  RETURNING 1
1519
1802
  )
1520
- SELECT acked_seq, acked_at FROM moved`,
1803
+ SELECT acked_seq, acked_at, delivered_seq FROM moved`,
1521
1804
  [tenant, input.deviceId, input.ackedSeq, now]
1522
1805
  );
1523
1806
  const row = moved.rows[0];
@@ -1525,21 +1808,51 @@ var PostgresMailboxStore = class {
1525
1808
  return {
1526
1809
  tenantId: tenant,
1527
1810
  deviceId: input.deviceId,
1811
+ deliveredSeq: Number(row.delivered_seq),
1528
1812
  ackedSeq: Number(row.acked_seq),
1529
1813
  updatedAt: row.acked_at ?? now
1530
1814
  };
1531
1815
  }
1532
1816
  const current = await this.readCursor(tenant, input.deviceId);
1817
+ if (input.ackedSeq < current.ackedSeq) {
1818
+ throw new CoreConflictError(
1819
+ "mailbox_cursor_regression",
1820
+ `Cursor for device ${input.deviceId} is at ${current.ackedSeq}; refusing to move it back to ${input.ackedSeq}.`,
1821
+ current,
1822
+ this.#now()
1823
+ );
1824
+ }
1533
1825
  throw new CoreConflictError(
1534
- "mailbox_cursor_regression",
1535
- `Cursor for device ${input.deviceId} is at ${current.ackedSeq}; refusing to move it back to ${input.ackedSeq}.`,
1826
+ "mailbox_cursor_ahead_of_delivery",
1827
+ `Cursor for device ${input.deviceId} was delivered through ${current.deliveredSeq}; refusing to acknowledge future cursor ${input.ackedSeq}.`,
1536
1828
  current,
1537
1829
  this.#now()
1538
1830
  );
1539
1831
  }
1832
+ async recordDelivery(tenant, input) {
1833
+ this.#requireDeviceId(input.deviceId);
1834
+ const recorded = await this.#pool.query(
1835
+ `INSERT INTO device_stream (
1836
+ tenant_id, device_id, next_seq, acked_seq, acked_at, delivered_seq
1837
+ )
1838
+ VALUES ($1, $2, 1, 0, NULL, $3::bigint)
1839
+ ON CONFLICT (tenant_id, device_id) DO UPDATE
1840
+ SET delivered_seq = GREATEST(device_stream.delivered_seq, EXCLUDED.delivered_seq)
1841
+ RETURNING acked_seq, acked_at, delivered_seq`,
1842
+ [tenant, input.deviceId, input.deliveredSeq]
1843
+ );
1844
+ const row = recorded.rows[0];
1845
+ return {
1846
+ tenantId: tenant,
1847
+ deviceId: input.deviceId,
1848
+ deliveredSeq: Number(row.delivered_seq),
1849
+ ackedSeq: Number(row.acked_seq),
1850
+ updatedAt: row.acked_at ?? this.#now()
1851
+ };
1852
+ }
1540
1853
  async readCursor(tenant, deviceId) {
1541
1854
  const result = await this.#pool.query(
1542
- `SELECT acked_seq, acked_at FROM device_stream
1855
+ `SELECT acked_seq, acked_at, delivered_seq FROM device_stream
1543
1856
  WHERE tenant_id = $1 AND device_id = $2`,
1544
1857
  [tenant, deviceId]
1545
1858
  );
@@ -1547,6 +1860,7 @@ var PostgresMailboxStore = class {
1547
1860
  return {
1548
1861
  tenantId: tenant,
1549
1862
  deviceId,
1863
+ deliveredSeq: row === void 0 ? 0 : Number(row.delivered_seq),
1550
1864
  ackedSeq: row === void 0 ? 0 : Number(row.acked_seq),
1551
1865
  updatedAt: row?.acked_at ?? this.#now()
1552
1866
  };
@@ -1712,8 +2026,35 @@ var PostgresActivityStore = class {
1712
2026
  const receivedAt = now.toISOString();
1713
2027
  const incoming = projectTimelineEvents(input, receivedAt);
1714
2028
  const expiresAt = new Date(now.getTime() + input.ttlMs).toISOString();
1715
- const result = await this.pool.query(
1716
- `WITH incoming AS (
2029
+ const client = await this.pool.connect();
2030
+ try {
2031
+ await client.query("BEGIN");
2032
+ await client.query(
2033
+ `SELECT pg_advisory_xact_lock(hashtextextended($1 || E'\\x1f' || $2, 0))`,
2034
+ [tenant, input.taskId]
2035
+ );
2036
+ const locked = await client.query(
2037
+ `SELECT tenant_id, task_id, entries, dropped, capacity, expires_at
2038
+ FROM activity_tail
2039
+ WHERE tenant_id = $1 AND task_id = $2
2040
+ FOR UPDATE`,
2041
+ [tenant, input.taskId]
2042
+ );
2043
+ const stored = locked.rows[0];
2044
+ const live = stored !== void 0 && receivedAt < stored.expires_at ? toTail(stored) : void 0;
2045
+ const sourceState = activitySourceBatchState(live?.entries ?? [], input);
2046
+ if (sourceState === "same") {
2047
+ await client.query("COMMIT");
2048
+ return live;
2049
+ }
2050
+ if (sourceState === "conflict") {
2051
+ throw new ByokCloudError(
2052
+ "coordination_input_invalid",
2053
+ `Activity source envelope ${input.sourceEnvelopeId} already belongs to another canonical batch.`
2054
+ );
2055
+ }
2056
+ const result = await client.query(
2057
+ `WITH incoming AS (
1717
2058
  SELECT entry
1718
2059
  FROM jsonb_array_elements($4::jsonb) AS element(entry)
1719
2060
  ), trimmed AS (
@@ -1780,16 +2121,24 @@ var PostgresActivityStore = class {
1780
2121
  )
1781
2122
  )
1782
2123
  RETURNING tenant_id, task_id, entries, dropped, capacity, expires_at`,
1783
- [tenant, input.taskId, receivedAt, JSON.stringify(incoming), input.dropped, capacity, expiresAt]
1784
- );
1785
- const row = result.rows[0];
1786
- if (row === void 0) {
1787
- throw new ByokCloudError(
1788
- "coordination_input_invalid",
1789
- `Activity batch ${input.batchSeq} conflicts with the existing typed tail authority.`
2124
+ [tenant, input.taskId, receivedAt, JSON.stringify(incoming), input.dropped, capacity, expiresAt]
1790
2125
  );
2126
+ const row = result.rows[0];
2127
+ if (row === void 0) {
2128
+ throw new ByokCloudError(
2129
+ "coordination_input_invalid",
2130
+ `Activity batch ${input.batchSeq} conflicts with the existing typed tail authority.`
2131
+ );
2132
+ }
2133
+ await client.query("COMMIT");
2134
+ return toTail(row);
2135
+ } catch (caught) {
2136
+ await client.query("ROLLBACK").catch(() => {
2137
+ });
2138
+ throw caught;
2139
+ } finally {
2140
+ client.release();
1791
2141
  }
1792
- return toTail(row);
1793
2142
  }
1794
2143
  async read(tenant, taskId) {
1795
2144
  const result = await this.pool.query(
@@ -1908,6 +2257,79 @@ var PostgresApprovalTimelineStore = class {
1908
2257
  client.release();
1909
2258
  }
1910
2259
  }
2260
+ async resolvePending(tenant, input) {
2261
+ const { capacity, ttlMs, event } = validateApprovalTimelineResolve(input);
2262
+ const now = this.clock.now();
2263
+ const receivedAt = now.toISOString();
2264
+ const expiresAt = new Date(now.getTime() + ttlMs).toISOString();
2265
+ const client = await this.pool.connect();
2266
+ try {
2267
+ await client.query("BEGIN");
2268
+ await client.query(
2269
+ `SELECT pg_advisory_xact_lock(hashtextextended($1 || E'\\x1f' || $2, 0))`,
2270
+ [tenant, input.taskId]
2271
+ );
2272
+ const stored = await readLocked(client, tenant, input.taskId);
2273
+ const live = stored !== void 0 && receivedAt < stored.expires_at ? toTail2(stored) : void 0;
2274
+ if (live === void 0) {
2275
+ await client.query("COMMIT");
2276
+ return { status: "absent" };
2277
+ }
2278
+ const duplicate = live.entries.find((entry) => entry.sourceEnvelopeId === input.sourceEnvelopeId);
2279
+ if (duplicate !== void 0) {
2280
+ await client.query("COMMIT");
2281
+ return JSON.stringify(duplicate.event) === JSON.stringify(event) ? { status: "replayed", tail: live } : { status: "conflict", tail: live };
2282
+ }
2283
+ const pending = pendingObservation(live.entries);
2284
+ if (pending === void 0) {
2285
+ await client.query("COMMIT");
2286
+ return { status: "absent", tail: live };
2287
+ }
2288
+ if (pending.sourceEnvelopeId !== input.expectedSourceEnvelopeId || pending.revision !== input.expectedRevision) {
2289
+ await client.query("COMMIT");
2290
+ return { status: "superseded", tail: live };
2291
+ }
2292
+ const revision = Number(stored.next_revision);
2293
+ const expectedRevision = (live.cursor ?? 0) + 1;
2294
+ if (!Number.isSafeInteger(revision) || revision <= 0 || revision !== expectedRevision) {
2295
+ throw new ByokCloudError(
2296
+ "coordination_input_invalid",
2297
+ "Approval timeline revision authority is malformed."
2298
+ );
2299
+ }
2300
+ const observation = ApprovalObservationSchema.parse({
2301
+ taskId: input.taskId,
2302
+ sourceEnvelopeId: input.sourceEnvelopeId,
2303
+ revision,
2304
+ receivedAt,
2305
+ event
2306
+ });
2307
+ const allEntries = [...live.entries, observation];
2308
+ const evicted = Math.max(allEntries.length - capacity, 0);
2309
+ const entries = allEntries.slice(evicted);
2310
+ const dropped = live.dropped + evicted;
2311
+ const result = await client.query(
2312
+ `INSERT INTO approval_timeline_tail
2313
+ (tenant_id, task_id, entries, next_revision, dropped, capacity, expires_at)
2314
+ VALUES ($1, $2, $3::jsonb, $4, $5, $6, $7)
2315
+ ON CONFLICT (tenant_id, task_id) DO UPDATE
2316
+ SET entries = EXCLUDED.entries,
2317
+ next_revision = EXCLUDED.next_revision,
2318
+ dropped = EXCLUDED.dropped,
2319
+ capacity = EXCLUDED.capacity,
2320
+ expires_at = EXCLUDED.expires_at
2321
+ RETURNING tenant_id, task_id, entries, next_revision, dropped, capacity, expires_at`,
2322
+ [tenant, input.taskId, JSON.stringify(entries), revision + 1, dropped, capacity, expiresAt]
2323
+ );
2324
+ await client.query("COMMIT");
2325
+ return { status: "applied", tail: toTail2(result.rows[0]) };
2326
+ } catch (caught) {
2327
+ await client.query("ROLLBACK");
2328
+ throw caught;
2329
+ } finally {
2330
+ client.release();
2331
+ }
2332
+ }
1911
2333
  async read(tenant, taskId) {
1912
2334
  const result = await this.pool.query(
1913
2335
  `SELECT tenant_id, task_id, entries, next_revision, dropped, capacity, expires_at
@@ -1919,6 +2341,19 @@ var PostgresApprovalTimelineStore = class {
1919
2341
  return row === void 0 ? void 0 : toTail2(row);
1920
2342
  }
1921
2343
  };
2344
+ function pendingObservation(entries) {
2345
+ let pending;
2346
+ for (const entry of entries) {
2347
+ if (entry.event.type === "approval_requested") {
2348
+ pending = entry;
2349
+ continue;
2350
+ }
2351
+ if (pending !== void 0 && (pending.event.approvalId === void 0 || pending.event.approvalId === entry.event.approvalId)) {
2352
+ pending = void 0;
2353
+ }
2354
+ }
2355
+ return pending;
2356
+ }
1922
2357
  var SELECT_COLUMNS4 = [
1923
2358
  "tenant_id",
1924
2359
  "device_id",
@@ -1965,7 +2400,7 @@ var PostgresAgentEgressStore = class {
1965
2400
  const inserted = await this.pool.query(
1966
2401
  `INSERT INTO agent_egress_event (${SELECT_COLUMNS4})
1967
2402
  VALUES ($1, $2, $3::uuid, $4, $5, $6, $7, $8::bigint, $9::jsonb, $10, $11::integer, $12::uuid, $13)
1968
- ON CONFLICT (tenant_id, device_id, event_id) DO NOTHING
2403
+ ON CONFLICT (tenant_id, device_id, agent_id, agent_profile_revision, event_id) DO NOTHING
1969
2404
  RETURNING ${SELECT_COLUMNS4}`,
1970
2405
  [
1971
2406
  tenant,
@@ -1985,16 +2420,18 @@ var PostgresAgentEgressStore = class {
1985
2420
  );
1986
2421
  const row = inserted.rows[0];
1987
2422
  if (row !== void 0) return { record: toRecord2(row), created: true };
1988
- const existing = await this.get(tenant, input.deviceId, payload.eventId);
2423
+ const existing = await this.get(tenant, input.deviceId, payload.agentRef, payload.eventId);
1989
2424
  if (existing === void 0) throw new Error(`Agent egress ${payload.eventId} vanished during first-write record.`);
1990
2425
  return { record: existing, created: false };
1991
2426
  }
1992
- async get(tenant, deviceId, eventId) {
2427
+ async get(tenant, deviceId, agentRef, eventId) {
1993
2428
  const result = await this.pool.query(
1994
2429
  `SELECT ${SELECT_COLUMNS4}
1995
2430
  FROM agent_egress_event
1996
- WHERE tenant_id = $1 AND device_id = $2 AND event_id = $3::uuid`,
1997
- [tenant, deviceId, eventId]
2431
+ WHERE tenant_id = $1 AND device_id = $2
2432
+ AND agent_id = $3 AND agent_profile_revision = $4
2433
+ AND event_id = $5::uuid`,
2434
+ [tenant, deviceId, agentRef.agentId, agentRef.profileRevision, eventId]
1998
2435
  );
1999
2436
  const row = result.rows[0];
2000
2437
  return row === void 0 ? void 0 : toRecord2(row);
@@ -2151,7 +2588,7 @@ var PostgresAgentMemoryProjectionStore = class {
2151
2588
  }
2152
2589
  });
2153
2590
  } catch (cause) {
2154
- await rollback2(client);
2591
+ await rollback3(client);
2155
2592
  throw cause;
2156
2593
  } finally {
2157
2594
  client.release();
@@ -2188,7 +2625,7 @@ var PostgresAgentMemoryProjectionStore = class {
2188
2625
  await client.query("COMMIT");
2189
2626
  return AgentMemoryProjectionEraseResultSchema.parse({ nextWriterEpoch });
2190
2627
  } catch (cause) {
2191
- await rollback2(client);
2628
+ await rollback3(client);
2192
2629
  throw cause;
2193
2630
  } finally {
2194
2631
  client.release();
@@ -2325,7 +2762,7 @@ function toBase64(bytes) {
2325
2762
  function toBase64Url(bytes) {
2326
2763
  return toBase64(bytes).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
2327
2764
  }
2328
- async function rollback2(client) {
2765
+ async function rollback3(client) {
2329
2766
  try {
2330
2767
  await client.query("ROLLBACK");
2331
2768
  } catch {
@@ -2384,11 +2821,16 @@ var PostgresDeviceAssertionReplayAuthority = class {
2384
2821
  // src/stores/index.ts
2385
2822
  function createPostgresCloudStores(options) {
2386
2823
  const { pool, clock, crypto } = options;
2824
+ const devices = new PostgresDeviceDirectory(pool, clock);
2825
+ const pairing = new PostgresPairingCodeStore(pool, clock);
2387
2826
  return {
2388
2827
  activity: new PostgresActivityStore(pool, clock),
2389
2828
  approvals: new PostgresApprovalTimelineStore(pool, clock),
2390
- devices: new PostgresDeviceDirectory(pool, clock),
2391
- pairingCodes: new PostgresPairingCodeStore(pool, clock),
2829
+ devices,
2830
+ // One durable code authority, projected into issuance and enrollment ports
2831
+ // so consumers cannot reopen the old redeem-then-register sequence.
2832
+ pairingCodes: { issue: pairing.issue.bind(pairing) },
2833
+ pairing: { redeemAndRegister: pairing.redeemAndRegister.bind(pairing) },
2392
2834
  nonces: new PostgresNonceStore(pool, clock, crypto),
2393
2835
  dedup: new PostgresInboundDedupStore(pool),
2394
2836
  tasks: new PostgresTaskAttemptStore(pool, clock),
@@ -3677,7 +4119,7 @@ function toReceipt4(tenant, row) {
3677
4119
  recordedAt: row.recorded_at.toISOString()
3678
4120
  };
3679
4121
  }
3680
- function sameBinding(receipt, input) {
4122
+ function sameBinding2(receipt, input) {
3681
4123
  return receipt.operation === input.operation && receipt.resource === input.resource && receipt.bodySha256 === input.proofBodySha256 && receipt.bodySize === input.proofBodySize;
3682
4124
  }
3683
4125
  function bodyColumns2(body) {
@@ -3716,7 +4158,7 @@ var PostgresTruthCommitter = class {
3716
4158
  ]);
3717
4159
  const replay = await this.#readReceipt(client, tenant, input.deviceId, input.requestId);
3718
4160
  if (replay !== void 0) {
3719
- if (!sameBinding(replay, input)) {
4161
+ if (!sameBinding2(replay, input)) {
3720
4162
  throw new TruthCommitError(
3721
4163
  "proof_request_conflict",
3722
4164
  `Request ${input.requestId} was already used with a different binding.`
@@ -4779,6 +5221,10 @@ var PostgresCloudCleanup = class {
4779
5221
  ), receipts AS (
4780
5222
  DELETE FROM device_request_receipts
4781
5223
  WHERE tenant_id = $1 AND recorded_at < $5::timestamptz
5224
+ -- Pairing completion is an immutable enrollment authority, not
5225
+ -- a TTL-bound request receipt. Removing it would turn an exact
5226
+ -- bearer-code retry into a new/failed enrollment after cleanup.
5227
+ AND key NOT LIKE 'pairing-completion:v1:%'
4782
5228
  RETURNING 1
4783
5229
  ), presence AS (
4784
5230
  DELETE FROM device_presence
@@ -5309,6 +5755,7 @@ var TENANT_ERASURE_TABLES = [
5309
5755
  "agent_egress_event",
5310
5756
  "device_request_receipts",
5311
5757
  "proof_request_receipt",
5758
+ "agent_message_admission",
5312
5759
  "task",
5313
5760
  "device_presence",
5314
5761
  "device_assertion_replay",