@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.
- package/CHANGELOG.md +222 -1
- package/README.md +312 -12
- package/ROADMAP.md +88 -7
- package/SECURITY.md +78 -5
- package/SKILL.md +108 -1
- package/dist/archive.d.ts +2 -2
- package/dist/archive.js +2 -2
- package/dist/{bridge-service-hX1ZQp4W.d.ts → bridge-service-CNBKdYXh.d.ts} +2 -1
- package/dist/{bridge-store-DPVSCwfs.d.ts → bridge-store-ByJ6CaJk.d.ts} +3 -0
- package/dist/bridge.d.ts +25 -8
- package/dist/bridge.js +19 -9
- package/dist/{chunk-MBPZXTJA.js → chunk-7VJI2MXI.js} +285 -42
- package/dist/chunk-7VJI2MXI.js.map +1 -0
- package/dist/{chunk-4VB44KJU.js → chunk-AWE3DFZV.js} +3 -2
- package/dist/chunk-AWE3DFZV.js.map +1 -0
- package/dist/{chunk-GADH244D.js → chunk-E4OWHGUE.js} +16 -5
- package/dist/chunk-E4OWHGUE.js.map +1 -0
- package/dist/{chunk-NCO7RLBM.js → chunk-GTK4KOED.js} +108 -8
- package/dist/chunk-GTK4KOED.js.map +1 -0
- package/dist/{chunk-SPSMZOCJ.js → chunk-HUHG7XTB.js} +6 -5
- package/dist/chunk-HUHG7XTB.js.map +1 -0
- package/dist/{chunk-5GMB3A77.js → chunk-IVG4XWWS.js} +26 -14
- package/dist/chunk-IVG4XWWS.js.map +1 -0
- package/dist/{chunk-WAI2IYIQ.js → chunk-P635FAN4.js} +50 -7
- package/dist/chunk-P635FAN4.js.map +1 -0
- package/dist/{chunk-45BZGB77.js → chunk-QNXHDOJ5.js} +218 -24
- package/dist/chunk-QNXHDOJ5.js.map +1 -0
- package/dist/{chunk-UAHMP6GC.js → chunk-QPC6JHA3.js} +10 -7
- package/dist/chunk-QPC6JHA3.js.map +1 -0
- package/dist/{chunk-Q6PPMXJC.js → chunk-SN7PIVDF.js} +377 -21
- package/dist/chunk-SN7PIVDF.js.map +1 -0
- package/dist/{chunk-A6W4QALF.js → chunk-TC6A7GMF.js} +2 -2
- package/dist/{chunk-2DFNOLMO.js → chunk-XDNT6ZNZ.js} +9 -6
- package/dist/chunk-XDNT6ZNZ.js.map +1 -0
- package/dist/{chunk-CEXBMOI5.js → chunk-Z6JP6MYD.js} +2 -2
- package/dist/cli.js +5867 -438
- package/dist/cli.js.map +1 -1
- package/dist/client-runtime.d.ts +4 -2
- package/dist/client-runtime.js +7 -7
- package/dist/contracts/generate.js +1 -1
- package/dist/dr.d.ts +9 -2
- package/dist/dr.js +6 -4
- package/dist/{gateway-CggQ8vTC.d.ts → gateway-CW-61lvs.d.ts} +27 -3
- package/dist/gateway-main.js +71 -7
- package/dist/gateway-main.js.map +1 -1
- package/dist/gateway.d.ts +3 -3
- package/dist/gateway.js +3 -3
- package/dist/index.js +8 -8
- package/dist/{postgres-bridge-store-Bfd3hRId.d.ts → postgres-bridge-store-C9KG9Z69.d.ts} +1 -1
- package/dist/postgres-production-preflight.d.ts +32 -0
- package/dist/postgres-production-preflight.js +249 -0
- package/dist/postgres-production-preflight.js.map +1 -0
- package/dist/server.js +8 -8
- package/dist/sqlite-edge-store-CdMIOrho.d.ts +148 -0
- package/dist/sqlite.d.ts +19 -80
- package/dist/sqlite.js +11 -3
- package/docs/architecture-v2.md +292 -15
- package/docs/decisions/0004-client-lifecycle-and-endpoint-migration.md +204 -0
- package/docs/deployment.md +349 -0
- package/docs/troubleshooting.md +23 -0
- package/llms.txt +69 -1
- package/openapi/agent-bridge-v2.openapi.json +1151 -124
- package/package.json +10 -2
- package/schemas/agent-bridge-v2.capabilities.json +26 -1
- package/schemas/agent-bridge-v2.mcp.json +11 -1
- package/schemas/agent-bridge-v2.schema.json +167 -0
- package/sql/migrations/007_runtime_role.sql +8 -4
- package/sql/migrations/013_row_isolation.sql +71 -8
- package/sql/migrations/014_owner_control_plane.sql +124 -31
- package/sql/migrations/015_portable_archives.sql +143 -41
- package/sql/migrations/016_native_dr_fence.sql +102 -35
- package/sql/migrations/017_gateway_authority_binding.sql +99 -0
- package/sql/migrations/018_endpoint_migration_challenges.sql +1054 -0
- package/sql/migrations/019_endpoint_migration_same_successor.sql +348 -0
- package/sql/migrations/020_managed_authority_compat.sql +698 -0
- package/sql/migrations/021_native_backup_reader.sql +421 -0
- package/sql/setup.sql +1 -3
- package/dist/chunk-2DFNOLMO.js.map +0 -1
- package/dist/chunk-45BZGB77.js.map +0 -1
- package/dist/chunk-4VB44KJU.js.map +0 -1
- package/dist/chunk-5GMB3A77.js.map +0 -1
- package/dist/chunk-GADH244D.js.map +0 -1
- package/dist/chunk-MBPZXTJA.js.map +0 -1
- package/dist/chunk-NCO7RLBM.js.map +0 -1
- package/dist/chunk-Q6PPMXJC.js.map +0 -1
- package/dist/chunk-SPSMZOCJ.js.map +0 -1
- package/dist/chunk-UAHMP6GC.js.map +0 -1
- package/dist/chunk-WAI2IYIQ.js.map +0 -1
- /package/dist/{chunk-A6W4QALF.js.map → chunk-TC6A7GMF.js.map} +0 -0
- /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-
|
|
7
|
+
} from "./chunk-HUHG7XTB.js";
|
|
8
8
|
import "./chunk-2EVLG3YA.js";
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
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-
|
|
16
|
-
import "./chunk-
|
|
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 {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
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,
|
|
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-
|
|
14
|
+
} from "./chunk-SN7PIVDF.js";
|
|
11
15
|
import {
|
|
12
16
|
SQLiteBridgeStore
|
|
13
|
-
} from "./chunk-
|
|
14
|
-
import "./chunk-
|
|
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
|
};
|