@hraness/oh 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +164 -114
  2. package/dist/cli.d.ts +1 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +811 -97
  5. package/dist/errors.d.ts +39 -0
  6. package/dist/errors.d.ts.map +1 -0
  7. package/dist/graph.d.ts.map +1 -1
  8. package/dist/index.js +676 -61
  9. package/dist/libsql.d.ts.map +1 -1
  10. package/dist/libsql.js +162 -35
  11. package/dist/memory-page.js +2 -2
  12. package/dist/memory.d.ts +87 -6
  13. package/dist/memory.d.ts.map +1 -1
  14. package/dist/memory.js +1106 -148
  15. package/dist/operation.d.ts +3 -1
  16. package/dist/operation.d.ts.map +1 -1
  17. package/dist/projection-public.js +2 -2
  18. package/dist/projection-suss.js +2 -2
  19. package/dist/sdk.js +780 -88
  20. package/dist/semantic-cloud.js +2 -2
  21. package/dist/semantic.js +2 -2
  22. package/dist/sqlite/index.js +1251 -306
  23. package/dist/sqlite/port.d.ts +31 -3
  24. package/dist/sqlite/port.d.ts.map +1 -1
  25. package/dist/sqlite/store.d.ts +18 -2
  26. package/dist/sqlite/store.d.ts.map +1 -1
  27. package/dist/store.d.ts +3 -12
  28. package/dist/store.d.ts.map +1 -1
  29. package/dist/store.js +154 -32
  30. package/dist/sync.d.ts +7 -1
  31. package/dist/sync.d.ts.map +1 -1
  32. package/dist/sync.js +668 -35
  33. package/package.json +5 -1
  34. package/skills/oh/SKILL.md +42 -16
  35. package/spec/README.md +2 -2
  36. package/spec/v1/memory.md +134 -16
  37. package/spec/v1/storage.md +8 -5
  38. package/spec/v1/store.md +20 -0
  39. package/spec/v1/sync.md +77 -8
  40. package/src/cli.test.ts +53 -1
  41. package/src/cli.ts +34 -8
  42. package/src/errors.test.ts +87 -0
  43. package/src/errors.ts +185 -0
  44. package/src/graph.ts +2 -2
  45. package/src/libsql.test.ts +36 -0
  46. package/src/libsql.ts +26 -5
  47. package/src/memory.test.ts +1488 -18
  48. package/src/memory.ts +1199 -122
  49. package/src/operation.ts +13 -3
  50. package/src/sqlite/port.test.ts +209 -0
  51. package/src/sqlite/port.ts +118 -4
  52. package/src/sqlite/store.test.ts +106 -1
  53. package/src/sqlite/store.ts +168 -30
  54. package/src/store.test.ts +12 -0
  55. package/src/store.ts +30 -20
  56. package/src/sync.test.ts +570 -2
  57. package/src/sync.ts +586 -36
@@ -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 || row.sequence !== operation.sequence + index
448
- || row.parent_operation_sha256 !== priorSha256
449
- || (index === 0 && row.operation_sha256 !== operation.operationSha256)) {
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 = row.operation_sha256;
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
- if (actorId === null || operationId === null) throw new TypeError("Invalid actor or operation ID.");
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 head = this.head();
562
- this.#assertCurrentHeadAuthority(head);
563
- const duplicate = this.database.query<OperationRow, [string]>(
564
- `SELECT ${OPERATION_COLUMNS} FROM oh_operations WHERE operation_sha256 = ?`,
565
- ).get(operation.operationSha256);
566
- if (duplicate !== null) {
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
- return { imported: false, operation };
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 (operation.sequence !== head.sequence + 1 || operation.parentOperationSha256 !== head.operationSha256) {
576
- throw new OhConflictError("The imported operation does not extend the local head.");
667
+ if (operations.length === 0) {
668
+ return { head: current, imported: 0, status: "already-present", v: 1 };
577
669
  }
578
- const transition = this.#transition(head, operation.changes, operation.operationId);
579
- if (transition.recordsSha256 !== operation.recordsSha256
580
- || transition.graphRevisionSha256 !== operation.graphRevisionSha256) {
581
- throw new OhIntegrityError("The imported operation does not reproduce its declared graph head.");
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
- this.#persist(operation);
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({ actorId, changes,
463
- contractId: OH_CONTRACT_MANIFEST_V1.contractId, graphRevisionSha256, instant,
464
- operationId, parentOperationSha256: head.operationSha256,
465
- recordsSha256, sequence: head.sequence + 1, spaceId, v: 1 });
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 };