@creatornader/agent-bridge 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/CHANGELOG.md +222 -1
  2. package/README.md +312 -12
  3. package/ROADMAP.md +88 -7
  4. package/SECURITY.md +78 -5
  5. package/SKILL.md +108 -1
  6. package/dist/archive.d.ts +2 -2
  7. package/dist/archive.js +2 -2
  8. package/dist/{bridge-service-hX1ZQp4W.d.ts → bridge-service-CNBKdYXh.d.ts} +2 -1
  9. package/dist/{bridge-store-DPVSCwfs.d.ts → bridge-store-ByJ6CaJk.d.ts} +3 -0
  10. package/dist/bridge.d.ts +25 -8
  11. package/dist/bridge.js +19 -9
  12. package/dist/{chunk-MBPZXTJA.js → chunk-7VJI2MXI.js} +285 -42
  13. package/dist/chunk-7VJI2MXI.js.map +1 -0
  14. package/dist/{chunk-4VB44KJU.js → chunk-AWE3DFZV.js} +3 -2
  15. package/dist/chunk-AWE3DFZV.js.map +1 -0
  16. package/dist/{chunk-GADH244D.js → chunk-E4OWHGUE.js} +16 -5
  17. package/dist/chunk-E4OWHGUE.js.map +1 -0
  18. package/dist/{chunk-NCO7RLBM.js → chunk-GTK4KOED.js} +108 -8
  19. package/dist/chunk-GTK4KOED.js.map +1 -0
  20. package/dist/{chunk-SPSMZOCJ.js → chunk-HUHG7XTB.js} +6 -5
  21. package/dist/chunk-HUHG7XTB.js.map +1 -0
  22. package/dist/{chunk-5GMB3A77.js → chunk-IVG4XWWS.js} +26 -14
  23. package/dist/chunk-IVG4XWWS.js.map +1 -0
  24. package/dist/{chunk-WAI2IYIQ.js → chunk-P635FAN4.js} +50 -7
  25. package/dist/chunk-P635FAN4.js.map +1 -0
  26. package/dist/{chunk-45BZGB77.js → chunk-QNXHDOJ5.js} +218 -24
  27. package/dist/chunk-QNXHDOJ5.js.map +1 -0
  28. package/dist/{chunk-UAHMP6GC.js → chunk-QPC6JHA3.js} +10 -7
  29. package/dist/chunk-QPC6JHA3.js.map +1 -0
  30. package/dist/{chunk-Q6PPMXJC.js → chunk-SN7PIVDF.js} +377 -21
  31. package/dist/chunk-SN7PIVDF.js.map +1 -0
  32. package/dist/{chunk-A6W4QALF.js → chunk-TC6A7GMF.js} +2 -2
  33. package/dist/{chunk-2DFNOLMO.js → chunk-XDNT6ZNZ.js} +9 -6
  34. package/dist/chunk-XDNT6ZNZ.js.map +1 -0
  35. package/dist/{chunk-CEXBMOI5.js → chunk-Z6JP6MYD.js} +2 -2
  36. package/dist/cli.js +5867 -438
  37. package/dist/cli.js.map +1 -1
  38. package/dist/client-runtime.d.ts +4 -2
  39. package/dist/client-runtime.js +7 -7
  40. package/dist/contracts/generate.js +1 -1
  41. package/dist/dr.d.ts +9 -2
  42. package/dist/dr.js +6 -4
  43. package/dist/{gateway-CggQ8vTC.d.ts → gateway-CW-61lvs.d.ts} +27 -3
  44. package/dist/gateway-main.js +71 -7
  45. package/dist/gateway-main.js.map +1 -1
  46. package/dist/gateway.d.ts +3 -3
  47. package/dist/gateway.js +3 -3
  48. package/dist/index.js +8 -8
  49. package/dist/{postgres-bridge-store-Bfd3hRId.d.ts → postgres-bridge-store-C9KG9Z69.d.ts} +1 -1
  50. package/dist/postgres-production-preflight.d.ts +32 -0
  51. package/dist/postgres-production-preflight.js +249 -0
  52. package/dist/postgres-production-preflight.js.map +1 -0
  53. package/dist/server.js +8 -8
  54. package/dist/sqlite-edge-store-CdMIOrho.d.ts +148 -0
  55. package/dist/sqlite.d.ts +19 -80
  56. package/dist/sqlite.js +11 -3
  57. package/docs/architecture-v2.md +292 -15
  58. package/docs/decisions/0004-client-lifecycle-and-endpoint-migration.md +204 -0
  59. package/docs/deployment.md +349 -0
  60. package/docs/troubleshooting.md +23 -0
  61. package/llms.txt +69 -1
  62. package/openapi/agent-bridge-v2.openapi.json +1151 -124
  63. package/package.json +10 -2
  64. package/schemas/agent-bridge-v2.capabilities.json +26 -1
  65. package/schemas/agent-bridge-v2.mcp.json +11 -1
  66. package/schemas/agent-bridge-v2.schema.json +167 -0
  67. package/sql/migrations/007_runtime_role.sql +8 -4
  68. package/sql/migrations/013_row_isolation.sql +71 -8
  69. package/sql/migrations/014_owner_control_plane.sql +124 -31
  70. package/sql/migrations/015_portable_archives.sql +143 -41
  71. package/sql/migrations/016_native_dr_fence.sql +102 -35
  72. package/sql/migrations/017_gateway_authority_binding.sql +99 -0
  73. package/sql/migrations/018_endpoint_migration_challenges.sql +1054 -0
  74. package/sql/migrations/019_endpoint_migration_same_successor.sql +348 -0
  75. package/sql/migrations/020_managed_authority_compat.sql +698 -0
  76. package/sql/migrations/021_native_backup_reader.sql +421 -0
  77. package/sql/setup.sql +1 -3
  78. package/dist/chunk-2DFNOLMO.js.map +0 -1
  79. package/dist/chunk-45BZGB77.js.map +0 -1
  80. package/dist/chunk-4VB44KJU.js.map +0 -1
  81. package/dist/chunk-5GMB3A77.js.map +0 -1
  82. package/dist/chunk-GADH244D.js.map +0 -1
  83. package/dist/chunk-MBPZXTJA.js.map +0 -1
  84. package/dist/chunk-NCO7RLBM.js.map +0 -1
  85. package/dist/chunk-Q6PPMXJC.js.map +0 -1
  86. package/dist/chunk-SPSMZOCJ.js.map +0 -1
  87. package/dist/chunk-UAHMP6GC.js.map +0 -1
  88. package/dist/chunk-WAI2IYIQ.js.map +0 -1
  89. /package/dist/{chunk-A6W4QALF.js.map → chunk-TC6A7GMF.js.map} +0 -0
  90. /package/dist/{chunk-CEXBMOI5.js.map → chunk-Z6JP6MYD.js.map} +0 -0
