@hraness/oh 0.4.1 → 0.4.2
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/LICENSE +3 -0
- package/README.md +4 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +15590 -1705
- package/dist/index.js +15209 -261
- package/dist/libsql-model.d.ts +75 -0
- package/dist/libsql-model.d.ts.map +1 -0
- package/dist/libsql-platform.d.ts +24 -0
- package/dist/libsql-platform.d.ts.map +1 -0
- package/dist/libsql-program.d.ts +64 -0
- package/dist/libsql-program.d.ts.map +1 -0
- package/dist/libsql-runtime.d.ts +13 -0
- package/dist/libsql-runtime.d.ts.map +1 -0
- package/dist/libsql.d.ts +4 -36
- package/dist/libsql.d.ts.map +1 -1
- package/dist/libsql.js +16583 -1426
- package/dist/memory-authority-platform.d.ts +210 -0
- package/dist/memory-authority-platform.d.ts.map +1 -0
- package/dist/memory-authority-program.d.ts +15 -0
- package/dist/memory-authority-program.d.ts.map +1 -0
- package/dist/memory-authority-runtime.d.ts +4 -0
- package/dist/memory-authority-runtime.d.ts.map +1 -0
- package/dist/memory-core.d.ts +522 -0
- package/dist/memory-core.d.ts.map +1 -0
- package/dist/memory.d.ts +5 -445
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +18232 -4143
- package/dist/sdk.js +15266 -318
- package/dist/semantic-cloud.js +1 -260
- package/dist/semantic-model.d.ts +75 -0
- package/dist/semantic-model.d.ts.map +1 -0
- package/dist/semantic-platform.d.ts +32 -0
- package/dist/semantic-platform.d.ts.map +1 -0
- package/dist/semantic-program.d.ts +18 -0
- package/dist/semantic-program.d.ts.map +1 -0
- package/dist/semantic-runtime.d.ts +6 -0
- package/dist/semantic-runtime.d.ts.map +1 -0
- package/dist/semantic.d.ts +6 -57
- package/dist/semantic.d.ts.map +1 -1
- package/dist/semantic.js +15541 -514
- package/dist/sqlite/index.js +1148 -1378
- package/dist/sqlite/port.d.ts +1 -1
- package/dist/sqlite/port.d.ts.map +1 -1
- package/dist/sync-libsql-program.d.ts +5 -0
- package/dist/sync-libsql-program.d.ts.map +1 -0
- package/dist/sync-model.d.ts +60 -0
- package/dist/sync-model.d.ts.map +1 -0
- package/dist/sync-platform.d.ts +41 -0
- package/dist/sync-platform.d.ts.map +1 -0
- package/dist/sync-program.d.ts +11 -0
- package/dist/sync-program.d.ts.map +1 -0
- package/dist/sync-runtime.d.ts +6 -0
- package/dist/sync-runtime.d.ts.map +1 -0
- package/dist/sync.d.ts +5 -55
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +15174 -226
- package/package.json +7 -5
- package/skills/oh/SKILL.md +2 -1
- package/spec/v1/store.md +14 -0
- package/src/cli.test.ts +35 -0
- package/src/cli.ts +4 -3
- package/src/cloudflare-embedding.ts +1 -1
- package/src/libsql-lifecycle.test.ts +76 -0
- package/src/libsql-model.ts +350 -0
- package/src/libsql-platform.ts +51 -0
- package/src/libsql-program.ts +1583 -0
- package/src/libsql-runtime.ts +89 -0
- package/src/libsql-semantic-v2.ts +1 -1
- package/src/libsql-semantic.ts +1 -1
- package/src/libsql.test.ts +245 -0
- package/src/libsql.ts +26 -1698
- package/src/memory-authority-platform.ts +133 -0
- package/src/memory-authority-program.ts +309 -0
- package/src/memory-authority-runtime.ts +40 -0
- package/src/memory-core.ts +2401 -0
- package/src/memory.test.ts +55 -0
- package/src/memory.ts +68 -2759
- package/src/semantic-model.ts +142 -0
- package/src/semantic-platform.ts +130 -0
- package/src/semantic-program.ts +113 -0
- package/src/semantic-runtime.ts +53 -0
- package/src/semantic.test.ts +115 -1
- package/src/semantic.ts +14 -324
- package/src/sqlite/port.ts +1 -1
- package/src/sync-libsql-program.ts +170 -0
- package/src/sync-lifecycle.test.ts +110 -0
- package/src/sync-model.ts +530 -0
- package/src/sync-platform.ts +87 -0
- package/src/sync-program.ts +117 -0
- package/src/sync-runtime.ts +33 -0
- package/src/sync.ts +10 -765
package/dist/sqlite/index.js
CHANGED
|
@@ -1,7 +1,93 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name, newValue) {
|
|
5
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, {
|
|
10
|
+
get: all[name],
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
set: __exportSetter.bind(all, name)
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// src/sqlite/driver.ts
|
|
18
|
+
import { existsSync } from "fs";
|
|
19
|
+
import { Database } from "bun:sqlite";
|
|
20
|
+
|
|
21
|
+
// src/sqlite/runtime.ts
|
|
22
|
+
var MACOS_SQLITE_LIBRARY_CANDIDATES = Object.freeze([
|
|
23
|
+
"/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib",
|
|
24
|
+
"/usr/local/opt/sqlite/lib/libsqlite3.dylib"
|
|
25
|
+
]);
|
|
26
|
+
function macosSqliteLibraryCandidates() {
|
|
27
|
+
return MACOS_SQLITE_LIBRARY_CANDIDATES;
|
|
28
|
+
}
|
|
29
|
+
function createOhSqliteRuntime(dependencies) {
|
|
30
|
+
let customLibrary = null;
|
|
31
|
+
if (dependencies.platform === "darwin") {
|
|
32
|
+
for (const candidate of macosSqliteLibraryCandidates()) {
|
|
33
|
+
if (!dependencies.exists(candidate))
|
|
34
|
+
continue;
|
|
35
|
+
try {
|
|
36
|
+
if (!dependencies.setCustomSQLite(candidate))
|
|
37
|
+
continue;
|
|
38
|
+
customLibrary = candidate;
|
|
39
|
+
break;
|
|
40
|
+
} catch {}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return Object.freeze({
|
|
44
|
+
customLibrary,
|
|
45
|
+
open: (path) => dependencies.open(path, { create: true, strict: true })
|
|
46
|
+
});
|
|
47
|
+
}
|
|
4
48
|
|
|
49
|
+
// src/sqlite/driver.ts
|
|
50
|
+
var SQLITE_RUNTIME = createOhSqliteRuntime({
|
|
51
|
+
exists: existsSync,
|
|
52
|
+
open: (path, options) => new Database(path, options),
|
|
53
|
+
platform: process.platform,
|
|
54
|
+
setCustomSQLite: (path) => Database.setCustomSQLite(path)
|
|
55
|
+
});
|
|
56
|
+
function openOhSqliteDatabase(path) {
|
|
57
|
+
const database = SQLITE_RUNTIME.open(path);
|
|
58
|
+
database.exec("PRAGMA foreign_keys = ON");
|
|
59
|
+
database.exec("PRAGMA journal_mode = WAL");
|
|
60
|
+
database.exec("PRAGMA synchronous = NORMAL");
|
|
61
|
+
database.exec("PRAGMA busy_timeout = 5000");
|
|
62
|
+
database.exec("PRAGMA trusted_schema = OFF");
|
|
63
|
+
return database;
|
|
64
|
+
}
|
|
65
|
+
function withImmediateTransaction(database, work) {
|
|
66
|
+
database.exec("BEGIN IMMEDIATE");
|
|
67
|
+
try {
|
|
68
|
+
const result = work();
|
|
69
|
+
database.exec("COMMIT");
|
|
70
|
+
return result;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
try {
|
|
73
|
+
database.exec("ROLLBACK");
|
|
74
|
+
} catch {}
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function withReadTransaction(database, work) {
|
|
79
|
+
database.exec("BEGIN");
|
|
80
|
+
try {
|
|
81
|
+
const result = work();
|
|
82
|
+
database.exec("COMMIT");
|
|
83
|
+
return result;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
try {
|
|
86
|
+
database.exec("ROLLBACK");
|
|
87
|
+
} catch {}
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
5
91
|
// src/canonical.ts
|
|
6
92
|
import { createHash, randomBytes } from "crypto";
|
|
7
93
|
|
|
@@ -192,6 +278,179 @@ function sortUnique(values, key) {
|
|
|
192
278
|
return sorted;
|
|
193
279
|
}
|
|
194
280
|
|
|
281
|
+
// src/sqlite/migrations.ts
|
|
282
|
+
var OH_SQLITE_SCHEMA_VERSION = 2;
|
|
283
|
+
var OH_SQLITE_MIGRATIONS = Object.freeze([
|
|
284
|
+
Object.freeze({
|
|
285
|
+
name: "0001_oh_core",
|
|
286
|
+
version: 1,
|
|
287
|
+
sql: `
|
|
288
|
+
CREATE TABLE oh_contracts (
|
|
289
|
+
contract_id TEXT PRIMARY KEY,
|
|
290
|
+
contract_sha256 TEXT NOT NULL CHECK(length(contract_sha256) = 64),
|
|
291
|
+
manifest_json TEXT NOT NULL CHECK(json_valid(manifest_json)),
|
|
292
|
+
created_at TEXT NOT NULL
|
|
293
|
+
) STRICT;
|
|
294
|
+
|
|
295
|
+
CREATE TABLE oh_spaces (
|
|
296
|
+
space_id TEXT PRIMARY KEY,
|
|
297
|
+
contract_id TEXT NOT NULL REFERENCES oh_contracts(contract_id),
|
|
298
|
+
generation INTEGER NOT NULL CHECK(generation >= 0),
|
|
299
|
+
head_operation_sha256 TEXT CHECK(head_operation_sha256 IS NULL OR length(head_operation_sha256) = 64),
|
|
300
|
+
graph_revision_sha256 TEXT CHECK(graph_revision_sha256 IS NULL OR length(graph_revision_sha256) = 64),
|
|
301
|
+
records_sha256 TEXT NOT NULL CHECK(length(records_sha256) = 64),
|
|
302
|
+
sequence INTEGER NOT NULL CHECK(sequence >= 0),
|
|
303
|
+
created_at TEXT NOT NULL,
|
|
304
|
+
updated_at TEXT NOT NULL,
|
|
305
|
+
CHECK(generation = sequence),
|
|
306
|
+
CHECK((sequence = 0) = (head_operation_sha256 IS NULL)),
|
|
307
|
+
CHECK((sequence = 0) = (graph_revision_sha256 IS NULL))
|
|
308
|
+
) STRICT;
|
|
309
|
+
|
|
310
|
+
CREATE TABLE oh_operations (
|
|
311
|
+
operation_sha256 TEXT PRIMARY KEY CHECK(length(operation_sha256) = 64),
|
|
312
|
+
space_id TEXT NOT NULL REFERENCES oh_spaces(space_id),
|
|
313
|
+
sequence INTEGER NOT NULL CHECK(sequence > 0),
|
|
314
|
+
operation_id TEXT NOT NULL,
|
|
315
|
+
parent_operation_sha256 TEXT CHECK(parent_operation_sha256 IS NULL OR length(parent_operation_sha256) = 64),
|
|
316
|
+
graph_revision_sha256 TEXT NOT NULL CHECK(length(graph_revision_sha256) = 64),
|
|
317
|
+
records_sha256 TEXT NOT NULL CHECK(length(records_sha256) = 64),
|
|
318
|
+
operation_json TEXT NOT NULL CHECK(json_valid(operation_json)),
|
|
319
|
+
instant TEXT NOT NULL,
|
|
320
|
+
UNIQUE(space_id, sequence),
|
|
321
|
+
UNIQUE(space_id, operation_id)
|
|
322
|
+
) STRICT;
|
|
323
|
+
|
|
324
|
+
CREATE TABLE oh_operation_records (
|
|
325
|
+
operation_sha256 TEXT NOT NULL REFERENCES oh_operations(operation_sha256),
|
|
326
|
+
ordinal INTEGER NOT NULL CHECK(ordinal >= 0),
|
|
327
|
+
record_key TEXT NOT NULL,
|
|
328
|
+
change_kind TEXT NOT NULL CHECK(change_kind IN ('put', 'tombstone')),
|
|
329
|
+
record_sha256 TEXT CHECK(record_sha256 IS NULL OR length(record_sha256) = 64),
|
|
330
|
+
PRIMARY KEY(operation_sha256, ordinal),
|
|
331
|
+
UNIQUE(operation_sha256, record_key)
|
|
332
|
+
) STRICT;
|
|
333
|
+
|
|
334
|
+
CREATE TABLE oh_records (
|
|
335
|
+
space_id TEXT NOT NULL REFERENCES oh_spaces(space_id),
|
|
336
|
+
record_key TEXT NOT NULL,
|
|
337
|
+
kind TEXT NOT NULL,
|
|
338
|
+
record_sha256 TEXT NOT NULL CHECK(length(record_sha256) = 64),
|
|
339
|
+
record_json TEXT NOT NULL CHECK(json_valid(record_json)),
|
|
340
|
+
operation_sha256 TEXT NOT NULL REFERENCES oh_operations(operation_sha256),
|
|
341
|
+
sequence INTEGER NOT NULL CHECK(sequence > 0),
|
|
342
|
+
PRIMARY KEY(space_id, record_key)
|
|
343
|
+
) STRICT;
|
|
344
|
+
|
|
345
|
+
CREATE TABLE oh_dependencies (
|
|
346
|
+
space_id TEXT NOT NULL,
|
|
347
|
+
record_key TEXT NOT NULL,
|
|
348
|
+
dependency_key TEXT NOT NULL,
|
|
349
|
+
PRIMARY KEY(space_id, record_key, dependency_key),
|
|
350
|
+
FOREIGN KEY(space_id, record_key) REFERENCES oh_records(space_id, record_key) ON DELETE CASCADE,
|
|
351
|
+
FOREIGN KEY(space_id, dependency_key) REFERENCES oh_records(space_id, record_key)
|
|
352
|
+
) STRICT;
|
|
353
|
+
|
|
354
|
+
CREATE TABLE oh_sync_outbox (
|
|
355
|
+
space_id TEXT NOT NULL REFERENCES oh_spaces(space_id),
|
|
356
|
+
sequence INTEGER NOT NULL,
|
|
357
|
+
operation_sha256 TEXT NOT NULL REFERENCES oh_operations(operation_sha256),
|
|
358
|
+
PRIMARY KEY(space_id, sequence),
|
|
359
|
+
UNIQUE(operation_sha256)
|
|
360
|
+
) STRICT;
|
|
361
|
+
|
|
362
|
+
CREATE TABLE oh_sync_state (
|
|
363
|
+
remote_id TEXT NOT NULL,
|
|
364
|
+
space_id TEXT NOT NULL REFERENCES oh_spaces(space_id),
|
|
365
|
+
pulled_sequence INTEGER NOT NULL CHECK(pulled_sequence >= 0),
|
|
366
|
+
pushed_sequence INTEGER NOT NULL CHECK(pushed_sequence >= 0),
|
|
367
|
+
remote_head_sha256 TEXT CHECK(remote_head_sha256 IS NULL OR length(remote_head_sha256) = 64),
|
|
368
|
+
updated_at TEXT NOT NULL,
|
|
369
|
+
PRIMARY KEY(remote_id, space_id)
|
|
370
|
+
) STRICT;
|
|
371
|
+
|
|
372
|
+
CREATE TABLE oh_search_documents (
|
|
373
|
+
space_id TEXT NOT NULL,
|
|
374
|
+
record_key TEXT NOT NULL,
|
|
375
|
+
record_sha256 TEXT NOT NULL CHECK(length(record_sha256) = 64),
|
|
376
|
+
text TEXT NOT NULL,
|
|
377
|
+
PRIMARY KEY(space_id, record_key),
|
|
378
|
+
FOREIGN KEY(space_id, record_key) REFERENCES oh_records(space_id, record_key) ON DELETE CASCADE
|
|
379
|
+
) STRICT;
|
|
380
|
+
|
|
381
|
+
CREATE VIRTUAL TABLE oh_search_fts USING fts5(
|
|
382
|
+
space_id UNINDEXED,
|
|
383
|
+
record_key UNINDEXED,
|
|
384
|
+
text,
|
|
385
|
+
tokenize='unicode61 remove_diacritics 2'
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
CREATE INDEX oh_operations_space_sequence ON oh_operations(space_id, sequence);
|
|
389
|
+
CREATE INDEX oh_records_space_kind ON oh_records(space_id, kind, record_key);
|
|
390
|
+
CREATE INDEX oh_dependencies_dependency ON oh_dependencies(space_id, dependency_key);
|
|
391
|
+
`
|
|
392
|
+
}),
|
|
393
|
+
Object.freeze({
|
|
394
|
+
name: "0002_store_realms",
|
|
395
|
+
version: 2,
|
|
396
|
+
sql: `
|
|
397
|
+
CREATE TABLE oh_space_bindings (
|
|
398
|
+
space_id TEXT PRIMARY KEY REFERENCES oh_spaces(space_id),
|
|
399
|
+
realm_id TEXT NOT NULL,
|
|
400
|
+
profile_id TEXT NOT NULL,
|
|
401
|
+
profile_kind TEXT NOT NULL CHECK(profile_kind IN ('canonical', 'working')),
|
|
402
|
+
profile_sha256 TEXT NOT NULL CHECK(length(profile_sha256) = 64),
|
|
403
|
+
binding_sha256 TEXT NOT NULL UNIQUE CHECK(length(binding_sha256) = 64),
|
|
404
|
+
binding_json TEXT NOT NULL CHECK(json_valid(binding_json)),
|
|
405
|
+
created_at TEXT NOT NULL
|
|
406
|
+
) STRICT;
|
|
407
|
+
|
|
408
|
+
CREATE TABLE oh_space_purges (
|
|
409
|
+
space_id TEXT PRIMARY KEY,
|
|
410
|
+
binding_sha256 TEXT NOT NULL CHECK(length(binding_sha256) = 64),
|
|
411
|
+
prior_operation_sha256 TEXT CHECK(prior_operation_sha256 IS NULL OR length(prior_operation_sha256) = 64),
|
|
412
|
+
prior_sequence INTEGER NOT NULL CHECK(prior_sequence >= 0),
|
|
413
|
+
purged_at TEXT NOT NULL,
|
|
414
|
+
receipt_sha256 TEXT NOT NULL UNIQUE CHECK(length(receipt_sha256) = 64),
|
|
415
|
+
receipt_json TEXT NOT NULL CHECK(json_valid(receipt_json))
|
|
416
|
+
) STRICT;
|
|
417
|
+
`
|
|
418
|
+
})
|
|
419
|
+
]);
|
|
420
|
+
function applyOhSqliteMigrations(database) {
|
|
421
|
+
database.exec(`CREATE TABLE IF NOT EXISTS oh_migrations (
|
|
422
|
+
version INTEGER PRIMARY KEY,
|
|
423
|
+
name TEXT NOT NULL UNIQUE,
|
|
424
|
+
migration_sha256 TEXT NOT NULL CHECK(length(migration_sha256) = 64),
|
|
425
|
+
applied_at TEXT NOT NULL
|
|
426
|
+
) STRICT`);
|
|
427
|
+
const select = database.query("SELECT name, migration_sha256 FROM oh_migrations WHERE version = ?");
|
|
428
|
+
const insert = database.query("INSERT INTO oh_migrations(version, name, migration_sha256, applied_at) VALUES (?, ?, ?, ?)");
|
|
429
|
+
for (const migration of OH_SQLITE_MIGRATIONS) {
|
|
430
|
+
const digest = sha256Hex(migration.sql);
|
|
431
|
+
database.exec("BEGIN IMMEDIATE");
|
|
432
|
+
try {
|
|
433
|
+
const existing = select.get(migration.version);
|
|
434
|
+
if (existing !== null) {
|
|
435
|
+
if (existing.name !== migration.name || existing.migration_sha256 !== digest) {
|
|
436
|
+
throw new Error(`SQLite migration ${migration.version} does not match the applied migration.`);
|
|
437
|
+
}
|
|
438
|
+
} else {
|
|
439
|
+
database.exec(migration.sql);
|
|
440
|
+
insert.run(migration.version, migration.name, digest, canonicalNow());
|
|
441
|
+
}
|
|
442
|
+
database.exec("COMMIT");
|
|
443
|
+
} catch (error) {
|
|
444
|
+
try {
|
|
445
|
+
database.exec("ROLLBACK");
|
|
446
|
+
} catch {}
|
|
447
|
+
throw error;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
// src/sqlite/port.ts
|
|
452
|
+
import { isProxy as isProxy2 } from "util/types";
|
|
453
|
+
|
|
195
454
|
// src/graph.ts
|
|
196
455
|
var OH_GRAPH_FORMAT_VERSION_V1 = 1;
|
|
197
456
|
var OH_GRAPH_LIMITS_V1 = Object.freeze({
|
|
@@ -1280,7 +1539,6 @@ class OhOperationSizeError extends RangeError {
|
|
|
1280
1539
|
});
|
|
1281
1540
|
}
|
|
1282
1541
|
}
|
|
1283
|
-
|
|
1284
1542
|
// src/operation.ts
|
|
1285
1543
|
var OH_OPERATION_MAX_BYTES_V1 = 64 * 1024 * 1024;
|
|
1286
1544
|
function parsePayload(value) {
|
|
@@ -1352,1461 +1610,973 @@ function parseOhOperationV1(value) {
|
|
|
1352
1610
|
return operationSha256 !== null && payload !== null && Buffer.byteLength(canonicalJson({ ...payload, operationSha256 }), "utf8") <= OH_OPERATION_MAX_BYTES_V1 && canonicalSha256(payload) === operationSha256 ? { ...payload, operationSha256 } : null;
|
|
1353
1611
|
}
|
|
1354
1612
|
|
|
1355
|
-
// src/
|
|
1356
|
-
var
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
var
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1613
|
+
// src/store.ts
|
|
1614
|
+
var OH_CANONICAL_STORE_PROFILE_V1 = createOhStoreProfileV1({
|
|
1615
|
+
applicationProfileSha256: null,
|
|
1616
|
+
capabilities: {
|
|
1617
|
+
changesSince: true,
|
|
1618
|
+
dependencyClosureExport: true,
|
|
1619
|
+
exactSnapshots: true,
|
|
1620
|
+
operationReplication: true,
|
|
1621
|
+
semanticBundleCommit: true,
|
|
1622
|
+
v: 1,
|
|
1623
|
+
wholeSpacePurge: false
|
|
1624
|
+
},
|
|
1625
|
+
profileId: "oh.store.canonical.v1",
|
|
1626
|
+
profileKind: "canonical",
|
|
1627
|
+
v: 1
|
|
1628
|
+
});
|
|
1629
|
+
var OH_WORKING_STORE_PROFILE_V1 = createOhStoreProfileV1({
|
|
1630
|
+
applicationProfileSha256: null,
|
|
1631
|
+
capabilities: {
|
|
1632
|
+
changesSince: true,
|
|
1633
|
+
dependencyClosureExport: true,
|
|
1634
|
+
exactSnapshots: true,
|
|
1635
|
+
operationReplication: false,
|
|
1636
|
+
semanticBundleCommit: true,
|
|
1637
|
+
v: 1,
|
|
1638
|
+
wholeSpacePurge: true
|
|
1639
|
+
},
|
|
1640
|
+
profileId: "oh.store.working.v1",
|
|
1641
|
+
profileKind: "working",
|
|
1642
|
+
v: 1
|
|
1643
|
+
});
|
|
1644
|
+
var OH_DEPENDENCY_CLOSURE_LIMITS_V1 = Object.freeze({
|
|
1645
|
+
bytes: 64 * 1024 * 1024,
|
|
1646
|
+
records: 8192,
|
|
1647
|
+
roots: 1024
|
|
1648
|
+
});
|
|
1649
|
+
|
|
1650
|
+
class OhPurgedSpaceError extends Error {
|
|
1651
|
+
receipt;
|
|
1652
|
+
constructor(receipt) {
|
|
1653
|
+
super(`Oh space ${receipt.spaceId} was purged at ${receipt.purgedAt}.`);
|
|
1654
|
+
this.name = "OhPurgedSpaceError";
|
|
1655
|
+
this.receipt = receipt;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
var EMPTY_RECORDS_SHA256 = canonicalSha256([]);
|
|
1659
|
+
function emptyOhHeadV1() {
|
|
1660
|
+
return {
|
|
1661
|
+
generation: 0,
|
|
1662
|
+
graphRevisionSha256: null,
|
|
1663
|
+
operationSha256: null,
|
|
1664
|
+
recordsSha256: EMPTY_RECORDS_SHA256,
|
|
1665
|
+
sequence: 0,
|
|
1666
|
+
v: 1
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
function parseOhHeadV1(value) {
|
|
1670
|
+
if (!isPlainRecord(value) || !hasExactKeys(value, [
|
|
1671
|
+
"generation",
|
|
1672
|
+
"graphRevisionSha256",
|
|
1673
|
+
"operationSha256",
|
|
1674
|
+
"recordsSha256",
|
|
1675
|
+
"sequence",
|
|
1676
|
+
"v"
|
|
1677
|
+
]) || value.v !== 1)
|
|
1678
|
+
return null;
|
|
1679
|
+
const graphRevisionSha256 = value.graphRevisionSha256 === null ? null : parseSha256Hex(value.graphRevisionSha256);
|
|
1680
|
+
const operationSha256 = value.operationSha256 === null ? null : parseSha256Hex(value.operationSha256);
|
|
1681
|
+
const recordsSha256 = parseSha256Hex(value.recordsSha256);
|
|
1682
|
+
const generation = Number.isSafeInteger(value.generation) && value.generation >= 0 ? value.generation : null;
|
|
1683
|
+
const sequence = Number.isSafeInteger(value.sequence) && value.sequence >= 0 ? value.sequence : null;
|
|
1684
|
+
return generation !== null && sequence !== null && generation === sequence && recordsSha256 !== null && (value.graphRevisionSha256 === null || graphRevisionSha256 !== null) && (value.operationSha256 === null || operationSha256 !== null) && sequence === 0 === (operationSha256 === null) && sequence === 0 === (graphRevisionSha256 === null) ? { generation, graphRevisionSha256, operationSha256, recordsSha256, sequence, v: 1 } : null;
|
|
1685
|
+
}
|
|
1686
|
+
function parseOhHeadRefV1(value) {
|
|
1687
|
+
const complete = parseOhHeadV1(value);
|
|
1688
|
+
if (complete !== null) {
|
|
1689
|
+
return { operationSha256: complete.operationSha256, sequence: complete.sequence };
|
|
1690
|
+
}
|
|
1691
|
+
if (!isPlainRecord(value) || !hasExactKeys(value, ["operationSha256", "sequence"]))
|
|
1692
|
+
return null;
|
|
1693
|
+
const operationSha256 = value.operationSha256 === null ? null : parseSha256Hex(value.operationSha256);
|
|
1694
|
+
const sequence = Number.isSafeInteger(value.sequence) && value.sequence >= 0 ? value.sequence : null;
|
|
1695
|
+
return sequence !== null && (value.operationSha256 === null || operationSha256 !== null) && sequence === 0 === (operationSha256 === null) ? { operationSha256, sequence } : null;
|
|
1696
|
+
}
|
|
1697
|
+
function parseCapabilities(value) {
|
|
1698
|
+
if (!isPlainRecord(value) || !hasExactKeys(value, [
|
|
1699
|
+
"changesSince",
|
|
1700
|
+
"dependencyClosureExport",
|
|
1701
|
+
"exactSnapshots",
|
|
1702
|
+
"operationReplication",
|
|
1703
|
+
"semanticBundleCommit",
|
|
1704
|
+
"v",
|
|
1705
|
+
"wholeSpacePurge"
|
|
1706
|
+
]) || value.changesSince !== true || value.dependencyClosureExport !== true || value.exactSnapshots !== true || typeof value.operationReplication !== "boolean" || value.semanticBundleCommit !== true || value.v !== 1 || typeof value.wholeSpacePurge !== "boolean")
|
|
1707
|
+
return null;
|
|
1708
|
+
return {
|
|
1709
|
+
changesSince: true,
|
|
1710
|
+
dependencyClosureExport: true,
|
|
1711
|
+
exactSnapshots: true,
|
|
1712
|
+
operationReplication: value.operationReplication,
|
|
1713
|
+
semanticBundleCommit: true,
|
|
1714
|
+
v: 1,
|
|
1715
|
+
wholeSpacePurge: value.wholeSpacePurge
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
function createOhStoreProfileV1(input) {
|
|
1719
|
+
if (!isPlainRecord(input) || !hasExactKeys(input, [
|
|
1720
|
+
"applicationProfileSha256",
|
|
1721
|
+
"capabilities",
|
|
1722
|
+
"profileId",
|
|
1723
|
+
"profileKind",
|
|
1724
|
+
"v"
|
|
1725
|
+
]) || input.v !== 1)
|
|
1726
|
+
throw new TypeError("Invalid Oh store profile input.");
|
|
1727
|
+
const profileId = safeCode(input.profileId);
|
|
1728
|
+
const applicationProfileSha256 = input.applicationProfileSha256 === null ? null : parseSha256Hex(input.applicationProfileSha256);
|
|
1729
|
+
const capabilities = parseCapabilities(input.capabilities);
|
|
1730
|
+
if (profileId === null || capabilities === null || input.applicationProfileSha256 !== null && applicationProfileSha256 === null || input.profileKind !== "canonical" && input.profileKind !== "working") {
|
|
1731
|
+
throw new TypeError("Invalid Oh store profile input.");
|
|
1732
|
+
}
|
|
1733
|
+
if (input.profileKind === "working" && (capabilities.operationReplication || !capabilities.wholeSpacePurge)) {
|
|
1734
|
+
throw new OhProfileError("A working profile must disable operation replication and permit whole-space purge.");
|
|
1735
|
+
}
|
|
1736
|
+
if (input.profileKind === "canonical" && capabilities.wholeSpacePurge) {
|
|
1737
|
+
throw new OhProfileError("A canonical profile cannot permit whole-space purge.");
|
|
1738
|
+
}
|
|
1739
|
+
const payload = {
|
|
1740
|
+
applicationProfileSha256,
|
|
1741
|
+
capabilities: Object.freeze(capabilities),
|
|
1742
|
+
profileId,
|
|
1743
|
+
profileKind: input.profileKind,
|
|
1744
|
+
v: 1
|
|
1745
|
+
};
|
|
1746
|
+
return Object.freeze({ ...payload, profileSha256: canonicalSha256(payload) });
|
|
1747
|
+
}
|
|
1748
|
+
function parseOhStoreProfileV1(value) {
|
|
1749
|
+
if (!isPlainRecord(value) || !Object.hasOwn(value, "profileSha256"))
|
|
1750
|
+
return null;
|
|
1751
|
+
const digest = parseSha256Hex(value.profileSha256);
|
|
1752
|
+
const { profileSha256: _profileSha256, ...input } = value;
|
|
1392
1753
|
try {
|
|
1393
|
-
|
|
1394
|
-
|
|
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;
|
|
1754
|
+
const created = createOhStoreProfileV1(input);
|
|
1755
|
+
return digest !== null && created.profileSha256 === digest ? created : null;
|
|
1412
1756
|
} catch {
|
|
1413
1757
|
return null;
|
|
1414
1758
|
}
|
|
1415
1759
|
}
|
|
1416
|
-
function
|
|
1760
|
+
function createOhStoreBindingV1(input) {
|
|
1761
|
+
const profile = parseOhStoreProfileV1(input.profile);
|
|
1762
|
+
const realmId = safeCode(input.realmId);
|
|
1763
|
+
const spaceId = safeCode(input.spaceId);
|
|
1764
|
+
if (input.v !== 1 || profile === null || realmId === null || spaceId === null) {
|
|
1765
|
+
throw new TypeError("Invalid Oh store binding input.");
|
|
1766
|
+
}
|
|
1767
|
+
const payload = {
|
|
1768
|
+
contractSha256: OH_CONTRACT_MANIFEST_V1.contractSha256,
|
|
1769
|
+
profile,
|
|
1770
|
+
realmId,
|
|
1771
|
+
spaceId,
|
|
1772
|
+
v: 1
|
|
1773
|
+
};
|
|
1774
|
+
return Object.freeze({ ...payload, bindingSha256: canonicalSha256(payload) });
|
|
1775
|
+
}
|
|
1776
|
+
function parseOhStoreBindingV1(value) {
|
|
1777
|
+
if (!isPlainRecord(value) || !hasExactKeys(value, [
|
|
1778
|
+
"bindingSha256",
|
|
1779
|
+
"contractSha256",
|
|
1780
|
+
"profile",
|
|
1781
|
+
"realmId",
|
|
1782
|
+
"spaceId",
|
|
1783
|
+
"v"
|
|
1784
|
+
]) || value.v !== 1 || value.contractSha256 !== OH_CONTRACT_MANIFEST_V1.contractSha256)
|
|
1785
|
+
return null;
|
|
1786
|
+
const bindingSha256 = parseSha256Hex(value.bindingSha256);
|
|
1787
|
+
const profile = parseOhStoreProfileV1(value.profile);
|
|
1417
1788
|
try {
|
|
1418
|
-
if (
|
|
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)))
|
|
1789
|
+
if (bindingSha256 === null || profile === null)
|
|
1426
1790
|
return null;
|
|
1427
|
-
const
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
return detached ?? value;
|
|
1791
|
+
const created = createOhStoreBindingV1({
|
|
1792
|
+
profile,
|
|
1793
|
+
realmId: value.realmId,
|
|
1794
|
+
spaceId: value.spaceId,
|
|
1795
|
+
v: 1
|
|
1796
|
+
});
|
|
1797
|
+
return created.bindingSha256 === bindingSha256 ? created : null;
|
|
1435
1798
|
} catch {
|
|
1436
1799
|
return null;
|
|
1437
1800
|
}
|
|
1438
1801
|
}
|
|
1439
|
-
function
|
|
1440
|
-
|
|
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;
|
|
1802
|
+
function sortedRecords(records) {
|
|
1803
|
+
return [...records].sort((left, right) => left.key < right.key ? -1 : left.key > right.key ? 1 : 0);
|
|
1478
1804
|
}
|
|
1479
|
-
function
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
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;
|
|
1805
|
+
function verifyDependencies(records) {
|
|
1806
|
+
for (const record of records.values()) {
|
|
1807
|
+
for (const dependency of record.dependencies) {
|
|
1808
|
+
if (!records.has(dependency))
|
|
1809
|
+
throw new OhDependencyError(`Missing dependency ${dependency} for ${record.key}.`);
|
|
1503
1810
|
}
|
|
1504
|
-
if (bytes > maximumBytes)
|
|
1505
|
-
throw new RangeError("String exceeds its canonical byte budget.");
|
|
1506
1811
|
}
|
|
1507
|
-
return bytes;
|
|
1508
1812
|
}
|
|
1509
|
-
function
|
|
1510
|
-
const
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
const
|
|
1518
|
-
|
|
1519
|
-
if (
|
|
1520
|
-
throw new
|
|
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;
|
|
1813
|
+
function replayOhOperationsV1(spaceId, values, maximumRecords = OH_GRAPH_LIMITS_V1.recordsPerSnapshot) {
|
|
1814
|
+
const parsedSpaceId = safeCode(spaceId);
|
|
1815
|
+
if (parsedSpaceId === null || !Number.isSafeInteger(maximumRecords) || maximumRecords < 1 || maximumRecords > OH_GRAPH_LIMITS_V1.recordsPerSnapshot) {
|
|
1816
|
+
throw new TypeError("Invalid operation replay input.");
|
|
1817
|
+
}
|
|
1818
|
+
const records = new Map;
|
|
1819
|
+
const operationIds = new Set;
|
|
1820
|
+
let head = emptyOhHeadV1();
|
|
1821
|
+
for (const value of values) {
|
|
1822
|
+
const operation = parseOhOperationV1(value);
|
|
1823
|
+
if (operation === null || operation.spaceId !== parsedSpaceId || operation.sequence !== head.sequence + 1 || operation.parentOperationSha256 !== head.operationSha256 || operationIds.has(operation.operationId)) {
|
|
1824
|
+
throw new OhIntegrityError("Operation replay chain is broken.");
|
|
1554
1825
|
}
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
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
|
-
});
|
|
1826
|
+
operationIds.add(operation.operationId);
|
|
1827
|
+
for (const change of operation.changes) {
|
|
1828
|
+
if (change.kind === "put")
|
|
1829
|
+
records.set(change.record.key, change.record);
|
|
1830
|
+
else {
|
|
1831
|
+
const prior = records.get(change.key);
|
|
1832
|
+
if (prior?.recordSha256 !== change.priorSha256) {
|
|
1833
|
+
throw new OhIntegrityError("Replay tombstone does not match its prior record.");
|
|
1620
1834
|
}
|
|
1835
|
+
records.delete(change.key);
|
|
1621
1836
|
}
|
|
1622
|
-
return detached === null ? candidate : Object.freeze(detached);
|
|
1623
|
-
} finally {
|
|
1624
|
-
ancestors.delete(candidate);
|
|
1625
1837
|
}
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
function preflightSyncIngressDependenciesV1(value, aggregate) {
|
|
1641
|
-
try {
|
|
1642
|
-
if (typeof value !== "object" || value === null || isProxy(value) || !Array.isArray(value)) {
|
|
1643
|
-
return null;
|
|
1838
|
+
if (records.size > maximumRecords)
|
|
1839
|
+
throw new RangeError("Operation replay exceeds its record bound.");
|
|
1840
|
+
verifyDependencies(records);
|
|
1841
|
+
const refs = sortedRecords(records.values()).map(knowledgeGraphRecordRefV1);
|
|
1842
|
+
const recordsSha256 = canonicalSha256(refs);
|
|
1843
|
+
const graphRevisionSha256 = graphRevisionSha256V1({
|
|
1844
|
+
changes: operation.changes,
|
|
1845
|
+
operationId: operation.operationId,
|
|
1846
|
+
parentGraphRevisionSha256: head.graphRevisionSha256,
|
|
1847
|
+
recordsSha256,
|
|
1848
|
+
revision: operation.sequence
|
|
1849
|
+
});
|
|
1850
|
+
if (recordsSha256 !== operation.recordsSha256 || graphRevisionSha256 !== operation.graphRevisionSha256) {
|
|
1851
|
+
throw new OhIntegrityError("Replay does not reproduce an operation head.");
|
|
1644
1852
|
}
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
}, aggregate, false);
|
|
1654
|
-
} catch {
|
|
1655
|
-
return null;
|
|
1853
|
+
head = {
|
|
1854
|
+
generation: operation.sequence,
|
|
1855
|
+
graphRevisionSha256,
|
|
1856
|
+
operationSha256: operation.operationSha256,
|
|
1857
|
+
recordsSha256,
|
|
1858
|
+
sequence: operation.sequence,
|
|
1859
|
+
v: 1
|
|
1860
|
+
};
|
|
1656
1861
|
}
|
|
1862
|
+
return { head, records: sortedRecords(records.values()), v: 1 };
|
|
1657
1863
|
}
|
|
1658
|
-
function
|
|
1659
|
-
const
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
}
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
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) {
|
|
1695
|
-
let priorSequence = null;
|
|
1696
|
-
let priorSha256 = null;
|
|
1697
|
-
const parsed = [];
|
|
1698
|
-
const bundleBudget = syncIngressBundleBudgetV1(parsedSpaceId);
|
|
1699
|
-
for (const candidate of operations) {
|
|
1700
|
-
const operation = parseOhOperationV1(candidate);
|
|
1701
|
-
if (operation === null || operation.spaceId !== parsedSpaceId || priorSequence !== null && operation.sequence !== priorSequence + 1 || priorSequence !== null && operation.parentOperationSha256 !== priorSha256) {
|
|
1702
|
-
throw new TypeError("Sync operations must form one ordered chain.");
|
|
1703
|
-
}
|
|
1704
|
-
const measurement = measureSyncOperationV1(operation);
|
|
1705
|
-
if (measurement === null) {
|
|
1706
|
-
throw new RangeError("Sync operation exceeds its canonical byte, node, or depth limit.");
|
|
1864
|
+
function transitionOhSnapshotV1(input) {
|
|
1865
|
+
const actorId = safeCode(input.actorId);
|
|
1866
|
+
const operationId = safeCode(input.operationId);
|
|
1867
|
+
const spaceId = safeCode(input.spaceId);
|
|
1868
|
+
const instant = parseCanonicalInstantV1(input.instant);
|
|
1869
|
+
const changes = canonicalKnowledgeGraphChangesV1(input.changes);
|
|
1870
|
+
if (actorId === null || operationId === null || spaceId === null || instant === null || changes.length === 0 || changes.length > OH_GRAPH_LIMITS_V1.changesPerOperation) {
|
|
1871
|
+
throw new TypeError("Invalid graph transition input.");
|
|
1872
|
+
}
|
|
1873
|
+
const head = parseOhHeadV1(input.snapshot.head);
|
|
1874
|
+
if (input.snapshot.v !== 1 || head === null || !Array.isArray(input.snapshot.records)) {
|
|
1875
|
+
throw new OhIntegrityError("The transition snapshot is invalid.");
|
|
1876
|
+
}
|
|
1877
|
+
const records = new Map;
|
|
1878
|
+
for (const value of input.snapshot.records) {
|
|
1879
|
+
const record = parseKnowledgeGraphRecordV1(value);
|
|
1880
|
+
if (record === null || records.has(record.key)) {
|
|
1881
|
+
throw new OhIntegrityError("The transition snapshot contains an invalid record.");
|
|
1707
1882
|
}
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1883
|
+
records.set(record.key, record);
|
|
1884
|
+
}
|
|
1885
|
+
verifyDependencies(records);
|
|
1886
|
+
const priorRecordsSha256 = canonicalSha256(sortedRecords(records.values()).map(knowledgeGraphRecordRefV1));
|
|
1887
|
+
if (priorRecordsSha256 !== head.recordsSha256) {
|
|
1888
|
+
throw new OhIntegrityError("The transition snapshot does not reproduce its head.");
|
|
1889
|
+
}
|
|
1890
|
+
for (const change of changes) {
|
|
1891
|
+
if (change.kind === "put")
|
|
1892
|
+
records.set(change.record.key, change.record);
|
|
1893
|
+
else {
|
|
1894
|
+
const prior = records.get(change.key);
|
|
1895
|
+
if (prior === undefined || prior.recordSha256 !== change.priorSha256) {
|
|
1896
|
+
throw new OhConflictError(`The prior digest for ${change.key} does not match the snapshot.`);
|
|
1897
|
+
}
|
|
1898
|
+
records.delete(change.key);
|
|
1712
1899
|
}
|
|
1713
|
-
parsed.push(operation);
|
|
1714
|
-
priorSequence = operation.sequence;
|
|
1715
|
-
priorSha256 = operation.operationSha256;
|
|
1716
1900
|
}
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1901
|
+
if (records.size > OH_GRAPH_LIMITS_V1.recordsPerSnapshot) {
|
|
1902
|
+
throw new RangeError("Graph transition exceeds its record snapshot limit.");
|
|
1903
|
+
}
|
|
1904
|
+
verifyDependencies(records);
|
|
1905
|
+
const nextRecords = sortedRecords(records.values());
|
|
1906
|
+
const recordsSha256 = canonicalSha256(nextRecords.map(knowledgeGraphRecordRefV1));
|
|
1907
|
+
const graphRevisionSha256 = graphRevisionSha256V1({
|
|
1908
|
+
changes,
|
|
1909
|
+
operationId,
|
|
1910
|
+
parentGraphRevisionSha256: head.graphRevisionSha256,
|
|
1911
|
+
recordsSha256,
|
|
1912
|
+
revision: head.sequence + 1
|
|
1913
|
+
});
|
|
1914
|
+
const operation = createOhOperationV1({
|
|
1915
|
+
actorId,
|
|
1916
|
+
changes,
|
|
1917
|
+
contractId: OH_CONTRACT_MANIFEST_V1.contractId,
|
|
1918
|
+
graphRevisionSha256,
|
|
1919
|
+
instant,
|
|
1920
|
+
operationId,
|
|
1921
|
+
parentOperationSha256: head.operationSha256,
|
|
1922
|
+
recordsSha256,
|
|
1923
|
+
sequence: head.sequence + 1,
|
|
1924
|
+
spaceId,
|
|
1925
|
+
v: 1
|
|
1926
|
+
}, input.maximumOperationBytes === undefined ? {} : {
|
|
1927
|
+
maximumOperationBytes: input.maximumOperationBytes
|
|
1928
|
+
});
|
|
1929
|
+
const nextHead = {
|
|
1930
|
+
generation: operation.sequence,
|
|
1931
|
+
graphRevisionSha256,
|
|
1932
|
+
operationSha256: operation.operationSha256,
|
|
1933
|
+
recordsSha256,
|
|
1934
|
+
sequence: operation.sequence,
|
|
1722
1935
|
v: 1
|
|
1723
1936
|
};
|
|
1724
|
-
return {
|
|
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;
|
|
1937
|
+
return { operation, snapshot: { head: nextHead, records: nextRecords, v: 1 } };
|
|
1730
1938
|
}
|
|
1731
|
-
function
|
|
1732
|
-
|
|
1733
|
-
|
|
1939
|
+
function normalizeRoots(values) {
|
|
1940
|
+
if (!Array.isArray(values) || values.length < 1 || values.length > OH_DEPENDENCY_CLOSURE_LIMITS_V1.roots) {
|
|
1941
|
+
throw new RangeError(`A dependency closure needs 1 through ${OH_DEPENDENCY_CLOSURE_LIMITS_V1.roots} roots.`);
|
|
1942
|
+
}
|
|
1943
|
+
const roots = values.map((value) => safeCode(value, 512));
|
|
1944
|
+
if (roots.some((value) => value === null))
|
|
1945
|
+
throw new TypeError("Invalid dependency closure root.");
|
|
1946
|
+
const sorted = [...roots].sort();
|
|
1947
|
+
if (sorted.some((value, index) => index > 0 && sorted[index - 1] === value)) {
|
|
1948
|
+
throw new TypeError("Dependency closure roots must be unique.");
|
|
1949
|
+
}
|
|
1950
|
+
return sorted;
|
|
1734
1951
|
}
|
|
1735
|
-
function
|
|
1736
|
-
const
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1952
|
+
function closureRecords(available, roots, maximumRecords, maximumBytes = OH_DEPENDENCY_CLOSURE_LIMITS_V1.bytes - 64 * 1024) {
|
|
1953
|
+
const selected = new Map;
|
|
1954
|
+
const pending = [...roots];
|
|
1955
|
+
let selectedBytes = 0;
|
|
1956
|
+
while (pending.length > 0) {
|
|
1957
|
+
const key = pending.pop();
|
|
1958
|
+
if (selected.has(key))
|
|
1959
|
+
continue;
|
|
1960
|
+
const record = available.get(key);
|
|
1961
|
+
if (record === undefined)
|
|
1962
|
+
throw new OhDependencyError(`Dependency closure record ${key} is missing.`);
|
|
1963
|
+
selectedBytes += Buffer.byteLength(canonicalJson(record), "utf8") + 1;
|
|
1964
|
+
if (selectedBytes > maximumBytes)
|
|
1965
|
+
throw new RangeError("Dependency closure exceeds its canonical byte bound.");
|
|
1966
|
+
selected.set(key, record);
|
|
1967
|
+
if (selected.size > maximumRecords)
|
|
1968
|
+
throw new RangeError("Dependency closure exceeds its record bound.");
|
|
1969
|
+
pending.push(...record.dependencies);
|
|
1970
|
+
}
|
|
1971
|
+
return sortedRecords(selected.values());
|
|
1741
1972
|
}
|
|
1742
|
-
function
|
|
1743
|
-
const
|
|
1744
|
-
const
|
|
1745
|
-
|
|
1746
|
-
|
|
1973
|
+
function createOhDependencyClosureV1(input) {
|
|
1974
|
+
const binding = parseOhStoreBindingV1(input.binding);
|
|
1975
|
+
const head = parseOhHeadV1(input.snapshot.head);
|
|
1976
|
+
const maximumRecords = input.maximumRecords ?? OH_DEPENDENCY_CLOSURE_LIMITS_V1.records;
|
|
1977
|
+
if (binding === null || head === null || !Number.isSafeInteger(maximumRecords) || maximumRecords < 1 || maximumRecords > OH_DEPENDENCY_CLOSURE_LIMITS_V1.records) {
|
|
1978
|
+
throw new TypeError("Invalid dependency closure input.");
|
|
1747
1979
|
}
|
|
1748
|
-
|
|
1980
|
+
const roots = normalizeRoots(input.roots);
|
|
1981
|
+
const available = new Map;
|
|
1982
|
+
for (const value of input.snapshot.records) {
|
|
1983
|
+
const record = parseKnowledgeGraphRecordV1(value);
|
|
1984
|
+
if (record === null || available.has(record.key))
|
|
1985
|
+
throw new OhIntegrityError("Snapshot contains an invalid record.");
|
|
1986
|
+
available.set(record.key, record);
|
|
1987
|
+
}
|
|
1988
|
+
const recordsSha256 = canonicalSha256(sortedRecords(available.values()).map(knowledgeGraphRecordRefV1));
|
|
1989
|
+
if (recordsSha256 !== head.recordsSha256)
|
|
1990
|
+
throw new OhIntegrityError("Snapshot records do not reproduce its head.");
|
|
1991
|
+
const records = closureRecords(available, roots, maximumRecords);
|
|
1992
|
+
const payload = { binding, head, records, roots, v: 1 };
|
|
1993
|
+
const closure = { ...payload, closureSha256: canonicalSha256(payload) };
|
|
1994
|
+
if (Buffer.byteLength(canonicalJson(closure), "utf8") > OH_DEPENDENCY_CLOSURE_LIMITS_V1.bytes) {
|
|
1995
|
+
throw new RangeError("Dependency closure exceeds its canonical byte bound.");
|
|
1996
|
+
}
|
|
1997
|
+
return Object.freeze(closure);
|
|
1749
1998
|
}
|
|
1750
|
-
function
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1999
|
+
function parseOhDependencyClosureV1(value) {
|
|
2000
|
+
if (!isPlainRecord(value) || !hasExactKeys(value, [
|
|
2001
|
+
"binding",
|
|
2002
|
+
"closureSha256",
|
|
2003
|
+
"head",
|
|
2004
|
+
"records",
|
|
2005
|
+
"roots",
|
|
2006
|
+
"v"
|
|
2007
|
+
]) || value.v !== 1 || !Array.isArray(value.records) || !Array.isArray(value.roots) || value.records.length > OH_DEPENDENCY_CLOSURE_LIMITS_V1.records)
|
|
1758
2008
|
return null;
|
|
1759
|
-
const
|
|
1760
|
-
|
|
2009
|
+
const binding = parseOhStoreBindingV1(value.binding);
|
|
2010
|
+
const head = parseOhHeadV1(value.head);
|
|
2011
|
+
const closureSha256 = parseSha256Hex(value.closureSha256);
|
|
2012
|
+
if (binding === null || head === null || closureSha256 === null)
|
|
1761
2013
|
return null;
|
|
1762
|
-
const
|
|
1763
|
-
const
|
|
1764
|
-
|
|
1765
|
-
if (
|
|
1766
|
-
return null;
|
|
1767
|
-
const detached = stagedSyncOperationV1(operation, bundleBudget);
|
|
1768
|
-
if (detached === null)
|
|
2014
|
+
const records = new Map;
|
|
2015
|
+
for (const item of value.records) {
|
|
2016
|
+
const record = parseKnowledgeGraphRecordV1(item);
|
|
2017
|
+
if (record === null || records.has(record.key))
|
|
1769
2018
|
return null;
|
|
1770
|
-
|
|
2019
|
+
records.set(record.key, record);
|
|
1771
2020
|
}
|
|
1772
2021
|
try {
|
|
1773
|
-
const
|
|
1774
|
-
if (
|
|
2022
|
+
const roots = normalizeRoots(value.roots);
|
|
2023
|
+
if (canonicalJson(roots) !== canonicalJson(value.roots))
|
|
1775
2024
|
return null;
|
|
1776
|
-
|
|
2025
|
+
const exact = closureRecords(records, roots, OH_DEPENDENCY_CLOSURE_LIMITS_V1.records);
|
|
2026
|
+
if (canonicalJson(exact) !== canonicalJson(value.records))
|
|
2027
|
+
return null;
|
|
2028
|
+
const payload = { binding, head, records: exact, roots, v: 1 };
|
|
2029
|
+
const parsed = { ...payload, closureSha256 };
|
|
2030
|
+
return canonicalSha256(payload) === closureSha256 && Buffer.byteLength(canonicalJson(parsed), "utf8") <= OH_DEPENDENCY_CLOSURE_LIMITS_V1.bytes ? Object.freeze(parsed) : null;
|
|
1777
2031
|
} catch {
|
|
1778
2032
|
return null;
|
|
1779
2033
|
}
|
|
1780
2034
|
}
|
|
1781
|
-
|
|
1782
|
-
const
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
const
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
return {
|
|
1797
|
-
};
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
throw new Error("Sync conflict: equal sequence numbers have different heads.");
|
|
1806
|
-
}
|
|
1807
|
-
return settled(remote, round);
|
|
1808
|
-
}
|
|
1809
|
-
if (local.sequence < remote.sequence) {
|
|
1810
|
-
const bundle = parseOhSyncBundleV1(await transport.pull(store.spaceId, local.sequence, batchSize));
|
|
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) {
|
|
1813
|
-
throw new Error("Sync conflict: remote history does not extend the local head.");
|
|
1814
|
-
}
|
|
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
|
-
}
|
|
1832
|
-
}
|
|
1833
|
-
} else {
|
|
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) {
|
|
1845
|
-
throw new Error("Sync conflict: local history does not extend the remote head.");
|
|
1846
|
-
}
|
|
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.");
|
|
1854
|
-
if (head.sequence !== operations.at(-1)?.sequence || head.operationSha256 !== operations.at(-1)?.operationSha256) {
|
|
1855
|
-
throw new Error("The sync transport acknowledged a different head.");
|
|
1856
|
-
}
|
|
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
|
-
}
|
|
1868
|
-
}
|
|
2035
|
+
function verifyOhDependencyClosureV1(value) {
|
|
2036
|
+
const closure = parseOhDependencyClosureV1(value);
|
|
2037
|
+
return closure === null ? { ok: false, reason: "invalid-closure" } : { closure, ok: true };
|
|
2038
|
+
}
|
|
2039
|
+
function verifyOhDependencyClosureAgainstV1(value, expected) {
|
|
2040
|
+
const binding = parseOhStoreBindingV1(expected.binding);
|
|
2041
|
+
const head = parseOhHeadV1(expected.head);
|
|
2042
|
+
if (binding === null || head === null)
|
|
2043
|
+
return { ok: false, reason: "invalid-expectation" };
|
|
2044
|
+
const closure = parseOhDependencyClosureV1(value);
|
|
2045
|
+
if (closure === null)
|
|
2046
|
+
return { ok: false, reason: "invalid-closure" };
|
|
2047
|
+
if (closure.binding.bindingSha256 !== binding.bindingSha256)
|
|
2048
|
+
return { ok: false, reason: "binding-mismatch" };
|
|
2049
|
+
if (canonicalJson(closure.head) !== canonicalJson(head))
|
|
2050
|
+
return { ok: false, reason: "head-mismatch" };
|
|
2051
|
+
return { closure, ok: true, verification: "expected-authority-and-head" };
|
|
2052
|
+
}
|
|
2053
|
+
function createOhSpacePurgeReceiptV1(input) {
|
|
2054
|
+
const binding = parseOhStoreBindingV1(input.binding);
|
|
2055
|
+
const priorHead = parseOhHeadV1(input.priorHead);
|
|
2056
|
+
const purgedAt = parseCanonicalInstantV1(input.purgedAt);
|
|
2057
|
+
if (binding === null || priorHead === null || purgedAt === null || binding.profile.profileKind !== "working" || !binding.profile.capabilities.wholeSpacePurge) {
|
|
2058
|
+
throw new OhProfileError("Only a bound working realm can produce a purge receipt.");
|
|
1869
2059
|
}
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
let ready = null;
|
|
1877
|
-
const setup = async (manifest) => {
|
|
1878
|
-
if (parseOhContractManifestV1(manifest) === null)
|
|
1879
|
-
throw new Error("Unsupported contract manifest.");
|
|
1880
|
-
await client.batch([
|
|
1881
|
-
{ sql: `CREATE TABLE IF NOT EXISTS oh_sync_contracts (
|
|
1882
|
-
contract_id TEXT PRIMARY KEY, contract_sha256 TEXT NOT NULL, manifest_json TEXT NOT NULL
|
|
1883
|
-
) STRICT` },
|
|
1884
|
-
{ sql: `CREATE TABLE IF NOT EXISTS oh_sync_operations (
|
|
1885
|
-
space_id TEXT NOT NULL, sequence INTEGER NOT NULL, operation_sha256 TEXT NOT NULL UNIQUE,
|
|
1886
|
-
operation_json TEXT NOT NULL, PRIMARY KEY(space_id, sequence)
|
|
1887
|
-
) STRICT` },
|
|
1888
|
-
{
|
|
1889
|
-
sql: "INSERT INTO oh_sync_contracts(contract_id, contract_sha256, manifest_json) VALUES (?, ?, ?) ON CONFLICT(contract_id) DO NOTHING",
|
|
1890
|
-
args: [manifest.contractId, manifest.contractSha256, canonicalJson(manifest)]
|
|
1891
|
-
}
|
|
1892
|
-
], "write");
|
|
1893
|
-
const result = await client.execute({
|
|
1894
|
-
sql: "SELECT contract_sha256, manifest_json FROM oh_sync_contracts WHERE contract_id = ?",
|
|
1895
|
-
args: [manifest.contractId]
|
|
1896
|
-
});
|
|
1897
|
-
const row = result.rows[0];
|
|
1898
|
-
if (row === undefined || rowValue(row, "contract_sha256", 0) !== manifest.contractSha256 || rowValue(row, "manifest_json", 1) !== canonicalJson(manifest)) {
|
|
1899
|
-
throw new Error("Remote contract manifest mismatch.");
|
|
1900
|
-
}
|
|
1901
|
-
};
|
|
1902
|
-
const ensure = (manifest = OH_CONTRACT_MANIFEST_V1) => {
|
|
1903
|
-
ready ??= setup(manifest).catch((error) => {
|
|
1904
|
-
ready = null;
|
|
1905
|
-
throw error;
|
|
1906
|
-
});
|
|
1907
|
-
return ready;
|
|
1908
|
-
};
|
|
1909
|
-
const head = async (spaceId) => {
|
|
1910
|
-
await ensure();
|
|
1911
|
-
const result = await client.execute({ sql: `SELECT sequence, operation_sha256 FROM oh_sync_operations
|
|
1912
|
-
WHERE space_id = ? ORDER BY sequence DESC LIMIT 1`, args: [spaceId] });
|
|
1913
|
-
const row = result.rows[0];
|
|
1914
|
-
if (row === undefined)
|
|
1915
|
-
return { operationSha256: null, sequence: 0, v: 1 };
|
|
1916
|
-
const sequence = Number(rowValue(row, "sequence", 0));
|
|
1917
|
-
const operationSha256 = parseSha256Hex(rowValue(row, "operation_sha256", 1));
|
|
1918
|
-
if (!Number.isSafeInteger(sequence) || sequence < 1 || operationSha256 === null)
|
|
1919
|
-
throw new Error("Invalid remote head.");
|
|
1920
|
-
return { operationSha256, sequence, v: 1 };
|
|
2060
|
+
const payload = {
|
|
2061
|
+
bindingSha256: binding.bindingSha256,
|
|
2062
|
+
priorHead,
|
|
2063
|
+
purgedAt,
|
|
2064
|
+
spaceId: binding.spaceId,
|
|
2065
|
+
v: 1
|
|
1921
2066
|
};
|
|
1922
|
-
return {
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
2067
|
+
return Object.freeze({ ...payload, receiptSha256: canonicalSha256(payload) });
|
|
2068
|
+
}
|
|
2069
|
+
function parseOhSpacePurgeReceiptV1(value) {
|
|
2070
|
+
if (!isPlainRecord(value) || !hasExactKeys(value, [
|
|
2071
|
+
"bindingSha256",
|
|
2072
|
+
"priorHead",
|
|
2073
|
+
"purgedAt",
|
|
2074
|
+
"receiptSha256",
|
|
2075
|
+
"spaceId",
|
|
2076
|
+
"v"
|
|
2077
|
+
]) || value.v !== 1)
|
|
2078
|
+
return null;
|
|
2079
|
+
const bindingSha256 = parseSha256Hex(value.bindingSha256);
|
|
2080
|
+
const priorHead = parseOhHeadV1(value.priorHead);
|
|
2081
|
+
const purgedAt = parseCanonicalInstantV1(value.purgedAt);
|
|
2082
|
+
const receiptSha256 = parseSha256Hex(value.receiptSha256);
|
|
2083
|
+
const spaceId = safeCode(value.spaceId);
|
|
2084
|
+
if (bindingSha256 === null || priorHead === null || purgedAt === null || receiptSha256 === null || spaceId === null)
|
|
2085
|
+
return null;
|
|
2086
|
+
const payload = { bindingSha256, priorHead, purgedAt, spaceId, v: 1 };
|
|
2087
|
+
return canonicalSha256(payload) === receiptSha256 ? Object.freeze({ ...payload, receiptSha256 }) : null;
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
class OhSemanticBundleIngressV1 {
|
|
2091
|
+
#codecs;
|
|
2092
|
+
#store;
|
|
2093
|
+
constructor(store, codecs) {
|
|
2094
|
+
this.#store = store;
|
|
2095
|
+
this.#codecs = codecs.seal();
|
|
2096
|
+
}
|
|
2097
|
+
async commit(value) {
|
|
2098
|
+
if (!isPlainRecord(value) || !hasExactKeys(value, [
|
|
2099
|
+
"actorId",
|
|
2100
|
+
"expectedHead",
|
|
2101
|
+
"instant",
|
|
2102
|
+
"operationId",
|
|
2103
|
+
"puts",
|
|
2104
|
+
"tombstones",
|
|
2105
|
+
"v"
|
|
2106
|
+
]) || value.v !== 1 || !Array.isArray(value.puts) || !Array.isArray(value.tombstones) || value.puts.length + value.tombstones.length < 1 || value.puts.length + value.tombstones.length > OH_GRAPH_LIMITS_V1.changesPerOperation) {
|
|
2107
|
+
throw new TypeError("Invalid semantic bundle.");
|
|
2108
|
+
}
|
|
2109
|
+
const actorId = safeCode(value.actorId);
|
|
2110
|
+
const operationId = safeCode(value.operationId);
|
|
2111
|
+
const expected = value.expectedHead;
|
|
2112
|
+
const instant = value.instant === null ? undefined : parseCanonicalInstantV1(value.instant);
|
|
2113
|
+
if (actorId === null || operationId === null || !isPlainRecord(expected) || !hasExactKeys(expected, ["generation", "operationSha256"]) || !Number.isSafeInteger(expected.generation) || expected.generation < 0 || expected.operationSha256 !== null && parseSha256Hex(expected.operationSha256) === null || expected.generation === 0 !== (expected.operationSha256 === null) || value.instant !== null && instant === null)
|
|
2114
|
+
throw new TypeError("Invalid semantic bundle identity.");
|
|
2115
|
+
const changes = [];
|
|
2116
|
+
for (const item of value.puts) {
|
|
2117
|
+
if (!isPlainRecord(item) || !hasExactKeys(item, ["dependencies", "key", "kind", "v", "value"]) || item.v !== 1 || !Array.isArray(item.dependencies) || !OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1.some((kind) => kind === item.kind)) {
|
|
2118
|
+
throw new TypeError("Invalid semantic bundle put.");
|
|
1959
2119
|
}
|
|
1960
|
-
const
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
throw new Error("Invalid remote operation.");
|
|
1970
|
-
return operation;
|
|
2120
|
+
const parsed = this.#codecs.parseRequired(item.kind, item.value);
|
|
2121
|
+
if (parsed === null)
|
|
2122
|
+
throw new TypeError(`The ${String(item.kind)} codec rejected a semantic value.`);
|
|
2123
|
+
const record = createKnowledgeGraphRecordV1({
|
|
2124
|
+
dependencies: item.dependencies,
|
|
2125
|
+
key: item.key,
|
|
2126
|
+
kind: item.kind,
|
|
2127
|
+
v: 1,
|
|
2128
|
+
value: parsed
|
|
1971
2129
|
});
|
|
1972
|
-
|
|
1973
|
-
}
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
if (bundle === null)
|
|
1978
|
-
throw new Error("Invalid outgoing sync bundle.");
|
|
1979
|
-
if (bundle.operations.length === 0)
|
|
1980
|
-
return head(bundle.spaceId);
|
|
1981
|
-
const remote = await head(bundle.spaceId);
|
|
1982
|
-
const first = bundle.operations[0];
|
|
1983
|
-
const last = bundle.operations.at(-1);
|
|
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
|
-
}
|
|
2013
|
-
if (first.sequence !== remote.sequence + 1 || first.parentOperationSha256 !== remote.operationSha256) {
|
|
2014
|
-
throw new Error("Sync conflict: pushed history does not extend the remote head.");
|
|
2130
|
+
changes.push({ kind: "put", record, v: 1 });
|
|
2131
|
+
}
|
|
2132
|
+
for (const item of value.tombstones) {
|
|
2133
|
+
if (!isPlainRecord(item) || !hasExactKeys(item, ["key", "priorSha256", "v"]) || item.v !== 1) {
|
|
2134
|
+
throw new TypeError("Invalid semantic bundle tombstone.");
|
|
2015
2135
|
}
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
return { operationSha256: last.operationSha256, sequence: last.sequence, v: 1 };
|
|
2136
|
+
const priorSha256 = parseSha256Hex(item.priorSha256);
|
|
2137
|
+
if (typeof item.key !== "string" || priorSha256 === null)
|
|
2138
|
+
throw new TypeError("Invalid semantic bundle tombstone.");
|
|
2139
|
+
changes.push({ key: item.key, kind: "tombstone", priorSha256, v: 1 });
|
|
2021
2140
|
}
|
|
2022
|
-
|
|
2141
|
+
const canonical = canonicalKnowledgeGraphChangesV1(changes);
|
|
2142
|
+
return await this.#store.commit({
|
|
2143
|
+
actorId,
|
|
2144
|
+
changes: canonical,
|
|
2145
|
+
expectedHead: {
|
|
2146
|
+
generation: expected.generation,
|
|
2147
|
+
operationSha256: expected.operationSha256
|
|
2148
|
+
},
|
|
2149
|
+
...typeof instant === "string" ? { instant } : {},
|
|
2150
|
+
operationId
|
|
2151
|
+
});
|
|
2152
|
+
}
|
|
2023
2153
|
}
|
|
2024
2154
|
|
|
2025
|
-
// src/
|
|
2026
|
-
import {
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
"
|
|
2032
|
-
"
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2155
|
+
// src/sync-model.ts
|
|
2156
|
+
import { isProxy } from "util/types";
|
|
2157
|
+
var OH_SYNC_PROTOCOL_V1 = "oh.sync.v1";
|
|
2158
|
+
var OH_SYNC_BUNDLE_MAX_OPERATIONS_V1 = 1000;
|
|
2159
|
+
var OH_SYNC_BUNDLE_KEYS_V1 = [
|
|
2160
|
+
"bundleSha256",
|
|
2161
|
+
"contractSha256",
|
|
2162
|
+
"operations",
|
|
2163
|
+
"protocol",
|
|
2164
|
+
"spaceId",
|
|
2165
|
+
"v"
|
|
2166
|
+
];
|
|
2167
|
+
var OH_SYNC_HEAD_KEYS_V1 = ["operationSha256", "sequence", "v"];
|
|
2168
|
+
var OH_SYNC_HEAD_REF_KEYS_V1 = ["operationSha256", "sequence"];
|
|
2169
|
+
var OH_OPERATION_KEYS_V1 = [
|
|
2170
|
+
"actorId",
|
|
2171
|
+
"changes",
|
|
2172
|
+
"contractId",
|
|
2173
|
+
"graphRevisionSha256",
|
|
2174
|
+
"instant",
|
|
2175
|
+
"operationId",
|
|
2176
|
+
"operationSha256",
|
|
2177
|
+
"parentOperationSha256",
|
|
2178
|
+
"recordsSha256",
|
|
2179
|
+
"sequence",
|
|
2180
|
+
"spaceId",
|
|
2181
|
+
"v"
|
|
2182
|
+
];
|
|
2183
|
+
var OH_PUT_CHANGE_KEYS_V1 = ["kind", "record", "v"];
|
|
2184
|
+
var OH_TOMBSTONE_CHANGE_KEYS_V1 = ["key", "kind", "priorSha256", "v"];
|
|
2185
|
+
var OH_RECORD_KEYS_V1 = ["dependencies", "key", "kind", "recordSha256", "v", "value"];
|
|
2186
|
+
var OH_SYNC_INGRESS_VALUE_DEPTH_V1 = 128;
|
|
2187
|
+
var OH_SYNC_INGRESS_VALUE_NODES_V1 = OH_GRAPH_LIMITS_V1.recordBytes;
|
|
2188
|
+
var OH_SYNC_INGRESS_OPERATION_DEPTH_V1 = OH_SYNC_INGRESS_VALUE_DEPTH_V1 + 4;
|
|
2189
|
+
var OH_SYNC_INGRESS_OPERATION_NODES_V1 = OH_OPERATION_MAX_BYTES_V1;
|
|
2190
|
+
var OH_SYNC_BUNDLE_MAX_BYTES_V1 = OH_OPERATION_MAX_BYTES_V1 + 4 * 1024;
|
|
2191
|
+
var OH_SYNC_INGRESS_BUNDLE_NODES_V1 = OH_SYNC_INGRESS_OPERATION_NODES_V1 + 4 * 1024;
|
|
2192
|
+
function exactDataRecordV1(value, expectedKeys) {
|
|
2193
|
+
try {
|
|
2194
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || isProxy(value))
|
|
2195
|
+
return null;
|
|
2196
|
+
const prototype = Object.getPrototypeOf(value);
|
|
2197
|
+
const keys = Reflect.ownKeys(value);
|
|
2198
|
+
if (prototype !== Object.prototype && prototype !== null || keys.length !== expectedKeys.length || keys.some((key) => typeof key !== "string") || expectedKeys.some((key) => !keys.includes(key)))
|
|
2199
|
+
return null;
|
|
2200
|
+
const detached = Object.create(null);
|
|
2201
|
+
for (const key of expectedKeys) {
|
|
2202
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
2203
|
+
if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined)
|
|
2204
|
+
return null;
|
|
2205
|
+
Object.defineProperty(detached, key, {
|
|
2206
|
+
configurable: false,
|
|
2207
|
+
enumerable: true,
|
|
2208
|
+
value: descriptor.value,
|
|
2209
|
+
writable: false
|
|
2210
|
+
});
|
|
2049
2211
|
}
|
|
2212
|
+
return detached;
|
|
2213
|
+
} catch {
|
|
2214
|
+
return null;
|
|
2050
2215
|
}
|
|
2051
|
-
return Object.freeze({
|
|
2052
|
-
customLibrary,
|
|
2053
|
-
open: (path) => dependencies.open(path, { create: true, strict: true })
|
|
2054
|
-
});
|
|
2055
|
-
}
|
|
2056
|
-
|
|
2057
|
-
// src/sqlite/driver.ts
|
|
2058
|
-
var SQLITE_RUNTIME = createOhSqliteRuntime({
|
|
2059
|
-
exists: existsSync,
|
|
2060
|
-
open: (path, options) => new Database(path, options),
|
|
2061
|
-
platform: process.platform,
|
|
2062
|
-
setCustomSQLite: (path) => Database.setCustomSQLite(path)
|
|
2063
|
-
});
|
|
2064
|
-
function openOhSqliteDatabase(path) {
|
|
2065
|
-
const database = SQLITE_RUNTIME.open(path);
|
|
2066
|
-
database.exec("PRAGMA foreign_keys = ON");
|
|
2067
|
-
database.exec("PRAGMA journal_mode = WAL");
|
|
2068
|
-
database.exec("PRAGMA synchronous = NORMAL");
|
|
2069
|
-
database.exec("PRAGMA busy_timeout = 5000");
|
|
2070
|
-
database.exec("PRAGMA trusted_schema = OFF");
|
|
2071
|
-
return database;
|
|
2072
2216
|
}
|
|
2073
|
-
function
|
|
2074
|
-
database.exec("BEGIN IMMEDIATE");
|
|
2217
|
+
function exactDataArrayV1(value, maximumLength, clone = true) {
|
|
2075
2218
|
try {
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
} catch {}
|
|
2096
|
-
throw error;
|
|
2097
|
-
}
|
|
2098
|
-
}
|
|
2099
|
-
// src/sqlite/migrations.ts
|
|
2100
|
-
var OH_SQLITE_SCHEMA_VERSION = 2;
|
|
2101
|
-
var OH_SQLITE_MIGRATIONS = Object.freeze([
|
|
2102
|
-
Object.freeze({
|
|
2103
|
-
name: "0001_oh_core",
|
|
2104
|
-
version: 1,
|
|
2105
|
-
sql: `
|
|
2106
|
-
CREATE TABLE oh_contracts (
|
|
2107
|
-
contract_id TEXT PRIMARY KEY,
|
|
2108
|
-
contract_sha256 TEXT NOT NULL CHECK(length(contract_sha256) = 64),
|
|
2109
|
-
manifest_json TEXT NOT NULL CHECK(json_valid(manifest_json)),
|
|
2110
|
-
created_at TEXT NOT NULL
|
|
2111
|
-
) STRICT;
|
|
2112
|
-
|
|
2113
|
-
CREATE TABLE oh_spaces (
|
|
2114
|
-
space_id TEXT PRIMARY KEY,
|
|
2115
|
-
contract_id TEXT NOT NULL REFERENCES oh_contracts(contract_id),
|
|
2116
|
-
generation INTEGER NOT NULL CHECK(generation >= 0),
|
|
2117
|
-
head_operation_sha256 TEXT CHECK(head_operation_sha256 IS NULL OR length(head_operation_sha256) = 64),
|
|
2118
|
-
graph_revision_sha256 TEXT CHECK(graph_revision_sha256 IS NULL OR length(graph_revision_sha256) = 64),
|
|
2119
|
-
records_sha256 TEXT NOT NULL CHECK(length(records_sha256) = 64),
|
|
2120
|
-
sequence INTEGER NOT NULL CHECK(sequence >= 0),
|
|
2121
|
-
created_at TEXT NOT NULL,
|
|
2122
|
-
updated_at TEXT NOT NULL,
|
|
2123
|
-
CHECK(generation = sequence),
|
|
2124
|
-
CHECK((sequence = 0) = (head_operation_sha256 IS NULL)),
|
|
2125
|
-
CHECK((sequence = 0) = (graph_revision_sha256 IS NULL))
|
|
2126
|
-
) STRICT;
|
|
2127
|
-
|
|
2128
|
-
CREATE TABLE oh_operations (
|
|
2129
|
-
operation_sha256 TEXT PRIMARY KEY CHECK(length(operation_sha256) = 64),
|
|
2130
|
-
space_id TEXT NOT NULL REFERENCES oh_spaces(space_id),
|
|
2131
|
-
sequence INTEGER NOT NULL CHECK(sequence > 0),
|
|
2132
|
-
operation_id TEXT NOT NULL,
|
|
2133
|
-
parent_operation_sha256 TEXT CHECK(parent_operation_sha256 IS NULL OR length(parent_operation_sha256) = 64),
|
|
2134
|
-
graph_revision_sha256 TEXT NOT NULL CHECK(length(graph_revision_sha256) = 64),
|
|
2135
|
-
records_sha256 TEXT NOT NULL CHECK(length(records_sha256) = 64),
|
|
2136
|
-
operation_json TEXT NOT NULL CHECK(json_valid(operation_json)),
|
|
2137
|
-
instant TEXT NOT NULL,
|
|
2138
|
-
UNIQUE(space_id, sequence),
|
|
2139
|
-
UNIQUE(space_id, operation_id)
|
|
2140
|
-
) STRICT;
|
|
2141
|
-
|
|
2142
|
-
CREATE TABLE oh_operation_records (
|
|
2143
|
-
operation_sha256 TEXT NOT NULL REFERENCES oh_operations(operation_sha256),
|
|
2144
|
-
ordinal INTEGER NOT NULL CHECK(ordinal >= 0),
|
|
2145
|
-
record_key TEXT NOT NULL,
|
|
2146
|
-
change_kind TEXT NOT NULL CHECK(change_kind IN ('put', 'tombstone')),
|
|
2147
|
-
record_sha256 TEXT CHECK(record_sha256 IS NULL OR length(record_sha256) = 64),
|
|
2148
|
-
PRIMARY KEY(operation_sha256, ordinal),
|
|
2149
|
-
UNIQUE(operation_sha256, record_key)
|
|
2150
|
-
) STRICT;
|
|
2151
|
-
|
|
2152
|
-
CREATE TABLE oh_records (
|
|
2153
|
-
space_id TEXT NOT NULL REFERENCES oh_spaces(space_id),
|
|
2154
|
-
record_key TEXT NOT NULL,
|
|
2155
|
-
kind TEXT NOT NULL,
|
|
2156
|
-
record_sha256 TEXT NOT NULL CHECK(length(record_sha256) = 64),
|
|
2157
|
-
record_json TEXT NOT NULL CHECK(json_valid(record_json)),
|
|
2158
|
-
operation_sha256 TEXT NOT NULL REFERENCES oh_operations(operation_sha256),
|
|
2159
|
-
sequence INTEGER NOT NULL CHECK(sequence > 0),
|
|
2160
|
-
PRIMARY KEY(space_id, record_key)
|
|
2161
|
-
) STRICT;
|
|
2162
|
-
|
|
2163
|
-
CREATE TABLE oh_dependencies (
|
|
2164
|
-
space_id TEXT NOT NULL,
|
|
2165
|
-
record_key TEXT NOT NULL,
|
|
2166
|
-
dependency_key TEXT NOT NULL,
|
|
2167
|
-
PRIMARY KEY(space_id, record_key, dependency_key),
|
|
2168
|
-
FOREIGN KEY(space_id, record_key) REFERENCES oh_records(space_id, record_key) ON DELETE CASCADE,
|
|
2169
|
-
FOREIGN KEY(space_id, dependency_key) REFERENCES oh_records(space_id, record_key)
|
|
2170
|
-
) STRICT;
|
|
2171
|
-
|
|
2172
|
-
CREATE TABLE oh_sync_outbox (
|
|
2173
|
-
space_id TEXT NOT NULL REFERENCES oh_spaces(space_id),
|
|
2174
|
-
sequence INTEGER NOT NULL,
|
|
2175
|
-
operation_sha256 TEXT NOT NULL REFERENCES oh_operations(operation_sha256),
|
|
2176
|
-
PRIMARY KEY(space_id, sequence),
|
|
2177
|
-
UNIQUE(operation_sha256)
|
|
2178
|
-
) STRICT;
|
|
2179
|
-
|
|
2180
|
-
CREATE TABLE oh_sync_state (
|
|
2181
|
-
remote_id TEXT NOT NULL,
|
|
2182
|
-
space_id TEXT NOT NULL REFERENCES oh_spaces(space_id),
|
|
2183
|
-
pulled_sequence INTEGER NOT NULL CHECK(pulled_sequence >= 0),
|
|
2184
|
-
pushed_sequence INTEGER NOT NULL CHECK(pushed_sequence >= 0),
|
|
2185
|
-
remote_head_sha256 TEXT CHECK(remote_head_sha256 IS NULL OR length(remote_head_sha256) = 64),
|
|
2186
|
-
updated_at TEXT NOT NULL,
|
|
2187
|
-
PRIMARY KEY(remote_id, space_id)
|
|
2188
|
-
) STRICT;
|
|
2189
|
-
|
|
2190
|
-
CREATE TABLE oh_search_documents (
|
|
2191
|
-
space_id TEXT NOT NULL,
|
|
2192
|
-
record_key TEXT NOT NULL,
|
|
2193
|
-
record_sha256 TEXT NOT NULL CHECK(length(record_sha256) = 64),
|
|
2194
|
-
text TEXT NOT NULL,
|
|
2195
|
-
PRIMARY KEY(space_id, record_key),
|
|
2196
|
-
FOREIGN KEY(space_id, record_key) REFERENCES oh_records(space_id, record_key) ON DELETE CASCADE
|
|
2197
|
-
) STRICT;
|
|
2198
|
-
|
|
2199
|
-
CREATE VIRTUAL TABLE oh_search_fts USING fts5(
|
|
2200
|
-
space_id UNINDEXED,
|
|
2201
|
-
record_key UNINDEXED,
|
|
2202
|
-
text,
|
|
2203
|
-
tokenize='unicode61 remove_diacritics 2'
|
|
2204
|
-
);
|
|
2205
|
-
|
|
2206
|
-
CREATE INDEX oh_operations_space_sequence ON oh_operations(space_id, sequence);
|
|
2207
|
-
CREATE INDEX oh_records_space_kind ON oh_records(space_id, kind, record_key);
|
|
2208
|
-
CREATE INDEX oh_dependencies_dependency ON oh_dependencies(space_id, dependency_key);
|
|
2209
|
-
`
|
|
2210
|
-
}),
|
|
2211
|
-
Object.freeze({
|
|
2212
|
-
name: "0002_store_realms",
|
|
2213
|
-
version: 2,
|
|
2214
|
-
sql: `
|
|
2215
|
-
CREATE TABLE oh_space_bindings (
|
|
2216
|
-
space_id TEXT PRIMARY KEY REFERENCES oh_spaces(space_id),
|
|
2217
|
-
realm_id TEXT NOT NULL,
|
|
2218
|
-
profile_id TEXT NOT NULL,
|
|
2219
|
-
profile_kind TEXT NOT NULL CHECK(profile_kind IN ('canonical', 'working')),
|
|
2220
|
-
profile_sha256 TEXT NOT NULL CHECK(length(profile_sha256) = 64),
|
|
2221
|
-
binding_sha256 TEXT NOT NULL UNIQUE CHECK(length(binding_sha256) = 64),
|
|
2222
|
-
binding_json TEXT NOT NULL CHECK(json_valid(binding_json)),
|
|
2223
|
-
created_at TEXT NOT NULL
|
|
2224
|
-
) STRICT;
|
|
2225
|
-
|
|
2226
|
-
CREATE TABLE oh_space_purges (
|
|
2227
|
-
space_id TEXT PRIMARY KEY,
|
|
2228
|
-
binding_sha256 TEXT NOT NULL CHECK(length(binding_sha256) = 64),
|
|
2229
|
-
prior_operation_sha256 TEXT CHECK(prior_operation_sha256 IS NULL OR length(prior_operation_sha256) = 64),
|
|
2230
|
-
prior_sequence INTEGER NOT NULL CHECK(prior_sequence >= 0),
|
|
2231
|
-
purged_at TEXT NOT NULL,
|
|
2232
|
-
receipt_sha256 TEXT NOT NULL UNIQUE CHECK(length(receipt_sha256) = 64),
|
|
2233
|
-
receipt_json TEXT NOT NULL CHECK(json_valid(receipt_json))
|
|
2234
|
-
) STRICT;
|
|
2235
|
-
`
|
|
2236
|
-
})
|
|
2237
|
-
]);
|
|
2238
|
-
function applyOhSqliteMigrations(database) {
|
|
2239
|
-
database.exec(`CREATE TABLE IF NOT EXISTS oh_migrations (
|
|
2240
|
-
version INTEGER PRIMARY KEY,
|
|
2241
|
-
name TEXT NOT NULL UNIQUE,
|
|
2242
|
-
migration_sha256 TEXT NOT NULL CHECK(length(migration_sha256) = 64),
|
|
2243
|
-
applied_at TEXT NOT NULL
|
|
2244
|
-
) STRICT`);
|
|
2245
|
-
const select = database.query("SELECT name, migration_sha256 FROM oh_migrations WHERE version = ?");
|
|
2246
|
-
const insert = database.query("INSERT INTO oh_migrations(version, name, migration_sha256, applied_at) VALUES (?, ?, ?, ?)");
|
|
2247
|
-
for (const migration of OH_SQLITE_MIGRATIONS) {
|
|
2248
|
-
const digest = sha256Hex(migration.sql);
|
|
2249
|
-
database.exec("BEGIN IMMEDIATE");
|
|
2250
|
-
try {
|
|
2251
|
-
const existing = select.get(migration.version);
|
|
2252
|
-
if (existing !== null) {
|
|
2253
|
-
if (existing.name !== migration.name || existing.migration_sha256 !== digest) {
|
|
2254
|
-
throw new Error(`SQLite migration ${migration.version} does not match the applied migration.`);
|
|
2255
|
-
}
|
|
2256
|
-
} else {
|
|
2257
|
-
database.exec(migration.sql);
|
|
2258
|
-
insert.run(migration.version, migration.name, digest, canonicalNow());
|
|
2259
|
-
}
|
|
2260
|
-
database.exec("COMMIT");
|
|
2261
|
-
} catch (error) {
|
|
2262
|
-
try {
|
|
2263
|
-
database.exec("ROLLBACK");
|
|
2264
|
-
} catch {}
|
|
2265
|
-
throw error;
|
|
2266
|
-
}
|
|
2267
|
-
}
|
|
2268
|
-
}
|
|
2269
|
-
// src/sqlite/port.ts
|
|
2270
|
-
import { isProxy as isProxy2 } from "util/types";
|
|
2271
|
-
// src/store.ts
|
|
2272
|
-
var OH_CANONICAL_STORE_PROFILE_V1 = createOhStoreProfileV1({
|
|
2273
|
-
applicationProfileSha256: null,
|
|
2274
|
-
capabilities: {
|
|
2275
|
-
changesSince: true,
|
|
2276
|
-
dependencyClosureExport: true,
|
|
2277
|
-
exactSnapshots: true,
|
|
2278
|
-
operationReplication: true,
|
|
2279
|
-
semanticBundleCommit: true,
|
|
2280
|
-
v: 1,
|
|
2281
|
-
wholeSpacePurge: false
|
|
2282
|
-
},
|
|
2283
|
-
profileId: "oh.store.canonical.v1",
|
|
2284
|
-
profileKind: "canonical",
|
|
2285
|
-
v: 1
|
|
2286
|
-
});
|
|
2287
|
-
var OH_WORKING_STORE_PROFILE_V1 = createOhStoreProfileV1({
|
|
2288
|
-
applicationProfileSha256: null,
|
|
2289
|
-
capabilities: {
|
|
2290
|
-
changesSince: true,
|
|
2291
|
-
dependencyClosureExport: true,
|
|
2292
|
-
exactSnapshots: true,
|
|
2293
|
-
operationReplication: false,
|
|
2294
|
-
semanticBundleCommit: true,
|
|
2295
|
-
v: 1,
|
|
2296
|
-
wholeSpacePurge: true
|
|
2297
|
-
},
|
|
2298
|
-
profileId: "oh.store.working.v1",
|
|
2299
|
-
profileKind: "working",
|
|
2300
|
-
v: 1
|
|
2301
|
-
});
|
|
2302
|
-
var OH_DEPENDENCY_CLOSURE_LIMITS_V1 = Object.freeze({
|
|
2303
|
-
bytes: 64 * 1024 * 1024,
|
|
2304
|
-
records: 8192,
|
|
2305
|
-
roots: 1024
|
|
2306
|
-
});
|
|
2307
|
-
|
|
2308
|
-
class OhPurgedSpaceError extends Error {
|
|
2309
|
-
receipt;
|
|
2310
|
-
constructor(receipt) {
|
|
2311
|
-
super(`Oh space ${receipt.spaceId} was purged at ${receipt.purgedAt}.`);
|
|
2312
|
-
this.name = "OhPurgedSpaceError";
|
|
2313
|
-
this.receipt = receipt;
|
|
2314
|
-
}
|
|
2315
|
-
}
|
|
2316
|
-
var EMPTY_RECORDS_SHA256 = canonicalSha256([]);
|
|
2317
|
-
function emptyOhHeadV1() {
|
|
2318
|
-
return {
|
|
2319
|
-
generation: 0,
|
|
2320
|
-
graphRevisionSha256: null,
|
|
2321
|
-
operationSha256: null,
|
|
2322
|
-
recordsSha256: EMPTY_RECORDS_SHA256,
|
|
2323
|
-
sequence: 0,
|
|
2324
|
-
v: 1
|
|
2325
|
-
};
|
|
2326
|
-
}
|
|
2327
|
-
function parseOhHeadV1(value) {
|
|
2328
|
-
if (!isPlainRecord(value) || !hasExactKeys(value, [
|
|
2329
|
-
"generation",
|
|
2330
|
-
"graphRevisionSha256",
|
|
2331
|
-
"operationSha256",
|
|
2332
|
-
"recordsSha256",
|
|
2333
|
-
"sequence",
|
|
2334
|
-
"v"
|
|
2335
|
-
]) || value.v !== 1)
|
|
2336
|
-
return null;
|
|
2337
|
-
const graphRevisionSha256 = value.graphRevisionSha256 === null ? null : parseSha256Hex(value.graphRevisionSha256);
|
|
2338
|
-
const operationSha256 = value.operationSha256 === null ? null : parseSha256Hex(value.operationSha256);
|
|
2339
|
-
const recordsSha256 = parseSha256Hex(value.recordsSha256);
|
|
2340
|
-
const generation = Number.isSafeInteger(value.generation) && value.generation >= 0 ? value.generation : null;
|
|
2341
|
-
const sequence = Number.isSafeInteger(value.sequence) && value.sequence >= 0 ? value.sequence : null;
|
|
2342
|
-
return generation !== null && sequence !== null && generation === sequence && recordsSha256 !== null && (value.graphRevisionSha256 === null || graphRevisionSha256 !== null) && (value.operationSha256 === null || operationSha256 !== null) && sequence === 0 === (operationSha256 === null) && sequence === 0 === (graphRevisionSha256 === null) ? { generation, graphRevisionSha256, operationSha256, recordsSha256, sequence, v: 1 } : null;
|
|
2343
|
-
}
|
|
2344
|
-
function parseOhHeadRefV1(value) {
|
|
2345
|
-
const complete = parseOhHeadV1(value);
|
|
2346
|
-
if (complete !== null) {
|
|
2347
|
-
return { operationSha256: complete.operationSha256, sequence: complete.sequence };
|
|
2348
|
-
}
|
|
2349
|
-
if (!isPlainRecord(value) || !hasExactKeys(value, ["operationSha256", "sequence"]))
|
|
2350
|
-
return null;
|
|
2351
|
-
const operationSha256 = value.operationSha256 === null ? null : parseSha256Hex(value.operationSha256);
|
|
2352
|
-
const sequence = Number.isSafeInteger(value.sequence) && value.sequence >= 0 ? value.sequence : null;
|
|
2353
|
-
return sequence !== null && (value.operationSha256 === null || operationSha256 !== null) && sequence === 0 === (operationSha256 === null) ? { operationSha256, sequence } : null;
|
|
2354
|
-
}
|
|
2355
|
-
function parseCapabilities(value) {
|
|
2356
|
-
if (!isPlainRecord(value) || !hasExactKeys(value, [
|
|
2357
|
-
"changesSince",
|
|
2358
|
-
"dependencyClosureExport",
|
|
2359
|
-
"exactSnapshots",
|
|
2360
|
-
"operationReplication",
|
|
2361
|
-
"semanticBundleCommit",
|
|
2362
|
-
"v",
|
|
2363
|
-
"wholeSpacePurge"
|
|
2364
|
-
]) || value.changesSince !== true || value.dependencyClosureExport !== true || value.exactSnapshots !== true || typeof value.operationReplication !== "boolean" || value.semanticBundleCommit !== true || value.v !== 1 || typeof value.wholeSpacePurge !== "boolean")
|
|
2365
|
-
return null;
|
|
2366
|
-
return {
|
|
2367
|
-
changesSince: true,
|
|
2368
|
-
dependencyClosureExport: true,
|
|
2369
|
-
exactSnapshots: true,
|
|
2370
|
-
operationReplication: value.operationReplication,
|
|
2371
|
-
semanticBundleCommit: true,
|
|
2372
|
-
v: 1,
|
|
2373
|
-
wholeSpacePurge: value.wholeSpacePurge
|
|
2374
|
-
};
|
|
2375
|
-
}
|
|
2376
|
-
function createOhStoreProfileV1(input) {
|
|
2377
|
-
if (!isPlainRecord(input) || !hasExactKeys(input, [
|
|
2378
|
-
"applicationProfileSha256",
|
|
2379
|
-
"capabilities",
|
|
2380
|
-
"profileId",
|
|
2381
|
-
"profileKind",
|
|
2382
|
-
"v"
|
|
2383
|
-
]) || input.v !== 1)
|
|
2384
|
-
throw new TypeError("Invalid Oh store profile input.");
|
|
2385
|
-
const profileId = safeCode(input.profileId);
|
|
2386
|
-
const applicationProfileSha256 = input.applicationProfileSha256 === null ? null : parseSha256Hex(input.applicationProfileSha256);
|
|
2387
|
-
const capabilities = parseCapabilities(input.capabilities);
|
|
2388
|
-
if (profileId === null || capabilities === null || input.applicationProfileSha256 !== null && applicationProfileSha256 === null || input.profileKind !== "canonical" && input.profileKind !== "working") {
|
|
2389
|
-
throw new TypeError("Invalid Oh store profile input.");
|
|
2390
|
-
}
|
|
2391
|
-
if (input.profileKind === "working" && (capabilities.operationReplication || !capabilities.wholeSpacePurge)) {
|
|
2392
|
-
throw new OhProfileError("A working profile must disable operation replication and permit whole-space purge.");
|
|
2393
|
-
}
|
|
2394
|
-
if (input.profileKind === "canonical" && capabilities.wholeSpacePurge) {
|
|
2395
|
-
throw new OhProfileError("A canonical profile cannot permit whole-space purge.");
|
|
2396
|
-
}
|
|
2397
|
-
const payload = {
|
|
2398
|
-
applicationProfileSha256,
|
|
2399
|
-
capabilities: Object.freeze(capabilities),
|
|
2400
|
-
profileId,
|
|
2401
|
-
profileKind: input.profileKind,
|
|
2402
|
-
v: 1
|
|
2403
|
-
};
|
|
2404
|
-
return Object.freeze({ ...payload, profileSha256: canonicalSha256(payload) });
|
|
2405
|
-
}
|
|
2406
|
-
function parseOhStoreProfileV1(value) {
|
|
2407
|
-
if (!isPlainRecord(value) || !Object.hasOwn(value, "profileSha256"))
|
|
2408
|
-
return null;
|
|
2409
|
-
const digest = parseSha256Hex(value.profileSha256);
|
|
2410
|
-
const { profileSha256: _profileSha256, ...input } = value;
|
|
2411
|
-
try {
|
|
2412
|
-
const created = createOhStoreProfileV1(input);
|
|
2413
|
-
return digest !== null && created.profileSha256 === digest ? created : null;
|
|
2414
|
-
} catch {
|
|
2415
|
-
return null;
|
|
2416
|
-
}
|
|
2417
|
-
}
|
|
2418
|
-
function createOhStoreBindingV1(input) {
|
|
2419
|
-
const profile = parseOhStoreProfileV1(input.profile);
|
|
2420
|
-
const realmId = safeCode(input.realmId);
|
|
2421
|
-
const spaceId = safeCode(input.spaceId);
|
|
2422
|
-
if (input.v !== 1 || profile === null || realmId === null || spaceId === null) {
|
|
2423
|
-
throw new TypeError("Invalid Oh store binding input.");
|
|
2424
|
-
}
|
|
2425
|
-
const payload = {
|
|
2426
|
-
contractSha256: OH_CONTRACT_MANIFEST_V1.contractSha256,
|
|
2427
|
-
profile,
|
|
2428
|
-
realmId,
|
|
2429
|
-
spaceId,
|
|
2430
|
-
v: 1
|
|
2431
|
-
};
|
|
2432
|
-
return Object.freeze({ ...payload, bindingSha256: canonicalSha256(payload) });
|
|
2433
|
-
}
|
|
2434
|
-
function parseOhStoreBindingV1(value) {
|
|
2435
|
-
if (!isPlainRecord(value) || !hasExactKeys(value, [
|
|
2436
|
-
"bindingSha256",
|
|
2437
|
-
"contractSha256",
|
|
2438
|
-
"profile",
|
|
2439
|
-
"realmId",
|
|
2440
|
-
"spaceId",
|
|
2441
|
-
"v"
|
|
2442
|
-
]) || value.v !== 1 || value.contractSha256 !== OH_CONTRACT_MANIFEST_V1.contractSha256)
|
|
2443
|
-
return null;
|
|
2444
|
-
const bindingSha256 = parseSha256Hex(value.bindingSha256);
|
|
2445
|
-
const profile = parseOhStoreProfileV1(value.profile);
|
|
2446
|
-
try {
|
|
2447
|
-
if (bindingSha256 === null || profile === null)
|
|
2448
|
-
return null;
|
|
2449
|
-
const created = createOhStoreBindingV1({
|
|
2450
|
-
profile,
|
|
2451
|
-
realmId: value.realmId,
|
|
2452
|
-
spaceId: value.spaceId,
|
|
2453
|
-
v: 1
|
|
2454
|
-
});
|
|
2455
|
-
return created.bindingSha256 === bindingSha256 ? created : null;
|
|
2456
|
-
} catch {
|
|
2457
|
-
return null;
|
|
2458
|
-
}
|
|
2459
|
-
}
|
|
2460
|
-
function sortedRecords(records) {
|
|
2461
|
-
return [...records].sort((left, right) => left.key < right.key ? -1 : left.key > right.key ? 1 : 0);
|
|
2462
|
-
}
|
|
2463
|
-
function verifyDependencies(records) {
|
|
2464
|
-
for (const record of records.values()) {
|
|
2465
|
-
for (const dependency of record.dependencies) {
|
|
2466
|
-
if (!records.has(dependency))
|
|
2467
|
-
throw new OhDependencyError(`Missing dependency ${dependency} for ${record.key}.`);
|
|
2468
|
-
}
|
|
2469
|
-
}
|
|
2470
|
-
}
|
|
2471
|
-
function replayOhOperationsV1(spaceId, values, maximumRecords = OH_GRAPH_LIMITS_V1.recordsPerSnapshot) {
|
|
2472
|
-
const parsedSpaceId = safeCode(spaceId);
|
|
2473
|
-
if (parsedSpaceId === null || !Number.isSafeInteger(maximumRecords) || maximumRecords < 1 || maximumRecords > OH_GRAPH_LIMITS_V1.recordsPerSnapshot) {
|
|
2474
|
-
throw new TypeError("Invalid operation replay input.");
|
|
2475
|
-
}
|
|
2476
|
-
const records = new Map;
|
|
2477
|
-
const operationIds = new Set;
|
|
2478
|
-
let head = emptyOhHeadV1();
|
|
2479
|
-
for (const value of values) {
|
|
2480
|
-
const operation = parseOhOperationV1(value);
|
|
2481
|
-
if (operation === null || operation.spaceId !== parsedSpaceId || operation.sequence !== head.sequence + 1 || operation.parentOperationSha256 !== head.operationSha256 || operationIds.has(operation.operationId)) {
|
|
2482
|
-
throw new OhIntegrityError("Operation replay chain is broken.");
|
|
2483
|
-
}
|
|
2484
|
-
operationIds.add(operation.operationId);
|
|
2485
|
-
for (const change of operation.changes) {
|
|
2486
|
-
if (change.kind === "put")
|
|
2487
|
-
records.set(change.record.key, change.record);
|
|
2488
|
-
else {
|
|
2489
|
-
const prior = records.get(change.key);
|
|
2490
|
-
if (prior?.recordSha256 !== change.priorSha256) {
|
|
2491
|
-
throw new OhIntegrityError("Replay tombstone does not match its prior record.");
|
|
2492
|
-
}
|
|
2493
|
-
records.delete(change.key);
|
|
2494
|
-
}
|
|
2495
|
-
}
|
|
2496
|
-
if (records.size > maximumRecords)
|
|
2497
|
-
throw new RangeError("Operation replay exceeds its record bound.");
|
|
2498
|
-
verifyDependencies(records);
|
|
2499
|
-
const refs = sortedRecords(records.values()).map(knowledgeGraphRecordRefV1);
|
|
2500
|
-
const recordsSha256 = canonicalSha256(refs);
|
|
2501
|
-
const graphRevisionSha256 = graphRevisionSha256V1({
|
|
2502
|
-
changes: operation.changes,
|
|
2503
|
-
operationId: operation.operationId,
|
|
2504
|
-
parentGraphRevisionSha256: head.graphRevisionSha256,
|
|
2505
|
-
recordsSha256,
|
|
2506
|
-
revision: operation.sequence
|
|
2507
|
-
});
|
|
2508
|
-
if (recordsSha256 !== operation.recordsSha256 || graphRevisionSha256 !== operation.graphRevisionSha256) {
|
|
2509
|
-
throw new OhIntegrityError("Replay does not reproduce an operation head.");
|
|
2510
|
-
}
|
|
2511
|
-
head = {
|
|
2512
|
-
generation: operation.sequence,
|
|
2513
|
-
graphRevisionSha256,
|
|
2514
|
-
operationSha256: operation.operationSha256,
|
|
2515
|
-
recordsSha256,
|
|
2516
|
-
sequence: operation.sequence,
|
|
2517
|
-
v: 1
|
|
2518
|
-
};
|
|
2519
|
-
}
|
|
2520
|
-
return { head, records: sortedRecords(records.values()), v: 1 };
|
|
2521
|
-
}
|
|
2522
|
-
function transitionOhSnapshotV1(input) {
|
|
2523
|
-
const actorId = safeCode(input.actorId);
|
|
2524
|
-
const operationId = safeCode(input.operationId);
|
|
2525
|
-
const spaceId = safeCode(input.spaceId);
|
|
2526
|
-
const instant = parseCanonicalInstantV1(input.instant);
|
|
2527
|
-
const changes = canonicalKnowledgeGraphChangesV1(input.changes);
|
|
2528
|
-
if (actorId === null || operationId === null || spaceId === null || instant === null || changes.length === 0 || changes.length > OH_GRAPH_LIMITS_V1.changesPerOperation) {
|
|
2529
|
-
throw new TypeError("Invalid graph transition input.");
|
|
2530
|
-
}
|
|
2531
|
-
const head = parseOhHeadV1(input.snapshot.head);
|
|
2532
|
-
if (input.snapshot.v !== 1 || head === null || !Array.isArray(input.snapshot.records)) {
|
|
2533
|
-
throw new OhIntegrityError("The transition snapshot is invalid.");
|
|
2534
|
-
}
|
|
2535
|
-
const records = new Map;
|
|
2536
|
-
for (const value of input.snapshot.records) {
|
|
2537
|
-
const record = parseKnowledgeGraphRecordV1(value);
|
|
2538
|
-
if (record === null || records.has(record.key)) {
|
|
2539
|
-
throw new OhIntegrityError("The transition snapshot contains an invalid record.");
|
|
2540
|
-
}
|
|
2541
|
-
records.set(record.key, record);
|
|
2542
|
-
}
|
|
2543
|
-
verifyDependencies(records);
|
|
2544
|
-
const priorRecordsSha256 = canonicalSha256(sortedRecords(records.values()).map(knowledgeGraphRecordRefV1));
|
|
2545
|
-
if (priorRecordsSha256 !== head.recordsSha256) {
|
|
2546
|
-
throw new OhIntegrityError("The transition snapshot does not reproduce its head.");
|
|
2547
|
-
}
|
|
2548
|
-
for (const change of changes) {
|
|
2549
|
-
if (change.kind === "put")
|
|
2550
|
-
records.set(change.record.key, change.record);
|
|
2551
|
-
else {
|
|
2552
|
-
const prior = records.get(change.key);
|
|
2553
|
-
if (prior === undefined || prior.recordSha256 !== change.priorSha256) {
|
|
2554
|
-
throw new OhConflictError(`The prior digest for ${change.key} does not match the snapshot.`);
|
|
2555
|
-
}
|
|
2556
|
-
records.delete(change.key);
|
|
2557
|
-
}
|
|
2558
|
-
}
|
|
2559
|
-
if (records.size > OH_GRAPH_LIMITS_V1.recordsPerSnapshot) {
|
|
2560
|
-
throw new RangeError("Graph transition exceeds its record snapshot limit.");
|
|
2561
|
-
}
|
|
2562
|
-
verifyDependencies(records);
|
|
2563
|
-
const nextRecords = sortedRecords(records.values());
|
|
2564
|
-
const recordsSha256 = canonicalSha256(nextRecords.map(knowledgeGraphRecordRefV1));
|
|
2565
|
-
const graphRevisionSha256 = graphRevisionSha256V1({
|
|
2566
|
-
changes,
|
|
2567
|
-
operationId,
|
|
2568
|
-
parentGraphRevisionSha256: head.graphRevisionSha256,
|
|
2569
|
-
recordsSha256,
|
|
2570
|
-
revision: head.sequence + 1
|
|
2571
|
-
});
|
|
2572
|
-
const operation = createOhOperationV1({
|
|
2573
|
-
actorId,
|
|
2574
|
-
changes,
|
|
2575
|
-
contractId: OH_CONTRACT_MANIFEST_V1.contractId,
|
|
2576
|
-
graphRevisionSha256,
|
|
2577
|
-
instant,
|
|
2578
|
-
operationId,
|
|
2579
|
-
parentOperationSha256: head.operationSha256,
|
|
2580
|
-
recordsSha256,
|
|
2581
|
-
sequence: head.sequence + 1,
|
|
2582
|
-
spaceId,
|
|
2583
|
-
v: 1
|
|
2584
|
-
}, input.maximumOperationBytes === undefined ? {} : {
|
|
2585
|
-
maximumOperationBytes: input.maximumOperationBytes
|
|
2586
|
-
});
|
|
2587
|
-
const nextHead = {
|
|
2588
|
-
generation: operation.sequence,
|
|
2589
|
-
graphRevisionSha256,
|
|
2590
|
-
operationSha256: operation.operationSha256,
|
|
2591
|
-
recordsSha256,
|
|
2592
|
-
sequence: operation.sequence,
|
|
2593
|
-
v: 1
|
|
2594
|
-
};
|
|
2595
|
-
return { operation, snapshot: { head: nextHead, records: nextRecords, v: 1 } };
|
|
2596
|
-
}
|
|
2597
|
-
function normalizeRoots(values) {
|
|
2598
|
-
if (!Array.isArray(values) || values.length < 1 || values.length > OH_DEPENDENCY_CLOSURE_LIMITS_V1.roots) {
|
|
2599
|
-
throw new RangeError(`A dependency closure needs 1 through ${OH_DEPENDENCY_CLOSURE_LIMITS_V1.roots} roots.`);
|
|
2600
|
-
}
|
|
2601
|
-
const roots = values.map((value) => safeCode(value, 512));
|
|
2602
|
-
if (roots.some((value) => value === null))
|
|
2603
|
-
throw new TypeError("Invalid dependency closure root.");
|
|
2604
|
-
const sorted = [...roots].sort();
|
|
2605
|
-
if (sorted.some((value, index) => index > 0 && sorted[index - 1] === value)) {
|
|
2606
|
-
throw new TypeError("Dependency closure roots must be unique.");
|
|
2219
|
+
if (typeof value !== "object" || value === null || isProxy(value) || !Array.isArray(value))
|
|
2220
|
+
return null;
|
|
2221
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
|
|
2222
|
+
const length = lengthDescriptor?.value;
|
|
2223
|
+
if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0 || length > maximumLength)
|
|
2224
|
+
return null;
|
|
2225
|
+
const keys = Reflect.ownKeys(value);
|
|
2226
|
+
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)))
|
|
2227
|
+
return null;
|
|
2228
|
+
const detached = clone ? [] : null;
|
|
2229
|
+
for (let index = 0;index < length; index += 1) {
|
|
2230
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
2231
|
+
if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined)
|
|
2232
|
+
return null;
|
|
2233
|
+
detached?.push(descriptor.value);
|
|
2234
|
+
}
|
|
2235
|
+
return detached ?? value;
|
|
2236
|
+
} catch {
|
|
2237
|
+
return null;
|
|
2607
2238
|
}
|
|
2608
|
-
return sorted;
|
|
2609
2239
|
}
|
|
2610
|
-
function
|
|
2611
|
-
const
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2240
|
+
function stagedSyncOperationV1(value, bundleBudget) {
|
|
2241
|
+
const operation = exactDataRecordV1(value, OH_OPERATION_KEYS_V1);
|
|
2242
|
+
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)
|
|
2243
|
+
return null;
|
|
2244
|
+
const parentOperationSha256 = operation.parentOperationSha256 === null ? null : parseSha256Hex(operation.parentOperationSha256);
|
|
2245
|
+
const sequence = Number.isSafeInteger(operation.sequence) && operation.sequence > 0 ? operation.sequence : null;
|
|
2246
|
+
if (sequence === null || operation.parentOperationSha256 !== null && parentOperationSha256 === null || sequence === 1 !== (parentOperationSha256 === null))
|
|
2247
|
+
return null;
|
|
2248
|
+
const changes = exactDataArrayV1(operation.changes, OH_GRAPH_LIMITS_V1.changesPerOperation, false);
|
|
2249
|
+
if (changes === null || changes.length === 0)
|
|
2250
|
+
return null;
|
|
2251
|
+
const aggregateDataBudget = {
|
|
2252
|
+
bytes: 0,
|
|
2253
|
+
maximumBytes: OH_OPERATION_MAX_BYTES_V1,
|
|
2254
|
+
maximumNodes: OH_SYNC_INGRESS_OPERATION_NODES_V1,
|
|
2255
|
+
nodes: 0
|
|
2256
|
+
};
|
|
2257
|
+
for (const change of changes) {
|
|
2258
|
+
const put = exactDataRecordV1(change, OH_PUT_CHANGE_KEYS_V1);
|
|
2259
|
+
if (put !== null && put.kind === "put" && put.v === 1) {
|
|
2260
|
+
const record = exactDataRecordV1(put.record, OH_RECORD_KEYS_V1);
|
|
2261
|
+
if (record === null)
|
|
2262
|
+
return null;
|
|
2263
|
+
const valuePreflight = preflightSyncIngressValueV1(record.value, aggregateDataBudget);
|
|
2264
|
+
const dependenciesPreflight = preflightSyncIngressDependenciesV1(record.dependencies, aggregateDataBudget);
|
|
2265
|
+
if (valuePreflight === null || dependenciesPreflight === null)
|
|
2266
|
+
return null;
|
|
2617
2267
|
continue;
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
if (selectedBytes > maximumBytes)
|
|
2623
|
-
throw new RangeError("Dependency closure exceeds its canonical byte bound.");
|
|
2624
|
-
selected.set(key, record);
|
|
2625
|
-
if (selected.size > maximumRecords)
|
|
2626
|
-
throw new RangeError("Dependency closure exceeds its record bound.");
|
|
2627
|
-
pending.push(...record.dependencies);
|
|
2268
|
+
}
|
|
2269
|
+
const tombstone = exactDataRecordV1(change, OH_TOMBSTONE_CHANGE_KEYS_V1);
|
|
2270
|
+
if (tombstone === null || tombstone.kind !== "tombstone" || tombstone.v !== 1)
|
|
2271
|
+
return null;
|
|
2628
2272
|
}
|
|
2629
|
-
|
|
2273
|
+
const detached = boundedSyncIngressV1(operation, {
|
|
2274
|
+
maximumBytes: OH_OPERATION_MAX_BYTES_V1,
|
|
2275
|
+
maximumDepth: OH_SYNC_INGRESS_OPERATION_DEPTH_V1,
|
|
2276
|
+
maximumNodes: OH_SYNC_INGRESS_OPERATION_NODES_V1
|
|
2277
|
+
}, bundleBudget, true);
|
|
2278
|
+
return detached === null ? null : detached.value;
|
|
2630
2279
|
}
|
|
2631
|
-
function
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2280
|
+
function canonicalStringBytesV1(value, maximumBytes) {
|
|
2281
|
+
if (value.length + 2 > maximumBytes)
|
|
2282
|
+
throw new RangeError("String exceeds its canonical byte budget.");
|
|
2283
|
+
let bytes = 2;
|
|
2284
|
+
for (let index = 0;index < value.length; index += 1) {
|
|
2285
|
+
const code = value.charCodeAt(index);
|
|
2286
|
+
if (code >= 55296 && code <= 56319) {
|
|
2287
|
+
const next = value.charCodeAt(index + 1);
|
|
2288
|
+
if (!(next >= 56320 && next <= 57343))
|
|
2289
|
+
throw new TypeError("Invalid Unicode string.");
|
|
2290
|
+
bytes += 4;
|
|
2291
|
+
index += 1;
|
|
2292
|
+
} else if (code >= 56320 && code <= 57343) {
|
|
2293
|
+
throw new TypeError("Invalid Unicode string.");
|
|
2294
|
+
} else if (code === 34 || code === 92 || code === 8 || code === 9 || code === 10 || code === 12 || code === 13) {
|
|
2295
|
+
bytes += 2;
|
|
2296
|
+
} else if (code <= 31) {
|
|
2297
|
+
bytes += 6;
|
|
2298
|
+
} else if (code <= 127) {
|
|
2299
|
+
bytes += 1;
|
|
2300
|
+
} else if (code <= 2047) {
|
|
2301
|
+
bytes += 2;
|
|
2302
|
+
} else {
|
|
2303
|
+
bytes += 3;
|
|
2304
|
+
}
|
|
2305
|
+
if (bytes > maximumBytes)
|
|
2306
|
+
throw new RangeError("String exceeds its canonical byte budget.");
|
|
2654
2307
|
}
|
|
2655
|
-
return
|
|
2308
|
+
return bytes;
|
|
2656
2309
|
}
|
|
2657
|
-
function
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2310
|
+
function boundedSyncIngressV1(value, limits, aggregate, clone) {
|
|
2311
|
+
const ancestors = new Set;
|
|
2312
|
+
const budget = {
|
|
2313
|
+
bytes: 0,
|
|
2314
|
+
maximumBytes: limits.maximumBytes,
|
|
2315
|
+
maximumNodes: limits.maximumNodes,
|
|
2316
|
+
nodes: 0
|
|
2317
|
+
};
|
|
2318
|
+
const canSpendBytes = (count) => budget.bytes + count <= budget.maximumBytes && (aggregate === null || aggregate.bytes + count <= aggregate.maximumBytes);
|
|
2319
|
+
const spendBytes = (count) => {
|
|
2320
|
+
if (!canSpendBytes(count))
|
|
2321
|
+
throw new RangeError("Sync ingress exceeds its canonical byte budget.");
|
|
2322
|
+
budget.bytes += count;
|
|
2323
|
+
if (aggregate !== null)
|
|
2324
|
+
aggregate.bytes += count;
|
|
2325
|
+
};
|
|
2326
|
+
const canSpendNodes = (count) => budget.nodes + count <= budget.maximumNodes && (aggregate === null || aggregate.nodes + count <= aggregate.maximumNodes);
|
|
2327
|
+
const spendNode = () => {
|
|
2328
|
+
if (!canSpendNodes(1))
|
|
2329
|
+
throw new RangeError("Sync ingress exceeds its node budget.");
|
|
2330
|
+
budget.nodes += 1;
|
|
2331
|
+
if (aggregate !== null)
|
|
2332
|
+
aggregate.nodes += 1;
|
|
2333
|
+
};
|
|
2334
|
+
const detach = (candidate, depth) => {
|
|
2335
|
+
if (depth > limits.maximumDepth)
|
|
2336
|
+
throw new RangeError("Sync ingress exceeds its depth budget.");
|
|
2337
|
+
spendNode();
|
|
2338
|
+
if (candidate === null) {
|
|
2339
|
+
spendBytes(4);
|
|
2340
|
+
return candidate;
|
|
2341
|
+
}
|
|
2342
|
+
if (typeof candidate === "boolean") {
|
|
2343
|
+
spendBytes(candidate ? 4 : 5);
|
|
2344
|
+
return candidate;
|
|
2345
|
+
}
|
|
2346
|
+
if (typeof candidate === "string") {
|
|
2347
|
+
spendBytes(canonicalStringBytesV1(candidate, Math.min(budget.maximumBytes - budget.bytes, aggregate === null ? Number.MAX_SAFE_INTEGER : aggregate.maximumBytes - aggregate.bytes)));
|
|
2348
|
+
return candidate;
|
|
2349
|
+
}
|
|
2350
|
+
if (typeof candidate === "number") {
|
|
2351
|
+
if (!Number.isFinite(candidate) || Object.is(candidate, -0))
|
|
2352
|
+
throw new TypeError("Invalid number.");
|
|
2353
|
+
spendBytes(utf8ByteLength(canonicalJson(candidate)));
|
|
2354
|
+
return candidate;
|
|
2355
|
+
}
|
|
2356
|
+
if (typeof candidate !== "object" || isProxy(candidate))
|
|
2357
|
+
throw new TypeError("Invalid data value.");
|
|
2358
|
+
if (ancestors.has(candidate))
|
|
2359
|
+
throw new TypeError("Cyclic data value.");
|
|
2360
|
+
ancestors.add(candidate);
|
|
2361
|
+
try {
|
|
2362
|
+
if (Array.isArray(candidate)) {
|
|
2363
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(candidate, "length");
|
|
2364
|
+
const length = lengthDescriptor?.value;
|
|
2365
|
+
if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0 || !canSpendNodes(length) || !canSpendBytes(2 + Math.max(0, length - 1) + length)) {
|
|
2366
|
+
throw new TypeError("Invalid or over-budget data array.");
|
|
2367
|
+
}
|
|
2368
|
+
const keys2 = Reflect.ownKeys(candidate);
|
|
2369
|
+
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))) {
|
|
2370
|
+
throw new TypeError("Invalid data array.");
|
|
2371
|
+
}
|
|
2372
|
+
spendBytes(2 + Math.max(0, length - 1));
|
|
2373
|
+
const detached2 = clone ? [] : null;
|
|
2374
|
+
for (let index = 0;index < length; index += 1) {
|
|
2375
|
+
const descriptor = Object.getOwnPropertyDescriptor(candidate, String(index));
|
|
2376
|
+
if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined) {
|
|
2377
|
+
throw new TypeError("Invalid data array entry.");
|
|
2378
|
+
}
|
|
2379
|
+
const item = detach(descriptor.value, depth + 1);
|
|
2380
|
+
detached2?.push(item);
|
|
2381
|
+
}
|
|
2382
|
+
return detached2 === null ? candidate : Object.freeze(detached2);
|
|
2383
|
+
}
|
|
2384
|
+
const prototype = Object.getPrototypeOf(candidate);
|
|
2385
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
2386
|
+
throw new TypeError("Invalid data object.");
|
|
2387
|
+
}
|
|
2388
|
+
let containerBytes = 2;
|
|
2389
|
+
let properties = 0;
|
|
2390
|
+
for (const key in candidate) {
|
|
2391
|
+
if (!Object.hasOwn(candidate, key))
|
|
2392
|
+
continue;
|
|
2393
|
+
properties += 1;
|
|
2394
|
+
if (!canSpendNodes(properties))
|
|
2395
|
+
throw new RangeError("Sync ingress exceeds its node budget.");
|
|
2396
|
+
const remainingBytes = Math.min(budget.maximumBytes - budget.bytes - containerBytes - properties, aggregate === null ? Number.MAX_SAFE_INTEGER : aggregate.maximumBytes - aggregate.bytes - containerBytes - properties);
|
|
2397
|
+
containerBytes += (properties === 1 ? 0 : 1) + canonicalStringBytesV1(key, remainingBytes) + 1;
|
|
2398
|
+
if (!canSpendBytes(containerBytes + properties)) {
|
|
2399
|
+
throw new RangeError("Sync ingress exceeds its canonical byte budget.");
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
const keys = Reflect.ownKeys(candidate);
|
|
2403
|
+
if (keys.length !== properties || keys.some((key) => typeof key !== "string")) {
|
|
2404
|
+
throw new TypeError("Invalid data object.");
|
|
2405
|
+
}
|
|
2406
|
+
spendBytes(containerBytes);
|
|
2407
|
+
const detached = clone ? Object.create(null) : null;
|
|
2408
|
+
for (const key of keys) {
|
|
2409
|
+
const descriptor = Object.getOwnPropertyDescriptor(candidate, key);
|
|
2410
|
+
if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined) {
|
|
2411
|
+
throw new TypeError("Invalid data property.");
|
|
2412
|
+
}
|
|
2413
|
+
const item = detach(descriptor.value, depth + 1);
|
|
2414
|
+
if (detached !== null) {
|
|
2415
|
+
Object.defineProperty(detached, key, {
|
|
2416
|
+
configurable: false,
|
|
2417
|
+
enumerable: true,
|
|
2418
|
+
value: item,
|
|
2419
|
+
writable: false
|
|
2420
|
+
});
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
return detached === null ? candidate : Object.freeze(detached);
|
|
2424
|
+
} finally {
|
|
2425
|
+
ancestors.delete(candidate);
|
|
2426
|
+
}
|
|
2427
|
+
};
|
|
2428
|
+
try {
|
|
2429
|
+
return Object.freeze({ value: detach(value, 0) });
|
|
2430
|
+
} catch {
|
|
2671
2431
|
return null;
|
|
2672
|
-
const records = new Map;
|
|
2673
|
-
for (const item of value.records) {
|
|
2674
|
-
const record = parseKnowledgeGraphRecordV1(item);
|
|
2675
|
-
if (record === null || records.has(record.key))
|
|
2676
|
-
return null;
|
|
2677
|
-
records.set(record.key, record);
|
|
2678
2432
|
}
|
|
2433
|
+
}
|
|
2434
|
+
function preflightSyncIngressValueV1(value, aggregate) {
|
|
2435
|
+
return boundedSyncIngressV1(value, {
|
|
2436
|
+
maximumBytes: OH_GRAPH_LIMITS_V1.recordBytes,
|
|
2437
|
+
maximumDepth: OH_SYNC_INGRESS_VALUE_DEPTH_V1,
|
|
2438
|
+
maximumNodes: OH_SYNC_INGRESS_VALUE_NODES_V1
|
|
2439
|
+
}, aggregate, false);
|
|
2440
|
+
}
|
|
2441
|
+
function preflightSyncIngressDependenciesV1(value, aggregate) {
|
|
2679
2442
|
try {
|
|
2680
|
-
|
|
2681
|
-
if (canonicalJson(roots) !== canonicalJson(value.roots))
|
|
2443
|
+
if (typeof value !== "object" || value === null || isProxy(value) || !Array.isArray(value)) {
|
|
2682
2444
|
return null;
|
|
2683
|
-
|
|
2684
|
-
|
|
2445
|
+
}
|
|
2446
|
+
const length = Object.getOwnPropertyDescriptor(value, "length")?.value;
|
|
2447
|
+
if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0 || length > OH_GRAPH_LIMITS_V1.dependenciesPerRecord)
|
|
2685
2448
|
return null;
|
|
2686
|
-
const
|
|
2687
|
-
|
|
2688
|
-
|
|
2449
|
+
const maximumBytes = length === 0 ? 2 : 1 + length * 515;
|
|
2450
|
+
return boundedSyncIngressV1(value, {
|
|
2451
|
+
maximumBytes,
|
|
2452
|
+
maximumDepth: 1,
|
|
2453
|
+
maximumNodes: length + 1
|
|
2454
|
+
}, aggregate, false);
|
|
2689
2455
|
} catch {
|
|
2690
2456
|
return null;
|
|
2691
2457
|
}
|
|
2692
2458
|
}
|
|
2693
|
-
function
|
|
2694
|
-
const
|
|
2695
|
-
|
|
2459
|
+
function syncIngressBundleBudgetV1(spaceId) {
|
|
2460
|
+
const emptyBundle = {
|
|
2461
|
+
bundleSha256: "0".repeat(64),
|
|
2462
|
+
contractSha256: OH_CONTRACT_MANIFEST_V1.contractSha256,
|
|
2463
|
+
operations: [],
|
|
2464
|
+
protocol: OH_SYNC_PROTOCOL_V1,
|
|
2465
|
+
spaceId,
|
|
2466
|
+
v: 1
|
|
2467
|
+
};
|
|
2468
|
+
return {
|
|
2469
|
+
bytes: utf8ByteLength(canonicalJson(emptyBundle)),
|
|
2470
|
+
maximumBytes: OH_SYNC_BUNDLE_MAX_BYTES_V1,
|
|
2471
|
+
maximumNodes: OH_SYNC_INGRESS_BUNDLE_NODES_V1,
|
|
2472
|
+
nodes: 7
|
|
2473
|
+
};
|
|
2696
2474
|
}
|
|
2697
|
-
function
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
if (closure === null)
|
|
2704
|
-
return { ok: false, reason: "invalid-closure" };
|
|
2705
|
-
if (closure.binding.bindingSha256 !== binding.bindingSha256)
|
|
2706
|
-
return { ok: false, reason: "binding-mismatch" };
|
|
2707
|
-
if (canonicalJson(closure.head) !== canonicalJson(head))
|
|
2708
|
-
return { ok: false, reason: "head-mismatch" };
|
|
2709
|
-
return { closure, ok: true, verification: "expected-authority-and-head" };
|
|
2475
|
+
function spendSyncIngressBudgetV1(budget, bytes, nodes = 0) {
|
|
2476
|
+
if (budget.bytes + bytes > budget.maximumBytes || budget.nodes + nodes > budget.maximumNodes)
|
|
2477
|
+
return false;
|
|
2478
|
+
budget.bytes += bytes;
|
|
2479
|
+
budget.nodes += nodes;
|
|
2480
|
+
return true;
|
|
2710
2481
|
}
|
|
2711
|
-
function
|
|
2712
|
-
const
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2482
|
+
function measureSyncOperationV1(operation) {
|
|
2483
|
+
const measurement = {
|
|
2484
|
+
bytes: 0,
|
|
2485
|
+
maximumBytes: OH_OPERATION_MAX_BYTES_V1,
|
|
2486
|
+
maximumNodes: OH_SYNC_INGRESS_OPERATION_NODES_V1,
|
|
2487
|
+
nodes: 0
|
|
2488
|
+
};
|
|
2489
|
+
return boundedSyncIngressV1(operation, {
|
|
2490
|
+
maximumBytes: OH_OPERATION_MAX_BYTES_V1,
|
|
2491
|
+
maximumDepth: OH_SYNC_INGRESS_OPERATION_DEPTH_V1,
|
|
2492
|
+
maximumNodes: OH_SYNC_INGRESS_OPERATION_NODES_V1
|
|
2493
|
+
}, measurement, false) === null ? null : measurement;
|
|
2494
|
+
}
|
|
2495
|
+
function buildOhSyncBundleV1(parsedSpaceId, operations, largestFittingPrefix) {
|
|
2496
|
+
let priorSequence = null;
|
|
2497
|
+
let priorSha256 = null;
|
|
2498
|
+
const parsed = [];
|
|
2499
|
+
const bundleBudget = syncIngressBundleBudgetV1(parsedSpaceId);
|
|
2500
|
+
for (const candidate of operations) {
|
|
2501
|
+
const operation = parseOhOperationV1(candidate);
|
|
2502
|
+
if (operation === null || operation.spaceId !== parsedSpaceId || priorSequence !== null && operation.sequence !== priorSequence + 1 || priorSequence !== null && operation.parentOperationSha256 !== priorSha256) {
|
|
2503
|
+
throw new TypeError("Sync operations must form one ordered chain.");
|
|
2504
|
+
}
|
|
2505
|
+
const measurement = measureSyncOperationV1(operation);
|
|
2506
|
+
if (measurement === null) {
|
|
2507
|
+
throw new RangeError("Sync operation exceeds its canonical byte, node, or depth limit.");
|
|
2508
|
+
}
|
|
2509
|
+
if (!spendSyncIngressBudgetV1(bundleBudget, measurement.bytes + (parsed.length === 0 ? 0 : 1), measurement.nodes)) {
|
|
2510
|
+
if (largestFittingPrefix && parsed.length > 0)
|
|
2511
|
+
break;
|
|
2512
|
+
throw new RangeError("Sync bundle exceeds its canonical byte or node limit.");
|
|
2513
|
+
}
|
|
2514
|
+
parsed.push(operation);
|
|
2515
|
+
priorSequence = operation.sequence;
|
|
2516
|
+
priorSha256 = operation.operationSha256;
|
|
2717
2517
|
}
|
|
2718
2518
|
const payload = {
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
spaceId:
|
|
2519
|
+
contractSha256: OH_CONTRACT_MANIFEST_V1.contractSha256,
|
|
2520
|
+
operations: parsed,
|
|
2521
|
+
protocol: OH_SYNC_PROTOCOL_V1,
|
|
2522
|
+
spaceId: parsedSpaceId,
|
|
2723
2523
|
v: 1
|
|
2724
2524
|
};
|
|
2725
|
-
return
|
|
2525
|
+
return { ...payload, bundleSha256: canonicalSha256(payload) };
|
|
2726
2526
|
}
|
|
2727
|
-
function
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
const
|
|
2738
|
-
|
|
2739
|
-
const purgedAt = parseCanonicalInstantV1(value.purgedAt);
|
|
2740
|
-
const receiptSha256 = parseSha256Hex(value.receiptSha256);
|
|
2741
|
-
const spaceId = safeCode(value.spaceId);
|
|
2742
|
-
if (bindingSha256 === null || priorHead === null || purgedAt === null || receiptSha256 === null || spaceId === null)
|
|
2527
|
+
function parseSyncHeadRefEnvelopeV1(value) {
|
|
2528
|
+
const operationSha256 = value.operationSha256 === null ? null : parseSha256Hex(value.operationSha256);
|
|
2529
|
+
const sequence = Number.isSafeInteger(value.sequence) && value.sequence >= 0 && !Object.is(value.sequence, -0) ? value.sequence : null;
|
|
2530
|
+
return sequence !== null && (value.operationSha256 === null || operationSha256 !== null) && sequence === 0 === (operationSha256 === null) ? { operationSha256, sequence } : null;
|
|
2531
|
+
}
|
|
2532
|
+
function parseOhSyncHeadRefV1(value) {
|
|
2533
|
+
const reference = exactDataRecordV1(value, OH_SYNC_HEAD_REF_KEYS_V1);
|
|
2534
|
+
return reference === null ? null : parseSyncHeadRefEnvelopeV1(reference);
|
|
2535
|
+
}
|
|
2536
|
+
function parseOhSyncHeadV1(value) {
|
|
2537
|
+
const envelope = exactDataRecordV1(value, OH_SYNC_HEAD_KEYS_V1);
|
|
2538
|
+
if (envelope === null || envelope.v !== 1)
|
|
2743
2539
|
return null;
|
|
2744
|
-
const
|
|
2745
|
-
return
|
|
2540
|
+
const reference = parseSyncHeadRefEnvelopeV1(envelope);
|
|
2541
|
+
return reference === null ? null : { ...reference, v: 1 };
|
|
2746
2542
|
}
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
this.#store = store;
|
|
2753
|
-
this.#codecs = codecs.seal();
|
|
2543
|
+
function createOhSyncBundleV1(spaceId, operations, options = {}) {
|
|
2544
|
+
const parsedSpaceId = safeCode(spaceId);
|
|
2545
|
+
const largestFittingPrefix = options.largestFittingPrefix ?? false;
|
|
2546
|
+
if (parsedSpaceId === null || operations.length > OH_SYNC_BUNDLE_MAX_OPERATIONS_V1 || typeof largestFittingPrefix !== "boolean") {
|
|
2547
|
+
throw new TypeError("Invalid sync bundle.");
|
|
2754
2548
|
}
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
value: parsed
|
|
2787
|
-
});
|
|
2788
|
-
changes.push({ kind: "put", record, v: 1 });
|
|
2789
|
-
}
|
|
2790
|
-
for (const item of value.tombstones) {
|
|
2791
|
-
if (!isPlainRecord(item) || !hasExactKeys(item, ["key", "priorSha256", "v"]) || item.v !== 1) {
|
|
2792
|
-
throw new TypeError("Invalid semantic bundle tombstone.");
|
|
2793
|
-
}
|
|
2794
|
-
const priorSha256 = parseSha256Hex(item.priorSha256);
|
|
2795
|
-
if (typeof item.key !== "string" || priorSha256 === null)
|
|
2796
|
-
throw new TypeError("Invalid semantic bundle tombstone.");
|
|
2797
|
-
changes.push({ key: item.key, kind: "tombstone", priorSha256, v: 1 });
|
|
2798
|
-
}
|
|
2799
|
-
const canonical = canonicalKnowledgeGraphChangesV1(changes);
|
|
2800
|
-
return await this.#store.commit({
|
|
2801
|
-
actorId,
|
|
2802
|
-
changes: canonical,
|
|
2803
|
-
expectedHead: {
|
|
2804
|
-
generation: expected.generation,
|
|
2805
|
-
operationSha256: expected.operationSha256
|
|
2806
|
-
},
|
|
2807
|
-
...typeof instant === "string" ? { instant } : {},
|
|
2808
|
-
operationId
|
|
2809
|
-
});
|
|
2549
|
+
return buildOhSyncBundleV1(parsedSpaceId, operations, largestFittingPrefix);
|
|
2550
|
+
}
|
|
2551
|
+
function parseOhSyncBundleV1(value) {
|
|
2552
|
+
const envelope = exactDataRecordV1(value, OH_SYNC_BUNDLE_KEYS_V1);
|
|
2553
|
+
if (envelope === null || envelope.protocol !== OH_SYNC_PROTOCOL_V1 || envelope.v !== 1)
|
|
2554
|
+
return null;
|
|
2555
|
+
const bundleSha256 = parseSha256Hex(envelope.bundleSha256);
|
|
2556
|
+
const contractSha256 = parseSha256Hex(envelope.contractSha256);
|
|
2557
|
+
const spaceId = safeCode(envelope.spaceId);
|
|
2558
|
+
if (bundleSha256 === null || contractSha256 !== OH_CONTRACT_MANIFEST_V1.contractSha256 || spaceId === null)
|
|
2559
|
+
return null;
|
|
2560
|
+
const operations = exactDataArrayV1(envelope.operations, OH_SYNC_BUNDLE_MAX_OPERATIONS_V1);
|
|
2561
|
+
if (operations === null)
|
|
2562
|
+
return null;
|
|
2563
|
+
const detachedOperations = [];
|
|
2564
|
+
const bundleBudget = syncIngressBundleBudgetV1(spaceId);
|
|
2565
|
+
for (const operation of operations) {
|
|
2566
|
+
if (detachedOperations.length > 0 && !spendSyncIngressBudgetV1(bundleBudget, 1))
|
|
2567
|
+
return null;
|
|
2568
|
+
const detached = stagedSyncOperationV1(operation, bundleBudget);
|
|
2569
|
+
if (detached === null)
|
|
2570
|
+
return null;
|
|
2571
|
+
detachedOperations.push(detached);
|
|
2572
|
+
}
|
|
2573
|
+
try {
|
|
2574
|
+
const created = createOhSyncBundleV1(spaceId, detachedOperations);
|
|
2575
|
+
if (detachedOperations.some((operation, index) => canonicalJson(operation) !== canonicalJson(created.operations[index])))
|
|
2576
|
+
return null;
|
|
2577
|
+
return created.bundleSha256 === bundleSha256 ? { ...created, bundleSha256 } : null;
|
|
2578
|
+
} catch {
|
|
2579
|
+
return null;
|
|
2810
2580
|
}
|
|
2811
2581
|
}
|
|
2812
2582
|
|