@codemem/server 0.22.4 → 0.24.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,6 +1,6 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
- import { CODEMEM_CONFIG_ENV_OVERRIDES, DEFAULT_TIME_WINDOW_S, MemoryStore, VERSION, VERSION as VERSION$1, applyReplicationOps, buildFilterClausesWithContext, buildRawEventEnvelopeFromHook, cleanupNonces, coordinatorCreateInviteAction, coordinatorImportInviteAction, coordinatorReviewJoinRequestAction, coordinatorStatusSnapshot, createCoordinatorReciprocalApproval, ensureDeviceIdentity, extractReplicationOps, fingerprintPublicKey, fromJson, getCodememConfigPath, getCodememEnvOverrides, getSyncResetState, listCoordinatorJoinRequests, listObserverProviderOptions, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, parseStrictInteger, probeAvailableCredentials, readCodememConfigFile, readCoordinatorSyncConfig, recordNonce, resolveDbPath, runSyncPass, schema, stripJsonComments, stripPrivateObj, stripTrailingCommas, verifySignature, writeCodememConfigFile } from "@codemem/core";
3
+ import { CODEMEM_CONFIG_ENV_OVERRIDES, DEFAULT_TIME_WINDOW_S, MemoryStore, VERSION, VERSION as VERSION$1, applyReplicationOps, buildBaseUrl, buildFilterClausesWithContext, buildRawEventEnvelopeFromHook, canonicalMemoryKind, cleanupNonces, coordinatorCreateInviteAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorStatusSnapshot, createCoordinatorReciprocalApproval, ensureDeviceIdentity, extractReplicationOps, fingerprintPublicKey, fromJson, getCodememConfigPath, getCodememEnvOverrides, getSyncResetState, isSummaryLikeMemory, listCoordinatorJoinRequests, listObserverProviderOptions, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, parseStrictInteger, probeAvailableCredentials, readCodememConfigFile, readCoordinatorSyncConfig, recordNonce, requestJson, resolveDbPath, runSyncPass, schema, stripJsonComments, stripPrivateObj, stripTrailingCommas, verifySignature, writeCodememConfigFile } from "@codemem/core";
4
4
  import { serveStatic } from "@hono/node-server/serve-static";
5
5
  import { Hono } from "hono";
6
6
  import { createMiddleware } from "hono/factory";