@@ -0,0 +1,249 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ loadMigrationPlan,
4
+ migrationRecordMatches
5
+ } from "./chunk-QNXHDOJ5.js";
6
+
7
+ // src/postgres-production-preflight.ts
8
+ import { fileURLToPath } from "url";
9
+ import { TLSSocket } from "tls";
10
+ import pg from "pg";
11
+ var SUPPORTED_MAJORS = /* @__PURE__ */ new Set([15, 16, 17, 18]);
12
+ var LEGACY_COLUMNS = /* @__PURE__ */ new Map([
13
+ ["id", "int8"],
14
+ ["source", "text"],
15
+ ["category", "text"],
16
+ ["content", "text"],
17
+ ["priority", "text"],
18
+ ["project", "text"],
19
+ ["metadata", "jsonb"],
20
+ ["created_at", "timestamptz"],
21
+ ["acked_by", "_text"]
22
+ ]);
23
+ function check(name, ok, detail) {
24
+ return { name, ok, detail };
25
+ }
26
+ function migrationPrefix(applied, expected) {
27
+ return applied.length <= expected.length && applied.every(
28
+ (entry, index) => expected[index] !== void 0 && migrationRecordMatches(entry, expected[index])
29
+ );
30
+ }
31
+ async function legacyChecks(db, checks) {
32
+ const owner = await db.query(`
33
+ SELECT relation.relowner=current_user::regrole::oid AS "ownedByCaller"
34
+ FROM pg_catalog.pg_class relation
35
+ WHERE relation.oid='public.shared_context'::regclass`);
36
+ checks.push(check(
37
+ "legacy.owner",
38
+ owner.rows[0]?.ownedByCaller === true,
39
+ "the migration authority owns public.shared_context"
40
+ ));
41
+ const columns = await db.query(`
42
+ SELECT column_name AS "columnName",udt_name AS "typeName"
43
+ FROM information_schema.columns
44
+ WHERE table_schema='public' AND table_name='shared_context'`);
45
+ const actual = new Map(columns.rows.map((row) => [row.columnName, row.typeName]));
46
+ const compatible = [...LEGACY_COLUMNS].every(([name, type]) => actual.get(name) === type);
47
+ checks.push(check(
48
+ "legacy.columns",
49
+ compatible,
50
+ "public.shared_context has the columns required by migration 006"
51
+ ));
52
+ if (!compatible) return void 0;
53
+ const legacy = await db.query(`
54
+ WITH mapped AS (
55
+ SELECT CASE
56
+ WHEN metadata #>> '{message_envelope,message_id}' ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
57
+ THEN lower(metadata #>> '{message_envelope,message_id}')
58
+ ELSE 'legacy:' || id::text
59
+ END AS message_id
60
+ FROM public.shared_context
61
+ )
62
+ SELECT
63
+ (SELECT count(*)::text FROM mapped) AS "rowCount",
64
+ (SELECT count(*)::text FROM (
65
+ SELECT message_id FROM mapped GROUP BY message_id HAVING count(*)>1
66
+ ) duplicates) AS "duplicateMessageIds"`);
67
+ const result = legacy.rows[0];
68
+ const duplicates = result?.duplicateMessageIds ?? "unknown";
69
+ checks.push(check(
70
+ "legacy.message_ids",
71
+ duplicates === "0",
72
+ "legacy rows map to unique immutable message IDs"
73
+ ));
74
+ return result?.rowCount;
75
+ }
76
+ async function runPostgresProductionPreflight(db, migrationsDirectory, options = {}) {
77
+ const expected = await loadMigrationPlan(migrationsDirectory);
78
+ await db.query("BEGIN TRANSACTION READ ONLY");
79
+ try {
80
+ await db.query("SET LOCAL statement_timeout='15s'");
81
+ await db.query("SET LOCAL lock_timeout='2s'");
82
+ const authority = await db.query(`
83
+ SELECT
84
+ current_setting('server_version_num') AS "serverVersionNum",
85
+ pg_is_in_recovery() AS "inRecovery",
86
+ role.rolcanlogin AS "canLogin",
87
+ role.rolinherit AS inherits,
88
+ role.rolsuper AS "isSuperuser",
89
+ role.rolcreaterole AS "canCreateRole",
90
+ role.rolbypassrls AS "bypassesRls",
91
+ has_database_privilege(current_user,current_database(),'CREATE') AS "canCreateDatabaseObject",
92
+ to_regnamespace('agent_bridge')::text AS "bridgeSchema",
93
+ to_regclass('agent_bridge.schema_migrations')::text AS "migrationTable",
94
+ to_regclass('public.shared_context')::text AS "legacyTable",
95
+ coalesce((SELECT ssl FROM pg_catalog.pg_stat_ssl WHERE pid=pg_backend_pid()),false) AS ssl,
96
+ pg_database_size(current_database())::text AS "databaseBytes"
97
+ FROM pg_catalog.pg_roles role
98
+ WHERE role.rolname=current_user`);
99
+ const row = authority.rows[0];
100
+ if (!row) throw new Error("current PostgreSQL role could not be inspected");
101
+ const serverMajor = Math.floor(Number(row.serverVersionNum) / 1e4);
102
+ const ssl = row.ssl || options.clientTransportSsl === true;
103
+ const checks = [
104
+ check("postgres.version", SUPPORTED_MAJORS.has(serverMajor), "PostgreSQL major is supported"),
105
+ check("postgres.primary", !row.inRecovery, "connection targets a writable primary"),
106
+ check("authority.login", row.canLogin && row.inherits, "migration role can log in and inherits grants"),
107
+ check("authority.database_create", row.canCreateDatabaseObject, "migration role can create database objects"),
108
+ check("authority.roles", row.isSuperuser || row.canCreateRole, "migration role can administer restricted roles"),
109
+ check(
110
+ "authority.native_dr",
111
+ row.isSuperuser || row.bypassesRls,
112
+ "native DR source role can bypass row-level security"
113
+ ),
114
+ ...options.requireSsl ? [check(
115
+ "connection.ssl",
116
+ ssl,
117
+ row.ssl ? "PostgreSQL backend reports TLS" : options.clientTransportSsl ? "client transport uses TLS" : "connection does not use TLS"
118
+ )] : []
119
+ ];
120
+ let applied = [];
121
+ if (row.migrationTable) {
122
+ const result = await db.query(
123
+ "SELECT version,name,checksum FROM agent_bridge.schema_migrations ORDER BY version"
124
+ );
125
+ applied = result.rows.map((entry) => ({
126
+ version: Number(entry.version),
127
+ name: entry.name,
128
+ checksum: entry.checksum
129
+ }));
130
+ }
131
+ const prefix = migrationPrefix(applied, expected);
132
+ const unmanagedSchema = Boolean(row.bridgeSchema) && (!row.migrationTable || applied.length === 0);
133
+ const migrationState = unmanagedSchema || !prefix || !row.bridgeSchema && Boolean(row.migrationTable) ? "invalid" : applied.length === expected.length ? "current" : applied.length === 0 ? "uninitialized" : "upgradeable";
134
+ checks.push(check(
135
+ "schema.migrations",
136
+ migrationState !== "invalid",
137
+ migrationState === "invalid" ? "agent_bridge namespace or migration ledger conflicts with the release plan" : `migration state is ${migrationState}`
138
+ ));
139
+ if (!row.bridgeSchema) {
140
+ const collisions = await db.query(`
141
+ WITH names(role_name) AS (VALUES
142
+ ('agent_bridge_runtime_' || substr(md5(current_database()),1,16)),
143
+ ('agent_bridge_data_owner_' || substr(md5(current_database()),1,16)),
144
+ ('agent_bridge_context_reader_' || substr(md5(current_database()),1,16)),
145
+ ('agent_bridge_event_writer_' || substr(md5(current_database()),1,16)),
146
+ ('agent_bridge_control_owner_' || substr(md5(current_database()),1,16)),
147
+ ('agent_bridge_control_operator_' || substr(md5(current_database()),1,16)),
148
+ ('agent_bridge_control_auditor_' || substr(md5(current_database()),1,16)),
149
+ ('agent_bridge_archive_operator_' || substr(md5(current_database()),1,16)),
150
+ ('agent_bridge_backup_reader_' || substr(md5(current_database()),1,16))
151
+ )
152
+ SELECT count(*)::text AS count FROM names JOIN pg_catalog.pg_roles role
153
+ ON role.rolname=names.role_name`);
154
+ checks.push(check(
155
+ "schema.role_collisions",
156
+ collisions.rows[0]?.count === "0",
157
+ "no derived Agent Bridge roles predate the schema"
158
+ ));
159
+ }
160
+ let legacyRows;
161
+ if (row.legacyTable && applied.length < 6) {
162
+ legacyRows = await legacyChecks(db, checks);
163
+ }
164
+ await db.query("COMMIT");
165
+ return {
166
+ schema: "agent-bridge-postgres-production-preflight-v1",
167
+ ok: checks.every((entry) => entry.ok),
168
+ checks,
169
+ observations: {
170
+ serverMajor,
171
+ databaseBytes: row.databaseBytes,
172
+ migrationState,
173
+ appliedMigrationCount: applied.length,
174
+ requiredMigrationCount: expected.length,
175
+ legacyTable: Boolean(row.legacyTable),
176
+ ...legacyRows === void 0 ? {} : { legacyRows },
177
+ ssl
178
+ }
179
+ };
180
+ } catch (error) {
181
+ await db.query("ROLLBACK").catch(() => void 0);
182
+ throw error;
183
+ }
184
+ }
185
+ function parseArguments(args) {
186
+ const options = { json: false, help: false, requireSsl: false };
187
+ for (const argument of args) {
188
+ if (argument === "--json") options.json = true;
189
+ else if (argument === "--require-ssl") options.requireSsl = true;
190
+ else if (argument === "--help" || argument === "-h") options.help = true;
191
+ else throw new Error(`unknown argument: ${argument}`);
192
+ }
193
+ return options;
194
+ }
195
+ function safeError(error, secret) {
196
+ const message = error instanceof Error ? error.message : String(error);
197
+ return message.split(secret).join("[redacted]").replace(/postgres(?:ql)?:\/\/\S+/giu, "[redacted]");
198
+ }
199
+ async function main() {
200
+ const options = parseArguments(process.argv.slice(2));
201
+ if (options.help) {
202
+ process.stdout.write("Usage: npm run preflight:postgres:production -- [--json] [--require-ssl]\n");
203
+ return;
204
+ }
205
+ const databaseUrl = process.env.AGENT_BRIDGE_DATABASE_URL;
206
+ if (!databaseUrl) throw new Error("AGENT_BRIDGE_DATABASE_URL is required");
207
+ const client = new pg.Client({
208
+ connectionString: databaseUrl,
209
+ connectionTimeoutMillis: 1e4,
210
+ application_name: "agent-bridge-production-preflight"
211
+ });
212
+ try {
213
+ await client.connect();
214
+ const clientTransportSsl = client.connection.stream instanceof TLSSocket && client.connection.stream.encrypted;
215
+ const migrationsDirectory = fileURLToPath(new URL("../sql/migrations", import.meta.url));
216
+ const report = await runPostgresProductionPreflight({
217
+ query: (sql) => client.query(sql)
218
+ }, migrationsDirectory, {
219
+ requireSsl: options.requireSsl,
220
+ clientTransportSsl
221
+ });
222
+ if (options.json) process.stdout.write(`${JSON.stringify(report)}
223
+ `);
224
+ else {
225
+ for (const entry of report.checks) {
226
+ process.stdout.write(`${entry.ok ? "OK" : "ERROR"} ${entry.name}: ${entry.detail}
227
+ `);
228
+ }
229
+ process.stdout.write(`${report.ok ? "PostgreSQL production preflight passed" : "PostgreSQL production preflight failed"}
230
+ `);
231
+ }
232
+ if (!report.ok) process.exitCode = 1;
233
+ } catch (error) {
234
+ throw new Error(safeError(error, databaseUrl));
235
+ } finally {
236
+ await client.end().catch(() => void 0);
237
+ }
238
+ }
239
+ if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
240
+ void main().catch((error) => {
241
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
242
+ `);
243
+ process.exitCode = 1;
244
+ });
245
+ }
246
+ export {
247
+ runPostgresProductionPreflight
248
+ };
249
+ //# sourceMappingURL=postgres-production-preflight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/postgres-production-preflight.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport { TLSSocket } from \"node:tls\";\nimport pg from \"pg\";\nimport { loadMigrationPlan, migrationRecordMatches, type AppliedMigration } from \"./migrations.js\";\n\nconst SUPPORTED_MAJORS = new Set([15, 16, 17, 18]);\nconst LEGACY_COLUMNS = new Map([\n [\"id\", \"int8\"],\n [\"source\", \"text\"],\n [\"category\", \"text\"],\n [\"content\", \"text\"],\n [\"priority\", \"text\"],\n [\"project\", \"text\"],\n [\"metadata\", \"jsonb\"],\n [\"created_at\", \"timestamptz\"],\n [\"acked_by\", \"_text\"],\n]);\n\ninterface Check {\n name: string;\n ok: boolean;\n detail: string;\n}\n\ninterface ProductionPreflightDatabase {\n query<T extends Record<string, unknown> = Record<string, unknown>>(\n sql: string,\n ): Promise<{ rows: T[]; rowCount: number | null }>;\n}\n\ninterface AuthorityRow extends Record<string, unknown> {\n serverVersionNum: string;\n inRecovery: boolean;\n canLogin: boolean;\n inherits: boolean;\n isSuperuser: boolean;\n canCreateRole: boolean;\n bypassesRls: boolean;\n canCreateDatabaseObject: boolean;\n bridgeSchema: string | null;\n migrationTable: string | null;\n legacyTable: string | null;\n ssl: boolean;\n databaseBytes: string;\n}\n\ninterface LegacyColumnRow extends Record<string, unknown> {\n columnName: string;\n typeName: string;\n}\n\nexport interface PostgresProductionPreflightReport {\n schema: \"agent-bridge-postgres-production-preflight-v1\";\n ok: boolean;\n checks: Check[];\n observations: {\n serverMajor: number;\n databaseBytes: string;\n migrationState: \"uninitialized\" | \"upgradeable\" | \"current\" | \"invalid\";\n appliedMigrationCount: number;\n requiredMigrationCount: number;\n legacyTable: boolean;\n legacyRows?: string;\n ssl: boolean;\n };\n}\n\nfunction check(name: string, ok: boolean, detail: string): Check {\n return { name, ok, detail };\n}\n\nfunction migrationPrefix(\n applied: AppliedMigration[],\n expected: ReadonlyArray<AppliedMigration>,\n): boolean {\n return applied.length <= expected.length && applied.every((entry, index) =>\n expected[index] !== undefined && migrationRecordMatches(entry, expected[index])\n );\n}\n\nasync function legacyChecks(\n db: ProductionPreflightDatabase,\n checks: Check[],\n): Promise<string | undefined> {\n const owner = await db.query<{ ownedByCaller: boolean }>(`\n SELECT relation.relowner=current_user::regrole::oid AS \"ownedByCaller\"\n FROM pg_catalog.pg_class relation\n WHERE relation.oid='public.shared_context'::regclass`);\n checks.push(check(\n \"legacy.owner\",\n owner.rows[0]?.ownedByCaller === true,\n \"the migration authority owns public.shared_context\",\n ));\n\n const columns = await db.query<LegacyColumnRow>(`\n SELECT column_name AS \"columnName\",udt_name AS \"typeName\"\n FROM information_schema.columns\n WHERE table_schema='public' AND table_name='shared_context'`);\n const actual = new Map(columns.rows.map((row) => [row.columnName, row.typeName]));\n const compatible = [...LEGACY_COLUMNS].every(([name, type]) => actual.get(name) === type);\n checks.push(check(\n \"legacy.columns\",\n compatible,\n \"public.shared_context has the columns required by migration 006\",\n ));\n\n if (!compatible) return undefined;\n const legacy = await db.query<{ rowCount: string; duplicateMessageIds: string }>(`\n WITH mapped AS (\n SELECT CASE\n WHEN metadata #>> '{message_envelope,message_id}' ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'\n THEN lower(metadata #>> '{message_envelope,message_id}')\n ELSE 'legacy:' || id::text\n END AS message_id\n FROM public.shared_context\n )\n SELECT\n (SELECT count(*)::text FROM mapped) AS \"rowCount\",\n (SELECT count(*)::text FROM (\n SELECT message_id FROM mapped GROUP BY message_id HAVING count(*)>1\n ) duplicates) AS \"duplicateMessageIds\"`);\n const result = legacy.rows[0];\n const duplicates = result?.duplicateMessageIds ?? \"unknown\";\n checks.push(check(\n \"legacy.message_ids\",\n duplicates === \"0\",\n \"legacy rows map to unique immutable message IDs\",\n ));\n return result?.rowCount;\n}\n\nexport async function runPostgresProductionPreflight(\n db: ProductionPreflightDatabase,\n migrationsDirectory: string,\n options: { requireSsl?: boolean; clientTransportSsl?: boolean } = {},\n): Promise<PostgresProductionPreflightReport> {\n const expected = await loadMigrationPlan(migrationsDirectory);\n await db.query(\"BEGIN TRANSACTION READ ONLY\");\n try {\n await db.query(\"SET LOCAL statement_timeout='15s'\");\n await db.query(\"SET LOCAL lock_timeout='2s'\");\n const authority = await db.query<AuthorityRow>(`\n SELECT\n current_setting('server_version_num') AS \"serverVersionNum\",\n pg_is_in_recovery() AS \"inRecovery\",\n role.rolcanlogin AS \"canLogin\",\n role.rolinherit AS inherits,\n role.rolsuper AS \"isSuperuser\",\n role.rolcreaterole AS \"canCreateRole\",\n role.rolbypassrls AS \"bypassesRls\",\n has_database_privilege(current_user,current_database(),'CREATE') AS \"canCreateDatabaseObject\",\n to_regnamespace('agent_bridge')::text AS \"bridgeSchema\",\n to_regclass('agent_bridge.schema_migrations')::text AS \"migrationTable\",\n to_regclass('public.shared_context')::text AS \"legacyTable\",\n coalesce((SELECT ssl FROM pg_catalog.pg_stat_ssl WHERE pid=pg_backend_pid()),false) AS ssl,\n pg_database_size(current_database())::text AS \"databaseBytes\"\n FROM pg_catalog.pg_roles role\n WHERE role.rolname=current_user`);\n const row = authority.rows[0];\n if (!row) throw new Error(\"current PostgreSQL role could not be inspected\");\n\n const serverMajor = Math.floor(Number(row.serverVersionNum) / 10_000);\n const ssl = row.ssl || options.clientTransportSsl === true;\n const checks: Check[] = [\n check(\"postgres.version\", SUPPORTED_MAJORS.has(serverMajor), \"PostgreSQL major is supported\"),\n check(\"postgres.primary\", !row.inRecovery, \"connection targets a writable primary\"),\n check(\"authority.login\", row.canLogin && row.inherits, \"migration role can log in and inherits grants\"),\n check(\"authority.database_create\", row.canCreateDatabaseObject, \"migration role can create database objects\"),\n check(\"authority.roles\", row.isSuperuser || row.canCreateRole, \"migration role can administer restricted roles\"),\n check(\n \"authority.native_dr\",\n row.isSuperuser || row.bypassesRls,\n \"native DR source role can bypass row-level security\",\n ),\n ...(options.requireSsl\n ? [check(\n \"connection.ssl\",\n ssl,\n row.ssl\n ? \"PostgreSQL backend reports TLS\"\n : options.clientTransportSsl\n ? \"client transport uses TLS\"\n : \"connection does not use TLS\",\n )]\n : []),\n ];\n\n let applied: AppliedMigration[] = [];\n if (row.migrationTable) {\n const result = await db.query<{ version: number; name: string; checksum: string }>(\n \"SELECT version,name,checksum FROM agent_bridge.schema_migrations ORDER BY version\",\n );\n applied = result.rows.map((entry) => ({\n version: Number(entry.version),\n name: entry.name,\n checksum: entry.checksum,\n }));\n }\n const prefix = migrationPrefix(applied, expected);\n const unmanagedSchema = Boolean(row.bridgeSchema) && (\n !row.migrationTable || applied.length === 0\n );\n const migrationState = unmanagedSchema || !prefix || (!row.bridgeSchema && Boolean(row.migrationTable))\n ? \"invalid\"\n : applied.length === expected.length\n ? \"current\"\n : applied.length === 0\n ? \"uninitialized\"\n : \"upgradeable\";\n checks.push(check(\n \"schema.migrations\",\n migrationState !== \"invalid\",\n migrationState === \"invalid\"\n ? \"agent_bridge namespace or migration ledger conflicts with the release plan\"\n : `migration state is ${migrationState}`,\n ));\n\n if (!row.bridgeSchema) {\n const collisions = await db.query<{ count: string }>(`\n WITH names(role_name) AS (VALUES\n ('agent_bridge_runtime_' || substr(md5(current_database()),1,16)),\n ('agent_bridge_data_owner_' || substr(md5(current_database()),1,16)),\n ('agent_bridge_context_reader_' || substr(md5(current_database()),1,16)),\n ('agent_bridge_event_writer_' || substr(md5(current_database()),1,16)),\n ('agent_bridge_control_owner_' || substr(md5(current_database()),1,16)),\n ('agent_bridge_control_operator_' || substr(md5(current_database()),1,16)),\n ('agent_bridge_control_auditor_' || substr(md5(current_database()),1,16)),\n ('agent_bridge_archive_operator_' || substr(md5(current_database()),1,16)),\n ('agent_bridge_backup_reader_' || substr(md5(current_database()),1,16))\n )\n SELECT count(*)::text AS count FROM names JOIN pg_catalog.pg_roles role\n ON role.rolname=names.role_name`);\n checks.push(check(\n \"schema.role_collisions\",\n collisions.rows[0]?.count === \"0\",\n \"no derived Agent Bridge roles predate the schema\",\n ));\n }\n\n let legacyRows: string | undefined;\n if (row.legacyTable && applied.length < 6) {\n legacyRows = await legacyChecks(db, checks);\n }\n\n await db.query(\"COMMIT\");\n return {\n schema: \"agent-bridge-postgres-production-preflight-v1\",\n ok: checks.every((entry) => entry.ok),\n checks,\n observations: {\n serverMajor,\n databaseBytes: row.databaseBytes,\n migrationState,\n appliedMigrationCount: applied.length,\n requiredMigrationCount: expected.length,\n legacyTable: Boolean(row.legacyTable),\n ...(legacyRows === undefined ? {} : { legacyRows }),\n ssl,\n },\n };\n } catch (error) {\n await db.query(\"ROLLBACK\").catch(() => undefined);\n throw error;\n }\n}\n\nfunction parseArguments(args: string[]): { json: boolean; help: boolean; requireSsl: boolean } {\n const options = { json: false, help: false, requireSsl: false };\n for (const argument of args) {\n if (argument === \"--json\") options.json = true;\n else if (argument === \"--require-ssl\") options.requireSsl = true;\n else if (argument === \"--help\" || argument === \"-h\") options.help = true;\n else throw new Error(`unknown argument: ${argument}`);\n }\n return options;\n}\n\nfunction safeError(error: unknown, secret: string): string {\n const message = error instanceof Error ? error.message : String(error);\n return message.split(secret).join(\"[redacted]\").replace(/postgres(?:ql)?:\\/\\/\\S+/giu, \"[redacted]\");\n}\n\nasync function main(): Promise<void> {\n const options = parseArguments(process.argv.slice(2));\n if (options.help) {\n process.stdout.write(\"Usage: npm run preflight:postgres:production -- [--json] [--require-ssl]\\n\");\n return;\n }\n const databaseUrl = process.env.AGENT_BRIDGE_DATABASE_URL;\n if (!databaseUrl) throw new Error(\"AGENT_BRIDGE_DATABASE_URL is required\");\n const client = new pg.Client({\n connectionString: databaseUrl,\n connectionTimeoutMillis: 10_000,\n application_name: \"agent-bridge-production-preflight\",\n });\n try {\n await client.connect();\n const clientTransportSsl = client.connection.stream instanceof TLSSocket &&\n client.connection.stream.encrypted;\n const migrationsDirectory = fileURLToPath(new URL(\"../sql/migrations\", import.meta.url));\n const report = await runPostgresProductionPreflight({\n query: (sql) => client.query(sql),\n }, migrationsDirectory, {\n requireSsl: options.requireSsl,\n clientTransportSsl,\n });\n if (options.json) process.stdout.write(`${JSON.stringify(report)}\\n`);\n else {\n for (const entry of report.checks) {\n process.stdout.write(`${entry.ok ? \"OK\" : \"ERROR\"} ${entry.name}: ${entry.detail}\\n`);\n }\n process.stdout.write(`${report.ok ? \"PostgreSQL production preflight passed\" : \"PostgreSQL production preflight failed\"}\\n`);\n }\n if (!report.ok) process.exitCode = 1;\n } catch (error) {\n throw new Error(safeError(error, databaseUrl));\n } finally {\n await client.end().catch(() => undefined);\n }\n}\n\nif (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {\n void main().catch((error) => {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`);\n process.exitCode = 1;\n });\n}\n"],"mappings":"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","names":[]}
package/dist/server.js CHANGED
@@ -4,16 +4,16 @@ import {
4
4
  configFromEnv,
5
5
  createAgentBridgeServer,
6
6
  startServer
7
- } from "./chunk-SPSMZOCJ.js";
7
+ } from "./chunk-HUHG7XTB.js";
8
8
  import "./chunk-2EVLG3YA.js";
