@hraness/oh 0.3.2 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +164 -114
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +811 -97
- package/dist/errors.d.ts +39 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/graph.d.ts.map +1 -1
- package/dist/index.js +676 -61
- package/dist/libsql.d.ts.map +1 -1
- package/dist/libsql.js +162 -35
- package/dist/memory-page.js +2 -2
- package/dist/memory.d.ts +87 -6
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +1106 -148
- package/dist/operation.d.ts +3 -1
- package/dist/operation.d.ts.map +1 -1
- package/dist/projection-public.js +2 -2
- package/dist/projection-suss.js +2 -2
- package/dist/sdk.js +780 -88
- package/dist/semantic-cloud.js +2 -2
- package/dist/semantic.js +2 -2
- package/dist/sqlite/index.js +1251 -306
- package/dist/sqlite/port.d.ts +31 -3
- package/dist/sqlite/port.d.ts.map +1 -1
- package/dist/sqlite/store.d.ts +18 -2
- package/dist/sqlite/store.d.ts.map +1 -1
- package/dist/store.d.ts +3 -12
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +154 -32
- package/dist/sync.d.ts +7 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +668 -35
- package/package.json +5 -1
- package/skills/oh/SKILL.md +42 -16
- package/spec/README.md +2 -2
- package/spec/v1/memory.md +134 -16
- package/spec/v1/storage.md +8 -5
- package/spec/v1/store.md +20 -0
- package/spec/v1/sync.md +77 -8
- package/src/cli.test.ts +53 -1
- package/src/cli.ts +34 -8
- package/src/errors.test.ts +87 -0
- package/src/errors.ts +185 -0
- package/src/graph.ts +2 -2
- package/src/libsql.test.ts +36 -0
- package/src/libsql.ts +26 -5
- package/src/memory.test.ts +1488 -18
- package/src/memory.ts +1199 -122
- package/src/operation.ts +13 -3
- package/src/sqlite/port.test.ts +209 -0
- package/src/sqlite/port.ts +118 -4
- package/src/sqlite/store.test.ts +106 -1
- package/src/sqlite/store.ts +168 -30
- package/src/store.test.ts +12 -0
- package/src/store.ts +30 -20
- package/src/sync.test.ts +570 -2
- package/src/sync.ts +586 -36
package/src/sqlite/store.ts
CHANGED
|
@@ -25,6 +25,7 @@ import { OH_CONTRACT_ID_V1 } from "../ontology";
|
|
|
25
25
|
import {
|
|
26
26
|
createOhOperationV1,
|
|
27
27
|
graphRevisionSha256V1,
|
|
28
|
+
OH_OPERATION_MAX_BYTES_V1,
|
|
28
29
|
parseOhOperationV1,
|
|
29
30
|
type OhOperationV1,
|
|
30
31
|
} from "../operation";
|
|
@@ -32,9 +33,16 @@ import {
|
|
|
32
33
|
createOhDependencyClosureV1,
|
|
33
34
|
createOhSpacePurgeReceiptV1,
|
|
34
35
|
emptyOhHeadV1,
|
|
36
|
+
isOhConflictError,
|
|
37
|
+
isOhDependencyError,
|
|
38
|
+
isOhIntegrityError,
|
|
39
|
+
isOhOperationSizeError,
|
|
40
|
+
isOhProfileError,
|
|
41
|
+
OH_OPERATION_SIZE_ERROR_CODE_V1,
|
|
35
42
|
OhConflictError,
|
|
36
43
|
OhDependencyError,
|
|
37
44
|
OhIntegrityError,
|
|
45
|
+
OhOperationSizeError,
|
|
38
46
|
OhProfileError,
|
|
39
47
|
OhPurgedSpaceError,
|
|
40
48
|
parseOhHeadRefV1,
|
|
@@ -57,9 +65,16 @@ import { applyOhSqliteMigrations, OH_SQLITE_SCHEMA_VERSION } from "./migrations"
|
|
|
57
65
|
const EMPTY_RECORDS_SHA256 = canonicalSha256([]);
|
|
58
66
|
|
|
59
67
|
export {
|
|
68
|
+
isOhConflictError,
|
|
69
|
+
isOhDependencyError,
|
|
70
|
+
isOhIntegrityError,
|
|
71
|
+
isOhOperationSizeError,
|
|
72
|
+
isOhProfileError,
|
|
73
|
+
OH_OPERATION_SIZE_ERROR_CODE_V1,
|
|
60
74
|
OhConflictError,
|
|
61
75
|
OhDependencyError,
|
|
62
76
|
OhIntegrityError,
|
|
77
|
+
OhOperationSizeError,
|
|
63
78
|
OhProfileError,
|
|
64
79
|
OhPurgedSpaceError,
|
|
65
80
|
};
|
|
@@ -87,6 +102,13 @@ export type OhReplayVerificationV1 = Readonly<{
|
|
|
87
102
|
v: 1;
|
|
88
103
|
}>;
|
|
89
104
|
|
|
105
|
+
export type OhOperationImportResultV1 = Readonly<{
|
|
106
|
+
head: OhHeadV1;
|
|
107
|
+
imported: number;
|
|
108
|
+
status: "already-present" | "imported";
|
|
109
|
+
v: 1;
|
|
110
|
+
}>;
|
|
111
|
+
|
|
90
112
|
type SpaceRow = {
|
|
91
113
|
generation: number;
|
|
92
114
|
graph_revision_sha256: string | null;
|
|
@@ -242,6 +264,11 @@ function normalizeLimit(value: number | undefined, fallback = 50, maximum = 1000
|
|
|
242
264
|
return value;
|
|
243
265
|
}
|
|
244
266
|
|
|
267
|
+
function exactHeadRef(left: OhHeadRefV1, right: OhHeadRefV1): boolean {
|
|
268
|
+
return left.sequence === right.sequence
|
|
269
|
+
&& left.operationSha256 === right.operationSha256;
|
|
270
|
+
}
|
|
271
|
+
|
|
245
272
|
function ftsQuery(value: string): string | null {
|
|
246
273
|
const normalized = boundedText(value.normalize("NFC"), 4096);
|
|
247
274
|
if (normalized === null) return null;
|
|
@@ -375,8 +402,8 @@ export class OhSqliteStore {
|
|
|
375
402
|
head: OhHeadV1,
|
|
376
403
|
changes: readonly KnowledgeGraphChangeV1[],
|
|
377
404
|
operationId: string,
|
|
405
|
+
records = this.#loadRecords(),
|
|
378
406
|
): Readonly<{ graphRevisionSha256: Sha256Hex; records: Map<string, KnowledgeGraphRecordV1>; recordsSha256: Sha256Hex }> {
|
|
379
|
-
const records = this.#loadRecords();
|
|
380
407
|
for (const change of changes) {
|
|
381
408
|
if (change.kind === "put") {
|
|
382
409
|
records.set(change.record.key, change.record);
|
|
@@ -433,9 +460,7 @@ export class OhSqliteStore {
|
|
|
433
460
|
if (operation.sequence < 1 || operation.sequence > head.sequence) {
|
|
434
461
|
throw new OhIntegrityError("A stored idempotent operation is not reachable from the current head.");
|
|
435
462
|
}
|
|
436
|
-
const rows = this.database.query<{
|
|
437
|
-
operation_sha256: string; parent_operation_sha256: string | null; sequence: number;
|
|
438
|
-
}, [string, number, number]>(`SELECT operation_sha256, parent_operation_sha256, sequence
|
|
463
|
+
const rows = this.database.query<OperationRow, [string, number, number]>(`SELECT ${OPERATION_COLUMNS}
|
|
439
464
|
FROM oh_operations WHERE space_id = ? AND sequence >= ? AND sequence <= ? ORDER BY sequence`)
|
|
440
465
|
.all(this.spaceId, operation.sequence, head.sequence);
|
|
441
466
|
if (rows.length !== head.sequence - operation.sequence + 1) {
|
|
@@ -444,12 +469,16 @@ export class OhSqliteStore {
|
|
|
444
469
|
let priorSha256: string | null = operation.parentOperationSha256;
|
|
445
470
|
for (let index = 0; index < rows.length; index += 1) {
|
|
446
471
|
const row = rows[index];
|
|
447
|
-
if (row === undefined
|
|
448
|
-
|
|
449
|
-
|
|
472
|
+
if (row === undefined) {
|
|
473
|
+
throw new OhIntegrityError("A stored idempotent operation is not on the current authority chain.");
|
|
474
|
+
}
|
|
475
|
+
const reachable = parseStoredOperationRow(row, { spaceId: this.spaceId });
|
|
476
|
+
if (reachable.sequence !== operation.sequence + index
|
|
477
|
+
|| reachable.parentOperationSha256 !== priorSha256
|
|
478
|
+
|| (index === 0 && reachable.operationSha256 !== operation.operationSha256)) {
|
|
450
479
|
throw new OhIntegrityError("A stored idempotent operation is not on the current authority chain.");
|
|
451
480
|
}
|
|
452
|
-
priorSha256 =
|
|
481
|
+
priorSha256 = reachable.operationSha256;
|
|
453
482
|
}
|
|
454
483
|
if (priorSha256 !== head.operationSha256) {
|
|
455
484
|
throw new OhIntegrityError("A stored idempotent operation does not reach the current head digest.");
|
|
@@ -521,7 +550,13 @@ export class OhSqliteStore {
|
|
|
521
550
|
this.#assertOpen();
|
|
522
551
|
const actorId = safeCode(input.actorId);
|
|
523
552
|
const operationId = safeCode(input.operationId);
|
|
524
|
-
|
|
553
|
+
const maximumOperationBytes = input.maximumOperationBytes ?? OH_OPERATION_MAX_BYTES_V1;
|
|
554
|
+
if (actorId === null || operationId === null
|
|
555
|
+
|| !Number.isSafeInteger(maximumOperationBytes)
|
|
556
|
+
|| maximumOperationBytes < 1
|
|
557
|
+
|| maximumOperationBytes > OH_OPERATION_MAX_BYTES_V1) {
|
|
558
|
+
throw new TypeError("Invalid actor, operation ID, or operation byte bound.");
|
|
559
|
+
}
|
|
525
560
|
const changes = canonicalKnowledgeGraphChangesV1(input.changes);
|
|
526
561
|
if (changes.length === 0 || changes.length > 8192) throw new TypeError("A commit needs 1 through 8192 changes.");
|
|
527
562
|
return withImmediateTransaction(this.database, () => {
|
|
@@ -536,6 +571,10 @@ export class OhSqliteStore {
|
|
|
536
571
|
if (existing.actorId !== actorId || canonicalJson(existing.changes) !== canonicalJson(changes)) {
|
|
537
572
|
throw new OhConflictError("The operation ID is already bound to different content.");
|
|
538
573
|
}
|
|
574
|
+
const operationBytes = Buffer.byteLength(canonicalJson(existing), "utf8");
|
|
575
|
+
if (operationBytes > maximumOperationBytes) {
|
|
576
|
+
throw new OhOperationSizeError(operationBytes, maximumOperationBytes);
|
|
577
|
+
}
|
|
539
578
|
return existing;
|
|
540
579
|
}
|
|
541
580
|
if (head.generation !== input.expectedHead.generation
|
|
@@ -546,7 +585,9 @@ export class OhSqliteStore {
|
|
|
546
585
|
const operation = createOhOperationV1({ actorId, changes, contractId: OH_CONTRACT_ID_V1,
|
|
547
586
|
graphRevisionSha256: transition.graphRevisionSha256, instant: input.instant ?? canonicalNow(),
|
|
548
587
|
operationId, parentOperationSha256: head.operationSha256,
|
|
549
|
-
recordsSha256: transition.recordsSha256, sequence: head.sequence + 1, spaceId: this.spaceId, v: 1 }
|
|
588
|
+
recordsSha256: transition.recordsSha256, sequence: head.sequence + 1, spaceId: this.spaceId, v: 1 }, {
|
|
589
|
+
maximumOperationBytes,
|
|
590
|
+
});
|
|
550
591
|
this.#persist(operation);
|
|
551
592
|
return operation;
|
|
552
593
|
});
|
|
@@ -557,31 +598,100 @@ export class OhSqliteStore {
|
|
|
557
598
|
this.#assertOperationReplication();
|
|
558
599
|
const operation = parseOhOperationV1(value);
|
|
559
600
|
if (operation === null || operation.spaceId !== this.spaceId) throw new OhIntegrityError("Invalid imported operation.");
|
|
601
|
+
const result = this.importOperations({
|
|
602
|
+
expectedHead: {
|
|
603
|
+
operationSha256: operation.parentOperationSha256,
|
|
604
|
+
sequence: operation.sequence - 1,
|
|
605
|
+
},
|
|
606
|
+
operations: [operation],
|
|
607
|
+
});
|
|
608
|
+
return { imported: result.imported === 1, operation };
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Imports one already-validated replication interval atomically. A hostile
|
|
613
|
+
* later operation cannot leave a valid prefix committed. Exact replays are
|
|
614
|
+
* accepted only when every supplied operation is already on the current
|
|
615
|
+
* authority chain.
|
|
616
|
+
*/
|
|
617
|
+
importOperations(input: Readonly<{
|
|
618
|
+
expectedHead: OhHeadRefV1;
|
|
619
|
+
operations: readonly unknown[];
|
|
620
|
+
}>): OhOperationImportResultV1 {
|
|
621
|
+
this.#assertOpen();
|
|
622
|
+
this.#assertOperationReplication();
|
|
623
|
+
const expectedHead = parseOhHeadRefV1(input.expectedHead);
|
|
624
|
+
if (expectedHead === null || !Array.isArray(input.operations)
|
|
625
|
+
|| input.operations.length > 1000) {
|
|
626
|
+
throw new TypeError("Invalid operation import interval.");
|
|
627
|
+
}
|
|
628
|
+
const operations = input.operations.map((value) => {
|
|
629
|
+
const operation = parseOhOperationV1(value);
|
|
630
|
+
if (operation === null || operation.spaceId !== this.spaceId) {
|
|
631
|
+
throw new OhIntegrityError("Invalid imported operation.");
|
|
632
|
+
}
|
|
633
|
+
return operation;
|
|
634
|
+
});
|
|
635
|
+
let prior: OhHeadRefV1 = expectedHead;
|
|
636
|
+
for (const operation of operations) {
|
|
637
|
+
if (operation.sequence !== prior.sequence + 1
|
|
638
|
+
|| operation.parentOperationSha256 !== prior.operationSha256) {
|
|
639
|
+
throw new OhConflictError("Imported operations do not extend the expected head.");
|
|
640
|
+
}
|
|
641
|
+
prior = { operationSha256: operation.operationSha256, sequence: operation.sequence };
|
|
642
|
+
}
|
|
560
643
|
return withImmediateTransaction(this.database, () => {
|
|
561
|
-
const
|
|
562
|
-
this.#assertCurrentHeadAuthority(
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
const existing = parseStoredOperationRow(duplicate, { operationSha256: operation.operationSha256,
|
|
568
|
-
spaceId: this.spaceId });
|
|
569
|
-
this.#assertOperationReachable(existing, head);
|
|
570
|
-
if (canonicalJson(existing) !== canonicalJson(operation)) {
|
|
571
|
-
throw new OhIntegrityError("An operation digest is bound to different bytes.");
|
|
644
|
+
const current = this.head();
|
|
645
|
+
this.#assertCurrentHeadAuthority(current);
|
|
646
|
+
this.#headAt(expectedHead);
|
|
647
|
+
if (!exactHeadRef(current, expectedHead)) {
|
|
648
|
+
if (operations.length === 0 || current.sequence < prior.sequence) {
|
|
649
|
+
throw new OhConflictError("The imported operation interval does not extend the local head.");
|
|
572
650
|
}
|
|
573
|
-
|
|
651
|
+
for (const operation of operations) {
|
|
652
|
+
const row = this.database.query<OperationRow, [string, number]>(
|
|
653
|
+
`SELECT ${OPERATION_COLUMNS} FROM oh_operations WHERE space_id = ? AND sequence = ?`,
|
|
654
|
+
).get(this.spaceId, operation.sequence);
|
|
655
|
+
if (row === null) throw new OhIntegrityError("An imported replay is missing from the authority chain.");
|
|
656
|
+
const existing = parseStoredOperationRow(row, { spaceId: this.spaceId });
|
|
657
|
+
if (existing.operationSha256 !== operation.operationSha256) {
|
|
658
|
+
throw new OhConflictError("The imported operation interval diverges from the local authority chain.");
|
|
659
|
+
}
|
|
660
|
+
if (canonicalJson(existing) !== canonicalJson(operation)) {
|
|
661
|
+
throw new OhIntegrityError("An operation digest is bound to different bytes.");
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
this.#assertOperationReachable(operations[operations.length - 1]!, current);
|
|
665
|
+
return { head: current, imported: 0, status: "already-present", v: 1 };
|
|
574
666
|
}
|
|
575
|
-
if (
|
|
576
|
-
|
|
667
|
+
if (operations.length === 0) {
|
|
668
|
+
return { head: current, imported: 0, status: "already-present", v: 1 };
|
|
577
669
|
}
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
670
|
+
const records = this.#loadRecords();
|
|
671
|
+
let head = current;
|
|
672
|
+
for (const operation of operations) {
|
|
673
|
+
const duplicate = this.database.query<OperationRow, [string, string]>(
|
|
674
|
+
`SELECT ${OPERATION_COLUMNS} FROM oh_operations WHERE space_id = ? AND operation_id = ?`,
|
|
675
|
+
).get(this.spaceId, operation.operationId);
|
|
676
|
+
if (duplicate !== null) {
|
|
677
|
+
throw new OhConflictError("An imported operation ID is already bound on the local authority chain.");
|
|
678
|
+
}
|
|
679
|
+
const transition = this.#transition(head, operation.changes, operation.operationId, records);
|
|
680
|
+
if (transition.recordsSha256 !== operation.recordsSha256
|
|
681
|
+
|| transition.graphRevisionSha256 !== operation.graphRevisionSha256) {
|
|
682
|
+
throw new OhIntegrityError("An imported operation does not reproduce its declared graph head.");
|
|
683
|
+
}
|
|
684
|
+
this.#persist(operation);
|
|
685
|
+
head = {
|
|
686
|
+
generation: operation.sequence,
|
|
687
|
+
graphRevisionSha256: operation.graphRevisionSha256,
|
|
688
|
+
operationSha256: operation.operationSha256,
|
|
689
|
+
recordsSha256: operation.recordsSha256,
|
|
690
|
+
sequence: operation.sequence,
|
|
691
|
+
v: 1,
|
|
692
|
+
};
|
|
582
693
|
}
|
|
583
|
-
|
|
584
|
-
return { imported: true, operation };
|
|
694
|
+
return { head, imported: operations.length, status: "imported", v: 1 };
|
|
585
695
|
});
|
|
586
696
|
}
|
|
587
697
|
|
|
@@ -819,6 +929,12 @@ export class OhSqliteStore {
|
|
|
819
929
|
this.#assertOpen();
|
|
820
930
|
const integrity = this.database.query<{ integrity_check: string }, []>("PRAGMA integrity_check").get();
|
|
821
931
|
if (integrity?.integrity_check !== "ok") throw new OhIntegrityError("SQLite integrity_check failed.");
|
|
932
|
+
const foreignKeyViolations = this.database.query<{
|
|
933
|
+
fkid: number; parent: string; rowid: number | null; table: string;
|
|
934
|
+
}, []>("PRAGMA foreign_key_check").all();
|
|
935
|
+
if (foreignKeyViolations.length !== 0) {
|
|
936
|
+
throw new OhIntegrityError("SQLite foreign_key_check failed.");
|
|
937
|
+
}
|
|
822
938
|
const storedCount = this.database.query<{ count: number }, [string]>(
|
|
823
939
|
"SELECT count(*) AS count FROM oh_operations WHERE space_id = ?",
|
|
824
940
|
).get(this.spaceId)?.count ?? 0;
|
|
@@ -889,6 +1005,28 @@ export class OhSqliteStore {
|
|
|
889
1005
|
if (canonicalJson(storedDependencies) !== canonicalJson(expectedDependencies)) {
|
|
890
1006
|
throw new OhIntegrityError("Materialized dependencies do not match operation replay.");
|
|
891
1007
|
}
|
|
1008
|
+
const expectedSearchDocuments = [...records.values()]
|
|
1009
|
+
.sort((left, right) => left.key < right.key ? -1 : left.key > right.key ? 1 : 0)
|
|
1010
|
+
.map((record) => ({
|
|
1011
|
+
record_key: record.key,
|
|
1012
|
+
record_sha256: record.recordSha256,
|
|
1013
|
+
text: `${record.key} ${record.kind} ${extractSearchText(record.value)}`,
|
|
1014
|
+
}));
|
|
1015
|
+
const storedSearchDocuments = this.database.query<{
|
|
1016
|
+
record_key: string; record_sha256: string; text: string;
|
|
1017
|
+
}, [string]>(`SELECT record_key, record_sha256, text FROM oh_search_documents
|
|
1018
|
+
WHERE space_id = ? ORDER BY record_key`).all(this.spaceId);
|
|
1019
|
+
if (canonicalJson(storedSearchDocuments) !== canonicalJson(expectedSearchDocuments)) {
|
|
1020
|
+
throw new OhIntegrityError("Materialized search documents do not match operation replay.");
|
|
1021
|
+
}
|
|
1022
|
+
const expectedSearchFts = expectedSearchDocuments.map(({ record_key, text }) => ({ record_key, text }));
|
|
1023
|
+
const storedSearchFts = this.database.query<{
|
|
1024
|
+
record_key: string; text: string;
|
|
1025
|
+
}, [string]>(`SELECT record_key, text FROM oh_search_fts
|
|
1026
|
+
WHERE space_id = ? ORDER BY record_key, text, rowid`).all(this.spaceId);
|
|
1027
|
+
if (canonicalJson(storedSearchFts) !== canonicalJson(expectedSearchFts)) {
|
|
1028
|
+
throw new OhIntegrityError("Materialized full-text search rows do not match operation replay.");
|
|
1029
|
+
}
|
|
892
1030
|
const storedOperationRecords = this.database.query<{
|
|
893
1031
|
change_kind: string; operation_sha256: string; ordinal: number; record_key: string; record_sha256: string | null;
|
|
894
1032
|
}, [string]>(`SELECT materialized.operation_sha256, materialized.ordinal, materialized.record_key,
|
package/src/store.test.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
createOhStoreProfileV1,
|
|
11
11
|
emptyOhHeadV1,
|
|
12
12
|
OH_WORKING_STORE_PROFILE_V1,
|
|
13
|
+
OhOperationSizeError,
|
|
13
14
|
OhProfileError,
|
|
14
15
|
OhSemanticBundleIngressV1,
|
|
15
16
|
parseOhDependencyClosureV1,
|
|
@@ -21,6 +22,17 @@ import {
|
|
|
21
22
|
} from "./store";
|
|
22
23
|
|
|
23
24
|
describe("runtime-neutral Oh store contracts", () => {
|
|
25
|
+
test("applies an injected operation byte bound during canonical construction", () => {
|
|
26
|
+
const snapshot = { head: emptyOhHeadV1(), records: [], v: 1 as const };
|
|
27
|
+
expect(() => transitionOhSnapshotV1({ actorId: "agent.test",
|
|
28
|
+
changes: [{ kind: "put", record: createKnowledgeGraphRecordV1({ dependencies: [],
|
|
29
|
+
key: "entity:bounded", kind: "entity", v: 1, value: { name: "Bounded" } }), v: 1 }],
|
|
30
|
+
instant: "2026-09-06T12:00:00.000Z", maximumOperationBytes: 1,
|
|
31
|
+
operationId: "op_bounded", snapshot, spaceId: "bounded" }))
|
|
32
|
+
.toThrow(OhOperationSizeError);
|
|
33
|
+
expect(snapshot).toEqual({ head: emptyOhHeadV1(), records: [], v: 1 });
|
|
34
|
+
});
|
|
35
|
+
|
|
24
36
|
test("binds a host-selected realm and application profile without changing V1 operations", () => {
|
|
25
37
|
const applicationProfileSha256 = canonicalSha256({ application: "fixture", v: 1 });
|
|
26
38
|
const profile = createOhStoreProfileV1({
|
package/src/store.ts
CHANGED
|
@@ -26,28 +26,31 @@ import {
|
|
|
26
26
|
type KnowledgeGraphRecordKindV1,
|
|
27
27
|
type KnowledgeGraphRecordV1,
|
|
28
28
|
} from "./graph";
|
|
29
|
+
export {
|
|
30
|
+
isOhConflictError,
|
|
31
|
+
isOhDependencyError,
|
|
32
|
+
isOhIntegrityError,
|
|
33
|
+
isOhOperationSizeError,
|
|
34
|
+
isOhProfileError,
|
|
35
|
+
OH_OPERATION_SIZE_ERROR_CODE_V1,
|
|
36
|
+
OhConflictError,
|
|
37
|
+
OhDependencyError,
|
|
38
|
+
OhIntegrityError,
|
|
39
|
+
OhOperationSizeError,
|
|
40
|
+
OhProfileError,
|
|
41
|
+
} from "./errors";
|
|
42
|
+
import {
|
|
43
|
+
OhConflictError,
|
|
44
|
+
OhDependencyError,
|
|
45
|
+
OhIntegrityError,
|
|
46
|
+
OhProfileError,
|
|
47
|
+
} from "./errors";
|
|
29
48
|
import {
|
|
30
49
|
createOhOperationV1,
|
|
31
50
|
parseOhOperationV1,
|
|
32
51
|
type OhOperationV1,
|
|
33
52
|
} from "./operation";
|
|
34
53
|
|
|
35
|
-
export class OhConflictError extends Error {
|
|
36
|
-
constructor(message: string) { super(message); this.name = "OhConflictError"; }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export class OhIntegrityError extends Error {
|
|
40
|
-
constructor(message: string) { super(message); this.name = "OhIntegrityError"; }
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export class OhDependencyError extends Error {
|
|
44
|
-
constructor(message: string) { super(message); this.name = "OhDependencyError"; }
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export class OhProfileError extends Error {
|
|
48
|
-
constructor(message: string) { super(message); this.name = "OhProfileError"; }
|
|
49
|
-
}
|
|
50
|
-
|
|
51
54
|
export type OhHeadV1 = Readonly<{
|
|
52
55
|
generation: number;
|
|
53
56
|
graphRevisionSha256: Sha256Hex | null;
|
|
@@ -64,6 +67,7 @@ export type OhCommitInputV1 = Readonly<{
|
|
|
64
67
|
changes: readonly KnowledgeGraphChangeV1[];
|
|
65
68
|
expectedHead: Pick<OhHeadV1, "generation" | "operationSha256">;
|
|
66
69
|
instant?: string;
|
|
70
|
+
maximumOperationBytes?: number;
|
|
67
71
|
operationId: string;
|
|
68
72
|
}>;
|
|
69
73
|
|
|
@@ -410,6 +414,7 @@ export function transitionOhSnapshotV1(input: Readonly<{
|
|
|
410
414
|
actorId: string;
|
|
411
415
|
changes: readonly KnowledgeGraphChangeV1[];
|
|
412
416
|
instant: string;
|
|
417
|
+
maximumOperationBytes?: number;
|
|
413
418
|
operationId: string;
|
|
414
419
|
snapshot: OhSnapshotV1;
|
|
415
420
|
spaceId: string;
|
|
@@ -459,10 +464,15 @@ export function transitionOhSnapshotV1(input: Readonly<{
|
|
|
459
464
|
const graphRevisionSha256 = graphRevisionSha256V1({ changes, operationId,
|
|
460
465
|
parentGraphRevisionSha256: head.graphRevisionSha256, recordsSha256,
|
|
461
466
|
revision: head.sequence + 1 });
|
|
462
|
-
const operation = createOhOperationV1(
|
|
463
|
-
contractId: OH_CONTRACT_MANIFEST_V1.contractId,
|
|
464
|
-
|
|
465
|
-
|
|
467
|
+
const operation = createOhOperationV1(
|
|
468
|
+
{ actorId, changes, contractId: OH_CONTRACT_MANIFEST_V1.contractId,
|
|
469
|
+
graphRevisionSha256, instant, operationId,
|
|
470
|
+
parentOperationSha256: head.operationSha256, recordsSha256,
|
|
471
|
+
sequence: head.sequence + 1, spaceId, v: 1 },
|
|
472
|
+
input.maximumOperationBytes === undefined ? {} : {
|
|
473
|
+
maximumOperationBytes: input.maximumOperationBytes,
|
|
474
|
+
},
|
|
475
|
+
);
|
|
466
476
|
const nextHead: OhHeadV1 = { generation: operation.sequence, graphRevisionSha256,
|
|
467
477
|
operationSha256: operation.operationSha256, recordsSha256,
|
|
468
478
|
sequence: operation.sequence, v: 1 };
|