@@ -193,6 +193,15 @@ var ALLOWED_KEYS = [
193
193
  "observer_base_url",
194
194
  "observer_provider",
195
195
  "observer_model",
196
+ "observer_tier_routing_enabled",
197
+ "observer_simple_model",
198
+ "observer_simple_temperature",
199
+ "observer_rich_model",
200
+ "observer_rich_temperature",
201
+ "observer_rich_openai_use_responses",
202
+ "observer_rich_reasoning_effort",
203
+ "observer_rich_reasoning_summary",
204
+ "observer_rich_max_output_tokens",
196
205
  "observer_runtime",
197
206
  "observer_auth_source",
198
207
  "observer_auth_file",
@@ -221,6 +230,8 @@ var DEFAULTS = {
221
230
  claude_command: ["claude"],
222
231
  observer_runtime: "api_http",
223
232
  observer_auth_source: "auto",
233
+ observer_tier_routing_enabled: false,
234
+ observer_rich_openai_use_responses: false,
224
235
  observer_auth_command: [],
225
236
  observer_auth_timeout_ms: 1500,
226
237
  observer_auth_cache_ttl_s: 300,
@@ -319,6 +330,13 @@ function parsePositiveInt(value, allowZero = false) {
319
330
  if (allowZero) return parsed >= 0 ? parsed : null;
320
331
  return parsed > 0 ? parsed : null;
321
332
  }
333
+ function parseFiniteNumber(value, allowZero = true) {
334
+ if (typeof value === "boolean") return null;
335
+ const parsed = typeof value === "number" ? value : typeof value === "string" && value.trim().length > 0 ? Number(value.trim()) : NaN;
336
+ if (!Number.isFinite(parsed)) return null;
337
+ if (allowZero) return parsed >= 0 ? parsed : null;
338
+ return parsed > 0 ? parsed : null;
339
+ }
322
340
  function asStringMap(value) {
323
341
  if (value == null || typeof value !== "object" || Array.isArray(value)) return null;
324
342
  const parsed = {};
@@ -388,13 +406,24 @@ function validateAndApplyUpdate(configData, key, value, providers) {
388
406
  configData[key] = value;
389
407
  return null;
390
408
  }
391
- if (key === "observer_base_url" || key === "observer_model" || key === "observer_auth_file" || key === "sync_host" || key === "sync_coordinator_url" || key === "sync_coordinator_group") {
409
+ if (key === "observer_tier_routing_enabled" || key === "observer_rich_openai_use_responses") {
410
+ if (typeof value !== "boolean") return `${key} must be boolean`;
411
+ configData[key] = value;
412
+ return null;
413
+ }
414
+ if (key === "observer_base_url" || key === "observer_model" || key === "observer_simple_model" || key === "observer_rich_model" || key === "observer_rich_reasoning_effort" || key === "observer_rich_reasoning_summary" || key === "observer_auth_file" || key === "sync_host" || key === "sync_coordinator_url" || key === "sync_coordinator_group") {
392
415
  if (typeof value !== "string") return `${key} must be string`;
393
416
  const trimmed = value.trim();
394
417
  if (!trimmed) delete configData[key];
395
418
  else configData[key] = trimmed;
396
419
  return null;
397
420
  }
421
+ if (key === "observer_simple_temperature" || key === "observer_rich_temperature") {
422
+ const parsed = parseFiniteNumber(value, true);
423
+ if (parsed == null) return `${key} must be non-negative number`;
424
+ configData[key] = parsed;
425
+ return null;
426
+ }
398
427
  const allowZero = key === "observer_auth_cache_ttl_s";
399
428
  const parsed = parsePositiveInt(value, allowZero);
400
429
  if (parsed == null) return `${key} must be ${allowZero ? "non-negative int" : "int"}`;
@@ -591,14 +620,15 @@ function queryMemoryPage(store, options) {
591
620
  ORDER BY memory_items.created_at DESC
592
621
  LIMIT ? OFFSET ?`).all(...filterResult.params, options.limit + 1, options.offset).map((row) => ({
593
622
  ...row,
623
+ kind: canonicalMemoryKind(row.kind ?? null, row.metadata_json),
594
624
  metadata_json: fromJson(row.metadata_json ?? null)
595
625
  }));
596
626
  }
597
- function isSummaryLikeMemory(item) {
598
- if (String(item.kind ?? "").toLowerCase() === "session_summary") return true;
599
- const metadata = item.metadata_json ?? {};
600
- if (metadata.is_summary === true) return true;
601
- return String(metadata.source ?? "").trim().toLowerCase() === "observer_summary";
627
+ function isSummaryLikeMemory$1(item) {
628
+ return isSummaryLikeMemory({
629
+ kind: item.kind,
630
+ metadata: item.metadata_json
631
+ });
602
632
  }
603
633
  function selectMemoryPage(store, options) {
604
634
  const pageSize = Math.max(options.limit + options.offset + 10, 50);
@@ -653,7 +683,7 @@ function memoryRoutes(getStore) {
653
683
  offset,
654
684
  project: c.req.query("project") || void 0,
655
685
  scope: normalizeScope(c.req.query("scope")),
656
- matcher: (item) => !isSummaryLikeMemory(item)
686
+ matcher: (item) => !isSummaryLikeMemory$1(item)
657
687
  });
658
688
  const hasMore = items.length > limit;
659
689
  const result = hasMore ? items.slice(0, limit) : items;
@@ -680,7 +710,7 @@ function memoryRoutes(getStore) {
680
710
  offset,
681
711
  project: c.req.query("project") || void 0,
682
712
  scope: normalizeScope(c.req.query("scope")),
683
- matcher: (item) => isSummaryLikeMemory(item)
713
+ matcher: (item) => isSummaryLikeMemory$1(item)
684
714
  });
685
715
  const hasMore = items.length > limit;
686
716
  const result = hasMore ? items.slice(0, limit) : items;
@@ -719,7 +749,7 @@ function memoryRoutes(getStore) {
719
749
  project: scopeProject
720
750
  });
721
751
  if (page.length === 0) break;
722
- total += page.filter((item) => !isSummaryLikeMemory(item)).length;
752
+ total += page.filter((item) => !isSummaryLikeMemory$1(item)).length;
723
753
  if (page.length < 200) break;
724
754
  offset += page.length;
725
755
  }
@@ -1288,6 +1318,9 @@ var SYNC_STALE_AFTER_SECONDS = 600;
1288
1318
  var SYNC_PROTOCOL_VERSION = "2";
1289
1319
  var LEGACY_SYNC_ACTOR_DISPLAY_NAME = "Legacy synced peer";
1290
1320
  var LEGACY_SHARED_WORKSPACE_ID = "shared:legacy";
1321
+ function syncKeysDir() {
1322
+ return process.env.CODEMEM_KEYS_DIR?.trim() || void 0;
1323
+ }
1291
1324
  function intEnvOr(name, fallback) {
1292
1325
  const value = Number.parseInt(process.env[name] ?? "", 10);
1293
1326
  return Number.isFinite(value) ? value : fallback;
@@ -1324,8 +1357,8 @@ function pathWithQuery(url) {
1324
1357
  const parsed = new URL(url);
1325
1358
  return parsed.search ? `${parsed.pathname}${parsed.search}` : parsed.pathname;
1326
1359
  }
1327
- function unauthorizedPayload(reason) {
1328
- if (process.env.CODEMEM_SYNC_AUTH_DIAGNOSTICS === "1") return {
1360
+ function unauthorizedPayload(reason, exposeReason = false) {
1361
+ if (exposeReason && process.env.CODEMEM_SYNC_AUTH_DIAGNOSTICS === "1") return {
1329
1362
  error: "unauthorized",
1330
1363
  reason
1331
1364
  };
@@ -1397,6 +1430,123 @@ function authorizeSyncRequest(store, request, body) {
1397
1430
  deviceId
1398
1431
  };
1399
1432
  }
1433
+ async function authorizeBootstrapGrantRequest(store, request, body) {
1434
+ const grantId = (request.header("X-Codemem-Bootstrap-Grant") ?? "").trim();
1435
+ const deviceId = (request.header("X-Opencode-Device") ?? "").trim();
1436
+ const signature = request.header("X-Opencode-Signature") ?? "";
1437
+ const timestamp = request.header("X-Opencode-Timestamp") ?? "";
1438
+ const nonce = request.header("X-Opencode-Nonce") ?? "";
1439
+ if (!grantId || !deviceId || !signature || !timestamp || !nonce) return {
1440
+ ok: false,
1441
+ reason: "missing_bootstrap_grant_headers",
1442
+ deviceId
1443
+ };
1444
+ const config = readCoordinatorSyncConfig();
1445
+ if (!config.syncCoordinatorUrl || !config.syncCoordinatorAdminSecret) return {
1446
+ ok: false,
1447
+ reason: "bootstrap_grant_coordinator_not_configured",
1448
+ deviceId
1449
+ };
1450
+ const [localDeviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
1451
+ let verification;
1452
+ try {
1453
+ const [status, payload] = await requestJson("GET", `${buildBaseUrl(config.syncCoordinatorUrl)}/v1/admin/bootstrap-grants/${encodeURIComponent(grantId)}`, {
1454
+ headers: { "X-Codemem-Coordinator-Admin": config.syncCoordinatorAdminSecret },
1455
+ timeoutS: Math.max(1, config.syncCoordinatorTimeoutS)
1456
+ });
1457
+ if (status !== 200 || !payload) return {
1458
+ ok: false,
1459
+ reason: "bootstrap_grant_lookup_failed",
1460
+ deviceId
1461
+ };
1462
+ verification = payload;
1463
+ } catch {
1464
+ return {
1465
+ ok: false,
1466
+ reason: "bootstrap_grant_lookup_failed",
1467
+ deviceId
1468
+ };
1469
+ }
1470
+ const grant = verification.grant;
1471
+ const workerEnrollment = verification.worker_enrollment;
1472
+ if (!grant || !workerEnrollment) return {
1473
+ ok: false,
1474
+ reason: "bootstrap_grant_invalid_payload",
1475
+ deviceId
1476
+ };
1477
+ if (grant.revoked_at) return {
1478
+ ok: false,
1479
+ reason: "bootstrap_grant_revoked",
1480
+ deviceId
1481
+ };
1482
+ if (String(workerEnrollment.device_id) !== String(grant.worker_device_id)) return {
1483
+ ok: false,
1484
+ reason: "bootstrap_grant_worker_enrollment_mismatch",
1485
+ deviceId
1486
+ };
1487
+ if (String(workerEnrollment.group_id) !== String(grant.group_id)) return {
1488
+ ok: false,
1489
+ reason: "bootstrap_grant_group_mismatch",
1490
+ deviceId
1491
+ };
1492
+ if (Number(workerEnrollment.enabled) !== 1) return {
1493
+ ok: false,
1494
+ reason: "bootstrap_grant_worker_disabled",
1495
+ deviceId
1496
+ };
1497
+ if (grant.worker_device_id !== deviceId) return {
1498
+ ok: false,
1499
+ reason: "bootstrap_grant_worker_mismatch",
1500
+ deviceId
1501
+ };
1502
+ if (grant.seed_device_id !== localDeviceId) return {
1503
+ ok: false,
1504
+ reason: "bootstrap_grant_seed_mismatch",
1505
+ deviceId
1506
+ };
1507
+ if (new Date(grant.expires_at) <= /* @__PURE__ */ new Date()) return {
1508
+ ok: false,
1509
+ reason: "bootstrap_grant_expired",
1510
+ deviceId
1511
+ };
1512
+ let valid = false;
1513
+ try {
1514
+ valid = verifySignature({
1515
+ method: request.method,
1516
+ pathWithQuery: pathWithQuery(request.url),
1517
+ bodyBytes: body,
1518
+ timestamp,
1519
+ nonce,
1520
+ signature,
1521
+ publicKey: String(workerEnrollment.public_key),
1522
+ deviceId
1523
+ });
1524
+ } catch {
1525
+ return {
1526
+ ok: false,
1527
+ reason: "bootstrap_grant_signature_verification_error",
1528
+ deviceId
1529
+ };
1530
+ }
1531
+ if (!valid) return {
1532
+ ok: false,
1533
+ reason: "bootstrap_grant_invalid_signature",
1534
+ deviceId
1535
+ };
1536
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
1537
+ if (!recordNonce(store.db, deviceId, nonce, createdAt)) return {
1538
+ ok: false,
1539
+ reason: "nonce_replay",
1540
+ deviceId
1541
+ };
1542
+ const cutoff = (/* @__PURE__ */ new Date(Date.now() - DEFAULT_TIME_WINDOW_S * 2 * 1e3)).toISOString();
1543
+ cleanupNonces(store.db, cutoff);
1544
+ return {
1545
+ ok: true,
1546
+ reason: "ok",
1547
+ deviceId
1548
+ };
1549
+ }
1400
1550
  function projectBasename(value) {
1401
1551
  const project = String(value ?? "").trim().replaceAll("\\", "/");
1402
1552
  if (!project) return "";
@@ -1795,29 +1945,43 @@ function syncProtocolRoutes(getStore, opts = {}) {
1795
1945
  }
1796
1946
  app.get("/v1/status", (c) => {
1797
1947
  const store = getStore();
1798
- const auth = authorizeSyncRequest(store, c.req, Buffer.alloc(0));
1799
- if (!auth.ok) return rateLimitedResponse(c, c.req.path, false) ?? c.json(unauthorizedPayload(auth.reason), 401);
1800
- const limited = rateLimitedResponse(c, auth.deviceId, true);
1801
- if (limited) return limited;
1802
- try {
1803
- let device = store.db.prepare("SELECT device_id, fingerprint FROM sync_device LIMIT 1").get();
1804
- if (!device) {
1805
- const [deviceId, fingerprint] = ensureDeviceIdentity(store.db);
1806
- device = {
1948
+ return (async () => {
1949
+ let auth = authorizeSyncRequest(store, c.req, Buffer.alloc(0));
1950
+ let preauthChecked = false;
1951
+ let bootstrapAttempted = false;
1952
+ if (!auth.ok) {
1953
+ if ((c.req.header("X-Codemem-Bootstrap-Grant") ?? "").trim()) {
1954
+ preauthChecked = true;
1955
+ const bootstrapLimited = rateLimitedResponse(c, c.req.path, false);
1956
+ if (bootstrapLimited) return bootstrapLimited;
1957
+ } else {
1958
+ preauthChecked = true;
1959
+ const unauthLimited = rateLimitedResponse(c, c.req.path, false);
1960
+ if (unauthLimited) return unauthLimited;
1961
+ }
1962
+ auth = await authorizeBootstrapGrantRequest(store, c.req, Buffer.alloc(0));
1963
+ bootstrapAttempted = true;
1964
+ }
1965
+ if (!auth.ok) {
1966
+ if (bootstrapAttempted) console.warn(`[sync] bootstrap grant auth failed: reason=${auth.reason} grant=${(c.req.header("X-Codemem-Bootstrap-Grant") ?? "").trim()} path=${c.req.path}`);
1967
+ const wireReason = bootstrapAttempted ? "bootstrap_grant_invalid" : auth.reason;
1968
+ return (preauthChecked ? null : rateLimitedResponse(c, c.req.path, false)) ?? c.json(unauthorizedPayload(wireReason), 401);
1969
+ }
1970
+ const limited = rateLimitedResponse(c, auth.deviceId, true);
1971
+ if (limited) return limited;
1972
+ try {
1973
+ const [deviceId, fingerprint] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
1974
+ const syncReset = getSyncResetState(store.db);
1975
+ return c.json({
1807
1976
  device_id: deviceId,
1808
- fingerprint
1809
- };
1977
+ protocol_version: SYNC_PROTOCOL_VERSION,
1978
+ fingerprint,
1979
+ sync_reset: syncReset
1980
+ });
1981
+ } catch {
1982
+ return c.json({ error: "internal_error" }, 500);
1810
1983
  }
1811
- const syncReset = getSyncResetState(store.db);
1812
- return c.json({
1813
- device_id: device.device_id,
1814
- protocol_version: SYNC_PROTOCOL_VERSION,
1815
- fingerprint: device.fingerprint,
1816
- sync_reset: syncReset
1817
- });
1818
- } catch {
1819
- return c.json({ error: "internal_error" }, 500);
1820
- }
1984
+ })();
1821
1985
  });
1822
1986
  app.get("/v1/ops", (c) => {
1823
1987
  const store = getStore();
@@ -1834,15 +1998,11 @@ function syncProtocolRoutes(getStore, opts = {}) {
1834
1998
  const snapshotId = c.req.query("snapshot_id") ?? null;
1835
1999
  const baselineCursor = c.req.query("baseline_cursor") ?? null;
1836
2000
  const limit = Number.isFinite(rawLimit) ? Math.max(1, Math.min(rawLimit, 1e3)) : 200;
1837
- let localDeviceId = store.db.prepare("SELECT device_id FROM sync_device LIMIT 1").get();
1838
- if (!localDeviceId) {
1839
- const [deviceId] = ensureDeviceIdentity(store.db);
1840
- localDeviceId = { device_id: deviceId };
1841
- }
2001
+ const [localDeviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
1842
2002
  const result = loadReplicationOpsForPeer(store.db, {
1843
2003
  since,
1844
2004
  limit,
1845
- deviceId: localDeviceId.device_id,
2005
+ deviceId: localDeviceId,
1846
2006
  generation: Number.isFinite(generation) ? generation : null,
1847
2007
  snapshotId,
1848
2008
  baselineCursor
@@ -1869,42 +2029,63 @@ function syncProtocolRoutes(getStore, opts = {}) {
1869
2029
  });
1870
2030
  app.get("/v1/snapshot", (c) => {
1871
2031
  const store = getStore();
1872
- const auth = authorizeSyncRequest(store, c.req, Buffer.alloc(0));
1873
- if (!auth.ok) return rateLimitedResponse(c, c.req.path, false) ?? c.json(unauthorizedPayload(auth.reason), 401);
1874
- const limited = rateLimitedResponse(c, auth.deviceId, true);
1875
- if (limited) return limited;
1876
- try {
1877
- const rawGeneration = c.req.query("generation");
1878
- const generation = rawGeneration != null && rawGeneration.trim().length > 0 ? Number.parseInt(rawGeneration, 10) : null;
1879
- const snapshotId = c.req.query("snapshot_id") ?? null;
1880
- const baselineCursor = c.req.query("baseline_cursor") ?? null;
1881
- const pageToken = c.req.query("page_token") ?? null;
1882
- const rawLimit = Number.parseInt(c.req.query("limit") ?? "200", 10);
1883
- const limit = Number.isFinite(rawLimit) ? Math.max(1, Math.min(rawLimit, 5e3)) : 200;
1884
- if (generation == null || !Number.isFinite(generation)) return c.json({ error: "missing_generation" }, 400);
1885
- if (!snapshotId) return c.json({ error: "missing_snapshot_id" }, 400);
1886
- const result = loadMemorySnapshotPageForPeer(store.db, {
1887
- generation,
1888
- snapshotId,
1889
- baselineCursor,
1890
- pageToken,
1891
- limit,
1892
- peerDeviceId: auth.deviceId
1893
- });
1894
- return c.json({
1895
- generation: result.boundary.generation,
1896
- snapshot_id: result.boundary.snapshot_id,
1897
- baseline_cursor: result.boundary.baseline_cursor,
1898
- retained_floor_cursor: result.boundary.retained_floor_cursor,
1899
- items: result.items,
1900
- next_page_token: result.nextPageToken,
1901
- has_more: result.hasMore
1902
- });
1903
- } catch (err) {
1904
- const message = err instanceof Error ? err.message : "";
1905
- if (message === "generation_mismatch" || message === "boundary_mismatch") return c.json({ error: message }, 409);
1906
- return c.json({ error: "internal_error" }, 500);
1907
- }
2032
+ return (async () => {
2033
+ let auth = authorizeSyncRequest(store, c.req, Buffer.alloc(0));
2034
+ let preauthChecked = false;
2035
+ let bootstrapAttempted = false;
2036
+ if (!auth.ok) {
2037
+ if ((c.req.header("X-Codemem-Bootstrap-Grant") ?? "").trim()) {
2038
+ preauthChecked = true;
2039
+ const bootstrapLimited = rateLimitedResponse(c, c.req.path, false);
2040
+ if (bootstrapLimited) return bootstrapLimited;
2041
+ } else {
2042
+ preauthChecked = true;
2043
+ const unauthLimited = rateLimitedResponse(c, c.req.path, false);
2044
+ if (unauthLimited) return unauthLimited;
2045
+ }
2046
+ auth = await authorizeBootstrapGrantRequest(store, c.req, Buffer.alloc(0));
2047
+ bootstrapAttempted = true;
2048
+ }
2049
+ if (!auth.ok) {
2050
+ if (bootstrapAttempted) console.warn(`[sync] bootstrap grant auth failed: reason=${auth.reason} grant=${(c.req.header("X-Codemem-Bootstrap-Grant") ?? "").trim()} path=${c.req.path}`);
2051
+ const wireReason = bootstrapAttempted ? "bootstrap_grant_invalid" : auth.reason;
2052
+ return (preauthChecked ? null : rateLimitedResponse(c, c.req.path, false)) ?? c.json(unauthorizedPayload(wireReason), 401);
2053
+ }
2054
+ const limited = rateLimitedResponse(c, auth.deviceId, true);
2055
+ if (limited) return limited;
2056
+ try {
2057
+ const rawGeneration = c.req.query("generation");
2058
+ const generation = rawGeneration != null && rawGeneration.trim().length > 0 ? Number.parseInt(rawGeneration, 10) : null;
2059
+ const snapshotId = c.req.query("snapshot_id") ?? null;
2060
+ const baselineCursor = c.req.query("baseline_cursor") ?? null;
2061
+ const pageToken = c.req.query("page_token") ?? null;
2062
+ const rawLimit = Number.parseInt(c.req.query("limit") ?? "200", 10);
2063
+ const limit = Number.isFinite(rawLimit) ? Math.max(1, Math.min(rawLimit, 5e3)) : 200;
2064
+ if (generation == null || !Number.isFinite(generation)) return c.json({ error: "missing_generation" }, 400);
2065
+ if (!snapshotId) return c.json({ error: "missing_snapshot_id" }, 400);
2066
+ const result = loadMemorySnapshotPageForPeer(store.db, {
2067
+ generation,
2068
+ snapshotId,
2069
+ baselineCursor,
2070
+ pageToken,
2071
+ limit,
2072
+ peerDeviceId: auth.deviceId
2073
+ });
2074
+ return c.json({
2075
+ generation: result.boundary.generation,
2076
+ snapshot_id: result.boundary.snapshot_id,
2077
+ baseline_cursor: result.boundary.baseline_cursor,
2078
+ retained_floor_cursor: result.boundary.retained_floor_cursor,
2079
+ items: result.items,
2080
+ next_page_token: result.nextPageToken,
2081
+ has_more: result.hasMore
2082
+ });
2083
+ } catch (err) {
2084
+ const message = err instanceof Error ? err.message : "";
2085
+ if (message === "generation_mismatch" || message === "boundary_mismatch") return c.json({ error: message }, 409);
2086
+ return c.json({ error: "internal_error" }, 500);
2087
+ }
2088
+ })();
1908
2089
  });
1909
2090
  app.post("/v1/ops", async (c) => {
1910
2091
  const store = getStore();
@@ -1931,13 +2112,9 @@ function syncProtocolRoutes(getStore, opts = {}) {
1931
2112
  reason: "device_id_mismatch",
1932
2113
  op_id: op.op_id
1933
2114
  }, 400);
1934
- let localDeviceId = store.db.prepare("SELECT device_id FROM sync_device LIMIT 1").get();
1935
- if (!localDeviceId) {
1936
- const [deviceId] = ensureDeviceIdentity(store.db);
1937
- localDeviceId = { device_id: deviceId };
1938
- }
2115
+ const [localDeviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
1939
2116
  const filteredInbound = filterOpsForPeer(store, peerDeviceId, normalizedOps);
1940
- const result = applyReplicationOps(store.db, filteredInbound.allowed, localDeviceId.device_id);
2117
+ const result = applyReplicationOps(store.db, filteredInbound.allowed, localDeviceId);
1941
2118
  return c.json({
1942
2119
  ...result,
1943
2120
  skipped: result.skipped + filteredInbound.skipped
@@ -2171,7 +2348,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
2171
2348
  publicKey = String(deviceRow.public_key);
2172
2349
  fingerprint = String(deviceRow.fingerprint);
2173
2350
  } else try {
2174
- const [id, fp] = ensureDeviceIdentity(store.db);
2351
+ const [id, fp] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
2175
2352
  deviceId = id;
2176
2353
  fingerprint = fp;
2177
2354
  publicKey = d.select({ public_key: schema.syncDevice.public_key }).from(schema.syncDevice).where(eq(schema.syncDevice.device_id, id)).get()?.public_key ?? "";
@@ -2552,6 +2729,47 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
2552
2729
  return c.json({ error: message }, message.includes("request_not_found") || message.includes("not found") ? 404 : 400);
2553
2730
  }
2554
2731
  });
2732
+ app.get("/api/sync/bootstrap-grants", async (c) => {
2733
+ const groupId = String(c.req.query("group_id") ?? "").trim();
2734
+ if (!groupId) return c.json({ error: "group_id_required" }, 400);
2735
+ const config = readCoordinatorSyncConfig();
2736
+ try {
2737
+ const items = await coordinatorListBootstrapGrantsAction({
2738
+ groupId,
2739
+ remoteUrl: config.syncCoordinatorUrl || null,
2740
+ adminSecret: config.syncCoordinatorAdminSecret || null
2741
+ });
2742
+ return c.json({ items });
2743
+ } catch (error) {
2744
+ const message = error instanceof Error ? error.message : "unknown";
2745
+ return c.json({ error: message }, 500);
2746
+ }
2747
+ });
2748
+ app.post("/api/sync/bootstrap-grants/revoke", async (c) => {
2749
+ let body;
2750
+ try {
2751
+ body = await c.req.json();
2752
+ } catch {
2753
+ return c.json({ error: "invalid json" }, 400);
2754
+ }
2755
+ const grantId = String(body.grant_id ?? "").trim();
2756
+ if (!grantId) return c.json({ error: "grant_id_required" }, 400);
2757
+ const config = readCoordinatorSyncConfig();
2758
+ try {
2759
+ if (!await coordinatorRevokeBootstrapGrantAction({
2760
+ grantId,
2761
+ remoteUrl: config.syncCoordinatorUrl || null,
2762
+ adminSecret: config.syncCoordinatorAdminSecret || null
2763
+ })) return c.json({ error: "grant_not_found" }, 404);
2764
+ return c.json({
2765
+ ok: true,
2766
+ grant_id: grantId
2767
+ });
2768
+ } catch (error) {
2769
+ const message = error instanceof Error ? error.message : "unknown";
2770
+ return c.json({ error: message }, 500);
2771
+ }
2772
+ });
2555
2773
  app.delete("/api/sync/peers/:peer_device_id", (c) => {
2556
2774
  const store = getStore();
2557
2775
  {