9
- import "./chunk-UAHMP6GC.js";
10
- import "./chunk-CEXBMOI5.js";
11
- import "./chunk-Q6PPMXJC.js";
12
- import "./chunk-5GMB3A77.js";
13
- import "./chunk-NCO7RLBM.js";
9
+ import "./chunk-QPC6JHA3.js";
10
+ import "./chunk-Z6JP6MYD.js";
11
+ import "./chunk-SN7PIVDF.js";
12
+ import "./chunk-IVG4XWWS.js";
13
+ import "./chunk-GTK4KOED.js";
14
14
  import "./chunk-VOQIDTCJ.js";
15
- import "./chunk-4VB44KJU.js";
16
- import "./chunk-GADH244D.js";
15
+ import "./chunk-AWE3DFZV.js";
16
+ import "./chunk-E4OWHGUE.js";
17
17
  import "./chunk-BZLMF5O5.js";
18
18
  export {
19
19
  MCP_IMPLEMENTATION_VERSION,
@@ -0,0 +1,148 @@
1
+ import { b as BridgeMessage, c as BridgePrincipal } from './bridge-domain-CBX3IVqC.js';
2
+ import { d as MessageQuery, M as MessagePage } from './bridge-store-ByJ6CaJk.js';
3
+
4
+ type PendingMessage = Omit<BridgeMessage, "sequence" | "createdAt">;
5
+ interface EdgeScope {
6
+ endpoint: string;
7
+ principal: BridgePrincipal;
8
+ }
9
+ /** A side-effect-free inspection used by migration dry-runs and preflight gates. */
10
+ interface EdgeReadOnlyInspection {
11
+ exists: boolean;
12
+ gate: EdgeMigrationGate;
13
+ pending: number;
14
+ due: number;
15
+ scheduled: number;
16
+ leased: number;
17
+ blocked: number;
18
+ cached: number;
19
+ }
20
+ /**
21
+ * Inspect an edge without creating a database, a schema object, or a WAL sidecar.
22
+ * An absent path has the same empty active state that initialization would create.
23
+ */
24
+ declare function inspectEdgeScopeReadOnly(path: string, scope: EdgeScope, now?: Date): EdgeReadOnlyInspection;
25
+ interface EdgeOutboxRecord {
26
+ position: number;
27
+ draft: PendingMessage;
28
+ payloadHash: string;
29
+ attempts: number;
30
+ leaseToken: string;
31
+ createdAt: string;
32
+ }
33
+ interface EdgeEnqueueResult {
34
+ draft: PendingMessage;
35
+ created: boolean;
36
+ }
37
+ interface EdgeStats {
38
+ pending: number;
39
+ due: number;
40
+ scheduled: number;
41
+ leased: number;
42
+ blocked: number;
43
+ cached: number;
44
+ pullCursor?: string;
45
+ lastSyncAt?: string;
46
+ lastError?: string;
47
+ oldestDueAt?: string;
48
+ queueLagMs: number;
49
+ oldestBlockedAt?: string;
50
+ blockedAgeMs: number | null;
51
+ blockedMaxAttempts: number;
52
+ blockedLastError?: string;
53
+ nextRetryAt?: string;
54
+ lastOutboundSyncAt?: string;
55
+ lastInboundSyncAt?: string;
56
+ lastAttemptAt?: string;
57
+ migrationState: EdgeMigrationGateState;
58
+ migrationOperationId?: string;
59
+ migrationLeaseExpiresAt?: string;
60
+ }
61
+ type EdgeMigrationGateState = "active" | "draining" | "retired";
62
+ interface EdgeMigrationGate {
63
+ scopeKey: string;
64
+ state: EdgeMigrationGateState;
65
+ operationId?: string;
66
+ leaseExpiresAt?: string;
67
+ updatedAt: string;
68
+ }
69
+ interface EdgeDrainLease {
70
+ scopeKey: string;
71
+ operationId: string;
72
+ leaseToken: string;
73
+ leaseExpiresAt: string;
74
+ }
75
+ declare class EdgeConflictError extends Error {
76
+ readonly code = "edge_idempotency_conflict";
77
+ }
78
+ declare class EdgeMigrationDrainingError extends Error {
79
+ readonly gate: EdgeMigrationGate;
80
+ readonly code = "client_migration_draining";
81
+ constructor(gate: EdgeMigrationGate);
82
+ }
83
+ declare class EdgeMigrationRetiredError extends Error {
84
+ readonly gate: EdgeMigrationGate;
85
+ readonly code = "client_migration_retired";
86
+ constructor(gate: EdgeMigrationGate);
87
+ }
88
+ declare class EdgeMigrationLeaseError extends Error {
89
+ readonly code = "client_migration_lease_lost";
90
+ constructor();
91
+ }
92
+ declare function edgeMessageFingerprint(message: PendingMessage | BridgeMessage): string;
93
+ declare function stableIdempotency(message: PendingMessage): PendingMessage;
94
+ declare function edgeScopeKey(scope: EdgeScope): string;
95
+ declare class SQLiteEdgeStore {
96
+ private readonly path;
97
+ private readonly scope;
98
+ private readonly busyTimeoutMs;
99
+ private readonly db;
100
+ private readonly databasePath;
101
+ private readonly preexistingFiles;
102
+ private readonly key;
103
+ private initialized;
104
+ private initialization?;
105
+ constructor(path: string, scope: EdgeScope, busyTimeoutMs?: number);
106
+ private restrictFiles;
107
+ get scopeKey(): string;
108
+ private gateFromRow;
109
+ private gateInTransaction;
110
+ private assertScopeActiveInTransaction;
111
+ private assertDrainLeaseInTransaction;
112
+ private assertClaimAuthorityInTransaction;
113
+ initialize(): Promise<void>;
114
+ private initializeOnce;
115
+ private ready;
116
+ migrationGate(): Promise<EdgeMigrationGate>;
117
+ /** Refuse new normal-client writes before any remote attempt begins. */
118
+ assertScopeActive(): Promise<void>;
119
+ /** Refuse remote publication unless this worker still owns the durable drain lease. */
120
+ assertDrainLease(lease: EdgeDrainLease, now?: Date): Promise<void>;
121
+ private claimDrainLease;
122
+ /** Start a new durable drain only from an active edge scope. */
123
+ beginDrain(operationId: string, now?: Date, leaseMs?: number): Promise<EdgeDrainLease>;
124
+ /** Resume the same persisted drain only after the prior worker lease expires. */
125
+ resumeDrain(operationId: string, now?: Date, leaseMs?: number): Promise<EdgeDrainLease>;
126
+ /** Backward-compatible dispatcher for callers that have already inspected the gate. */
127
+ acquireDrainLease(operationId: string, now?: Date, leaseMs?: number): Promise<EdgeDrainLease>;
128
+ /** Verify the held lease and require an exact zero-work outbox before a cutover continues. */
129
+ assertDrainComplete(lease: EdgeDrainLease, now?: Date): Promise<void>;
130
+ renewDrainLease(lease: EdgeDrainLease, now?: Date, leaseMs?: number): Promise<EdgeDrainLease>;
131
+ retireScope(lease: EdgeDrainLease, now?: Date): Promise<void>;
132
+ enqueue(input: PendingMessage, now?: Date): Promise<EdgeEnqueueResult>;
133
+ claimNext(now?: Date, leaseMs?: number, migrationLease?: EdgeDrainLease): Promise<EdgeOutboxRecord | undefined>;
134
+ retry(record: EdgeOutboxRecord, error: string, availableAt: Date): Promise<void>;
135
+ block(record: EdgeOutboxRecord, error: string, now?: Date): Promise<void>;
136
+ commit(record: EdgeOutboxRecord, message: BridgeMessage, now?: Date): Promise<void>;
137
+ private cacheOne;
138
+ cachePage(messages: BridgeMessage[], cursor: string | undefined, now?: Date): Promise<void>;
139
+ cacheLatest(messages: BridgeMessage[], now?: Date): Promise<void>;
140
+ list(query?: MessageQuery): Promise<MessagePage>;
141
+ pullCursor(): Promise<string | undefined>;
142
+ noteError(error: string): Promise<void>;
143
+ noteAttempt(now?: Date): Promise<void>;
144
+ stats(now?: Date): Promise<EdgeStats>;
145
+ close(): Promise<void>;
146
+ }
147
+
148
+ export { EdgeConflictError as E, type PendingMessage as P, SQLiteEdgeStore as S, type EdgeDrainLease as a, type EdgeEnqueueResult as b, EdgeMigrationDrainingError as c, type EdgeMigrationGate as d, type EdgeMigrationGateState as e, EdgeMigrationLeaseError as f, EdgeMigrationRetiredError as g, type EdgeOutboxRecord as h, type EdgeReadOnlyInspection as i, type EdgeScope as j, type EdgeStats as k, edgeMessageFingerprint as l, edgeScopeKey as m, inspectEdgeScopeReadOnly as n, stableIdempotency as s };
package/dist/sqlite.d.ts CHANGED
@@ -1,81 +1,7 @@
1
- import { b as BridgeMessage, c as BridgePrincipal, B as BridgeDelivery, g as DeliveryState, R as RetryPolicy, A as AgentPresence, a as BridgeDeliveryEvent } from './bridge-domain-CBX3IVqC.js';
2
- import { d as MessageQuery, M as MessagePage, B as BridgeDiagnostics, I as InsertMessageResult, a as BridgeStore, C as ClaimOptions, c as DeliveryQuery, b as DeliveryPage, D as DeliveryEventPage } from './bridge-store-DPVSCwfs.js';
3
-
4
- type PendingMessage = Omit<BridgeMessage, "sequence" | "createdAt">;
5
- interface EdgeScope {
6
- endpoint: string;
7
- principal: BridgePrincipal;
8
- }
9
- interface EdgeOutboxRecord {
10
- position: number;
11
- draft: PendingMessage;
12
- payloadHash: string;
13
- attempts: number;
14
- leaseToken: string;
15
- createdAt: string;
16
- }
17
- interface EdgeEnqueueResult {
18
- draft: PendingMessage;
19
- created: boolean;
20
- }
21
- interface EdgeStats {
22
- pending: number;
23
- due: number;
24
- scheduled: number;
25
- leased: number;
26
- blocked: number;
27
- cached: number;
28
- pullCursor?: string;
29
- lastSyncAt?: string;
30
- lastError?: string;
31
- oldestDueAt?: string;
32
- queueLagMs: number;
33
- oldestBlockedAt?: string;
34
- blockedAgeMs: number | null;
35
- blockedMaxAttempts: number;
36
- blockedLastError?: string;
37
- nextRetryAt?: string;
38
- lastOutboundSyncAt?: string;
39
- lastInboundSyncAt?: string;
40
- lastAttemptAt?: string;
41
- }
42
- declare class EdgeConflictError extends Error {
43
- readonly code = "edge_idempotency_conflict";
44
- }
45
- declare function edgeMessageFingerprint(message: PendingMessage | BridgeMessage): string;
46
- declare function stableIdempotency(message: PendingMessage): PendingMessage;
47
- declare function edgeScopeKey(scope: EdgeScope): string;
48
- declare class SQLiteEdgeStore {
49
- private readonly path;
50
- private readonly scope;
51
- private readonly busyTimeoutMs;
52
- private readonly db;
53
- private readonly databasePath;
54
- private readonly preexistingFiles;
55
- private readonly key;
56
- private initialized;
57
- private initialization?;
58
- constructor(path: string, scope: EdgeScope, busyTimeoutMs?: number);
59
- private restrictFiles;
60
- get scopeKey(): string;
61
- initialize(): Promise<void>;
62
- private initializeOnce;
63
- private ready;
64
- enqueue(input: PendingMessage, now?: Date): Promise<EdgeEnqueueResult>;
65
- claimNext(now?: Date, leaseMs?: number): Promise<EdgeOutboxRecord | undefined>;
66
- retry(record: EdgeOutboxRecord, error: string, availableAt: Date): Promise<void>;
67
- block(record: EdgeOutboxRecord, error: string, now?: Date): Promise<void>;
68
- commit(record: EdgeOutboxRecord, message: BridgeMessage, now?: Date): Promise<void>;
69
- private cacheOne;
70
- cachePage(messages: BridgeMessage[], cursor: string | undefined, now?: Date): Promise<void>;
71
- cacheLatest(messages: BridgeMessage[], now?: Date): Promise<void>;
72
- list(query?: MessageQuery): Promise<MessagePage>;
73
- pullCursor(): Promise<string | undefined>;
74
- noteError(error: string): Promise<void>;
75
- noteAttempt(now?: Date): Promise<void>;
76
- stats(now?: Date): Promise<EdgeStats>;
77
- close(): Promise<void>;
78
- }
1
+ import { c as BridgePrincipal, B as BridgeDelivery, g as DeliveryState, R as RetryPolicy, A as AgentPresence, b as BridgeMessage, a as BridgeDeliveryEvent } from './bridge-domain-CBX3IVqC.js';
2
+ import { M as MessagePage, B as BridgeDiagnostics, I as InsertMessageResult, a as BridgeStore, d as MessageQuery, C as ClaimOptions, c as DeliveryQuery, b as DeliveryPage, D as DeliveryEventPage } from './bridge-store-ByJ6CaJk.js';
3
+ import { e as EdgeMigrationGateState, S as SQLiteEdgeStore, a as EdgeDrainLease, P as PendingMessage } from './sqlite-edge-store-CdMIOrho.js';
4
+ export { E as EdgeConflictError, b as EdgeEnqueueResult, c as EdgeMigrationDrainingError, d as EdgeMigrationGate, f as EdgeMigrationLeaseError, g as EdgeMigrationRetiredError, h as EdgeOutboxRecord, i as EdgeReadOnlyInspection, j as EdgeScope, k as EdgeStats, l as edgeMessageFingerprint, m as edgeScopeKey, n as inspectEdgeScopeReadOnly, s as stableIdempotency } from './sqlite-edge-store-CdMIOrho.js';
79
5
 
80
6
  interface SyncInsertResult extends InsertMessageResult {
81
7
  disposition: "committed" | "queued";
@@ -100,6 +26,9 @@ interface SyncReport {
100
26
  lastSyncedAt?: string;
101
27
  lastError?: string;
102
28
  failureRetryable?: boolean;
29
+ migrationState?: EdgeMigrationGateState;
30
+ migrationOperationId?: string;
31
+ migrationLeaseExpiresAt?: string;
103
32
  }
104
33
  interface SyncDiagnostics extends BridgeDiagnostics {
105
34
  remoteReachable: boolean | null;
@@ -121,9 +50,12 @@ interface SyncDiagnostics extends BridgeDiagnostics {
121
50
  lastOutboundSyncAt?: string;
122
51
  lastInboundSyncAt?: string;
123
52
  lastSyncAttemptAt?: string;
124
- syncLoopState: "disabled" | "idle" | "running" | "backoff" | "stopped" | "failed";
53
+ syncLoopState: "disabled" | "idle" | "running" | "backoff" | "paused" | "stopped" | "failed";
125
54
  syncLoopError?: string;
126
55
  remoteError?: string;
56
+ migrationState: EdgeMigrationGateState;
57
+ migrationOperationId?: string;
58
+ migrationLeaseExpiresAt?: string;
127
59
  }
128
60
  interface SyncingBridgeStoreOptions {
129
61
  now?: () => Date;
@@ -133,6 +65,9 @@ interface SyncingBridgeStoreOptions {
133
65
  leaseMs?: number;
134
66
  autoSync?: boolean;
135
67
  idleDelayMs?: number;
68
+ edgeDrainLease?: EdgeDrainLease;
69
+ /** A maintenance caller may retain the edge while closing the sync wrapper. */
70
+ closeEdge?: boolean;
136
71
  }
137
72
  declare function isRetryableSyncError(error: unknown): boolean;
138
73
  declare class SyncingBridgeStore implements BridgeStore {
@@ -146,6 +81,8 @@ declare class SyncingBridgeStore implements BridgeStore {
146
81
  private readonly leaseMs;
147
82
  private readonly autoSync;
148
83
  private readonly idleDelayMs;
84
+ private edgeDrainLease?;
85
+ private readonly closeEdge;
149
86
  private remoteReady;
150
87
  private stopped;
151
88
  private wakeTimer;
@@ -158,6 +95,8 @@ declare class SyncingBridgeStore implements BridgeStore {
158
95
  private loopState;
159
96
  constructor(edge: SQLiteEdgeStore, remote: BridgeStore, principal: BridgePrincipal, options?: SyncingBridgeStoreOptions);
160
97
  initialize(): Promise<void>;
98
+ /** Replace a renewed maintenance lease without creating another edge wrapper. */
99
+ setDrainLease(lease: EdgeDrainLease | undefined): void;
161
100
  private startTransportLoop;
162
101
  private wait;
163
102
  private wakeTransport;
@@ -235,4 +174,4 @@ declare class SQLiteBridgeStore implements BridgeStore {
235
174
  close(): Promise<void>;
236
175
  }
237
176
 
238
- export { type CachedMessagePage, EdgeConflictError, type EdgeEnqueueResult, type EdgeOutboxRecord, type EdgeScope, type EdgeStats, type PendingMessage, SQLiteBridgeStore, SQLiteEdgeStore, type SyncDiagnostics, type SyncInsertResult, type SyncReport, SyncingBridgeStore, type SyncingBridgeStoreOptions, edgeMessageFingerprint, edgeScopeKey, isRetryableSyncError, stableIdempotency };
177
+ export { type CachedMessagePage, EdgeDrainLease, EdgeMigrationGateState, PendingMessage, SQLiteBridgeStore, SQLiteEdgeStore, type SyncDiagnostics, type SyncInsertResult, type SyncReport, SyncingBridgeStore, type SyncingBridgeStoreOptions, isRetryableSyncError };
package/dist/sqlite.js CHANGED
@@ -1,26 +1,34 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  EdgeConflictError,
4
+ EdgeMigrationDrainingError,
5
+ EdgeMigrationLeaseError,
6
+ EdgeMigrationRetiredError,
4
7
  SQLiteEdgeStore,
5
8
  SyncingBridgeStore,
6
9
  edgeMessageFingerprint,
7
10
  edgeScopeKey,
11
+ inspectEdgeScopeReadOnly,
8
12
  isRetryableSyncError,
9
13
  stableIdempotency
10
- } from "./chunk-Q6PPMXJC.js";
14
+ } from "./chunk-SN7PIVDF.js";
11
15
  import {
12
16
  SQLiteBridgeStore
13
- } from "./chunk-5GMB3A77.js";
14
- import "./chunk-NCO7RLBM.js";
17
+ } from "./chunk-IVG4XWWS.js";
18
+ import "./chunk-GTK4KOED.js";
15
19
  import "./chunk-VOQIDTCJ.js";
16
20
  import "./chunk-BZLMF5O5.js";
17
21
  export {
18
22
  EdgeConflictError,
23
+ EdgeMigrationDrainingError,
24
+ EdgeMigrationLeaseError,
25
+ EdgeMigrationRetiredError,
19
26
  SQLiteBridgeStore,
20
27
  SQLiteEdgeStore,
21
28
  SyncingBridgeStore,
22
29
  edgeMessageFingerprint,
23
30
  edgeScopeKey,
31
+ inspectEdgeScopeReadOnly,
24
32
  isRetryableSyncError,
25
33
  stableIdempotency
26
34
  };