@hraness/oh 0.3.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +164 -114
  2. package/dist/cli.d.ts +1 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +811 -97
  5. package/dist/errors.d.ts +39 -0
  6. package/dist/errors.d.ts.map +1 -0
  7. package/dist/graph.d.ts.map +1 -1
  8. package/dist/index.js +676 -61
  9. package/dist/libsql.d.ts.map +1 -1
  10. package/dist/libsql.js +162 -35
  11. package/dist/memory-page.js +2 -2
  12. package/dist/memory.d.ts +87 -6
  13. package/dist/memory.d.ts.map +1 -1
  14. package/dist/memory.js +1106 -148
  15. package/dist/operation.d.ts +3 -1
  16. package/dist/operation.d.ts.map +1 -1
  17. package/dist/projection-public.js +2 -2
  18. package/dist/projection-suss.js +2 -2
  19. package/dist/sdk.js +780 -88
  20. package/dist/semantic-cloud.js +2 -2
  21. package/dist/semantic.js +2 -2
  22. package/dist/sqlite/index.js +1251 -306
  23. package/dist/sqlite/port.d.ts +31 -3
  24. package/dist/sqlite/port.d.ts.map +1 -1
  25. package/dist/sqlite/store.d.ts +18 -2
  26. package/dist/sqlite/store.d.ts.map +1 -1
  27. package/dist/store.d.ts +3 -12
  28. package/dist/store.d.ts.map +1 -1
  29. package/dist/store.js +154 -32
  30. package/dist/sync.d.ts +7 -1
  31. package/dist/sync.d.ts.map +1 -1
  32. package/dist/sync.js +668 -35
  33. package/package.json +5 -1
  34. package/skills/oh/SKILL.md +42 -16
  35. package/spec/README.md +2 -2
  36. package/spec/v1/memory.md +134 -16
  37. package/spec/v1/storage.md +8 -5
  38. package/spec/v1/store.md +20 -0
  39. package/spec/v1/sync.md +77 -8
  40. package/src/cli.test.ts +53 -1
  41. package/src/cli.ts +34 -8
  42. package/src/errors.test.ts +87 -0
  43. package/src/errors.ts +185 -0
  44. package/src/graph.ts +2 -2
  45. package/src/libsql.test.ts +36 -0
  46. package/src/libsql.ts +26 -5
  47. package/src/memory.test.ts +1488 -18
  48. package/src/memory.ts +1199 -122
  49. package/src/operation.ts +13 -3
  50. package/src/sqlite/port.test.ts +209 -0
  51. package/src/sqlite/port.ts +118 -4
  52. package/src/sqlite/store.test.ts +106 -1
  53. package/src/sqlite/store.ts +168 -30
  54. package/src/store.test.ts +12 -0
  55. package/src/store.ts +30 -20
  56. package/src/sync.test.ts +570 -2
  57. package/src/sync.ts +586 -36
package/dist/sdk.js CHANGED
@@ -1,4 +1,7 @@
1
1
  // @bun
2
+ // src/sync.ts
3
+ import { isProxy } from "util/types";
4
+
2
5
  // src/canonical.ts
3
6
  import { createHash, randomBytes } from "crypto";
4
7
 
@@ -197,7 +200,7 @@ var OH_GRAPH_LIMITS_V1 = Object.freeze({
197
200
  recordBytes: 1024 * 1024,
198
201
  recordsPerSnapshot: 65536
199
202
  });
200
- var OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1 = [
203
+ var OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1 = Object.freeze([
201
204
  "activity",
202
205
  "assertion",
203
206
  "context",
@@ -216,7 +219,7 @@ var OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1 = [
216
219
  "type-membership",
217
220
  "view",
218
221
  "vocabulary"
219
- ];
222
+ ]);
220
223
  var KNOWLEDGE_GRAPH_RECORD_KEYS_V1 = [
221
224
  "dependencies",
222
225
  "key",
@@ -1142,6 +1145,142 @@ class OhRecordCodecRegistry {
1142
1145
  }
1143
1146
  }
1144
1147
 
1148
+ // src/errors.ts
1149
+ var OH_OPERATION_SIZE_ERROR_CODE_V1 = "oh.operation-size.v1";
1150
+ var OH_CONFLICT_ERROR_BRAND_V1 = Symbol.for("@hraness/oh/OhConflictError/v1");
1151
+ var OH_DEPENDENCY_ERROR_BRAND_V1 = Symbol.for("@hraness/oh/OhDependencyError/v1");
1152
+ var OH_INTEGRITY_ERROR_BRAND_V1 = Symbol.for("@hraness/oh/OhIntegrityError/v1");
1153
+ var OH_OPERATION_SIZE_ERROR_BRAND_V1 = Symbol.for("@hraness/oh/OhOperationSizeError/v1");
1154
+ var OH_PROFILE_ERROR_BRAND_V1 = Symbol.for("@hraness/oh/OhProfileError/v1");
1155
+ function immutableOwnValue(value, key) {
1156
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
1157
+ return descriptor !== undefined && descriptor.get === undefined && descriptor.set === undefined && descriptor.configurable === false && descriptor.writable === false ? descriptor.value : undefined;
1158
+ }
1159
+ function brandNativeError(value, brand) {
1160
+ Object.defineProperty(value, brand, {
1161
+ configurable: false,
1162
+ enumerable: false,
1163
+ value: true,
1164
+ writable: false
1165
+ });
1166
+ }
1167
+ function hasNativeErrorBrand(value, brand) {
1168
+ try {
1169
+ return Error.isError(value) && immutableOwnValue(value, brand) === true;
1170
+ } catch {
1171
+ return false;
1172
+ }
1173
+ }
1174
+ function hasNativeSubclassInstance(constructor, value) {
1175
+ return Function.prototype[Symbol.hasInstance].call(constructor, value);
1176
+ }
1177
+ function isOhConflictError(value) {
1178
+ return hasNativeErrorBrand(value, OH_CONFLICT_ERROR_BRAND_V1);
1179
+ }
1180
+
1181
+ class OhConflictError extends Error {
1182
+ static [Symbol.hasInstance](value) {
1183
+ return this === OhConflictError ? isOhConflictError(value) : hasNativeSubclassInstance(this, value);
1184
+ }
1185
+ constructor(message) {
1186
+ super(message);
1187
+ this.name = "OhConflictError";
1188
+ brandNativeError(this, OH_CONFLICT_ERROR_BRAND_V1);
1189
+ }
1190
+ }
1191
+ function isOhIntegrityError(value) {
1192
+ return hasNativeErrorBrand(value, OH_INTEGRITY_ERROR_BRAND_V1);
1193
+ }
1194
+
1195
+ class OhIntegrityError extends Error {
1196
+ static [Symbol.hasInstance](value) {
1197
+ return this === OhIntegrityError ? isOhIntegrityError(value) : hasNativeSubclassInstance(this, value);
1198
+ }
1199
+ constructor(message) {
1200
+ super(message);
1201
+ this.name = "OhIntegrityError";
1202
+ brandNativeError(this, OH_INTEGRITY_ERROR_BRAND_V1);
1203
+ }
1204
+ }
1205
+ function isOhDependencyError(value) {
1206
+ return hasNativeErrorBrand(value, OH_DEPENDENCY_ERROR_BRAND_V1);
1207
+ }
1208
+
1209
+ class OhDependencyError extends Error {
1210
+ static [Symbol.hasInstance](value) {
1211
+ return this === OhDependencyError ? isOhDependencyError(value) : hasNativeSubclassInstance(this, value);
1212
+ }
1213
+ constructor(message) {
1214
+ super(message);
1215
+ this.name = "OhDependencyError";
1216
+ brandNativeError(this, OH_DEPENDENCY_ERROR_BRAND_V1);
1217
+ }
1218
+ }
1219
+ function isOhProfileError(value) {
1220
+ return hasNativeErrorBrand(value, OH_PROFILE_ERROR_BRAND_V1);
1221
+ }
1222
+
1223
+ class OhProfileError extends Error {
1224
+ static [Symbol.hasInstance](value) {
1225
+ return this === OhProfileError ? isOhProfileError(value) : hasNativeSubclassInstance(this, value);
1226
+ }
1227
+ constructor(message) {
1228
+ super(message);
1229
+ this.name = "OhProfileError";
1230
+ brandNativeError(this, OH_PROFILE_ERROR_BRAND_V1);
1231
+ }
1232
+ }
1233
+ function isOhOperationSizeError(value) {
1234
+ try {
1235
+ if (!Error.isError(value) || !(value instanceof RangeError))
1236
+ return false;
1237
+ const operationBytes = immutableOwnValue(value, "operationBytes");
1238
+ const maximumOperationBytes = immutableOwnValue(value, "maximumOperationBytes");
1239
+ return immutableOwnValue(value, OH_OPERATION_SIZE_ERROR_BRAND_V1) === true && immutableOwnValue(value, "code") === OH_OPERATION_SIZE_ERROR_CODE_V1 && Number.isSafeInteger(operationBytes) && operationBytes > 0 && Number.isSafeInteger(maximumOperationBytes) && maximumOperationBytes > 0 && operationBytes > maximumOperationBytes;
1240
+ } catch {
1241
+ return false;
1242
+ }
1243
+ }
1244
+
1245
+ class OhOperationSizeError extends RangeError {
1246
+ static [Symbol.hasInstance](value) {
1247
+ return this === OhOperationSizeError ? isOhOperationSizeError(value) : hasNativeSubclassInstance(this, value);
1248
+ }
1249
+ constructor(operationBytes, maximumOperationBytes) {
1250
+ if (!Number.isSafeInteger(operationBytes) || operationBytes < 1 || !Number.isSafeInteger(maximumOperationBytes) || maximumOperationBytes < 1 || operationBytes <= maximumOperationBytes) {
1251
+ throw new TypeError("Invalid Oh operation size refusal.");
1252
+ }
1253
+ super(`The ${operationBytes}-byte operation exceeds the host-declared ${maximumOperationBytes}-byte canonical bound.`);
1254
+ this.name = "OhOperationSizeError";
1255
+ Object.defineProperties(this, {
1256
+ [OH_OPERATION_SIZE_ERROR_BRAND_V1]: {
1257
+ configurable: false,
1258
+ enumerable: false,
1259
+ value: true,
1260
+ writable: false
1261
+ },
1262
+ code: {
1263
+ configurable: false,
1264
+ enumerable: true,
1265
+ value: OH_OPERATION_SIZE_ERROR_CODE_V1,
1266
+ writable: false
1267
+ },
1268
+ maximumOperationBytes: {
1269
+ configurable: false,
1270
+ enumerable: true,
1271
+ value: maximumOperationBytes,
1272
+ writable: false
1273
+ },
1274
+ operationBytes: {
1275
+ configurable: false,
1276
+ enumerable: true,
1277
+ value: operationBytes,
1278
+ writable: false
1279
+ }
1280
+ });
1281
+ }
1282
+ }
1283
+
1145
1284
  // src/operation.ts
1146
1285
  var OH_OPERATION_MAX_BYTES_V1 = 64 * 1024 * 1024;
1147
1286
  function parsePayload(value) {
@@ -1189,13 +1328,18 @@ function parsePayload(value) {
1189
1328
  v: 1
1190
1329
  } : null;
1191
1330
  }
1192
- function createOhOperationV1(input) {
1331
+ function createOhOperationV1(input, options = {}) {
1332
+ const maximumOperationBytes = options.maximumOperationBytes ?? OH_OPERATION_MAX_BYTES_V1;
1333
+ if (!Number.isSafeInteger(maximumOperationBytes) || maximumOperationBytes < 1 || maximumOperationBytes > OH_OPERATION_MAX_BYTES_V1) {
1334
+ throw new TypeError("Invalid Oh operation byte bound.");
1335
+ }
1193
1336
  const payload = parsePayload(input);
1194
1337
  if (payload === null)
1195
1338
  throw new TypeError("Invalid Oh operation payload.");
1196
1339
  const operation = { ...payload, operationSha256: canonicalSha256(payload) };
1197
- if (Buffer.byteLength(canonicalJson(operation), "utf8") > OH_OPERATION_MAX_BYTES_V1) {
1198
- throw new RangeError("Oh operation exceeds its canonical byte limit.");
1340
+ const operationBytes = Buffer.byteLength(canonicalJson(operation), "utf8");
1341
+ if (operationBytes > maximumOperationBytes) {
1342
+ throw new OhOperationSizeError(operationBytes, maximumOperationBytes);
1199
1343
  }
1200
1344
  return operation;
1201
1345
  }
@@ -1210,18 +1354,362 @@ function parseOhOperationV1(value) {
1210
1354
 
1211
1355
  // src/sync.ts
1212
1356
  var OH_SYNC_PROTOCOL_V1 = "oh.sync.v1";
1213
- function createOhSyncBundleV1(spaceId, operations) {
1214
- const parsedSpaceId = safeCode(spaceId);
1215
- if (parsedSpaceId === null || operations.length > 1000)
1216
- throw new TypeError("Invalid sync bundle.");
1357
+ var OH_SYNC_BUNDLE_MAX_OPERATIONS_V1 = 1000;
1358
+ var OH_SYNC_BUNDLE_KEYS_V1 = [
1359
+ "bundleSha256",
1360
+ "contractSha256",
1361
+ "operations",
1362
+ "protocol",
1363
+ "spaceId",
1364
+ "v"
1365
+ ];
1366
+ var OH_SYNC_HEAD_KEYS_V1 = ["operationSha256", "sequence", "v"];
1367
+ var OH_SYNC_HEAD_REF_KEYS_V1 = ["operationSha256", "sequence"];
1368
+ var OH_OPERATION_KEYS_V1 = [
1369
+ "actorId",
1370
+ "changes",
1371
+ "contractId",
1372
+ "graphRevisionSha256",
1373
+ "instant",
1374
+ "operationId",
1375
+ "operationSha256",
1376
+ "parentOperationSha256",
1377
+ "recordsSha256",
1378
+ "sequence",
1379
+ "spaceId",
1380
+ "v"
1381
+ ];
1382
+ var OH_PUT_CHANGE_KEYS_V1 = ["kind", "record", "v"];
1383
+ var OH_TOMBSTONE_CHANGE_KEYS_V1 = ["key", "kind", "priorSha256", "v"];
1384
+ var OH_RECORD_KEYS_V1 = ["dependencies", "key", "kind", "recordSha256", "v", "value"];
1385
+ var OH_SYNC_INGRESS_VALUE_DEPTH_V1 = 128;
1386
+ var OH_SYNC_INGRESS_VALUE_NODES_V1 = OH_GRAPH_LIMITS_V1.recordBytes;
1387
+ var OH_SYNC_INGRESS_OPERATION_DEPTH_V1 = OH_SYNC_INGRESS_VALUE_DEPTH_V1 + 4;
1388
+ var OH_SYNC_INGRESS_OPERATION_NODES_V1 = OH_OPERATION_MAX_BYTES_V1;
1389
+ var OH_SYNC_BUNDLE_MAX_BYTES_V1 = OH_OPERATION_MAX_BYTES_V1 + 4 * 1024;
1390
+ var OH_SYNC_INGRESS_BUNDLE_NODES_V1 = OH_SYNC_INGRESS_OPERATION_NODES_V1 + 4 * 1024;
1391
+ function exactDataRecordV1(value, expectedKeys) {
1392
+ try {
1393
+ if (typeof value !== "object" || value === null || Array.isArray(value) || isProxy(value))
1394
+ return null;
1395
+ const prototype = Object.getPrototypeOf(value);
1396
+ const keys = Reflect.ownKeys(value);
1397
+ if (prototype !== Object.prototype && prototype !== null || keys.length !== expectedKeys.length || keys.some((key) => typeof key !== "string") || expectedKeys.some((key) => !keys.includes(key)))
1398
+ return null;
1399
+ const detached = Object.create(null);
1400
+ for (const key of expectedKeys) {
1401
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
1402
+ if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined)
1403
+ return null;
1404
+ Object.defineProperty(detached, key, {
1405
+ configurable: false,
1406
+ enumerable: true,
1407
+ value: descriptor.value,
1408
+ writable: false
1409
+ });
1410
+ }
1411
+ return detached;
1412
+ } catch {
1413
+ return null;
1414
+ }
1415
+ }
1416
+ function exactDataArrayV1(value, maximumLength, clone = true) {
1417
+ try {
1418
+ if (typeof value !== "object" || value === null || isProxy(value) || !Array.isArray(value))
1419
+ return null;
1420
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
1421
+ const length = lengthDescriptor?.value;
1422
+ if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0 || length > maximumLength)
1423
+ return null;
1424
+ const keys = Reflect.ownKeys(value);
1425
+ if (keys.length !== length + 1 || !keys.includes("length") || keys.some((key) => key !== "length" && (typeof key !== "string" || !/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= length)))
1426
+ return null;
1427
+ const detached = clone ? [] : null;
1428
+ for (let index = 0;index < length; index += 1) {
1429
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
1430
+ if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined)
1431
+ return null;
1432
+ detached?.push(descriptor.value);
1433
+ }
1434
+ return detached ?? value;
1435
+ } catch {
1436
+ return null;
1437
+ }
1438
+ }
1439
+ function stagedSyncOperationV1(value, bundleBudget) {
1440
+ const operation = exactDataRecordV1(value, OH_OPERATION_KEYS_V1);
1441
+ if (operation === null || operation.v !== 1 || operation.contractId !== OH_CONTRACT_ID_V1 || safeCode(operation.actorId) === null || safeCode(operation.operationId) === null || safeCode(operation.spaceId) === null || parseCanonicalInstantV1(operation.instant) === null || parseSha256Hex(operation.operationSha256) === null || parseSha256Hex(operation.graphRevisionSha256) === null || parseSha256Hex(operation.recordsSha256) === null)
1442
+ return null;
1443
+ const parentOperationSha256 = operation.parentOperationSha256 === null ? null : parseSha256Hex(operation.parentOperationSha256);
1444
+ const sequence = Number.isSafeInteger(operation.sequence) && operation.sequence > 0 ? operation.sequence : null;
1445
+ if (sequence === null || operation.parentOperationSha256 !== null && parentOperationSha256 === null || sequence === 1 !== (parentOperationSha256 === null))
1446
+ return null;
1447
+ const changes = exactDataArrayV1(operation.changes, OH_GRAPH_LIMITS_V1.changesPerOperation, false);
1448
+ if (changes === null || changes.length === 0)
1449
+ return null;
1450
+ const aggregateDataBudget = {
1451
+ bytes: 0,
1452
+ maximumBytes: OH_OPERATION_MAX_BYTES_V1,
1453
+ maximumNodes: OH_SYNC_INGRESS_OPERATION_NODES_V1,
1454
+ nodes: 0
1455
+ };
1456
+ for (const change of changes) {
1457
+ const put = exactDataRecordV1(change, OH_PUT_CHANGE_KEYS_V1);
1458
+ if (put !== null && put.kind === "put" && put.v === 1) {
1459
+ const record = exactDataRecordV1(put.record, OH_RECORD_KEYS_V1);
1460
+ if (record === null)
1461
+ return null;
1462
+ const valuePreflight = preflightSyncIngressValueV1(record.value, aggregateDataBudget);
1463
+ const dependenciesPreflight = preflightSyncIngressDependenciesV1(record.dependencies, aggregateDataBudget);
1464
+ if (valuePreflight === null || dependenciesPreflight === null)
1465
+ return null;
1466
+ continue;
1467
+ }
1468
+ const tombstone = exactDataRecordV1(change, OH_TOMBSTONE_CHANGE_KEYS_V1);
1469
+ if (tombstone === null || tombstone.kind !== "tombstone" || tombstone.v !== 1)
1470
+ return null;
1471
+ }
1472
+ const detached = boundedSyncIngressV1(operation, {
1473
+ maximumBytes: OH_OPERATION_MAX_BYTES_V1,
1474
+ maximumDepth: OH_SYNC_INGRESS_OPERATION_DEPTH_V1,
1475
+ maximumNodes: OH_SYNC_INGRESS_OPERATION_NODES_V1
1476
+ }, bundleBudget, true);
1477
+ return detached === null ? null : detached.value;
1478
+ }
1479
+ function canonicalStringBytesV1(value, maximumBytes) {
1480
+ if (value.length + 2 > maximumBytes)
1481
+ throw new RangeError("String exceeds its canonical byte budget.");
1482
+ let bytes = 2;
1483
+ for (let index = 0;index < value.length; index += 1) {
1484
+ const code = value.charCodeAt(index);
1485
+ if (code >= 55296 && code <= 56319) {
1486
+ const next = value.charCodeAt(index + 1);
1487
+ if (!(next >= 56320 && next <= 57343))
1488
+ throw new TypeError("Invalid Unicode string.");
1489
+ bytes += 4;
1490
+ index += 1;
1491
+ } else if (code >= 56320 && code <= 57343) {
1492
+ throw new TypeError("Invalid Unicode string.");
1493
+ } else if (code === 34 || code === 92 || code === 8 || code === 9 || code === 10 || code === 12 || code === 13) {
1494
+ bytes += 2;
1495
+ } else if (code <= 31) {
1496
+ bytes += 6;
1497
+ } else if (code <= 127) {
1498
+ bytes += 1;
1499
+ } else if (code <= 2047) {
1500
+ bytes += 2;
1501
+ } else {
1502
+ bytes += 3;
1503
+ }
1504
+ if (bytes > maximumBytes)
1505
+ throw new RangeError("String exceeds its canonical byte budget.");
1506
+ }
1507
+ return bytes;
1508
+ }
1509
+ function boundedSyncIngressV1(value, limits, aggregate, clone) {
1510
+ const ancestors = new Set;
1511
+ const budget = {
1512
+ bytes: 0,
1513
+ maximumBytes: limits.maximumBytes,
1514
+ maximumNodes: limits.maximumNodes,
1515
+ nodes: 0
1516
+ };
1517
+ const canSpendBytes = (count) => budget.bytes + count <= budget.maximumBytes && (aggregate === null || aggregate.bytes + count <= aggregate.maximumBytes);
1518
+ const spendBytes = (count) => {
1519
+ if (!canSpendBytes(count))
1520
+ throw new RangeError("Sync ingress exceeds its canonical byte budget.");
1521
+ budget.bytes += count;
1522
+ if (aggregate !== null)
1523
+ aggregate.bytes += count;
1524
+ };
1525
+ const canSpendNodes = (count) => budget.nodes + count <= budget.maximumNodes && (aggregate === null || aggregate.nodes + count <= aggregate.maximumNodes);
1526
+ const spendNode = () => {
1527
+ if (!canSpendNodes(1))
1528
+ throw new RangeError("Sync ingress exceeds its node budget.");
1529
+ budget.nodes += 1;
1530
+ if (aggregate !== null)
1531
+ aggregate.nodes += 1;
1532
+ };
1533
+ const detach = (candidate, depth) => {
1534
+ if (depth > limits.maximumDepth)
1535
+ throw new RangeError("Sync ingress exceeds its depth budget.");
1536
+ spendNode();
1537
+ if (candidate === null) {
1538
+ spendBytes(4);
1539
+ return candidate;
1540
+ }
1541
+ if (typeof candidate === "boolean") {
1542
+ spendBytes(candidate ? 4 : 5);
1543
+ return candidate;
1544
+ }
1545
+ if (typeof candidate === "string") {
1546
+ spendBytes(canonicalStringBytesV1(candidate, Math.min(budget.maximumBytes - budget.bytes, aggregate === null ? Number.MAX_SAFE_INTEGER : aggregate.maximumBytes - aggregate.bytes)));
1547
+ return candidate;
1548
+ }
1549
+ if (typeof candidate === "number") {
1550
+ if (!Number.isFinite(candidate) || Object.is(candidate, -0))
1551
+ throw new TypeError("Invalid number.");
1552
+ spendBytes(utf8ByteLength(canonicalJson(candidate)));
1553
+ return candidate;
1554
+ }
1555
+ if (typeof candidate !== "object" || isProxy(candidate))
1556
+ throw new TypeError("Invalid data value.");
1557
+ if (ancestors.has(candidate))
1558
+ throw new TypeError("Cyclic data value.");
1559
+ ancestors.add(candidate);
1560
+ try {
1561
+ if (Array.isArray(candidate)) {
1562
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(candidate, "length");
1563
+ const length = lengthDescriptor?.value;
1564
+ if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0 || !canSpendNodes(length) || !canSpendBytes(2 + Math.max(0, length - 1) + length)) {
1565
+ throw new TypeError("Invalid or over-budget data array.");
1566
+ }
1567
+ const keys2 = Reflect.ownKeys(candidate);
1568
+ if (keys2.length !== length + 1 || !keys2.includes("length") || keys2.some((key) => key !== "length" && (typeof key !== "string" || !/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= length))) {
1569
+ throw new TypeError("Invalid data array.");
1570
+ }
1571
+ spendBytes(2 + Math.max(0, length - 1));
1572
+ const detached2 = clone ? [] : null;
1573
+ for (let index = 0;index < length; index += 1) {
1574
+ const descriptor = Object.getOwnPropertyDescriptor(candidate, String(index));
1575
+ if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined) {
1576
+ throw new TypeError("Invalid data array entry.");
1577
+ }
1578
+ const item = detach(descriptor.value, depth + 1);
1579
+ detached2?.push(item);
1580
+ }
1581
+ return detached2 === null ? candidate : Object.freeze(detached2);
1582
+ }
1583
+ const prototype = Object.getPrototypeOf(candidate);
1584
+ if (prototype !== Object.prototype && prototype !== null) {
1585
+ throw new TypeError("Invalid data object.");
1586
+ }
1587
+ let containerBytes = 2;
1588
+ let properties = 0;
1589
+ for (const key in candidate) {
1590
+ if (!Object.hasOwn(candidate, key))
1591
+ continue;
1592
+ properties += 1;
1593
+ if (!canSpendNodes(properties))
1594
+ throw new RangeError("Sync ingress exceeds its node budget.");
1595
+ const remainingBytes = Math.min(budget.maximumBytes - budget.bytes - containerBytes - properties, aggregate === null ? Number.MAX_SAFE_INTEGER : aggregate.maximumBytes - aggregate.bytes - containerBytes - properties);
1596
+ containerBytes += (properties === 1 ? 0 : 1) + canonicalStringBytesV1(key, remainingBytes) + 1;
1597
+ if (!canSpendBytes(containerBytes + properties)) {
1598
+ throw new RangeError("Sync ingress exceeds its canonical byte budget.");
1599
+ }
1600
+ }
1601
+ const keys = Reflect.ownKeys(candidate);
1602
+ if (keys.length !== properties || keys.some((key) => typeof key !== "string")) {
1603
+ throw new TypeError("Invalid data object.");
1604
+ }
1605
+ spendBytes(containerBytes);
1606
+ const detached = clone ? Object.create(null) : null;
1607
+ for (const key of keys) {
1608
+ const descriptor = Object.getOwnPropertyDescriptor(candidate, key);
1609
+ if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined) {
1610
+ throw new TypeError("Invalid data property.");
1611
+ }
1612
+ const item = detach(descriptor.value, depth + 1);
1613
+ if (detached !== null) {
1614
+ Object.defineProperty(detached, key, {
1615
+ configurable: false,
1616
+ enumerable: true,
1617
+ value: item,
1618
+ writable: false
1619
+ });
1620
+ }
1621
+ }
1622
+ return detached === null ? candidate : Object.freeze(detached);
1623
+ } finally {
1624
+ ancestors.delete(candidate);
1625
+ }
1626
+ };
1627
+ try {
1628
+ return Object.freeze({ value: detach(value, 0) });
1629
+ } catch {
1630
+ return null;
1631
+ }
1632
+ }
1633
+ function preflightSyncIngressValueV1(value, aggregate) {
1634
+ return boundedSyncIngressV1(value, {
1635
+ maximumBytes: OH_GRAPH_LIMITS_V1.recordBytes,
1636
+ maximumDepth: OH_SYNC_INGRESS_VALUE_DEPTH_V1,
1637
+ maximumNodes: OH_SYNC_INGRESS_VALUE_NODES_V1
1638
+ }, aggregate, false);
1639
+ }
1640
+ function preflightSyncIngressDependenciesV1(value, aggregate) {
1641
+ try {
1642
+ if (typeof value !== "object" || value === null || isProxy(value) || !Array.isArray(value)) {
1643
+ return null;
1644
+ }
1645
+ const length = Object.getOwnPropertyDescriptor(value, "length")?.value;
1646
+ if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0 || length > OH_GRAPH_LIMITS_V1.dependenciesPerRecord)
1647
+ return null;
1648
+ const maximumBytes = length === 0 ? 2 : 1 + length * 515;
1649
+ return boundedSyncIngressV1(value, {
1650
+ maximumBytes,
1651
+ maximumDepth: 1,
1652
+ maximumNodes: length + 1
1653
+ }, aggregate, false);
1654
+ } catch {
1655
+ return null;
1656
+ }
1657
+ }
1658
+ function syncIngressBundleBudgetV1(spaceId) {
1659
+ const emptyBundle = {
1660
+ bundleSha256: "0".repeat(64),
1661
+ contractSha256: OH_CONTRACT_MANIFEST_V1.contractSha256,
1662
+ operations: [],
1663
+ protocol: OH_SYNC_PROTOCOL_V1,
1664
+ spaceId,
1665
+ v: 1
1666
+ };
1667
+ return {
1668
+ bytes: utf8ByteLength(canonicalJson(emptyBundle)),
1669
+ maximumBytes: OH_SYNC_BUNDLE_MAX_BYTES_V1,
1670
+ maximumNodes: OH_SYNC_INGRESS_BUNDLE_NODES_V1,
1671
+ nodes: 7
1672
+ };
1673
+ }
1674
+ function spendSyncIngressBudgetV1(budget, bytes, nodes = 0) {
1675
+ if (budget.bytes + bytes > budget.maximumBytes || budget.nodes + nodes > budget.maximumNodes)
1676
+ return false;
1677
+ budget.bytes += bytes;
1678
+ budget.nodes += nodes;
1679
+ return true;
1680
+ }
1681
+ function measureSyncOperationV1(operation) {
1682
+ const measurement = {
1683
+ bytes: 0,
1684
+ maximumBytes: OH_OPERATION_MAX_BYTES_V1,
1685
+ maximumNodes: OH_SYNC_INGRESS_OPERATION_NODES_V1,
1686
+ nodes: 0
1687
+ };
1688
+ return boundedSyncIngressV1(operation, {
1689
+ maximumBytes: OH_OPERATION_MAX_BYTES_V1,
1690
+ maximumDepth: OH_SYNC_INGRESS_OPERATION_DEPTH_V1,
1691
+ maximumNodes: OH_SYNC_INGRESS_OPERATION_NODES_V1
1692
+ }, measurement, false) === null ? null : measurement;
1693
+ }
1694
+ function buildOhSyncBundleV1(parsedSpaceId, operations, largestFittingPrefix) {
1217
1695
  let priorSequence = null;
1218
1696
  let priorSha256 = null;
1219
1697
  const parsed = [];
1698
+ const bundleBudget = syncIngressBundleBudgetV1(parsedSpaceId);
1220
1699
  for (const candidate of operations) {
1221
1700
  const operation = parseOhOperationV1(candidate);
1222
1701
  if (operation === null || operation.spaceId !== parsedSpaceId || priorSequence !== null && operation.sequence !== priorSequence + 1 || priorSequence !== null && operation.parentOperationSha256 !== priorSha256) {
1223
1702
  throw new TypeError("Sync operations must form one ordered chain.");
1224
1703
  }
1704
+ const measurement = measureSyncOperationV1(operation);
1705
+ if (measurement === null) {
1706
+ throw new RangeError("Sync operation exceeds its canonical byte, node, or depth limit.");
1707
+ }
1708
+ if (!spendSyncIngressBudgetV1(bundleBudget, measurement.bytes + (parsed.length === 0 ? 0 : 1), measurement.nodes)) {
1709
+ if (largestFittingPrefix && parsed.length > 0)
1710
+ break;
1711
+ throw new RangeError("Sync bundle exceeds its canonical byte or node limit.");
1712
+ }
1225
1713
  parsed.push(operation);
1226
1714
  priorSequence = operation.sequence;
1227
1715
  priorSha256 = operation.operationSha256;
@@ -1235,15 +1723,56 @@ function createOhSyncBundleV1(spaceId, operations) {
1235
1723
  };
1236
1724
  return { ...payload, bundleSha256: canonicalSha256(payload) };
1237
1725
  }
1726
+ function parseSyncHeadRefEnvelopeV1(value) {
1727
+ const operationSha256 = value.operationSha256 === null ? null : parseSha256Hex(value.operationSha256);
1728
+ const sequence = Number.isSafeInteger(value.sequence) && value.sequence >= 0 && !Object.is(value.sequence, -0) ? value.sequence : null;
1729
+ return sequence !== null && (value.operationSha256 === null || operationSha256 !== null) && sequence === 0 === (operationSha256 === null) ? { operationSha256, sequence } : null;
1730
+ }
1731
+ function parseOhSyncHeadRefV1(value) {
1732
+ const reference = exactDataRecordV1(value, OH_SYNC_HEAD_REF_KEYS_V1);
1733
+ return reference === null ? null : parseSyncHeadRefEnvelopeV1(reference);
1734
+ }
1735
+ function parseOhSyncHeadV1(value) {
1736
+ const envelope = exactDataRecordV1(value, OH_SYNC_HEAD_KEYS_V1);
1737
+ if (envelope === null || envelope.v !== 1)
1738
+ return null;
1739
+ const reference = parseSyncHeadRefEnvelopeV1(envelope);
1740
+ return reference === null ? null : { ...reference, v: 1 };
1741
+ }
1742
+ function createOhSyncBundleV1(spaceId, operations, options = {}) {
1743
+ const parsedSpaceId = safeCode(spaceId);
1744
+ const largestFittingPrefix = options.largestFittingPrefix ?? false;
1745
+ if (parsedSpaceId === null || operations.length > OH_SYNC_BUNDLE_MAX_OPERATIONS_V1 || typeof largestFittingPrefix !== "boolean") {
1746
+ throw new TypeError("Invalid sync bundle.");
1747
+ }
1748
+ return buildOhSyncBundleV1(parsedSpaceId, operations, largestFittingPrefix);
1749
+ }
1238
1750
  function parseOhSyncBundleV1(value) {
1239
- if (!isPlainRecord(value) || !hasExactKeys(value, ["bundleSha256", "contractSha256", "operations", "protocol", "spaceId", "v"]) || value.protocol !== OH_SYNC_PROTOCOL_V1 || value.v !== 1 || !Array.isArray(value.operations))
1751
+ const envelope = exactDataRecordV1(value, OH_SYNC_BUNDLE_KEYS_V1);
1752
+ if (envelope === null || envelope.protocol !== OH_SYNC_PROTOCOL_V1 || envelope.v !== 1)
1753
+ return null;
1754
+ const bundleSha256 = parseSha256Hex(envelope.bundleSha256);
1755
+ const contractSha256 = parseSha256Hex(envelope.contractSha256);
1756
+ const spaceId = safeCode(envelope.spaceId);
1757
+ if (bundleSha256 === null || contractSha256 !== OH_CONTRACT_MANIFEST_V1.contractSha256 || spaceId === null)
1240
1758
  return null;
1241
- const bundleSha256 = parseSha256Hex(value.bundleSha256);
1242
- const contractSha256 = parseSha256Hex(value.contractSha256);
1243
- if (bundleSha256 === null || contractSha256 !== OH_CONTRACT_MANIFEST_V1.contractSha256)
1759
+ const operations = exactDataArrayV1(envelope.operations, OH_SYNC_BUNDLE_MAX_OPERATIONS_V1);
1760
+ if (operations === null)
1244
1761
  return null;
1762
+ const detachedOperations = [];
1763
+ const bundleBudget = syncIngressBundleBudgetV1(spaceId);
1764
+ for (const operation of operations) {
1765
+ if (detachedOperations.length > 0 && !spendSyncIngressBudgetV1(bundleBudget, 1))
1766
+ return null;
1767
+ const detached = stagedSyncOperationV1(operation, bundleBudget);
1768
+ if (detached === null)
1769
+ return null;
1770
+ detachedOperations.push(detached);
1771
+ }
1245
1772
  try {
1246
- const created = createOhSyncBundleV1(value.spaceId, value.operations);
1773
+ const created = createOhSyncBundleV1(spaceId, detachedOperations);
1774
+ if (detachedOperations.some((operation, index) => canonicalJson(operation) !== canonicalJson(created.operations[index])))
1775
+ return null;
1247
1776
  return created.bundleSha256 === bundleSha256 ? { ...created, bundleSha256 } : null;
1248
1777
  } catch {
1249
1778
  return null;
@@ -1258,39 +1787,84 @@ async function synchronizeOhStoreV1(store, transport, options = {}) {
1258
1787
  await transport.handshake(OH_CONTRACT_MANIFEST_V1);
1259
1788
  let pulled = 0;
1260
1789
  let pushed = 0;
1790
+ const settled = (head, rounds) => {
1791
+ store.updateSyncState(remoteId, {
1792
+ pulledSequence: head.sequence,
1793
+ pushedSequence: head.sequence,
1794
+ remoteHeadSha256: head.operationSha256
1795
+ });
1796
+ return { head, pulled, pushed, rounds, v: 1 };
1797
+ };
1261
1798
  for (let round = 1;round <= maximumRounds; round += 1) {
1799
+ const remote = parseOhSyncHeadV1(await transport.head(store.spaceId));
1800
+ if (remote === null)
1801
+ throw new Error("The sync transport returned an invalid head.");
1262
1802
  const local = store.head();
1263
- const remote = await transport.head(store.spaceId);
1264
1803
  if (local.sequence === remote.sequence) {
1265
1804
  if (local.operationSha256 !== remote.operationSha256) {
1266
1805
  throw new Error("Sync conflict: equal sequence numbers have different heads.");
1267
1806
  }
1268
- store.updateSyncState(remoteId, {
1269
- pulledSequence: local.sequence,
1270
- pushedSequence: local.sequence,
1271
- remoteHeadSha256: remote.operationSha256
1272
- });
1273
- return { head: remote, pulled, pushed, rounds: round, v: 1 };
1807
+ return settled(remote, round);
1274
1808
  }
1275
1809
  if (local.sequence < remote.sequence) {
1276
1810
  const bundle = parseOhSyncBundleV1(await transport.pull(store.spaceId, local.sequence, batchSize));
1277
- if (bundle === null || bundle.operations.length === 0 || bundle.operations[0]?.sequence !== local.sequence + 1 || bundle.operations[0]?.parentOperationSha256 !== local.operationSha256) {
1811
+ const terminal = bundle?.operations.at(-1);
1812
+ if (bundle === null || bundle.operations.length === 0 || bundle.operations.length > batchSize || bundle.spaceId !== store.spaceId || bundle.operations[0]?.sequence !== local.sequence + 1 || bundle.operations[0]?.parentOperationSha256 !== local.operationSha256 || terminal === undefined || terminal.sequence > remote.sequence || terminal.sequence === remote.sequence && terminal.operationSha256 !== remote.operationSha256) {
1278
1813
  throw new Error("Sync conflict: remote history does not extend the local head.");
1279
1814
  }
1280
- for (const operation of bundle.operations) {
1281
- store.importOperation(operation);
1282
- pulled += 1;
1815
+ store.importOperations({
1816
+ expectedHead: {
1817
+ operationSha256: local.operationSha256,
1818
+ sequence: local.sequence
1819
+ },
1820
+ operations: bundle.operations
1821
+ });
1822
+ pulled += bundle.operations.length;
1823
+ const afterPull = store.head();
1824
+ if (afterPull.sequence === remote.sequence && afterPull.operationSha256 === remote.operationSha256) {
1825
+ const confirmed = parseOhSyncHeadV1(await transport.head(store.spaceId));
1826
+ if (confirmed === null)
1827
+ throw new Error("The sync transport returned an invalid head.");
1828
+ const confirmedLocal = store.head();
1829
+ if (confirmed.sequence === remote.sequence && confirmed.operationSha256 === remote.operationSha256 && confirmedLocal.sequence === confirmed.sequence && confirmedLocal.operationSha256 === confirmed.operationSha256) {
1830
+ return settled(confirmed, round);
1831
+ }
1283
1832
  }
1284
1833
  } else {
1285
- const operations = store.exportOperations(remote.sequence, batchSize);
1286
- if (operations.length === 0 || operations[0]?.sequence !== remote.sequence + 1 || operations[0]?.parentOperationSha256 !== remote.operationSha256) {
1834
+ const candidates = store.changesSince({
1835
+ operationSha256: remote.operationSha256,
1836
+ sequence: remote.sequence
1837
+ }, {
1838
+ limit: batchSize,
1839
+ through: {
1840
+ operationSha256: local.operationSha256,
1841
+ sequence: local.sequence
1842
+ }
1843
+ }).operations;
1844
+ if (candidates.length === 0 || candidates[0]?.sequence !== remote.sequence + 1 || candidates[0]?.parentOperationSha256 !== remote.operationSha256) {
1287
1845
  throw new Error("Sync conflict: local history does not extend the remote head.");
1288
1846
  }
1289
- const head = await transport.push(createOhSyncBundleV1(store.spaceId, operations));
1847
+ const bundle = createOhSyncBundleV1(store.spaceId, candidates, {
1848
+ largestFittingPrefix: true
1849
+ });
1850
+ const operations = bundle.operations;
1851
+ const head = parseOhSyncHeadV1(await transport.push(bundle));
1852
+ if (head === null)
1853
+ throw new Error("The sync transport returned an invalid push head.");
1290
1854
  if (head.sequence !== operations.at(-1)?.sequence || head.operationSha256 !== operations.at(-1)?.operationSha256) {
1291
1855
  throw new Error("The sync transport acknowledged a different head.");
1292
1856
  }
1293
1857
  pushed += operations.length;
1858
+ const afterPush = store.head();
1859
+ if (afterPush.sequence === head.sequence && afterPush.operationSha256 === head.operationSha256) {
1860
+ const confirmed = parseOhSyncHeadV1(await transport.head(store.spaceId));
1861
+ if (confirmed === null)
1862
+ throw new Error("The sync transport returned an invalid head.");
1863
+ const confirmedLocal = store.head();
1864
+ if (confirmed.sequence === head.sequence && confirmed.operationSha256 === head.operationSha256 && confirmedLocal.sequence === confirmed.sequence && confirmedLocal.operationSha256 === confirmed.operationSha256) {
1865
+ return settled(confirmed, round);
1866
+ }
1867
+ }
1294
1868
  }
1295
1869
  }
1296
1870
  throw new Error("Sync did not settle within maximumRounds.");
@@ -1354,19 +1928,48 @@ function createLibSqlOperationSyncTransportV1(client) {
1354
1928
  },
1355
1929
  head,
1356
1930
  pull: async (spaceId, afterSequence, limit) => {
1931
+ const parsedSpaceId = safeCode(spaceId);
1932
+ if (parsedSpaceId === null || !Number.isSafeInteger(afterSequence) || afterSequence < 0 || Object.is(afterSequence, -0) || !Number.isSafeInteger(limit) || limit < 1 || limit > OH_SYNC_BUNDLE_MAX_OPERATIONS_V1) {
1933
+ throw new TypeError("Invalid sync pull request.");
1934
+ }
1357
1935
  await ensure();
1358
- const result = await client.execute({ sql: `SELECT operation_json FROM oh_sync_operations
1359
- WHERE space_id = ? AND sequence > ? ORDER BY sequence LIMIT ?`, args: [spaceId, afterSequence, limit] });
1360
- const operations = result.rows.map((row) => {
1936
+ const bundleBudget = syncIngressBundleBudgetV1(parsedSpaceId);
1937
+ const result = await client.execute({
1938
+ sql: `SELECT operation_json FROM (
1939
+ SELECT sequence, operation_json,
1940
+ row_number() OVER (ORDER BY sequence) AS ordinal,
1941
+ sum(length(CAST(operation_json AS BLOB))) OVER (
1942
+ ORDER BY sequence ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
1943
+ ) AS cumulative_bytes
1944
+ FROM (
1945
+ SELECT sequence, operation_json FROM oh_sync_operations
1946
+ WHERE space_id = ? AND sequence > ? ORDER BY sequence LIMIT ?
1947
+ )
1948
+ ) WHERE ordinal = 1 OR cumulative_bytes + ordinal - 1 <= ? ORDER BY sequence`,
1949
+ args: [
1950
+ parsedSpaceId,
1951
+ afterSequence,
1952
+ limit,
1953
+ bundleBudget.maximumBytes - bundleBudget.bytes
1954
+ ]
1955
+ });
1956
+ const rows = result.rows;
1957
+ if (!Array.isArray(rows) || rows.length > limit) {
1958
+ throw new Error("Remote sync pull exceeded its requested row limit.");
1959
+ }
1960
+ const operations = rows.map((row) => {
1361
1961
  const json = rowValue(row, "operation_json", 0);
1362
1962
  if (typeof json !== "string")
1363
1963
  throw new Error("Invalid remote operation JSON.");
1964
+ if (utf8ByteLength(json) > OH_OPERATION_MAX_BYTES_V1) {
1965
+ throw new Error("Remote operation JSON exceeds the canonical operation byte limit.");
1966
+ }
1364
1967
  const operation = parseOhOperationV1(JSON.parse(json));
1365
1968
  if (operation === null || canonicalJson(operation) !== json)
1366
1969
  throw new Error("Invalid remote operation.");
1367
1970
  return operation;
1368
1971
  });
1369
- return createOhSyncBundleV1(spaceId, operations);
1972
+ return createOhSyncBundleV1(parsedSpaceId, operations);
1370
1973
  },
1371
1974
  push: async (value) => {
1372
1975
  await ensure();
@@ -1378,8 +1981,35 @@ function createLibSqlOperationSyncTransportV1(client) {
1378
1981
  const remote = await head(bundle.spaceId);
1379
1982
  const first = bundle.operations[0];
1380
1983
  const last = bundle.operations.at(-1);
1381
- if (remote.sequence === last.sequence && remote.operationSha256 === last.operationSha256)
1382
- return remote;
1984
+ if (remote.sequence >= last.sequence) {
1985
+ const result = await client.execute({ sql: `SELECT sequence, operation_sha256, operation_json
1986
+ FROM oh_sync_operations WHERE space_id = ? AND sequence >= ? AND sequence <= ?
1987
+ ORDER BY sequence LIMIT ?`, args: [
1988
+ bundle.spaceId,
1989
+ first.sequence,
1990
+ last.sequence,
1991
+ bundle.operations.length
1992
+ ] });
1993
+ const rows = result.rows;
1994
+ if (!Array.isArray(rows) || rows.length !== bundle.operations.length) {
1995
+ throw new Error("Sync conflict: remote history does not contain the exact pushed operations.");
1996
+ }
1997
+ for (let index = 0;index < bundle.operations.length; index += 1) {
1998
+ const operation = bundle.operations[index];
1999
+ const row = rows[index];
2000
+ if (row === undefined) {
2001
+ throw new Error("Sync conflict: remote history does not contain the exact pushed operations.");
2002
+ }
2003
+ const sequence = Number(rowValue(row, "sequence", 0));
2004
+ const operationSha256 = parseSha256Hex(rowValue(row, "operation_sha256", 1));
2005
+ const operationJson = rowValue(row, "operation_json", 2);
2006
+ const expectedJson = canonicalJson(operation);
2007
+ if (sequence !== operation.sequence || operationSha256 !== operation.operationSha256 || typeof operationJson !== "string" || utf8ByteLength(operationJson) > OH_OPERATION_MAX_BYTES_V1 || operationJson !== expectedJson) {
2008
+ throw new Error("Sync conflict: remote history differs from the pushed operations.");
2009
+ }
2010
+ }
2011
+ return { operationSha256: last.operationSha256, sequence: last.sequence, v: 1 };
2012
+ }
1383
2013
  if (first.sequence !== remote.sequence + 1 || first.parentOperationSha256 !== remote.operationSha256) {
1384
2014
  throw new Error("Sync conflict: pushed history does not extend the remote head.");
1385
2015
  }
@@ -1441,35 +2071,7 @@ async function searchOhV1(input) {
1441
2071
  // src/sqlite/store.ts
1442
2072
  import { mkdirSync } from "fs";
1443
2073
  import { dirname } from "path";
1444
-
1445
2074
  // src/store.ts
1446
- class OhConflictError extends Error {
1447
- constructor(message) {
1448
- super(message);
1449
- this.name = "OhConflictError";
1450
- }
1451
- }
1452
-
1453
- class OhIntegrityError extends Error {
1454
- constructor(message) {
1455
- super(message);
1456
- this.name = "OhIntegrityError";
1457
- }
1458
- }
1459
-
1460
- class OhDependencyError extends Error {
1461
- constructor(message) {
1462
- super(message);
1463
- this.name = "OhDependencyError";
1464
- }
1465
- }
1466
-
1467
- class OhProfileError extends Error {
1468
- constructor(message) {
1469
- super(message);
1470
- this.name = "OhProfileError";
1471
- }
1472
- }
1473
2075
  var OH_CANONICAL_STORE_PROFILE_V1 = createOhStoreProfileV1({
1474
2076
  applicationProfileSha256: null,
1475
2077
  capabilities: {
@@ -1782,6 +2384,8 @@ function transitionOhSnapshotV1(input) {
1782
2384
  sequence: head.sequence + 1,
1783
2385
  spaceId,
1784
2386
  v: 1
2387
+ }, input.maximumOperationBytes === undefined ? {} : {
2388
+ maximumOperationBytes: input.maximumOperationBytes
1785
2389
  });
1786
2390
  const nextHead = {
1787
2391
  generation: operation.sequence,
@@ -2357,6 +2961,9 @@ function normalizeLimit(value, fallback = 50, maximum = 1000) {
2357
2961
  }
2358
2962
  return value;
2359
2963
  }
2964
+ function exactHeadRef(left, right) {
2965
+ return left.sequence === right.sequence && left.operationSha256 === right.operationSha256;
2966
+ }
2360
2967
  function ftsQuery(value) {
2361
2968
  const normalized = boundedText(value.normalize("NFC"), 4096);
2362
2969
  if (normalized === null)
@@ -2473,8 +3080,7 @@ class OhSqliteStore {
2473
3080
  }
2474
3081
  return records;
2475
3082
  }
2476
- #transition(head, changes, operationId) {
2477
- const records = this.#loadRecords();
3083
+ #transition(head, changes, operationId, records = this.#loadRecords()) {
2478
3084
  for (const change of changes) {
2479
3085
  if (change.kind === "put") {
2480
3086
  records.set(change.record.key, change.record);
@@ -2531,7 +3137,7 @@ class OhSqliteStore {
2531
3137
  if (operation.sequence < 1 || operation.sequence > head.sequence) {
2532
3138
  throw new OhIntegrityError("A stored idempotent operation is not reachable from the current head.");
2533
3139
  }
2534
- const rows = this.database.query(`SELECT operation_sha256, parent_operation_sha256, sequence
3140
+ const rows = this.database.query(`SELECT ${OPERATION_COLUMNS}
2535
3141
  FROM oh_operations WHERE space_id = ? AND sequence >= ? AND sequence <= ? ORDER BY sequence`).all(this.spaceId, operation.sequence, head.sequence);
2536
3142
  if (rows.length !== head.sequence - operation.sequence + 1) {
2537
3143
  throw new OhIntegrityError("A stored idempotent operation has an incomplete path to the current head.");
@@ -2539,10 +3145,14 @@ class OhSqliteStore {
2539
3145
  let priorSha256 = operation.parentOperationSha256;
2540
3146
  for (let index = 0;index < rows.length; index += 1) {
2541
3147
  const row = rows[index];
2542
- if (row === undefined || row.sequence !== operation.sequence + index || row.parent_operation_sha256 !== priorSha256 || index === 0 && row.operation_sha256 !== operation.operationSha256) {
3148
+ if (row === undefined) {
3149
+ throw new OhIntegrityError("A stored idempotent operation is not on the current authority chain.");
3150
+ }
3151
+ const reachable = parseStoredOperationRow(row, { spaceId: this.spaceId });
3152
+ if (reachable.sequence !== operation.sequence + index || reachable.parentOperationSha256 !== priorSha256 || index === 0 && reachable.operationSha256 !== operation.operationSha256) {
2543
3153
  throw new OhIntegrityError("A stored idempotent operation is not on the current authority chain.");
2544
3154
  }
2545
- priorSha256 = row.operation_sha256;
3155
+ priorSha256 = reachable.operationSha256;
2546
3156
  }
2547
3157
  if (priorSha256 !== head.operationSha256) {
2548
3158
  throw new OhIntegrityError("A stored idempotent operation does not reach the current head digest.");
@@ -2603,8 +3213,10 @@ class OhSqliteStore {
2603
3213
  this.#assertOpen();
2604
3214
  const actorId = safeCode(input.actorId);
2605
3215
  const operationId = safeCode(input.operationId);
2606
- if (actorId === null || operationId === null)
2607
- throw new TypeError("Invalid actor or operation ID.");
3216
+ const maximumOperationBytes = input.maximumOperationBytes ?? OH_OPERATION_MAX_BYTES_V1;
3217
+ if (actorId === null || operationId === null || !Number.isSafeInteger(maximumOperationBytes) || maximumOperationBytes < 1 || maximumOperationBytes > OH_OPERATION_MAX_BYTES_V1) {
3218
+ throw new TypeError("Invalid actor, operation ID, or operation byte bound.");
3219
+ }
2608
3220
  const changes = canonicalKnowledgeGraphChangesV1(input.changes);
2609
3221
  if (changes.length === 0 || changes.length > 8192)
2610
3222
  throw new TypeError("A commit needs 1 through 8192 changes.");
@@ -2618,6 +3230,10 @@ class OhSqliteStore {
2618
3230
  if (existing.actorId !== actorId || canonicalJson(existing.changes) !== canonicalJson(changes)) {
2619
3231
  throw new OhConflictError("The operation ID is already bound to different content.");
2620
3232
  }
3233
+ const operationBytes = Buffer.byteLength(canonicalJson(existing), "utf8");
3234
+ if (operationBytes > maximumOperationBytes) {
3235
+ throw new OhOperationSizeError(operationBytes, maximumOperationBytes);
3236
+ }
2621
3237
  return existing;
2622
3238
  }
2623
3239
  if (head.generation !== input.expectedHead.generation || head.operationSha256 !== input.expectedHead.operationSha256) {
@@ -2636,6 +3252,8 @@ class OhSqliteStore {
2636
3252
  sequence: head.sequence + 1,
2637
3253
  spaceId: this.spaceId,
2638
3254
  v: 1
3255
+ }, {
3256
+ maximumOperationBytes
2639
3257
  });
2640
3258
  this.#persist(operation);
2641
3259
  return operation;
@@ -2647,30 +3265,84 @@ class OhSqliteStore {
2647
3265
  const operation = parseOhOperationV1(value);
2648
3266
  if (operation === null || operation.spaceId !== this.spaceId)
2649
3267
  throw new OhIntegrityError("Invalid imported operation.");
3268
+ const result = this.importOperations({
3269
+ expectedHead: {
3270
+ operationSha256: operation.parentOperationSha256,
3271
+ sequence: operation.sequence - 1
3272
+ },
3273
+ operations: [operation]
3274
+ });
3275
+ return { imported: result.imported === 1, operation };
3276
+ }
3277
+ importOperations(input) {
3278
+ this.#assertOpen();
3279
+ this.#assertOperationReplication();
3280
+ const expectedHead = parseOhHeadRefV1(input.expectedHead);
3281
+ if (expectedHead === null || !Array.isArray(input.operations) || input.operations.length > 1000) {
3282
+ throw new TypeError("Invalid operation import interval.");
3283
+ }
3284
+ const operations = input.operations.map((value) => {
3285
+ const operation = parseOhOperationV1(value);
3286
+ if (operation === null || operation.spaceId !== this.spaceId) {
3287
+ throw new OhIntegrityError("Invalid imported operation.");
3288
+ }
3289
+ return operation;
3290
+ });
3291
+ let prior = expectedHead;
3292
+ for (const operation of operations) {
3293
+ if (operation.sequence !== prior.sequence + 1 || operation.parentOperationSha256 !== prior.operationSha256) {
3294
+ throw new OhConflictError("Imported operations do not extend the expected head.");
3295
+ }
3296
+ prior = { operationSha256: operation.operationSha256, sequence: operation.sequence };
3297
+ }
2650
3298
  return withImmediateTransaction(this.database, () => {
2651
- const head = this.head();
2652
- this.#assertCurrentHeadAuthority(head);
2653
- const duplicate = this.database.query(`SELECT ${OPERATION_COLUMNS} FROM oh_operations WHERE operation_sha256 = ?`).get(operation.operationSha256);
2654
- if (duplicate !== null) {
2655
- const existing = parseStoredOperationRow(duplicate, {
2656
- operationSha256: operation.operationSha256,
2657
- spaceId: this.spaceId
2658
- });
2659
- this.#assertOperationReachable(existing, head);
2660
- if (canonicalJson(existing) !== canonicalJson(operation)) {
2661
- throw new OhIntegrityError("An operation digest is bound to different bytes.");
3299
+ const current = this.head();
3300
+ this.#assertCurrentHeadAuthority(current);
3301
+ this.#headAt(expectedHead);
3302
+ if (!exactHeadRef(current, expectedHead)) {
3303
+ if (operations.length === 0 || current.sequence < prior.sequence) {
3304
+ throw new OhConflictError("The imported operation interval does not extend the local head.");
2662
3305
  }
2663
- return { imported: false, operation };
3306
+ for (const operation of operations) {
3307
+ const row = this.database.query(`SELECT ${OPERATION_COLUMNS} FROM oh_operations WHERE space_id = ? AND sequence = ?`).get(this.spaceId, operation.sequence);
3308
+ if (row === null)
3309
+ throw new OhIntegrityError("An imported replay is missing from the authority chain.");
3310
+ const existing = parseStoredOperationRow(row, { spaceId: this.spaceId });
3311
+ if (existing.operationSha256 !== operation.operationSha256) {
3312
+ throw new OhConflictError("The imported operation interval diverges from the local authority chain.");
3313
+ }
3314
+ if (canonicalJson(existing) !== canonicalJson(operation)) {
3315
+ throw new OhIntegrityError("An operation digest is bound to different bytes.");
3316
+ }
3317
+ }
3318
+ this.#assertOperationReachable(operations[operations.length - 1], current);
3319
+ return { head: current, imported: 0, status: "already-present", v: 1 };
2664
3320
  }
2665
- if (operation.sequence !== head.sequence + 1 || operation.parentOperationSha256 !== head.operationSha256) {
2666
- throw new OhConflictError("The imported operation does not extend the local head.");
3321
+ if (operations.length === 0) {
3322
+ return { head: current, imported: 0, status: "already-present", v: 1 };
2667
3323
  }
2668
- const transition = this.#transition(head, operation.changes, operation.operationId);
2669
- if (transition.recordsSha256 !== operation.recordsSha256 || transition.graphRevisionSha256 !== operation.graphRevisionSha256) {
2670
- throw new OhIntegrityError("The imported operation does not reproduce its declared graph head.");
3324
+ const records = this.#loadRecords();
3325
+ let head = current;
3326
+ for (const operation of operations) {
3327
+ const duplicate = this.database.query(`SELECT ${OPERATION_COLUMNS} FROM oh_operations WHERE space_id = ? AND operation_id = ?`).get(this.spaceId, operation.operationId);
3328
+ if (duplicate !== null) {
3329
+ throw new OhConflictError("An imported operation ID is already bound on the local authority chain.");
3330
+ }
3331
+ const transition = this.#transition(head, operation.changes, operation.operationId, records);
3332
+ if (transition.recordsSha256 !== operation.recordsSha256 || transition.graphRevisionSha256 !== operation.graphRevisionSha256) {
3333
+ throw new OhIntegrityError("An imported operation does not reproduce its declared graph head.");
3334
+ }
3335
+ this.#persist(operation);
3336
+ head = {
3337
+ generation: operation.sequence,
3338
+ graphRevisionSha256: operation.graphRevisionSha256,
3339
+ operationSha256: operation.operationSha256,
3340
+ recordsSha256: operation.recordsSha256,
3341
+ sequence: operation.sequence,
3342
+ v: 1
3343
+ };
2671
3344
  }
2672
- this.#persist(operation);
2673
- return { imported: true, operation };
3345
+ return { head, imported: operations.length, status: "imported", v: 1 };
2674
3346
  });
2675
3347
  }
2676
3348
  exportOperations(afterSequence = 0, limit = 1000) {
@@ -2888,6 +3560,10 @@ class OhSqliteStore {
2888
3560
  const integrity = this.database.query("PRAGMA integrity_check").get();
2889
3561
  if (integrity?.integrity_check !== "ok")
2890
3562
  throw new OhIntegrityError("SQLite integrity_check failed.");
3563
+ const foreignKeyViolations = this.database.query("PRAGMA foreign_key_check").all();
3564
+ if (foreignKeyViolations.length !== 0) {
3565
+ throw new OhIntegrityError("SQLite foreign_key_check failed.");
3566
+ }
2891
3567
  const storedCount = this.database.query("SELECT count(*) AS count FROM oh_operations WHERE space_id = ?").get(this.spaceId)?.count ?? 0;
2892
3568
  const operations = this.database.query(`SELECT ${OPERATION_COLUMNS} FROM oh_operations WHERE space_id = ? ORDER BY sequence`).all(this.spaceId).map((row) => parseStoredOperationRow(row, { spaceId: this.spaceId }));
2893
3569
  if (operations.length !== storedCount)
@@ -2964,6 +3640,22 @@ class OhSqliteStore {
2964
3640
  if (canonicalJson(storedDependencies) !== canonicalJson(expectedDependencies)) {
2965
3641
  throw new OhIntegrityError("Materialized dependencies do not match operation replay.");
2966
3642
  }
3643
+ const expectedSearchDocuments = [...records.values()].sort((left, right) => left.key < right.key ? -1 : left.key > right.key ? 1 : 0).map((record) => ({
3644
+ record_key: record.key,
3645
+ record_sha256: record.recordSha256,
3646
+ text: `${record.key} ${record.kind} ${extractSearchText(record.value)}`
3647
+ }));
3648
+ const storedSearchDocuments = this.database.query(`SELECT record_key, record_sha256, text FROM oh_search_documents
3649
+ WHERE space_id = ? ORDER BY record_key`).all(this.spaceId);
3650
+ if (canonicalJson(storedSearchDocuments) !== canonicalJson(expectedSearchDocuments)) {
3651
+ throw new OhIntegrityError("Materialized search documents do not match operation replay.");
3652
+ }
3653
+ const expectedSearchFts = expectedSearchDocuments.map(({ record_key, text }) => ({ record_key, text }));
3654
+ const storedSearchFts = this.database.query(`SELECT record_key, text FROM oh_search_fts
3655
+ WHERE space_id = ? ORDER BY record_key, text, rowid`).all(this.spaceId);
3656
+ if (canonicalJson(storedSearchFts) !== canonicalJson(expectedSearchFts)) {
3657
+ throw new OhIntegrityError("Materialized full-text search rows do not match operation replay.");
3658
+ }
2967
3659
  const storedOperationRecords = this.database.query(`SELECT materialized.operation_sha256, materialized.ordinal, materialized.record_key,
2968
3660
  materialized.change_kind, materialized.record_sha256
2969
3661
  FROM oh_operation_records AS materialized