@hraness/oh 0.3.1 → 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.
- package/README.md +194 -127
- 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-semantic-v2.d.ts +160 -0
- package/dist/libsql-semantic-v2.d.ts.map +1 -0
- 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.d.ts +1 -0
- package/dist/semantic-cloud.d.ts.map +1 -1
- package/dist/semantic-cloud.js +1545 -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 +10 -3
- package/spec/manifest.json +5 -1
- package/spec/v1/libsql-semantic-cache-schema-v1.sql +114 -0
- package/spec/v1/libsql-semantic-digest-fixture-v1.json +13 -0
- 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/spec/v2/libsql-semantic-cache-schema-v2.sql +175 -0
- package/spec/v2/libsql-semantic-digest-fixture-v2.json +14 -0
- package/spec/v2/manifest.json +11 -0
- package/spec/v2/semantic-cloud.md +106 -0
- 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-semantic-v2.test.ts +1114 -0
- package/src/libsql-semantic-v2.ts +1891 -0
- package/src/libsql-semantic.test.ts +50 -1
- 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/semantic-cloud.ts +1 -0
- 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/dist/memory.js
CHANGED
|
@@ -190,7 +190,7 @@ var OH_GRAPH_LIMITS_V1 = Object.freeze({
|
|
|
190
190
|
recordBytes: 1024 * 1024,
|
|
191
191
|
recordsPerSnapshot: 65536
|
|
192
192
|
});
|
|
193
|
-
var OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1 = [
|
|
193
|
+
var OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1 = Object.freeze([
|
|
194
194
|
"activity",
|
|
195
195
|
"assertion",
|
|
196
196
|
"context",
|
|
@@ -209,7 +209,7 @@ var OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1 = [
|
|
|
209
209
|
"type-membership",
|
|
210
210
|
"view",
|
|
211
211
|
"vocabulary"
|
|
212
|
-
];
|
|
212
|
+
]);
|
|
213
213
|
var KNOWLEDGE_GRAPH_RECORD_KEYS_V1 = [
|
|
214
214
|
"dependencies",
|
|
215
215
|
"key",
|
|
@@ -422,6 +422,141 @@ class OhRecordCodecRegistry {
|
|
|
422
422
|
return this.#sealed;
|
|
423
423
|
}
|
|
424
424
|
}
|
|
425
|
+
// src/errors.ts
|
|
426
|
+
var OH_OPERATION_SIZE_ERROR_CODE_V1 = "oh.operation-size.v1";
|
|
427
|
+
var OH_CONFLICT_ERROR_BRAND_V1 = Symbol.for("@hraness/oh/OhConflictError/v1");
|
|
428
|
+
var OH_DEPENDENCY_ERROR_BRAND_V1 = Symbol.for("@hraness/oh/OhDependencyError/v1");
|
|
429
|
+
var OH_INTEGRITY_ERROR_BRAND_V1 = Symbol.for("@hraness/oh/OhIntegrityError/v1");
|
|
430
|
+
var OH_OPERATION_SIZE_ERROR_BRAND_V1 = Symbol.for("@hraness/oh/OhOperationSizeError/v1");
|
|
431
|
+
var OH_PROFILE_ERROR_BRAND_V1 = Symbol.for("@hraness/oh/OhProfileError/v1");
|
|
432
|
+
function immutableOwnValue(value, key) {
|
|
433
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
434
|
+
return descriptor !== undefined && descriptor.get === undefined && descriptor.set === undefined && descriptor.configurable === false && descriptor.writable === false ? descriptor.value : undefined;
|
|
435
|
+
}
|
|
436
|
+
function brandNativeError(value, brand) {
|
|
437
|
+
Object.defineProperty(value, brand, {
|
|
438
|
+
configurable: false,
|
|
439
|
+
enumerable: false,
|
|
440
|
+
value: true,
|
|
441
|
+
writable: false
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
function hasNativeErrorBrand(value, brand) {
|
|
445
|
+
try {
|
|
446
|
+
return Error.isError(value) && immutableOwnValue(value, brand) === true;
|
|
447
|
+
} catch {
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
function hasNativeSubclassInstance(constructor, value) {
|
|
452
|
+
return Function.prototype[Symbol.hasInstance].call(constructor, value);
|
|
453
|
+
}
|
|
454
|
+
function isOhConflictError(value) {
|
|
455
|
+
return hasNativeErrorBrand(value, OH_CONFLICT_ERROR_BRAND_V1);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
class OhConflictError extends Error {
|
|
459
|
+
static [Symbol.hasInstance](value) {
|
|
460
|
+
return this === OhConflictError ? isOhConflictError(value) : hasNativeSubclassInstance(this, value);
|
|
461
|
+
}
|
|
462
|
+
constructor(message) {
|
|
463
|
+
super(message);
|
|
464
|
+
this.name = "OhConflictError";
|
|
465
|
+
brandNativeError(this, OH_CONFLICT_ERROR_BRAND_V1);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
function isOhIntegrityError(value) {
|
|
469
|
+
return hasNativeErrorBrand(value, OH_INTEGRITY_ERROR_BRAND_V1);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
class OhIntegrityError extends Error {
|
|
473
|
+
static [Symbol.hasInstance](value) {
|
|
474
|
+
return this === OhIntegrityError ? isOhIntegrityError(value) : hasNativeSubclassInstance(this, value);
|
|
475
|
+
}
|
|
476
|
+
constructor(message) {
|
|
477
|
+
super(message);
|
|
478
|
+
this.name = "OhIntegrityError";
|
|
479
|
+
brandNativeError(this, OH_INTEGRITY_ERROR_BRAND_V1);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function isOhDependencyError(value) {
|
|
483
|
+
return hasNativeErrorBrand(value, OH_DEPENDENCY_ERROR_BRAND_V1);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
class OhDependencyError extends Error {
|
|
487
|
+
static [Symbol.hasInstance](value) {
|
|
488
|
+
return this === OhDependencyError ? isOhDependencyError(value) : hasNativeSubclassInstance(this, value);
|
|
489
|
+
}
|
|
490
|
+
constructor(message) {
|
|
491
|
+
super(message);
|
|
492
|
+
this.name = "OhDependencyError";
|
|
493
|
+
brandNativeError(this, OH_DEPENDENCY_ERROR_BRAND_V1);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
function isOhProfileError(value) {
|
|
497
|
+
return hasNativeErrorBrand(value, OH_PROFILE_ERROR_BRAND_V1);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
class OhProfileError extends Error {
|
|
501
|
+
static [Symbol.hasInstance](value) {
|
|
502
|
+
return this === OhProfileError ? isOhProfileError(value) : hasNativeSubclassInstance(this, value);
|
|
503
|
+
}
|
|
504
|
+
constructor(message) {
|
|
505
|
+
super(message);
|
|
506
|
+
this.name = "OhProfileError";
|
|
507
|
+
brandNativeError(this, OH_PROFILE_ERROR_BRAND_V1);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
function isOhOperationSizeError(value) {
|
|
511
|
+
try {
|
|
512
|
+
if (!Error.isError(value) || !(value instanceof RangeError))
|
|
513
|
+
return false;
|
|
514
|
+
const operationBytes = immutableOwnValue(value, "operationBytes");
|
|
515
|
+
const maximumOperationBytes = immutableOwnValue(value, "maximumOperationBytes");
|
|
516
|
+
return immutableOwnValue(value, OH_OPERATION_SIZE_ERROR_BRAND_V1) === true && immutableOwnValue(value, "code") === OH_OPERATION_SIZE_ERROR_CODE_V1 && Number.isSafeInteger(operationBytes) && operationBytes > 0 && Number.isSafeInteger(maximumOperationBytes) && maximumOperationBytes > 0 && operationBytes > maximumOperationBytes;
|
|
517
|
+
} catch {
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
class OhOperationSizeError extends RangeError {
|
|
523
|
+
static [Symbol.hasInstance](value) {
|
|
524
|
+
return this === OhOperationSizeError ? isOhOperationSizeError(value) : hasNativeSubclassInstance(this, value);
|
|
525
|
+
}
|
|
526
|
+
constructor(operationBytes, maximumOperationBytes) {
|
|
527
|
+
if (!Number.isSafeInteger(operationBytes) || operationBytes < 1 || !Number.isSafeInteger(maximumOperationBytes) || maximumOperationBytes < 1 || operationBytes <= maximumOperationBytes) {
|
|
528
|
+
throw new TypeError("Invalid Oh operation size refusal.");
|
|
529
|
+
}
|
|
530
|
+
super(`The ${operationBytes}-byte operation exceeds the host-declared ${maximumOperationBytes}-byte canonical bound.`);
|
|
531
|
+
this.name = "OhOperationSizeError";
|
|
532
|
+
Object.defineProperties(this, {
|
|
533
|
+
[OH_OPERATION_SIZE_ERROR_BRAND_V1]: {
|
|
534
|
+
configurable: false,
|
|
535
|
+
enumerable: false,
|
|
536
|
+
value: true,
|
|
537
|
+
writable: false
|
|
538
|
+
},
|
|
539
|
+
code: {
|
|
540
|
+
configurable: false,
|
|
541
|
+
enumerable: true,
|
|
542
|
+
value: OH_OPERATION_SIZE_ERROR_CODE_V1,
|
|
543
|
+
writable: false
|
|
544
|
+
},
|
|
545
|
+
maximumOperationBytes: {
|
|
546
|
+
configurable: false,
|
|
547
|
+
enumerable: true,
|
|
548
|
+
value: maximumOperationBytes,
|
|
549
|
+
writable: false
|
|
550
|
+
},
|
|
551
|
+
operationBytes: {
|
|
552
|
+
configurable: false,
|
|
553
|
+
enumerable: true,
|
|
554
|
+
value: operationBytes,
|
|
555
|
+
writable: false
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
}
|
|
425
560
|
// src/operation.ts
|
|
426
561
|
var OH_OPERATION_MAX_BYTES_V1 = 64 * 1024 * 1024;
|
|
427
562
|
function parsePayload(value) {
|
|
@@ -469,13 +604,18 @@ function parsePayload(value) {
|
|
|
469
604
|
v: 1
|
|
470
605
|
} : null;
|
|
471
606
|
}
|
|
472
|
-
function createOhOperationV1(input) {
|
|
607
|
+
function createOhOperationV1(input, options = {}) {
|
|
608
|
+
const maximumOperationBytes = options.maximumOperationBytes ?? OH_OPERATION_MAX_BYTES_V1;
|
|
609
|
+
if (!Number.isSafeInteger(maximumOperationBytes) || maximumOperationBytes < 1 || maximumOperationBytes > OH_OPERATION_MAX_BYTES_V1) {
|
|
610
|
+
throw new TypeError("Invalid Oh operation byte bound.");
|
|
611
|
+
}
|
|
473
612
|
const payload = parsePayload(input);
|
|
474
613
|
if (payload === null)
|
|
475
614
|
throw new TypeError("Invalid Oh operation payload.");
|
|
476
615
|
const operation = { ...payload, operationSha256: canonicalSha256(payload) };
|
|
477
|
-
|
|
478
|
-
|
|
616
|
+
const operationBytes = Buffer.byteLength(canonicalJson(operation), "utf8");
|
|
617
|
+
if (operationBytes > maximumOperationBytes) {
|
|
618
|
+
throw new OhOperationSizeError(operationBytes, maximumOperationBytes);
|
|
479
619
|
}
|
|
480
620
|
return operation;
|
|
481
621
|
}
|
|
@@ -489,33 +629,6 @@ function parseOhOperationV1(value) {
|
|
|
489
629
|
}
|
|
490
630
|
|
|
491
631
|
// src/store.ts
|
|
492
|
-
class OhConflictError extends Error {
|
|
493
|
-
constructor(message) {
|
|
494
|
-
super(message);
|
|
495
|
-
this.name = "OhConflictError";
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
class OhIntegrityError extends Error {
|
|
500
|
-
constructor(message) {
|
|
501
|
-
super(message);
|
|
502
|
-
this.name = "OhIntegrityError";
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
class OhDependencyError extends Error {
|
|
507
|
-
constructor(message) {
|
|
508
|
-
super(message);
|
|
509
|
-
this.name = "OhDependencyError";
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
class OhProfileError extends Error {
|
|
514
|
-
constructor(message) {
|
|
515
|
-
super(message);
|
|
516
|
-
this.name = "OhProfileError";
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
632
|
var OH_CANONICAL_STORE_PROFILE_V1 = createOhStoreProfileV1({
|
|
520
633
|
applicationProfileSha256: null,
|
|
521
634
|
capabilities: {
|
|
@@ -828,6 +941,8 @@ function transitionOhSnapshotV1(input) {
|
|
|
828
941
|
sequence: head.sequence + 1,
|
|
829
942
|
spaceId,
|
|
830
943
|
v: 1
|
|
944
|
+
}, input.maximumOperationBytes === undefined ? {} : {
|
|
945
|
+
maximumOperationBytes: input.maximumOperationBytes
|
|
831
946
|
});
|
|
832
947
|
const nextHead = {
|
|
833
948
|
generation: operation.sequence,
|
|
@@ -1057,6 +1172,7 @@ class OhSemanticBundleIngressV1 {
|
|
|
1057
1172
|
|
|
1058
1173
|
// src/memory.ts
|
|
1059
1174
|
import { createHmac, randomBytes as randomBytes2, timingSafeEqual } from "node:crypto";
|
|
1175
|
+
import { isProxy } from "node:util/types";
|
|
1060
1176
|
|
|
1061
1177
|
// src/memory-pages.ts
|
|
1062
1178
|
var OH_MEMORY_PAGE_FORMAT_V1 = "oh.memory-page.v1";
|
|
@@ -2706,6 +2822,9 @@ function isOhProjectionRecordKindV1(value) {
|
|
|
2706
2822
|
var OH_MEMORY_FORMAT_VERSION_V1 = 1;
|
|
2707
2823
|
var OH_MEMORY_CONFLICT_POLICY_V1 = "visible-conflicts.v1";
|
|
2708
2824
|
var OH_MEMORY_LIMITS_V1 = Object.freeze({
|
|
2825
|
+
detachedCanonicalBreadth: 65536,
|
|
2826
|
+
detachedCanonicalDepth: 128,
|
|
2827
|
+
detachedCanonicalNodes: 1048576,
|
|
2709
2828
|
explainCapabilityEntryBytes: 32 * 1024 * 1024,
|
|
2710
2829
|
explainCapabilities: 256,
|
|
2711
2830
|
explainCapabilityLifetimeMs: 15 * 60 * 1000,
|
|
@@ -2745,6 +2864,40 @@ var OH_MEMORY_QUERY_LIMITS_V2 = Object.freeze({
|
|
|
2745
2864
|
minimumPageBytes: 64 * 1024,
|
|
2746
2865
|
requestBytes: 80 * 1024
|
|
2747
2866
|
});
|
|
2867
|
+
|
|
2868
|
+
class OhMemoryContinuationError extends OhIntegrityError {
|
|
2869
|
+
constructor(reason, message) {
|
|
2870
|
+
super(message);
|
|
2871
|
+
this.name = "OhMemoryContinuationError";
|
|
2872
|
+
Object.defineProperties(this, {
|
|
2873
|
+
code: { configurable: false, enumerable: true, value: "memory-continuation", writable: false },
|
|
2874
|
+
reason: { configurable: false, enumerable: true, value: reason, writable: false }
|
|
2875
|
+
});
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
var OH_MEMORY_AUTHORITY_LIMITS_V1 = Object.freeze({
|
|
2879
|
+
adoptionReplacements: 128,
|
|
2880
|
+
adoptionRequestBytes: OH_DEPENDENCY_CLOSURE_LIMITS_V1.bytes + 192 * 1024,
|
|
2881
|
+
canonicalAdvanceOperations: 16384,
|
|
2882
|
+
canonicalAdvancePages: 64,
|
|
2883
|
+
canonicalChangeFeedPage: 1000,
|
|
2884
|
+
canonicalChangeFeedPageBytes: 64 * 1024 * 1024,
|
|
2885
|
+
retainedExplanationRoutes: OH_MEMORY_LIMITS_V1.explainCapabilities,
|
|
2886
|
+
reportedAdoptionConflicts: 128
|
|
2887
|
+
});
|
|
2888
|
+
|
|
2889
|
+
class OhMemoryAdoptionConflictError extends OhConflictError {
|
|
2890
|
+
constructor(conflict) {
|
|
2891
|
+
super("The nominated records conflict with the current canonical memory head.");
|
|
2892
|
+
this.name = "OhMemoryAdoptionConflictError";
|
|
2893
|
+
Object.defineProperty(this, "conflict", {
|
|
2894
|
+
configurable: false,
|
|
2895
|
+
enumerable: true,
|
|
2896
|
+
value: immutableClone(conflict),
|
|
2897
|
+
writable: false
|
|
2898
|
+
});
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2748
2901
|
var builtInFactPolicy = Object.freeze({
|
|
2749
2902
|
extractorSha256: OH_MEMORY_COMPOSITE_FACT_EXTRACTOR_V1.extractorSha256,
|
|
2750
2903
|
factPackId: OH_MEMORY_COMPOSITE_FACT_EXTRACTOR_V1.factPackId,
|
|
@@ -2771,6 +2924,149 @@ function immutableClone(value) {
|
|
|
2771
2924
|
}
|
|
2772
2925
|
return value;
|
|
2773
2926
|
}
|
|
2927
|
+
function canonicalStringByteLength(value, label, path, maximumBytes) {
|
|
2928
|
+
let bytes = 2;
|
|
2929
|
+
for (let index = 0;index < value.length; index += 1) {
|
|
2930
|
+
const code = value.charCodeAt(index);
|
|
2931
|
+
if (code >= 55296 && code <= 56319) {
|
|
2932
|
+
const next = value.charCodeAt(index + 1);
|
|
2933
|
+
if (!(next >= 56320 && next <= 57343)) {
|
|
2934
|
+
throw new TypeError(`${label} contains invalid Unicode at ${path}.`);
|
|
2935
|
+
}
|
|
2936
|
+
bytes += 4;
|
|
2937
|
+
index += 1;
|
|
2938
|
+
} else if (code >= 56320 && code <= 57343) {
|
|
2939
|
+
throw new TypeError(`${label} contains invalid Unicode at ${path}.`);
|
|
2940
|
+
} else if (code === 34 || code === 92 || code === 8 || code === 9 || code === 10 || code === 12 || code === 13) {
|
|
2941
|
+
bytes += 2;
|
|
2942
|
+
} else if (code <= 31) {
|
|
2943
|
+
bytes += 6;
|
|
2944
|
+
} else if (code <= 127) {
|
|
2945
|
+
bytes += 1;
|
|
2946
|
+
} else if (code <= 2047) {
|
|
2947
|
+
bytes += 2;
|
|
2948
|
+
} else {
|
|
2949
|
+
bytes += 3;
|
|
2950
|
+
}
|
|
2951
|
+
if (bytes > maximumBytes) {
|
|
2952
|
+
throw new RangeError(`${label} exceeds its canonical byte bound.`);
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
return bytes;
|
|
2956
|
+
}
|
|
2957
|
+
function detachCanonicalData(value, label, maximumBytes) {
|
|
2958
|
+
const ancestors = new Set;
|
|
2959
|
+
let bytes = 0;
|
|
2960
|
+
let nodes = 0;
|
|
2961
|
+
const spendBytes = (count) => {
|
|
2962
|
+
bytes += count;
|
|
2963
|
+
if (bytes > maximumBytes)
|
|
2964
|
+
throw new RangeError(`${label} exceeds its canonical byte bound.`);
|
|
2965
|
+
};
|
|
2966
|
+
const detach = (candidate, path, depth) => {
|
|
2967
|
+
if (depth > OH_MEMORY_LIMITS_V1.detachedCanonicalDepth) {
|
|
2968
|
+
throw new RangeError(`${label} exceeds its canonical nesting depth bound.`);
|
|
2969
|
+
}
|
|
2970
|
+
nodes += 1;
|
|
2971
|
+
if (nodes > OH_MEMORY_LIMITS_V1.detachedCanonicalNodes) {
|
|
2972
|
+
throw new RangeError(`${label} exceeds its canonical node bound.`);
|
|
2973
|
+
}
|
|
2974
|
+
if (candidate === null) {
|
|
2975
|
+
spendBytes(4);
|
|
2976
|
+
return candidate;
|
|
2977
|
+
}
|
|
2978
|
+
if (typeof candidate === "boolean") {
|
|
2979
|
+
spendBytes(candidate ? 4 : 5);
|
|
2980
|
+
return candidate;
|
|
2981
|
+
}
|
|
2982
|
+
if (typeof candidate === "string") {
|
|
2983
|
+
spendBytes(canonicalStringByteLength(candidate, label, path, maximumBytes - bytes));
|
|
2984
|
+
return candidate;
|
|
2985
|
+
}
|
|
2986
|
+
if (typeof candidate === "number") {
|
|
2987
|
+
if (!Number.isFinite(candidate) || Object.is(candidate, -0)) {
|
|
2988
|
+
throw new TypeError(`${label} contains a noncanonical number at ${path}.`);
|
|
2989
|
+
}
|
|
2990
|
+
spendBytes(utf8ByteLength(canonicalJson(candidate)));
|
|
2991
|
+
return candidate;
|
|
2992
|
+
}
|
|
2993
|
+
if (typeof candidate !== "object") {
|
|
2994
|
+
throw new TypeError(`${label} contains a non-JSON value at ${path}.`);
|
|
2995
|
+
}
|
|
2996
|
+
if (isProxy(candidate))
|
|
2997
|
+
throw new TypeError(`${label} contains a proxy at ${path}.`);
|
|
2998
|
+
if (ancestors.has(candidate))
|
|
2999
|
+
throw new TypeError(`${label} contains a cycle at ${path}.`);
|
|
3000
|
+
ancestors.add(candidate);
|
|
3001
|
+
try {
|
|
3002
|
+
if (Array.isArray(candidate)) {
|
|
3003
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(candidate, "length");
|
|
3004
|
+
const length = lengthDescriptor?.value;
|
|
3005
|
+
if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0) {
|
|
3006
|
+
throw new TypeError(`${label} contains an invalid array at ${path}.`);
|
|
3007
|
+
}
|
|
3008
|
+
if (length > OH_MEMORY_LIMITS_V1.detachedCanonicalBreadth) {
|
|
3009
|
+
throw new RangeError(`${label} exceeds its canonical breadth bound.`);
|
|
3010
|
+
}
|
|
3011
|
+
if (length > OH_MEMORY_LIMITS_V1.detachedCanonicalNodes - nodes) {
|
|
3012
|
+
throw new RangeError(`${label} exceeds its canonical node bound.`);
|
|
3013
|
+
}
|
|
3014
|
+
spendBytes(2 + Math.max(0, length - 1));
|
|
3015
|
+
const keys2 = Reflect.ownKeys(candidate);
|
|
3016
|
+
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))) {
|
|
3017
|
+
throw new TypeError(`${label} contains a non-data array at ${path}.`);
|
|
3018
|
+
}
|
|
3019
|
+
const detached3 = [];
|
|
3020
|
+
for (let index = 0;index < length; index += 1) {
|
|
3021
|
+
const descriptor = Object.getOwnPropertyDescriptor(candidate, String(index));
|
|
3022
|
+
if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined) {
|
|
3023
|
+
throw new TypeError(`${label} contains a non-data array entry at ${path}[${index}].`);
|
|
3024
|
+
}
|
|
3025
|
+
detached3.push(detach(descriptor.value, `${path}[${index}]`, depth + 1));
|
|
3026
|
+
}
|
|
3027
|
+
return Object.freeze(detached3);
|
|
3028
|
+
}
|
|
3029
|
+
const prototype = Object.getPrototypeOf(candidate);
|
|
3030
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
3031
|
+
throw new TypeError(`${label} contains a non-plain object at ${path}.`);
|
|
3032
|
+
}
|
|
3033
|
+
const keys = Reflect.ownKeys(candidate);
|
|
3034
|
+
if (keys.some((key) => typeof key !== "string")) {
|
|
3035
|
+
throw new TypeError(`${label} contains a symbol property at ${path}.`);
|
|
3036
|
+
}
|
|
3037
|
+
if (keys.length > OH_MEMORY_LIMITS_V1.detachedCanonicalBreadth) {
|
|
3038
|
+
throw new RangeError(`${label} exceeds its canonical breadth bound.`);
|
|
3039
|
+
}
|
|
3040
|
+
if (keys.length > OH_MEMORY_LIMITS_V1.detachedCanonicalNodes - nodes) {
|
|
3041
|
+
throw new RangeError(`${label} exceeds its canonical node bound.`);
|
|
3042
|
+
}
|
|
3043
|
+
spendBytes(2 + Math.max(0, keys.length - 1));
|
|
3044
|
+
const detached2 = {};
|
|
3045
|
+
for (const key of keys) {
|
|
3046
|
+
spendBytes(canonicalStringByteLength(key, label, `${path}.<key>`, maximumBytes - bytes - 1) + 1);
|
|
3047
|
+
const descriptor = Object.getOwnPropertyDescriptor(candidate, key);
|
|
3048
|
+
if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined) {
|
|
3049
|
+
throw new TypeError(`${label} contains a non-data property at ${path}.${key}.`);
|
|
3050
|
+
}
|
|
3051
|
+
Object.defineProperty(detached2, key, {
|
|
3052
|
+
configurable: false,
|
|
3053
|
+
enumerable: true,
|
|
3054
|
+
value: detach(descriptor.value, `${path}.${key}`, depth + 1),
|
|
3055
|
+
writable: false
|
|
3056
|
+
});
|
|
3057
|
+
}
|
|
3058
|
+
return Object.freeze(detached2);
|
|
3059
|
+
} finally {
|
|
3060
|
+
ancestors.delete(candidate);
|
|
3061
|
+
}
|
|
3062
|
+
};
|
|
3063
|
+
const detached = detach(value, "$root", 0);
|
|
3064
|
+
const canonical = canonicalJson(detached);
|
|
3065
|
+
if (utf8ByteLength(canonical) !== bytes) {
|
|
3066
|
+
throw new OhIntegrityError(`${label} canonical byte accounting did not reproduce its snapshot.`);
|
|
3067
|
+
}
|
|
3068
|
+
return Object.freeze({ canonical, value: detached });
|
|
3069
|
+
}
|
|
2774
3070
|
function compareText(left, right) {
|
|
2775
3071
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
2776
3072
|
}
|
|
@@ -2804,8 +3100,43 @@ function laneIdentity(value) {
|
|
|
2804
3100
|
v: 1
|
|
2805
3101
|
});
|
|
2806
3102
|
}
|
|
2807
|
-
function
|
|
2808
|
-
const
|
|
3103
|
+
function parseDetachedStoreHead(value, label) {
|
|
3104
|
+
const head = parseOhHeadV1(detachCanonicalData(value, `${label} head`, 4 * 1024).value);
|
|
3105
|
+
if (head === null)
|
|
3106
|
+
throw new OhIntegrityError(`${label} returned an invalid head.`);
|
|
3107
|
+
return immutableClone(head);
|
|
3108
|
+
}
|
|
3109
|
+
function parseDetachedStoreSnapshot(value, label, expectedHead, spaceId) {
|
|
3110
|
+
const detachedSnapshot = detachCanonicalData(value, `${label} snapshot`, OH_MEMORY_LIMITS_V1.snapshotBytesPerLane);
|
|
3111
|
+
if (!isPlainRecord(detachedSnapshot.value) || !hasExactKeys(detachedSnapshot.value, ["head", "records", "v"]) || detachedSnapshot.value.v !== 1 || !Array.isArray(detachedSnapshot.value.records)) {
|
|
3112
|
+
throw new OhIntegrityError(`${label} returned an invalid snapshot envelope.`);
|
|
3113
|
+
}
|
|
3114
|
+
const detached = detachedSnapshot.value;
|
|
3115
|
+
const detachedHead = parseOhHeadV1(detached.head);
|
|
3116
|
+
if (detachedHead === null)
|
|
3117
|
+
throw new OhIntegrityError(`${label} returned an invalid snapshot head.`);
|
|
3118
|
+
const snapshot = immutableClone({
|
|
3119
|
+
head: detachedHead,
|
|
3120
|
+
records: detached.records,
|
|
3121
|
+
v: 1
|
|
3122
|
+
});
|
|
3123
|
+
if (!exactHead(snapshot.head, expectedHead)) {
|
|
3124
|
+
throw new OhIntegrityError(`${label} snapshot differs from its pinned head.`);
|
|
3125
|
+
}
|
|
3126
|
+
let projectionSnapshot;
|
|
3127
|
+
try {
|
|
3128
|
+
projectionSnapshot = createOhProjectionSnapshotV1({
|
|
3129
|
+
head: snapshot.head,
|
|
3130
|
+
records: snapshot.records,
|
|
3131
|
+
spaceId
|
|
3132
|
+
});
|
|
3133
|
+
} catch {
|
|
3134
|
+
throw new OhIntegrityError(`${label} returned invalid snapshot records.`);
|
|
3135
|
+
}
|
|
3136
|
+
return Object.freeze({ projectionSnapshot, snapshot });
|
|
3137
|
+
}
|
|
3138
|
+
function datasetForSnapshot(binding, snapshot, validatedProjectionSnapshot) {
|
|
3139
|
+
const projectionSnapshot = validatedProjectionSnapshot ?? createOhProjectionSnapshotV1({
|
|
2809
3140
|
head: snapshot.head,
|
|
2810
3141
|
records: snapshot.records,
|
|
2811
3142
|
spaceId: binding.spaceId
|
|
@@ -2820,40 +3151,21 @@ function datasetForSnapshot(binding, snapshot) {
|
|
|
2820
3151
|
return { dataset, projectionSnapshot };
|
|
2821
3152
|
}
|
|
2822
3153
|
async function readLane(authority, lane, expectedHead) {
|
|
2823
|
-
const
|
|
2824
|
-
const head =
|
|
2825
|
-
if (head === null)
|
|
2826
|
-
throw new OhIntegrityError(`The ${lane} store returned an invalid head.`);
|
|
3154
|
+
const label = `The ${lane} store`;
|
|
3155
|
+
const head = expectedHead === undefined ? parseDetachedStoreHead(await authority.store.head(), label) : immutableClone(expectedHead);
|
|
2827
3156
|
const returnedSnapshot = await authority.store.snapshot({
|
|
2828
3157
|
head: { operationSha256: head.operationSha256, sequence: head.sequence },
|
|
2829
3158
|
maximumRecords: OH_MEMORY_LIMITS_V1.maximumRecordsPerLane
|
|
2830
3159
|
});
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
}
|
|
2834
|
-
const detached = immutableClone(returnedSnapshot);
|
|
2835
|
-
const detachedHead = parseOhHeadV1(detached.head);
|
|
2836
|
-
if (detachedHead === null)
|
|
2837
|
-
throw new OhIntegrityError(`The ${lane} store returned an invalid snapshot head.`);
|
|
2838
|
-
const snapshot = immutableClone({
|
|
2839
|
-
head: detachedHead,
|
|
2840
|
-
records: detached.records,
|
|
2841
|
-
v: 1
|
|
2842
|
-
});
|
|
2843
|
-
if (!exactHead(snapshot.head, head)) {
|
|
2844
|
-
throw new OhIntegrityError(`The ${lane} snapshot differs from its pinned head.`);
|
|
2845
|
-
}
|
|
2846
|
-
if (utf8ByteLength(canonicalJson(snapshot)) > OH_MEMORY_LIMITS_V1.snapshotBytesPerLane) {
|
|
2847
|
-
throw new RangeError(`The ${lane} memory snapshot exceeds its canonical byte bound.`);
|
|
2848
|
-
}
|
|
2849
|
-
const projected = datasetForSnapshot(authority.binding, snapshot);
|
|
3160
|
+
const parsed = parseDetachedStoreSnapshot(returnedSnapshot, label, head, authority.binding.spaceId);
|
|
3161
|
+
const projected = datasetForSnapshot(authority.binding, parsed.snapshot, parsed.projectionSnapshot);
|
|
2850
3162
|
return Object.freeze({
|
|
2851
3163
|
authorityId: authority.authorityId,
|
|
2852
3164
|
binding: authority.binding,
|
|
2853
3165
|
dataset: projected.dataset,
|
|
2854
3166
|
lane,
|
|
2855
3167
|
projectionSnapshot: projected.projectionSnapshot,
|
|
2856
|
-
snapshot
|
|
3168
|
+
snapshot: parsed.snapshot
|
|
2857
3169
|
});
|
|
2858
3170
|
}
|
|
2859
3171
|
function syntheticKey(lane, recordSha256) {
|
|
@@ -3167,32 +3479,93 @@ function resolveNominationRoutes(routes) {
|
|
|
3167
3479
|
return resolved;
|
|
3168
3480
|
}
|
|
3169
3481
|
function parseQueryRequest(value) {
|
|
3170
|
-
|
|
3482
|
+
const detached = detachCanonicalData(value, "The named memory query", OH_MEMORY_QUERY_LIMITS_V2.requestBytes).value;
|
|
3483
|
+
if (!isPlainRecord(detached) || !hasExactKeys(detached, ["programId", "v"]) || detached.v !== 1)
|
|
3171
3484
|
throw new TypeError("Invalid named memory query.");
|
|
3172
|
-
const programId = safeCode(
|
|
3485
|
+
const programId = safeCode(detached.programId, 128);
|
|
3173
3486
|
if (programId === null)
|
|
3174
3487
|
throw new TypeError("Invalid named memory query identity.");
|
|
3175
3488
|
return { programId };
|
|
3176
3489
|
}
|
|
3177
3490
|
function parseExplainRequest(value) {
|
|
3178
|
-
|
|
3491
|
+
const detached = detachCanonicalData(value, "The memory explanation request", OH_MEMORY_QUERY_LIMITS_V2.requestBytes).value;
|
|
3492
|
+
if (!isPlainRecord(detached) || !hasExactKeys(detached, ["resultSha256", "row", "token", "v"]) || detached.v !== 1 || typeof detached.token !== "string" || detached.token.length !== 43 || !Number.isSafeInteger(detached.row) || detached.row < 0) {
|
|
3179
3493
|
throw new TypeError("Invalid memory explanation request.");
|
|
3180
3494
|
}
|
|
3181
|
-
const resultSha256 = parseSha256Hex(
|
|
3495
|
+
const resultSha256 = parseSha256Hex(detached.resultSha256);
|
|
3182
3496
|
if (resultSha256 === null)
|
|
3183
3497
|
throw new TypeError("Invalid memory explanation result identity.");
|
|
3184
|
-
return { resultSha256, row:
|
|
3498
|
+
return { resultSha256, row: detached.row, token: detached.token };
|
|
3185
3499
|
}
|
|
3186
3500
|
function parseNominationRequest(value) {
|
|
3187
|
-
|
|
3501
|
+
const detached = detachCanonicalData(value, "The memory nomination request", OH_MEMORY_QUERY_LIMITS_V2.requestBytes * 8).value;
|
|
3502
|
+
if (!isPlainRecord(detached) || !hasExactKeys(detached, ["nominationId", "roots", "v"]) || detached.v !== 1 || !Array.isArray(detached.roots) || detached.roots.length < 1 || detached.roots.length > OH_DEPENDENCY_CLOSURE_LIMITS_V1.roots) {
|
|
3188
3503
|
throw new TypeError("Invalid memory nomination request.");
|
|
3189
3504
|
}
|
|
3190
|
-
const nominationId = safeCode(
|
|
3191
|
-
const roots =
|
|
3505
|
+
const nominationId = safeCode(detached.nominationId, 128);
|
|
3506
|
+
const roots = detached.roots.map((root) => safeCode(root, 512)).sort();
|
|
3192
3507
|
if (nominationId === null || roots.some((root) => root === null) || new Set(roots).size !== roots.length)
|
|
3193
3508
|
throw new TypeError("Invalid memory nomination identity.");
|
|
3194
3509
|
return { nominationId, roots };
|
|
3195
3510
|
}
|
|
3511
|
+
function parseDetachedMemoryNominationV1(value) {
|
|
3512
|
+
try {
|
|
3513
|
+
const keys = ownDataKeysV2(value, 7, "The memory nomination");
|
|
3514
|
+
if (keys.length !== 7 || ![
|
|
3515
|
+
"closure",
|
|
3516
|
+
"destinationPurpose",
|
|
3517
|
+
"nominationId",
|
|
3518
|
+
"nominationSha256",
|
|
3519
|
+
"source",
|
|
3520
|
+
"status",
|
|
3521
|
+
"v"
|
|
3522
|
+
].every((key) => keys.includes(key)))
|
|
3523
|
+
return null;
|
|
3524
|
+
const record = value;
|
|
3525
|
+
if (record.status !== "prepared" || record.v !== 1)
|
|
3526
|
+
return null;
|
|
3527
|
+
const closure = parseOhDependencyClosureV1(record.closure);
|
|
3528
|
+
const destinationPurpose = safeCode(record.destinationPurpose, 256);
|
|
3529
|
+
const nominationId = safeCode(record.nominationId, 128);
|
|
3530
|
+
const nominationSha256 = parseSha256Hex(record.nominationSha256);
|
|
3531
|
+
if (closure === null || destinationPurpose === null || nominationId === null || nominationSha256 === null || closure.binding.profile.profileKind !== "working" || [...ownDataKeysV2(record.source, 5, "The memory nomination source")].sort().join("\x00") !== ["authorityId", "bindingSha256", "head", "lane", "v"].sort().join("\x00"))
|
|
3532
|
+
return null;
|
|
3533
|
+
const sourceRecord = record.source;
|
|
3534
|
+
if (sourceRecord.lane !== "working" || sourceRecord.v !== 1)
|
|
3535
|
+
return null;
|
|
3536
|
+
const authorityIdValue = safeCode(sourceRecord.authorityId, 128);
|
|
3537
|
+
const bindingSha256 = parseSha256Hex(sourceRecord.bindingSha256);
|
|
3538
|
+
const head = parseOhHeadV1(sourceRecord.head);
|
|
3539
|
+
if (authorityIdValue === null || bindingSha256 === null || head === null || bindingSha256 !== closure.binding.bindingSha256 || !exactHead(head, closure.head))
|
|
3540
|
+
return null;
|
|
3541
|
+
const source = {
|
|
3542
|
+
authorityId: authorityIdValue,
|
|
3543
|
+
bindingSha256,
|
|
3544
|
+
head,
|
|
3545
|
+
lane: "working",
|
|
3546
|
+
v: 1
|
|
3547
|
+
};
|
|
3548
|
+
const payload = {
|
|
3549
|
+
closure,
|
|
3550
|
+
destinationPurpose,
|
|
3551
|
+
nominationId,
|
|
3552
|
+
source,
|
|
3553
|
+
status: "prepared",
|
|
3554
|
+
v: 1
|
|
3555
|
+
};
|
|
3556
|
+
return canonicalSha256(payload) === nominationSha256 ? immutableClone({ ...payload, nominationSha256 }) : null;
|
|
3557
|
+
} catch {
|
|
3558
|
+
return null;
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
function parseOhMemoryNominationV1(value) {
|
|
3562
|
+
try {
|
|
3563
|
+
const detached = detachCanonicalData(value, "The memory nomination", OH_MEMORY_AUTHORITY_LIMITS_V1.adoptionRequestBytes);
|
|
3564
|
+
return parseDetachedMemoryNominationV1(detached.value);
|
|
3565
|
+
} catch {
|
|
3566
|
+
return null;
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3196
3569
|
function isoInstant(date) {
|
|
3197
3570
|
const value = date.toISOString();
|
|
3198
3571
|
if (parseCanonicalInstantV1(value) === null)
|
|
@@ -3226,13 +3599,11 @@ async function createOhMemoryAgentV1(options) {
|
|
|
3226
3599
|
}
|
|
3227
3600
|
const canonicalBinding = bindingFor(canonicalStore, options.canonical.expectedBindingSha256, "canonical");
|
|
3228
3601
|
const workingBinding = bindingFor(workingStore, options.working.expectedBindingSha256, "working");
|
|
3229
|
-
const expectedCanonicalHead =
|
|
3230
|
-
if (expectedCanonicalHead === null)
|
|
3231
|
-
throw new TypeError("Invalid pinned canonical memory head.");
|
|
3602
|
+
const expectedCanonicalHead = parseMemoryAuthorityHead(options.canonical.expectedHead, "pinned canonical");
|
|
3232
3603
|
const programs = resolvePrograms(options.programs);
|
|
3233
3604
|
const extractors = resolveExtractors(options.extractors ?? []);
|
|
3234
3605
|
const nominationRoutes = resolveNominationRoutes(options.nominationRoutes ?? []);
|
|
3235
|
-
const ingress = new OhSemanticBundleIngressV1(workingStore, workingCodecs);
|
|
3606
|
+
const ingress = new OhSemanticBundleIngressV1(capacityGuardedWorkingStore(workingStore, workingBinding), workingCodecs);
|
|
3236
3607
|
const now = options.now ?? (() => new Date);
|
|
3237
3608
|
const monotonicNow = options.monotonicNow ?? (() => performance.now());
|
|
3238
3609
|
const capabilityLifetime = options.explainCapabilityLifetimeMs ?? OH_MEMORY_LIMITS_V1.explainCapabilityLifetimeMs;
|
|
@@ -3268,13 +3639,11 @@ async function createOhMemoryAgentV1(options) {
|
|
|
3268
3639
|
explanationBytes -= stored.bytes;
|
|
3269
3640
|
};
|
|
3270
3641
|
const remember = async (value) => {
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
}
|
|
3274
|
-
if (!isPlainRecord(value) || !hasExactKeys(value, ["expectedHead", "puts", "requestId", "tombstones", "v"]) || value.v !== 1) {
|
|
3642
|
+
const detached = detachCanonicalData(value, "The memory semantic bundle", OH_MEMORY_LIMITS_V1.rememberBytes).value;
|
|
3643
|
+
if (!isPlainRecord(detached) || !hasExactKeys(detached, ["expectedHead", "puts", "requestId", "tombstones", "v"]) || detached.v !== 1) {
|
|
3275
3644
|
throw new TypeError("Invalid memory remember request.");
|
|
3276
3645
|
}
|
|
3277
|
-
const requestId = safeCode(
|
|
3646
|
+
const requestId = safeCode(detached.requestId, 128);
|
|
3278
3647
|
if (requestId === null)
|
|
3279
3648
|
throw new TypeError("Invalid memory remember request identity.");
|
|
3280
3649
|
const operationId = `memory_${canonicalSha256({
|
|
@@ -3283,15 +3652,19 @@ async function createOhMemoryAgentV1(options) {
|
|
|
3283
3652
|
requestId,
|
|
3284
3653
|
v: 1
|
|
3285
3654
|
}).slice(0, 48)}`;
|
|
3286
|
-
const
|
|
3655
|
+
const returnedOperation = await ingress.commit({
|
|
3287
3656
|
actorId: memoryActorId,
|
|
3288
|
-
expectedHead:
|
|
3657
|
+
expectedHead: detached.expectedHead,
|
|
3289
3658
|
instant: isoInstant(new Date(wallClock())),
|
|
3290
3659
|
operationId,
|
|
3291
|
-
puts:
|
|
3292
|
-
tombstones:
|
|
3660
|
+
puts: detached.puts,
|
|
3661
|
+
tombstones: detached.tombstones,
|
|
3293
3662
|
v: 1
|
|
3294
3663
|
});
|
|
3664
|
+
const operation = parseOhOperationV1(detachCanonicalData(returnedOperation, "The returned working memory operation", OH_MEMORY_LIMITS_V1.rememberBytes * 2).value);
|
|
3665
|
+
if (operation === null || operation.actorId !== memoryActorId || operation.operationId !== operationId || operation.spaceId !== workingBinding.spaceId) {
|
|
3666
|
+
throw new OhIntegrityError("The working authority returned a different memory operation.");
|
|
3667
|
+
}
|
|
3295
3668
|
const head = {
|
|
3296
3669
|
generation: operation.sequence,
|
|
3297
3670
|
graphRevisionSha256: operation.graphRevisionSha256,
|
|
@@ -3428,13 +3801,14 @@ async function createOhMemoryAgentV1(options) {
|
|
|
3428
3801
|
const route = nominationRoutes.get(request.nominationId);
|
|
3429
3802
|
if (route === undefined)
|
|
3430
3803
|
throw new TypeError("Unknown named memory nomination route.");
|
|
3431
|
-
const head = parseOhHeadV1(
|
|
3804
|
+
const head = parseOhHeadV1(detachCanonicalData(await workingStore.head(), "The working nomination store head", 4 * 1024).value);
|
|
3432
3805
|
if (head === null)
|
|
3433
3806
|
throw new OhIntegrityError("The working nomination store returned an invalid head.");
|
|
3434
|
-
const
|
|
3807
|
+
const returnedClosure = await workingStore.exportDependencyClosure({ head: {
|
|
3435
3808
|
operationSha256: head.operationSha256,
|
|
3436
3809
|
sequence: head.sequence
|
|
3437
3810
|
}, roots: request.roots });
|
|
3811
|
+
const closure = detachCanonicalData(returnedClosure, "The working nomination closure", OH_DEPENDENCY_CLOSURE_LIMITS_V1.bytes).value;
|
|
3438
3812
|
const verified = verifyOhDependencyClosureAgainstV1(closure, { binding: workingBinding, head });
|
|
3439
3813
|
if (!verified.ok)
|
|
3440
3814
|
throw new OhIntegrityError("The working nomination closure failed exact verification.");
|
|
@@ -3460,6 +3834,21 @@ async function createOhMemoryAgentV1(options) {
|
|
|
3460
3834
|
};
|
|
3461
3835
|
return Object.freeze({ explain, nominate, query, remember });
|
|
3462
3836
|
}
|
|
3837
|
+
function createOhMemoryRuntimeV2(options) {
|
|
3838
|
+
const capabilityLifetime = options.explainCapabilityLifetimeMs ?? OH_MEMORY_LIMITS_V1.explainCapabilityLifetimeMs;
|
|
3839
|
+
if (!Number.isSafeInteger(capabilityLifetime) || capabilityLifetime < 1000 || capabilityLifetime > 60 * 60 * 1000) {
|
|
3840
|
+
throw new RangeError("Invalid memory explanation capability lifetime.");
|
|
3841
|
+
}
|
|
3842
|
+
return {
|
|
3843
|
+
capabilityLifetime,
|
|
3844
|
+
explanationBytes: 0,
|
|
3845
|
+
explanations: new Map,
|
|
3846
|
+
lastMonotonicMs: -1,
|
|
3847
|
+
lastWallClockMs: Number.NEGATIVE_INFINITY,
|
|
3848
|
+
monotonicNow: options.monotonicNow ?? (() => performance.now()),
|
|
3849
|
+
now: options.now ?? (() => new Date)
|
|
3850
|
+
};
|
|
3851
|
+
}
|
|
3463
3852
|
function ownDataKeysV2(value, maximum, label) {
|
|
3464
3853
|
if (!isPlainRecord(value))
|
|
3465
3854
|
throw new TypeError(`${label} must be a plain data object.`);
|
|
@@ -3599,25 +3988,48 @@ function parsePrimitiveBindingV2(value) {
|
|
|
3599
3988
|
return value;
|
|
3600
3989
|
}
|
|
3601
3990
|
function parseQueryRequestV2(value) {
|
|
3991
|
+
let detached;
|
|
3992
|
+
let detachedCanonical = "";
|
|
3602
3993
|
let keys;
|
|
3603
3994
|
try {
|
|
3604
|
-
|
|
3605
|
-
|
|
3995
|
+
const detachedData = detachCanonicalData(value, "The parameterized memory query", OH_MEMORY_QUERY_LIMITS_V2.requestBytes);
|
|
3996
|
+
detached = detachedData.value;
|
|
3997
|
+
detachedCanonical = detachedData.canonical;
|
|
3998
|
+
} catch (error) {
|
|
3999
|
+
if (error instanceof RangeError)
|
|
4000
|
+
throw error;
|
|
4001
|
+
if (error instanceof Error && error.message.includes("$root.bindings")) {
|
|
4002
|
+
throw new TypeError("Memory bindings must be JSON primitives.");
|
|
4003
|
+
}
|
|
4004
|
+
throw new TypeError("Invalid parameterized memory query.");
|
|
4005
|
+
}
|
|
4006
|
+
try {
|
|
4007
|
+
keys = ownDataKeysV2(detached, 4, "The parameterized memory query");
|
|
4008
|
+
} catch (error) {
|
|
4009
|
+
if (error instanceof Error && error.message.includes("$root.bindings")) {
|
|
4010
|
+
throw new TypeError("Memory bindings must be JSON primitives.");
|
|
4011
|
+
}
|
|
3606
4012
|
throw new TypeError("Invalid parameterized memory query.");
|
|
3607
4013
|
}
|
|
3608
4014
|
if (keys.length !== 4 || !["bindings", "continuation", "programId", "v"].every((key) => keys.includes(key))) {
|
|
3609
4015
|
throw new TypeError("Invalid parameterized memory query.");
|
|
3610
4016
|
}
|
|
3611
|
-
|
|
3612
|
-
|
|
4017
|
+
if (utf8ByteLength(detachedCanonical) > OH_MEMORY_QUERY_LIMITS_V2.requestBytes) {
|
|
4018
|
+
throw new RangeError("The parameterized memory query exceeds its canonical byte bound.");
|
|
4019
|
+
}
|
|
4020
|
+
const record = detached;
|
|
4021
|
+
if (record.v !== 2) {
|
|
3613
4022
|
throw new TypeError("Invalid parameterized memory query.");
|
|
3614
4023
|
}
|
|
4024
|
+
if (record.continuation !== null && typeof record.continuation !== "string") {
|
|
4025
|
+
throw new OhMemoryContinuationError("encoding", "Invalid memory continuation encoding.");
|
|
4026
|
+
}
|
|
3615
4027
|
const programId = safeCode(record.programId, 128);
|
|
3616
4028
|
if (programId === null)
|
|
3617
4029
|
throw new TypeError("Invalid parameterized memory query identity.");
|
|
3618
4030
|
const continuation = record.continuation;
|
|
3619
4031
|
if (typeof continuation === "string" && (continuation.length < 1 || continuation.length > OH_MEMORY_QUERY_LIMITS_V2.continuationBytes || utf8ByteLength(continuation) > OH_MEMORY_QUERY_LIMITS_V2.continuationBytes)) {
|
|
3620
|
-
throw new
|
|
4032
|
+
throw new OhMemoryContinuationError("encoding", "The memory continuation exceeds its byte bound.");
|
|
3621
4033
|
}
|
|
3622
4034
|
const bindingKeys = ownDataKeysV2(record.bindings, OH_MEMORY_QUERY_LIMITS_V2.bindings, "The parameterized memory query bindings");
|
|
3623
4035
|
const bindingRecord = record.bindings;
|
|
@@ -3696,18 +4108,19 @@ function encodeContinuationV2(value, key) {
|
|
|
3696
4108
|
return Object.freeze({ continuation, continuationSha256 });
|
|
3697
4109
|
}
|
|
3698
4110
|
function parseContinuationV2(value, key) {
|
|
3699
|
-
if (value.length < 1 || utf8ByteLength(value) > OH_MEMORY_QUERY_LIMITS_V2.continuationBytes || !/^[A-Za-z0-9_-]+$/u.test(value))
|
|
3700
|
-
throw new
|
|
4111
|
+
if (value.length < 1 || utf8ByteLength(value) > OH_MEMORY_QUERY_LIMITS_V2.continuationBytes || !/^[A-Za-z0-9_-]+$/u.test(value)) {
|
|
4112
|
+
throw new OhMemoryContinuationError("encoding", "Invalid memory continuation encoding.");
|
|
4113
|
+
}
|
|
3701
4114
|
const bytes = Buffer.from(value, "base64url");
|
|
3702
4115
|
if (bytes.toString("base64url") !== value || bytes.byteLength > OH_MEMORY_QUERY_LIMITS_V2.continuationBytes) {
|
|
3703
|
-
throw new
|
|
4116
|
+
throw new OhMemoryContinuationError("encoding", "Invalid memory continuation encoding.");
|
|
3704
4117
|
}
|
|
3705
4118
|
const text = bytes.toString("utf8");
|
|
3706
4119
|
let decoded;
|
|
3707
4120
|
try {
|
|
3708
4121
|
decoded = JSON.parse(text);
|
|
3709
4122
|
} catch {
|
|
3710
|
-
throw new
|
|
4123
|
+
throw new OhMemoryContinuationError("encoding", "Invalid memory continuation JSON.");
|
|
3711
4124
|
}
|
|
3712
4125
|
if (!isPlainRecord(decoded) || !hasExactKeys(decoded, [
|
|
3713
4126
|
"bindingsSha256",
|
|
@@ -3720,8 +4133,9 @@ function parseContinuationV2(value, key) {
|
|
|
3720
4133
|
"projectionResultSha256",
|
|
3721
4134
|
"totalRows",
|
|
3722
4135
|
"v"
|
|
3723
|
-
]) || decoded.v !== 2)
|
|
3724
|
-
throw new
|
|
4136
|
+
]) || decoded.v !== 2) {
|
|
4137
|
+
throw new OhMemoryContinuationError("encoding", "Invalid memory continuation payload.");
|
|
4138
|
+
}
|
|
3725
4139
|
const bindingsSha256 = parseSha256Hex(decoded.bindingsSha256);
|
|
3726
4140
|
const continuationHmacSha256 = parseSha256Hex(decoded.continuationHmacSha256);
|
|
3727
4141
|
const continuationSha256 = parseSha256Hex(decoded.continuationSha256);
|
|
@@ -3729,7 +4143,7 @@ function parseContinuationV2(value, key) {
|
|
|
3729
4143
|
const programSha256 = parseSha256Hex(decoded.programSha256);
|
|
3730
4144
|
const projectionResultSha256 = parseSha256Hex(decoded.projectionResultSha256);
|
|
3731
4145
|
if (bindingsSha256 === null || continuationHmacSha256 === null || continuationSha256 === null || memorySha256 === null || programSha256 === null || projectionResultSha256 === null || !Number.isSafeInteger(decoded.nextOffset) || decoded.nextOffset < 1 || decoded.nextOffset > OH_MEMORY_QUERY_LIMITS_V2.maximumProgramRows || !Number.isSafeInteger(decoded.pageSize) || decoded.pageSize < 1 || decoded.pageSize > OH_MEMORY_QUERY_LIMITS_V2.maximumPageRows || !Number.isSafeInteger(decoded.totalRows) || decoded.totalRows < 1 || decoded.totalRows > OH_MEMORY_QUERY_LIMITS_V2.maximumProgramRows || decoded.nextOffset >= decoded.totalRows || decoded.nextOffset % decoded.pageSize !== 0) {
|
|
3732
|
-
throw new
|
|
4146
|
+
throw new OhMemoryContinuationError("identity", "Invalid memory continuation identity.");
|
|
3733
4147
|
}
|
|
3734
4148
|
const identity = {
|
|
3735
4149
|
bindingsSha256,
|
|
@@ -3743,28 +4157,30 @@ function parseContinuationV2(value, key) {
|
|
|
3743
4157
|
};
|
|
3744
4158
|
const signed = { ...identity, continuationSha256 };
|
|
3745
4159
|
const envelope = { ...signed, continuationHmacSha256 };
|
|
3746
|
-
if (canonicalJson(envelope) !== text)
|
|
3747
|
-
throw new
|
|
4160
|
+
if (canonicalJson(envelope) !== text) {
|
|
4161
|
+
throw new OhMemoryContinuationError("encoding", "Invalid memory continuation payload.");
|
|
4162
|
+
}
|
|
3748
4163
|
const expectedHmac = continuationHmacV2(key, signed);
|
|
3749
4164
|
const receivedHmac = Buffer.from(continuationHmacSha256, "hex");
|
|
3750
4165
|
if (!timingSafeEqual(expectedHmac, receivedHmac)) {
|
|
3751
|
-
throw new
|
|
4166
|
+
throw new OhMemoryContinuationError("authentication", "The memory continuation is not an issued capability.");
|
|
3752
4167
|
}
|
|
3753
4168
|
if (canonicalSha256(identity) !== continuationSha256) {
|
|
3754
|
-
throw new
|
|
4169
|
+
throw new OhMemoryContinuationError("identity", "The memory continuation digest is invalid.");
|
|
3755
4170
|
}
|
|
3756
4171
|
return Object.freeze(signed);
|
|
3757
4172
|
}
|
|
3758
4173
|
function parseExplainRequestV2(value) {
|
|
3759
|
-
|
|
4174
|
+
const detached = detachCanonicalData(value, "The V2 memory explanation request", OH_MEMORY_QUERY_LIMITS_V2.requestBytes).value;
|
|
4175
|
+
if (!isPlainRecord(detached) || !hasExactKeys(detached, ["pageRow", "resultSha256", "token", "v"]) || detached.v !== 2 || typeof detached.token !== "string" || detached.token.length !== 43 || !Number.isSafeInteger(detached.pageRow) || detached.pageRow < 0) {
|
|
3760
4176
|
throw new TypeError("Invalid V2 memory explanation request.");
|
|
3761
4177
|
}
|
|
3762
|
-
const resultSha256 = parseSha256Hex(
|
|
4178
|
+
const resultSha256 = parseSha256Hex(detached.resultSha256);
|
|
3763
4179
|
if (resultSha256 === null)
|
|
3764
4180
|
throw new TypeError("Invalid V2 memory explanation result identity.");
|
|
3765
|
-
return { pageRow:
|
|
4181
|
+
return { pageRow: detached.pageRow, resultSha256, token: detached.token };
|
|
3766
4182
|
}
|
|
3767
|
-
async function
|
|
4183
|
+
async function createOhMemoryAgentV2WithRuntime(options, sharedRuntime) {
|
|
3768
4184
|
const memoryActorId = safeCode(options.actorId, 128);
|
|
3769
4185
|
if (memoryActorId === null)
|
|
3770
4186
|
throw new TypeError("Invalid host-bound memory actor ID.");
|
|
@@ -3779,55 +4195,45 @@ async function createOhMemoryAgentV2(options) {
|
|
|
3779
4195
|
}
|
|
3780
4196
|
const canonicalBinding = bindingFor(canonicalStore, options.canonical.expectedBindingSha256, "canonical");
|
|
3781
4197
|
const workingBinding = bindingFor(workingStore, options.working.expectedBindingSha256, "working");
|
|
3782
|
-
const expectedCanonicalHead =
|
|
3783
|
-
if (expectedCanonicalHead === null)
|
|
3784
|
-
throw new TypeError("Invalid pinned canonical memory head.");
|
|
4198
|
+
const expectedCanonicalHead = parseMemoryAuthorityHead(options.canonical.expectedHead, "pinned canonical");
|
|
3785
4199
|
const programs = resolveProgramsV2(options.programs);
|
|
3786
4200
|
const extractors = resolveExtractors(options.extractors ?? []);
|
|
3787
4201
|
const nominationRoutes = resolveNominationRoutes(options.nominationRoutes ?? []);
|
|
3788
|
-
const ingress = new OhSemanticBundleIngressV1(workingStore, workingCodecs);
|
|
3789
|
-
const
|
|
3790
|
-
const monotonicNow = options.monotonicNow ?? (() => performance.now());
|
|
3791
|
-
const capabilityLifetime = options.explainCapabilityLifetimeMs ?? OH_MEMORY_LIMITS_V1.explainCapabilityLifetimeMs;
|
|
3792
|
-
if (!Number.isSafeInteger(capabilityLifetime) || capabilityLifetime < 1000 || capabilityLifetime > 60 * 60 * 1000) {
|
|
3793
|
-
throw new RangeError("Invalid memory explanation capability lifetime.");
|
|
3794
|
-
}
|
|
4202
|
+
const ingress = new OhSemanticBundleIngressV1(capacityGuardedWorkingStore(workingStore, workingBinding), workingCodecs);
|
|
4203
|
+
const runtime = sharedRuntime ?? createOhMemoryRuntimeV2(options);
|
|
3795
4204
|
const canonical = await readLane({
|
|
3796
4205
|
authorityId: canonicalAuthorityId,
|
|
3797
4206
|
binding: canonicalBinding,
|
|
3798
4207
|
store: canonicalStore
|
|
3799
4208
|
}, "canonical", expectedCanonicalHead);
|
|
3800
|
-
const explanations = new Map;
|
|
3801
|
-
let explanationBytes = 0;
|
|
3802
|
-
let lastMonotonicMs = -1;
|
|
3803
|
-
let lastWallClockMs = Number.NEGATIVE_INFINITY;
|
|
3804
4209
|
const wallClock = () => {
|
|
3805
|
-
const milliseconds = clockMilliseconds(now);
|
|
3806
|
-
if (milliseconds < lastWallClockMs)
|
|
4210
|
+
const milliseconds = clockMilliseconds(runtime.now);
|
|
4211
|
+
if (milliseconds < runtime.lastWallClockMs) {
|
|
3807
4212
|
throw new OhProfileError("The memory wall clock regressed.");
|
|
3808
|
-
|
|
4213
|
+
}
|
|
4214
|
+
runtime.lastWallClockMs = milliseconds;
|
|
3809
4215
|
return milliseconds;
|
|
3810
4216
|
};
|
|
3811
4217
|
const monotonicClock = () => {
|
|
3812
|
-
const milliseconds = monotonicMilliseconds(monotonicNow);
|
|
3813
|
-
if (milliseconds < lastMonotonicMs)
|
|
4218
|
+
const milliseconds = monotonicMilliseconds(runtime.monotonicNow);
|
|
4219
|
+
if (milliseconds < runtime.lastMonotonicMs) {
|
|
3814
4220
|
throw new OhProfileError("The memory monotonic clock regressed.");
|
|
3815
|
-
|
|
4221
|
+
}
|
|
4222
|
+
runtime.lastMonotonicMs = milliseconds;
|
|
3816
4223
|
return milliseconds;
|
|
3817
4224
|
};
|
|
3818
4225
|
const deleteExplanation = (token) => {
|
|
3819
|
-
const stored = explanations.get(token);
|
|
3820
|
-
if (stored !== undefined && explanations.delete(token))
|
|
3821
|
-
explanationBytes -= stored.bytes;
|
|
4226
|
+
const stored = runtime.explanations.get(token);
|
|
4227
|
+
if (stored !== undefined && runtime.explanations.delete(token)) {
|
|
4228
|
+
runtime.explanationBytes -= stored.bytes;
|
|
4229
|
+
}
|
|
3822
4230
|
};
|
|
3823
4231
|
const remember = async (value) => {
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
}
|
|
3827
|
-
if (!isPlainRecord(value) || !hasExactKeys(value, ["expectedHead", "puts", "requestId", "tombstones", "v"]) || value.v !== 1) {
|
|
4232
|
+
const detached = detachCanonicalData(value, "The memory semantic bundle", OH_MEMORY_LIMITS_V1.rememberBytes).value;
|
|
4233
|
+
if (!isPlainRecord(detached) || !hasExactKeys(detached, ["expectedHead", "puts", "requestId", "tombstones", "v"]) || detached.v !== 1) {
|
|
3828
4234
|
throw new TypeError("Invalid memory remember request.");
|
|
3829
4235
|
}
|
|
3830
|
-
const requestId = safeCode(
|
|
4236
|
+
const requestId = safeCode(detached.requestId, 128);
|
|
3831
4237
|
if (requestId === null)
|
|
3832
4238
|
throw new TypeError("Invalid memory remember request identity.");
|
|
3833
4239
|
const operationId = `memory_${canonicalSha256({
|
|
@@ -3836,15 +4242,19 @@ async function createOhMemoryAgentV2(options) {
|
|
|
3836
4242
|
requestId,
|
|
3837
4243
|
v: 1
|
|
3838
4244
|
}).slice(0, 48)}`;
|
|
3839
|
-
const
|
|
4245
|
+
const returnedOperation = await ingress.commit({
|
|
3840
4246
|
actorId: memoryActorId,
|
|
3841
|
-
expectedHead:
|
|
4247
|
+
expectedHead: detached.expectedHead,
|
|
3842
4248
|
instant: isoInstant(new Date(wallClock())),
|
|
3843
4249
|
operationId,
|
|
3844
|
-
puts:
|
|
3845
|
-
tombstones:
|
|
4250
|
+
puts: detached.puts,
|
|
4251
|
+
tombstones: detached.tombstones,
|
|
3846
4252
|
v: 1
|
|
3847
4253
|
});
|
|
4254
|
+
const operation = parseOhOperationV1(detachCanonicalData(returnedOperation, "The returned working memory operation", OH_MEMORY_LIMITS_V1.rememberBytes * 2).value);
|
|
4255
|
+
if (operation === null || operation.actorId !== memoryActorId || operation.operationId !== operationId || operation.spaceId !== workingBinding.spaceId) {
|
|
4256
|
+
throw new OhIntegrityError("The working authority returned a different memory operation.");
|
|
4257
|
+
}
|
|
3848
4258
|
const head = {
|
|
3849
4259
|
generation: operation.sequence,
|
|
3850
4260
|
graphRevisionSha256: operation.graphRevisionSha256,
|
|
@@ -3875,7 +4285,7 @@ async function createOhMemoryAgentV2(options) {
|
|
|
3875
4285
|
const bound = parseBindingsV2(request.bindingsValue, program.parameters);
|
|
3876
4286
|
const requestedContinuation = request.continuation === null ? null : parseContinuationV2(request.continuation, continuationKey);
|
|
3877
4287
|
if (requestedContinuation !== null && (requestedContinuation.bindingsSha256 !== bound.bindingsSha256 || requestedContinuation.pageSize !== program.pageSize || requestedContinuation.programSha256 !== program.programSha256 || requestedContinuation.totalRows > program.maximumRows || requestedContinuation.nextOffset >= requestedContinuation.totalRows || requestedContinuation.nextOffset % program.pageSize !== 0)) {
|
|
3878
|
-
throw new
|
|
4288
|
+
throw new OhMemoryContinuationError("identity", "The memory continuation does not match this exact program, binding, and page identity.");
|
|
3879
4289
|
}
|
|
3880
4290
|
const boundQuery = bindQueryV2(program.query, bound.bindings);
|
|
3881
4291
|
const working = await readLane({
|
|
@@ -3920,10 +4330,10 @@ async function createOhMemoryAgentV2(options) {
|
|
|
3920
4330
|
memorySha256: canonicalSha256(identityPayload)
|
|
3921
4331
|
});
|
|
3922
4332
|
if (requestedContinuation !== null && (requestedContinuation.memorySha256 !== identity.memorySha256 || requestedContinuation.projectionResultSha256 !== projection.resultSha256)) {
|
|
3923
|
-
throw new
|
|
4333
|
+
throw new OhMemoryContinuationError("identity", "The memory continuation does not match this exact source and projection identity.");
|
|
3924
4334
|
}
|
|
3925
4335
|
if (requestedContinuation !== null && (requestedContinuation.totalRows !== projection.rows.length || requestedContinuation.nextOffset >= projection.rows.length || requestedContinuation.nextOffset % program.pageSize !== 0)) {
|
|
3926
|
-
throw new
|
|
4336
|
+
throw new OhMemoryContinuationError("identity", "The memory continuation does not match this exact row identity.");
|
|
3927
4337
|
}
|
|
3928
4338
|
const start = requestedContinuation?.nextOffset ?? 0;
|
|
3929
4339
|
const endExclusive = Math.min(start + program.pageSize, projection.rows.length);
|
|
@@ -3974,8 +4384,8 @@ async function createOhMemoryAgentV2(options) {
|
|
|
3974
4384
|
const resultPayload = immutableClone({ ...resultIdentityPayload, continuation });
|
|
3975
4385
|
const issuedAt = wallClock();
|
|
3976
4386
|
const issuedAtMonotonic = monotonicClock();
|
|
3977
|
-
const expiresAtMs = issuedAt + capabilityLifetime;
|
|
3978
|
-
const expiresAtMonotonicMs = issuedAtMonotonic + capabilityLifetime;
|
|
4387
|
+
const expiresAtMs = issuedAt + runtime.capabilityLifetime;
|
|
4388
|
+
const expiresAtMonotonicMs = issuedAtMonotonic + runtime.capabilityLifetime;
|
|
3979
4389
|
const expiresAt = isoInstant(new Date(expiresAtMs));
|
|
3980
4390
|
const pageBytePreflight = {
|
|
3981
4391
|
...resultPayload,
|
|
@@ -3985,7 +4395,7 @@ async function createOhMemoryAgentV2(options) {
|
|
|
3985
4395
|
if (utf8ByteLength(canonicalJson(pageBytePreflight)) > program.maximumPageBytes) {
|
|
3986
4396
|
throw new RangeError("The V2 memory page exceeds its host-declared canonical byte bound.");
|
|
3987
4397
|
}
|
|
3988
|
-
for (const [existingToken, stored] of explanations) {
|
|
4398
|
+
for (const [existingToken, stored] of runtime.explanations) {
|
|
3989
4399
|
if (issuedAtMonotonic >= stored.expiresAtMonotonicMs)
|
|
3990
4400
|
deleteExplanation(existingToken);
|
|
3991
4401
|
}
|
|
@@ -4001,17 +4411,17 @@ async function createOhMemoryAgentV2(options) {
|
|
|
4001
4411
|
if (storedBytes > OH_MEMORY_LIMITS_V1.explainCapabilityEntryBytes || storedBytes > OH_MEMORY_LIMITS_V1.explainCapabilityTotalBytes) {
|
|
4002
4412
|
throw new RangeError("The V2 memory explanation exceeds its retained capability bound.");
|
|
4003
4413
|
}
|
|
4004
|
-
while (explanations.size >= OH_MEMORY_LIMITS_V1.explainCapabilities || explanationBytes + storedBytes > OH_MEMORY_LIMITS_V1.explainCapabilityTotalBytes) {
|
|
4005
|
-
const oldest = explanations.keys().next().value;
|
|
4414
|
+
while (runtime.explanations.size >= OH_MEMORY_LIMITS_V1.explainCapabilities || runtime.explanationBytes + storedBytes > OH_MEMORY_LIMITS_V1.explainCapabilityTotalBytes) {
|
|
4415
|
+
const oldest = runtime.explanations.keys().next().value;
|
|
4006
4416
|
if (oldest === undefined)
|
|
4007
4417
|
break;
|
|
4008
4418
|
deleteExplanation(oldest);
|
|
4009
4419
|
}
|
|
4010
4420
|
let token = randomBytes2(32).toString("base64url");
|
|
4011
|
-
while (explanations.has(token))
|
|
4421
|
+
while (runtime.explanations.has(token))
|
|
4012
4422
|
token = randomBytes2(32).toString("base64url");
|
|
4013
|
-
explanations.set(token, immutableClone({ ...storedPayload, bytes: storedBytes }));
|
|
4014
|
-
explanationBytes += storedBytes;
|
|
4423
|
+
runtime.explanations.set(token, immutableClone({ ...storedPayload, bytes: storedBytes }));
|
|
4424
|
+
runtime.explanationBytes += storedBytes;
|
|
4015
4425
|
const result = immutableClone({
|
|
4016
4426
|
...resultPayload,
|
|
4017
4427
|
explainCapability: { expiresAt, token, v: 2 },
|
|
@@ -4025,7 +4435,7 @@ async function createOhMemoryAgentV2(options) {
|
|
|
4025
4435
|
};
|
|
4026
4436
|
const explain = async (value) => {
|
|
4027
4437
|
const request = parseExplainRequestV2(value);
|
|
4028
|
-
const stored = explanations.get(request.token);
|
|
4438
|
+
const stored = runtime.explanations.get(request.token);
|
|
4029
4439
|
const currentTime = monotonicClock();
|
|
4030
4440
|
if (stored === undefined || stored.resultSha256 !== request.resultSha256 || currentTime >= stored.expiresAtMonotonicMs) {
|
|
4031
4441
|
deleteExplanation(request.token);
|
|
@@ -4057,13 +4467,14 @@ async function createOhMemoryAgentV2(options) {
|
|
|
4057
4467
|
const route = nominationRoutes.get(request.nominationId);
|
|
4058
4468
|
if (route === undefined)
|
|
4059
4469
|
throw new TypeError("Unknown named memory nomination route.");
|
|
4060
|
-
const head = parseOhHeadV1(
|
|
4470
|
+
const head = parseOhHeadV1(detachCanonicalData(await workingStore.head(), "The working nomination store head", 4 * 1024).value);
|
|
4061
4471
|
if (head === null)
|
|
4062
4472
|
throw new OhIntegrityError("The working nomination store returned an invalid head.");
|
|
4063
|
-
const
|
|
4473
|
+
const returnedClosure = await workingStore.exportDependencyClosure({ head: {
|
|
4064
4474
|
operationSha256: head.operationSha256,
|
|
4065
4475
|
sequence: head.sequence
|
|
4066
4476
|
}, roots: request.roots });
|
|
4477
|
+
const closure = detachCanonicalData(returnedClosure, "The working nomination closure", OH_DEPENDENCY_CLOSURE_LIMITS_V1.bytes).value;
|
|
4067
4478
|
const verified = verifyOhDependencyClosureAgainstV1(closure, { binding: workingBinding, head });
|
|
4068
4479
|
if (!verified.ok)
|
|
4069
4480
|
throw new OhIntegrityError("The working nomination closure failed exact verification.");
|
|
@@ -4089,15 +4500,561 @@ async function createOhMemoryAgentV2(options) {
|
|
|
4089
4500
|
};
|
|
4090
4501
|
return Object.freeze({ explain, nominate, query, remember });
|
|
4091
4502
|
}
|
|
4503
|
+
async function createOhMemoryAgentV2(options) {
|
|
4504
|
+
return await createOhMemoryAgentV2WithRuntime(options);
|
|
4505
|
+
}
|
|
4506
|
+
function parseDetachedMemoryAuthorityHead(value, label) {
|
|
4507
|
+
const head = parseOhHeadV1(value);
|
|
4508
|
+
if (head === null)
|
|
4509
|
+
throw new TypeError(`Invalid ${label} memory head.`);
|
|
4510
|
+
return immutableClone(head);
|
|
4511
|
+
}
|
|
4512
|
+
function parseMemoryAuthorityHead(value, label) {
|
|
4513
|
+
return parseDetachedMemoryAuthorityHead(detachCanonicalData(value, `The ${label} memory head`, 4 * 1024).value, label);
|
|
4514
|
+
}
|
|
4515
|
+
function parseCanonicalAdvanceRequest(value) {
|
|
4516
|
+
const detached = detachCanonicalData(value, "The canonical memory advance request", OH_MEMORY_QUERY_LIMITS_V2.requestBytes).value;
|
|
4517
|
+
if (!isPlainRecord(detached) || !hasExactKeys(detached, ["expectedHead", "nextHead", "v"]) || detached.v !== 1)
|
|
4518
|
+
throw new TypeError("Invalid canonical memory advance request.");
|
|
4519
|
+
return Object.freeze({
|
|
4520
|
+
expectedHead: parseDetachedMemoryAuthorityHead(detached.expectedHead, "expected canonical"),
|
|
4521
|
+
nextHead: parseDetachedMemoryAuthorityHead(detached.nextHead, "next canonical")
|
|
4522
|
+
});
|
|
4523
|
+
}
|
|
4524
|
+
function parseAdoptionRequest(value) {
|
|
4525
|
+
const detached = detachCanonicalData(value, "The memory adoption request", OH_MEMORY_AUTHORITY_LIMITS_V1.adoptionRequestBytes).value;
|
|
4526
|
+
if (!isPlainRecord(detached) || !hasExactKeys(detached, ["expectedCanonicalHead", "nomination", "v"]) && !hasExactKeys(detached, ["expectedCanonicalHead", "nomination", "replacements", "v"]) || detached.v !== 1)
|
|
4527
|
+
throw new TypeError("Invalid memory adoption request.");
|
|
4528
|
+
const nomination = parseDetachedMemoryNominationV1(detached.nomination);
|
|
4529
|
+
if (nomination === null)
|
|
4530
|
+
throw new TypeError("Invalid memory adoption nomination.");
|
|
4531
|
+
const replacementValues = "replacements" in detached ? detached.replacements : [];
|
|
4532
|
+
if (!Array.isArray(replacementValues) || replacementValues.length > OH_MEMORY_AUTHORITY_LIMITS_V1.adoptionReplacements) {
|
|
4533
|
+
throw new TypeError("Invalid memory adoption replacements.");
|
|
4534
|
+
}
|
|
4535
|
+
const nominatedByKey = new Map(nomination.closure.records.map((record) => [record.key, record]));
|
|
4536
|
+
const replacements = replacementValues.map((replacement) => {
|
|
4537
|
+
if (!isPlainRecord(replacement) || !hasExactKeys(replacement, ["expectedPriorRecordSha256", "key", "v"]) || replacement.v !== 1)
|
|
4538
|
+
throw new TypeError("Invalid memory adoption replacement.");
|
|
4539
|
+
const key = safeCode(replacement.key, 512);
|
|
4540
|
+
const expectedPriorRecordSha256 = parseSha256Hex(replacement.expectedPriorRecordSha256);
|
|
4541
|
+
const nominated = key === null ? undefined : nominatedByKey.get(key);
|
|
4542
|
+
if (key === null || expectedPriorRecordSha256 === null || nominated === undefined) {
|
|
4543
|
+
throw new TypeError("Invalid memory adoption replacement.");
|
|
4544
|
+
}
|
|
4545
|
+
return { expectedPriorRecordSha256, key, v: 1 };
|
|
4546
|
+
}).sort((left, right) => compareText(left.key, right.key));
|
|
4547
|
+
if (!orderedUnique(replacements, (replacement) => replacement.key)) {
|
|
4548
|
+
throw new TypeError("Memory adoption replacement keys must be unique.");
|
|
4549
|
+
}
|
|
4550
|
+
return Object.freeze({
|
|
4551
|
+
expectedCanonicalHead: parseDetachedMemoryAuthorityHead(detached.expectedCanonicalHead, "expected canonical adoption"),
|
|
4552
|
+
nomination,
|
|
4553
|
+
replacements: immutableClone(replacements)
|
|
4554
|
+
});
|
|
4555
|
+
}
|
|
4556
|
+
function headRef(head) {
|
|
4557
|
+
return Object.freeze({ operationSha256: head.operationSha256, sequence: head.sequence });
|
|
4558
|
+
}
|
|
4559
|
+
async function proveCanonicalDescendant(authority, priorHead, nextHead, requiredFirstHead) {
|
|
4560
|
+
if (nextHead.sequence <= priorHead.sequence) {
|
|
4561
|
+
throw new OhConflictError("The next canonical memory head is not a descendant of the current pin.");
|
|
4562
|
+
}
|
|
4563
|
+
const distance = nextHead.sequence - priorHead.sequence;
|
|
4564
|
+
if (distance > OH_MEMORY_AUTHORITY_LIMITS_V1.canonicalAdvanceOperations || Math.ceil(distance / OH_MEMORY_AUTHORITY_LIMITS_V1.canonicalChangeFeedPage) > OH_MEMORY_AUTHORITY_LIMITS_V1.canonicalAdvancePages) {
|
|
4565
|
+
throw new RangeError("The canonical memory advance exceeds its total proof bound; advance in host-reviewed chunks.");
|
|
4566
|
+
}
|
|
4567
|
+
const through = headRef(nextHead);
|
|
4568
|
+
let cursor = headRef(priorHead);
|
|
4569
|
+
let pageCount = 0;
|
|
4570
|
+
let reachedHead = null;
|
|
4571
|
+
let firstHead = null;
|
|
4572
|
+
while (cursor.sequence < through.sequence) {
|
|
4573
|
+
if (pageCount >= OH_MEMORY_AUTHORITY_LIMITS_V1.canonicalAdvancePages) {
|
|
4574
|
+
throw new RangeError("The canonical memory advance exceeded its page proof bound; advance in host-reviewed chunks.");
|
|
4575
|
+
}
|
|
4576
|
+
pageCount += 1;
|
|
4577
|
+
const remaining = through.sequence - cursor.sequence;
|
|
4578
|
+
const limit = Math.min(remaining, OH_MEMORY_AUTHORITY_LIMITS_V1.canonicalChangeFeedPage);
|
|
4579
|
+
const returnedData = detachCanonicalData(await authority.store.changesSince(cursor, { limit, through }), "The canonical change-feed page", OH_MEMORY_AUTHORITY_LIMITS_V1.canonicalChangeFeedPageBytes).value;
|
|
4580
|
+
const returned = returnedData;
|
|
4581
|
+
if (!isPlainRecord(returned) || !hasExactKeys(returned, ["from", "hasMore", "operations", "through", "to", "v"]) || returned.v !== 1 || typeof returned.hasMore !== "boolean" || !Array.isArray(returned.operations) || returned.operations.length > limit) {
|
|
4582
|
+
throw new OhIntegrityError("The canonical change feed returned an invalid page envelope.");
|
|
4583
|
+
}
|
|
4584
|
+
const from = parseOhHeadRefV1(returned.from);
|
|
4585
|
+
const returnedThrough = parseOhHeadV1(returned.through);
|
|
4586
|
+
const returnedTo = parseOhHeadRefV1(returned.to);
|
|
4587
|
+
if (from === null || returnedThrough === null || returnedTo === null || canonicalJson(from) !== canonicalJson(cursor) || !exactHead(returnedThrough, nextHead)) {
|
|
4588
|
+
throw new OhIntegrityError("The canonical change feed changed its pinned bounds.");
|
|
4589
|
+
}
|
|
4590
|
+
let reached = cursor;
|
|
4591
|
+
for (const value of returned.operations) {
|
|
4592
|
+
const operation = parseOhOperationV1(value);
|
|
4593
|
+
if (operation === null || operation.spaceId !== authority.binding.spaceId || operation.sequence !== reached.sequence + 1 || operation.parentOperationSha256 !== reached.operationSha256) {
|
|
4594
|
+
throw new OhIntegrityError("The canonical change feed contains a gap or different authority.");
|
|
4595
|
+
}
|
|
4596
|
+
reached = Object.freeze({
|
|
4597
|
+
operationSha256: operation.operationSha256,
|
|
4598
|
+
sequence: operation.sequence
|
|
4599
|
+
});
|
|
4600
|
+
reachedHead = immutableClone({
|
|
4601
|
+
generation: operation.sequence,
|
|
4602
|
+
graphRevisionSha256: operation.graphRevisionSha256,
|
|
4603
|
+
operationSha256: operation.operationSha256,
|
|
4604
|
+
recordsSha256: operation.recordsSha256,
|
|
4605
|
+
sequence: operation.sequence,
|
|
4606
|
+
v: 1
|
|
4607
|
+
});
|
|
4608
|
+
firstHead ??= reachedHead;
|
|
4609
|
+
}
|
|
4610
|
+
if (canonicalJson(reached) !== canonicalJson(returnedTo) || returned.hasMore && returned.operations.length === 0 || returned.hasMore && reached.sequence >= through.sequence || reached.sequence > through.sequence || !returned.hasMore && canonicalJson(reached) !== canonicalJson(through)) {
|
|
4611
|
+
throw new OhIntegrityError("The canonical change feed did not prove the requested descendant.");
|
|
4612
|
+
}
|
|
4613
|
+
cursor = reached;
|
|
4614
|
+
}
|
|
4615
|
+
if (reachedHead === null || !exactHead(reachedHead, nextHead)) {
|
|
4616
|
+
throw new OhIntegrityError("The canonical change feed did not prove the requested full head.");
|
|
4617
|
+
}
|
|
4618
|
+
if (requiredFirstHead !== undefined && (firstHead === null || !exactHead(firstHead, requiredFirstHead))) {
|
|
4619
|
+
throw new OhIntegrityError("The returned adoption operation is not on the current canonical path.");
|
|
4620
|
+
}
|
|
4621
|
+
return await readLane(authority, "canonical", nextHead);
|
|
4622
|
+
}
|
|
4623
|
+
function canonicalAdvanceReceipt(authorityIdValue, bindingSha256, priorHead, head, status) {
|
|
4624
|
+
const payload = {
|
|
4625
|
+
authorityId: authorityIdValue,
|
|
4626
|
+
bindingSha256,
|
|
4627
|
+
head,
|
|
4628
|
+
priorHead,
|
|
4629
|
+
status,
|
|
4630
|
+
v: 1
|
|
4631
|
+
};
|
|
4632
|
+
return immutableClone({ ...payload, receiptSha256: canonicalSha256(payload) });
|
|
4633
|
+
}
|
|
4634
|
+
function adoptionReceipt(actorId, authorityIdValue, bindingSha256, nominationSha256, operationSha256, priorHead, head, status) {
|
|
4635
|
+
const payload = {
|
|
4636
|
+
actorId,
|
|
4637
|
+
authorityId: authorityIdValue,
|
|
4638
|
+
bindingSha256,
|
|
4639
|
+
head,
|
|
4640
|
+
nominationSha256,
|
|
4641
|
+
operationSha256,
|
|
4642
|
+
priorHead,
|
|
4643
|
+
status,
|
|
4644
|
+
v: 1
|
|
4645
|
+
};
|
|
4646
|
+
return immutableClone({ ...payload, receiptSha256: canonicalSha256(payload) });
|
|
4647
|
+
}
|
|
4648
|
+
function adoptionDifferences(snapshot, records) {
|
|
4649
|
+
const canonicalByKey = new Map(snapshot.records.map((record) => [record.key, record]));
|
|
4650
|
+
return immutableClone(records.flatMap((record) => {
|
|
4651
|
+
const canonicalRecord = canonicalByKey.get(record.key);
|
|
4652
|
+
return canonicalRecord?.recordSha256 === record.recordSha256 ? [] : [{
|
|
4653
|
+
canonicalRecordSha256: canonicalRecord?.recordSha256 ?? null,
|
|
4654
|
+
key: record.key,
|
|
4655
|
+
nominatedRecordSha256: record.recordSha256,
|
|
4656
|
+
v: 1
|
|
4657
|
+
}];
|
|
4658
|
+
}).sort((left, right) => compareText(left.key, right.key)));
|
|
4659
|
+
}
|
|
4660
|
+
function unauthorizedAdoptionDifferences(reviewedSnapshot, currentSnapshot, records, replacements) {
|
|
4661
|
+
const reviewedByKey = new Map(reviewedSnapshot.records.map((record) => [record.key, record]));
|
|
4662
|
+
const currentByKey = new Map(currentSnapshot.records.map((record) => [record.key, record]));
|
|
4663
|
+
const replacementByKey = new Map(replacements.map((replacement) => [
|
|
4664
|
+
replacement.key,
|
|
4665
|
+
replacement.expectedPriorRecordSha256
|
|
4666
|
+
]));
|
|
4667
|
+
const conflicts = records.flatMap((nominated) => {
|
|
4668
|
+
const reviewed = reviewedByKey.get(nominated.key);
|
|
4669
|
+
const expectedPriorRecordSha256 = replacementByKey.get(nominated.key);
|
|
4670
|
+
const alreadyEqual = reviewed?.recordSha256 === nominated.recordSha256;
|
|
4671
|
+
const authorized = reviewed === undefined ? expectedPriorRecordSha256 === undefined : alreadyEqual ? expectedPriorRecordSha256 === undefined || expectedPriorRecordSha256 === reviewed.recordSha256 : expectedPriorRecordSha256 === reviewed.recordSha256;
|
|
4672
|
+
if (authorized)
|
|
4673
|
+
return [];
|
|
4674
|
+
return [{
|
|
4675
|
+
canonicalRecordSha256: currentByKey.get(nominated.key)?.recordSha256 ?? null,
|
|
4676
|
+
key: nominated.key,
|
|
4677
|
+
nominatedRecordSha256: nominated.recordSha256,
|
|
4678
|
+
v: 1
|
|
4679
|
+
}];
|
|
4680
|
+
});
|
|
4681
|
+
return immutableClone(conflicts.sort((left, right) => compareText(left.key, right.key)));
|
|
4682
|
+
}
|
|
4683
|
+
async function assertWorkingCommitCapacity(store, binding, changes, head) {
|
|
4684
|
+
const returnedSnapshot = await store.snapshot({
|
|
4685
|
+
head: { operationSha256: head.operationSha256, sequence: head.sequence },
|
|
4686
|
+
maximumRecords: OH_MEMORY_LIMITS_V1.maximumRecordsPerLane
|
|
4687
|
+
});
|
|
4688
|
+
const { snapshot } = parseDetachedStoreSnapshot(returnedSnapshot, "The working capacity store", head, binding.spaceId);
|
|
4689
|
+
const recordsByKey = new Map(snapshot.records.map((record) => [record.key, record]));
|
|
4690
|
+
for (const change of canonicalKnowledgeGraphChangesV1(changes)) {
|
|
4691
|
+
if (change.kind === "put")
|
|
4692
|
+
recordsByKey.set(change.record.key, change.record);
|
|
4693
|
+
else
|
|
4694
|
+
recordsByKey.delete(change.key);
|
|
4695
|
+
}
|
|
4696
|
+
if (recordsByKey.size > OH_MEMORY_LIMITS_V1.maximumRecordsPerLane) {
|
|
4697
|
+
throw new RangeError("The remembered working memory would exceed its record snapshot bound.");
|
|
4698
|
+
}
|
|
4699
|
+
const nextSequence = snapshot.head.sequence + 1;
|
|
4700
|
+
if (!Number.isSafeInteger(nextSequence)) {
|
|
4701
|
+
throw new RangeError("The remembered working memory would exceed its head sequence bound.");
|
|
4702
|
+
}
|
|
4703
|
+
const records = [...recordsByKey.values()].sort((left, right) => compareText(left.key, right.key));
|
|
4704
|
+
const placeholderDigest = canonicalSha256({ kind: "oh.memory.remember-capacity", v: 1 });
|
|
4705
|
+
const prospective = {
|
|
4706
|
+
head: {
|
|
4707
|
+
generation: nextSequence,
|
|
4708
|
+
graphRevisionSha256: placeholderDigest,
|
|
4709
|
+
operationSha256: placeholderDigest,
|
|
4710
|
+
recordsSha256: canonicalSha256(records.map(knowledgeGraphRecordRefV1)),
|
|
4711
|
+
sequence: nextSequence,
|
|
4712
|
+
v: 1
|
|
4713
|
+
},
|
|
4714
|
+
records,
|
|
4715
|
+
v: 1
|
|
4716
|
+
};
|
|
4717
|
+
if (utf8ByteLength(canonicalJson(prospective)) > OH_MEMORY_LIMITS_V1.snapshotBytesPerLane) {
|
|
4718
|
+
throw new RangeError("The remembered working memory would exceed its snapshot byte bound.");
|
|
4719
|
+
}
|
|
4720
|
+
}
|
|
4721
|
+
function capacityGuardedWorkingStore(store, binding) {
|
|
4722
|
+
const guarded = {
|
|
4723
|
+
binding,
|
|
4724
|
+
changesSince: async (from, options) => await store.changesSince(from, options),
|
|
4725
|
+
close: async () => await store.close(),
|
|
4726
|
+
commit: async (input) => {
|
|
4727
|
+
const current = parseDetachedStoreHead(await store.head(), "The working capacity store");
|
|
4728
|
+
if (current.generation === input.expectedHead.generation && current.operationSha256 === input.expectedHead.operationSha256)
|
|
4729
|
+
await assertWorkingCommitCapacity(store, binding, input.changes, current);
|
|
4730
|
+
return await store.commit(input);
|
|
4731
|
+
},
|
|
4732
|
+
exportDependencyClosure: async (input) => await store.exportDependencyClosure(input),
|
|
4733
|
+
head: async () => await store.head(),
|
|
4734
|
+
snapshot: async (options) => await store.snapshot(options),
|
|
4735
|
+
verify: async () => await store.verify()
|
|
4736
|
+
};
|
|
4737
|
+
return Object.freeze(guarded);
|
|
4738
|
+
}
|
|
4739
|
+
function assertAdoptionSnapshotCapacity(snapshot, changedRecords) {
|
|
4740
|
+
const recordsByKey = new Map(snapshot.records.map((record) => [record.key, record]));
|
|
4741
|
+
for (const record of changedRecords)
|
|
4742
|
+
recordsByKey.set(record.key, record);
|
|
4743
|
+
if (recordsByKey.size > OH_MEMORY_LIMITS_V1.maximumRecordsPerLane) {
|
|
4744
|
+
throw new RangeError("The adopted canonical memory would exceed its record snapshot bound.");
|
|
4745
|
+
}
|
|
4746
|
+
const nextSequence = snapshot.head.sequence + 1;
|
|
4747
|
+
if (!Number.isSafeInteger(nextSequence)) {
|
|
4748
|
+
throw new RangeError("The adopted canonical memory would exceed its head sequence bound.");
|
|
4749
|
+
}
|
|
4750
|
+
const records = [...recordsByKey.values()].sort((left, right) => compareText(left.key, right.key));
|
|
4751
|
+
const placeholderDigest = canonicalSha256({ kind: "oh.memory.adoption-capacity", v: 1 });
|
|
4752
|
+
const prospective = {
|
|
4753
|
+
head: {
|
|
4754
|
+
generation: nextSequence,
|
|
4755
|
+
graphRevisionSha256: placeholderDigest,
|
|
4756
|
+
operationSha256: placeholderDigest,
|
|
4757
|
+
recordsSha256: canonicalSha256(records.map(knowledgeGraphRecordRefV1)),
|
|
4758
|
+
sequence: nextSequence,
|
|
4759
|
+
v: 1
|
|
4760
|
+
},
|
|
4761
|
+
records,
|
|
4762
|
+
v: 1
|
|
4763
|
+
};
|
|
4764
|
+
if (utf8ByteLength(canonicalJson(prospective)) > OH_MEMORY_LIMITS_V1.snapshotBytesPerLane) {
|
|
4765
|
+
throw new RangeError("The adopted canonical memory would exceed its canonical snapshot byte bound.");
|
|
4766
|
+
}
|
|
4767
|
+
}
|
|
4768
|
+
function adoptionConflict(expectedHead, actualHead, completeConflicts) {
|
|
4769
|
+
const sorted = immutableClone([...completeConflicts].sort((left, right) => compareText(left.key, right.key)));
|
|
4770
|
+
const conflicts = immutableClone(sorted.slice(0, OH_MEMORY_AUTHORITY_LIMITS_V1.reportedAdoptionConflicts));
|
|
4771
|
+
const conflict = immutableClone({
|
|
4772
|
+
actualHead,
|
|
4773
|
+
conflicts,
|
|
4774
|
+
conflictsSha256: canonicalSha256({ conflicts: sorted, v: 1 }),
|
|
4775
|
+
expectedHead,
|
|
4776
|
+
reportedConflicts: conflicts.length,
|
|
4777
|
+
totalConflicts: sorted.length,
|
|
4778
|
+
truncated: conflicts.length !== sorted.length,
|
|
4779
|
+
v: 1
|
|
4780
|
+
});
|
|
4781
|
+
return new OhMemoryAdoptionConflictError(conflict);
|
|
4782
|
+
}
|
|
4783
|
+
async function createOhMemoryAuthorityV1(options) {
|
|
4784
|
+
const maximumCanonicalOperationBytes = options.maximumCanonicalOperationBytes ?? OH_OPERATION_MAX_BYTES_V1;
|
|
4785
|
+
if (!Number.isSafeInteger(maximumCanonicalOperationBytes) || maximumCanonicalOperationBytes < 1 || maximumCanonicalOperationBytes > OH_OPERATION_MAX_BYTES_V1) {
|
|
4786
|
+
throw new TypeError("Invalid canonical memory operation byte bound.");
|
|
4787
|
+
}
|
|
4788
|
+
const memoryActorId = safeCode(options.actorId, 128);
|
|
4789
|
+
const adoptionActorId = safeCode(options.adoptionActorId, 128);
|
|
4790
|
+
if (memoryActorId === null || adoptionActorId === null) {
|
|
4791
|
+
throw new TypeError("Invalid host-bound memory authority actor ID.");
|
|
4792
|
+
}
|
|
4793
|
+
const canonicalStore = options.canonical.store;
|
|
4794
|
+
const workingStore = options.working.store;
|
|
4795
|
+
const canonicalAuthorityId = authorityId(options.canonical.authorityId);
|
|
4796
|
+
const workingAuthorityId = authorityId(options.working.authorityId);
|
|
4797
|
+
if (canonicalAuthorityId === workingAuthorityId) {
|
|
4798
|
+
throw new OhProfileError("Working and canonical memory must be distinct physical authorities.");
|
|
4799
|
+
}
|
|
4800
|
+
const canonicalBinding = bindingFor(canonicalStore, options.canonical.expectedBindingSha256, "canonical");
|
|
4801
|
+
const workingBinding = bindingFor(workingStore, options.working.expectedBindingSha256, "working");
|
|
4802
|
+
const initialCanonicalHead = parseMemoryAuthorityHead(options.canonical.expectedHead, "initial canonical");
|
|
4803
|
+
const workingCodecs = options.working.codecs;
|
|
4804
|
+
const explainCapabilityLifetimeMs = options.explainCapabilityLifetimeMs;
|
|
4805
|
+
const monotonicNow = options.monotonicNow;
|
|
4806
|
+
const now = options.now;
|
|
4807
|
+
const continuationKey = continuationKeyV2(options.continuationKey);
|
|
4808
|
+
const programs = Object.freeze([...resolveProgramsV2(options.programs).values()].map((program) => immutableClone({
|
|
4809
|
+
evaluation: program.evaluation,
|
|
4810
|
+
maximumPageBytes: program.maximumPageBytes,
|
|
4811
|
+
maximumRows: program.maximumRows,
|
|
4812
|
+
pageSize: program.pageSize,
|
|
4813
|
+
parameters: program.parameters,
|
|
4814
|
+
programId: program.programId,
|
|
4815
|
+
purpose: program.purpose,
|
|
4816
|
+
query: program.query,
|
|
4817
|
+
rulePack: program.rulePack,
|
|
4818
|
+
v: 2
|
|
4819
|
+
})));
|
|
4820
|
+
const extractors = resolveExtractors(options.extractors ?? []);
|
|
4821
|
+
const nominationRoutes = Object.freeze([...resolveNominationRoutes(options.nominationRoutes ?? []).values()]);
|
|
4822
|
+
const routesById = new Map(nominationRoutes.map((route) => [route.nominationId, route]));
|
|
4823
|
+
const runtime = createOhMemoryRuntimeV2(options);
|
|
4824
|
+
const createAgentAt = async (expectedHead) => await createOhMemoryAgentV2WithRuntime({
|
|
4825
|
+
actorId: memoryActorId,
|
|
4826
|
+
canonical: {
|
|
4827
|
+
authorityId: canonicalAuthorityId,
|
|
4828
|
+
expectedBindingSha256: canonicalBinding.bindingSha256,
|
|
4829
|
+
expectedHead,
|
|
4830
|
+
store: canonicalStore
|
|
4831
|
+
},
|
|
4832
|
+
continuationKey,
|
|
4833
|
+
...explainCapabilityLifetimeMs === undefined ? {} : {
|
|
4834
|
+
explainCapabilityLifetimeMs
|
|
4835
|
+
},
|
|
4836
|
+
extractors,
|
|
4837
|
+
...monotonicNow === undefined ? {} : { monotonicNow },
|
|
4838
|
+
nominationRoutes,
|
|
4839
|
+
...now === undefined ? {} : { now },
|
|
4840
|
+
programs,
|
|
4841
|
+
working: {
|
|
4842
|
+
authorityId: workingAuthorityId,
|
|
4843
|
+
codecs: workingCodecs,
|
|
4844
|
+
expectedBindingSha256: workingBinding.bindingSha256,
|
|
4845
|
+
store: workingStore
|
|
4846
|
+
}
|
|
4847
|
+
}, runtime);
|
|
4848
|
+
let activeCanonicalHead = initialCanonicalHead;
|
|
4849
|
+
let activeAgent = await createAgentAt(activeCanonicalHead);
|
|
4850
|
+
const agent = Object.freeze({
|
|
4851
|
+
async explain(value) {
|
|
4852
|
+
const selected = activeAgent;
|
|
4853
|
+
return await selected.explain(value);
|
|
4854
|
+
},
|
|
4855
|
+
nominate(value) {
|
|
4856
|
+
const selected = activeAgent;
|
|
4857
|
+
return selected.nominate(value);
|
|
4858
|
+
},
|
|
4859
|
+
async query(value) {
|
|
4860
|
+
const selected = activeAgent;
|
|
4861
|
+
return await selected.query(value);
|
|
4862
|
+
},
|
|
4863
|
+
remember(value) {
|
|
4864
|
+
const selected = activeAgent;
|
|
4865
|
+
return selected.remember(value);
|
|
4866
|
+
}
|
|
4867
|
+
});
|
|
4868
|
+
let hostTail = Promise.resolve();
|
|
4869
|
+
const serialized = (operation) => {
|
|
4870
|
+
const result = hostTail.then(operation);
|
|
4871
|
+
hostTail = result.then(() => {
|
|
4872
|
+
return;
|
|
4873
|
+
}, () => {
|
|
4874
|
+
return;
|
|
4875
|
+
});
|
|
4876
|
+
return result;
|
|
4877
|
+
};
|
|
4878
|
+
const installCanonicalHead = async (head) => {
|
|
4879
|
+
const nextAgent = await createAgentAt(head);
|
|
4880
|
+
activeAgent = nextAgent;
|
|
4881
|
+
activeCanonicalHead = immutableClone(head);
|
|
4882
|
+
};
|
|
4883
|
+
const canonicalAuthority = Object.freeze({
|
|
4884
|
+
authorityId: canonicalAuthorityId,
|
|
4885
|
+
binding: canonicalBinding,
|
|
4886
|
+
store: canonicalStore
|
|
4887
|
+
});
|
|
4888
|
+
const readPhysicalCanonicalHead = async () => parseMemoryAuthorityHead(await canonicalStore.head(), "physical canonical");
|
|
4889
|
+
const advanceCanonical = (value) => {
|
|
4890
|
+
let request;
|
|
4891
|
+
try {
|
|
4892
|
+
request = parseCanonicalAdvanceRequest(value);
|
|
4893
|
+
} catch (error) {
|
|
4894
|
+
return Promise.reject(error);
|
|
4895
|
+
}
|
|
4896
|
+
return serialized(async () => {
|
|
4897
|
+
const priorHead = activeCanonicalHead;
|
|
4898
|
+
if (!exactHead(request.expectedHead, priorHead)) {
|
|
4899
|
+
throw new OhConflictError("The expected canonical memory head does not match the current pin.");
|
|
4900
|
+
}
|
|
4901
|
+
if (exactHead(request.nextHead, priorHead)) {
|
|
4902
|
+
return canonicalAdvanceReceipt(canonicalAuthorityId, canonicalBinding.bindingSha256, priorHead, priorHead, "unchanged");
|
|
4903
|
+
}
|
|
4904
|
+
await proveCanonicalDescendant(canonicalAuthority, priorHead, request.nextHead);
|
|
4905
|
+
await installCanonicalHead(request.nextHead);
|
|
4906
|
+
return canonicalAdvanceReceipt(canonicalAuthorityId, canonicalBinding.bindingSha256, priorHead, request.nextHead, "advanced");
|
|
4907
|
+
});
|
|
4908
|
+
};
|
|
4909
|
+
const adoptNomination = (value) => {
|
|
4910
|
+
let request;
|
|
4911
|
+
try {
|
|
4912
|
+
request = parseAdoptionRequest(value);
|
|
4913
|
+
} catch (error) {
|
|
4914
|
+
return Promise.reject(error);
|
|
4915
|
+
}
|
|
4916
|
+
return serialized(async () => {
|
|
4917
|
+
const route = routesById.get(request.nomination.nominationId);
|
|
4918
|
+
if (route === undefined || route.destinationPurpose !== request.nomination.destinationPurpose) {
|
|
4919
|
+
throw new OhProfileError("The memory nomination is not bound to this adoption route.");
|
|
4920
|
+
}
|
|
4921
|
+
if (request.nomination.source.authorityId !== workingAuthorityId || request.nomination.source.bindingSha256 !== workingBinding.bindingSha256 || request.nomination.closure.binding.bindingSha256 !== workingBinding.bindingSha256) {
|
|
4922
|
+
throw new OhProfileError("The memory nomination is not from the bound working authority.");
|
|
4923
|
+
}
|
|
4924
|
+
const returnedReexport = await workingStore.exportDependencyClosure({
|
|
4925
|
+
head: headRef(request.nomination.source.head),
|
|
4926
|
+
maximumRecords: OH_DEPENDENCY_CLOSURE_LIMITS_V1.records,
|
|
4927
|
+
roots: request.nomination.closure.roots
|
|
4928
|
+
});
|
|
4929
|
+
const reexported = detachCanonicalData(returnedReexport, "The working re-exported nomination", OH_DEPENDENCY_CLOSURE_LIMITS_V1.bytes);
|
|
4930
|
+
if (reexported.canonical !== canonicalJson(request.nomination.closure)) {
|
|
4931
|
+
throw new OhIntegrityError("The working authority did not re-export the nominated closure exactly.");
|
|
4932
|
+
}
|
|
4933
|
+
const priorHead = activeCanonicalHead;
|
|
4934
|
+
const physicalHead = await readPhysicalCanonicalHead();
|
|
4935
|
+
const replacementConflictsAt = async (currentLane) => {
|
|
4936
|
+
if (request.replacements.length === 0)
|
|
4937
|
+
return [];
|
|
4938
|
+
const reviewedLane = exactHead(request.expectedCanonicalHead, currentLane.snapshot.head) ? currentLane : await readLane(canonicalAuthority, "canonical", request.expectedCanonicalHead);
|
|
4939
|
+
return unauthorizedAdoptionDifferences(reviewedLane.snapshot, currentLane.snapshot, request.nomination.closure.records, request.replacements);
|
|
4940
|
+
};
|
|
4941
|
+
if (!exactHead(physicalHead, priorHead)) {
|
|
4942
|
+
const physicalLane = await proveCanonicalDescendant(canonicalAuthority, priorHead, physicalHead);
|
|
4943
|
+
const physicalDifferences = adoptionDifferences(physicalLane.snapshot, request.nomination.closure.records);
|
|
4944
|
+
if (physicalDifferences.length === 0) {
|
|
4945
|
+
const replacementConflicts = await replacementConflictsAt(physicalLane);
|
|
4946
|
+
if (replacementConflicts.length > 0) {
|
|
4947
|
+
throw adoptionConflict(request.expectedCanonicalHead, physicalHead, replacementConflicts);
|
|
4948
|
+
}
|
|
4949
|
+
await installCanonicalHead(physicalHead);
|
|
4950
|
+
return adoptionReceipt(adoptionActorId, canonicalAuthorityId, canonicalBinding.bindingSha256, request.nomination.nominationSha256, null, priorHead, physicalHead, "already-present");
|
|
4951
|
+
}
|
|
4952
|
+
throw adoptionConflict(request.expectedCanonicalHead, physicalHead, physicalDifferences);
|
|
4953
|
+
}
|
|
4954
|
+
const lane = await readLane(canonicalAuthority, "canonical", priorHead);
|
|
4955
|
+
const differences = adoptionDifferences(lane.snapshot, request.nomination.closure.records);
|
|
4956
|
+
if (!exactHead(request.expectedCanonicalHead, priorHead)) {
|
|
4957
|
+
if (differences.length === 0) {
|
|
4958
|
+
const replacementConflicts = await replacementConflictsAt(lane);
|
|
4959
|
+
if (replacementConflicts.length > 0) {
|
|
4960
|
+
throw adoptionConflict(request.expectedCanonicalHead, priorHead, replacementConflicts);
|
|
4961
|
+
}
|
|
4962
|
+
return adoptionReceipt(adoptionActorId, canonicalAuthorityId, canonicalBinding.bindingSha256, request.nomination.nominationSha256, null, priorHead, priorHead, "already-present");
|
|
4963
|
+
}
|
|
4964
|
+
throw adoptionConflict(request.expectedCanonicalHead, priorHead, differences);
|
|
4965
|
+
}
|
|
4966
|
+
if (differences.length === 0) {
|
|
4967
|
+
const replacementConflicts = await replacementConflictsAt(lane);
|
|
4968
|
+
if (replacementConflicts.length > 0) {
|
|
4969
|
+
throw adoptionConflict(request.expectedCanonicalHead, priorHead, replacementConflicts);
|
|
4970
|
+
}
|
|
4971
|
+
return adoptionReceipt(adoptionActorId, canonicalAuthorityId, canonicalBinding.bindingSha256, request.nomination.nominationSha256, null, priorHead, priorHead, "already-present");
|
|
4972
|
+
}
|
|
4973
|
+
const unauthorized = unauthorizedAdoptionDifferences(lane.snapshot, lane.snapshot, request.nomination.closure.records, request.replacements);
|
|
4974
|
+
if (unauthorized.length > 0) {
|
|
4975
|
+
throw adoptionConflict(request.expectedCanonicalHead, priorHead, unauthorized);
|
|
4976
|
+
}
|
|
4977
|
+
const changedKeys = new Set(differences.map(({ key }) => key));
|
|
4978
|
+
const changedRecords = request.nomination.closure.records.filter(({ key }) => changedKeys.has(key));
|
|
4979
|
+
if (changedRecords.length === 0) {
|
|
4980
|
+
return adoptionReceipt(adoptionActorId, canonicalAuthorityId, canonicalBinding.bindingSha256, request.nomination.nominationSha256, null, priorHead, priorHead, "already-present");
|
|
4981
|
+
}
|
|
4982
|
+
assertAdoptionSnapshotCapacity(lane.snapshot, changedRecords);
|
|
4983
|
+
const changes = canonicalKnowledgeGraphChangesV1(changedRecords.map((record) => ({ kind: "put", record, v: 1 })));
|
|
4984
|
+
const operationId = `memory_adopt_${canonicalSha256({
|
|
4985
|
+
actorId: adoptionActorId,
|
|
4986
|
+
bindingSha256: canonicalBinding.bindingSha256,
|
|
4987
|
+
nominationSha256: request.nomination.nominationSha256,
|
|
4988
|
+
priorHead,
|
|
4989
|
+
v: 1
|
|
4990
|
+
}).slice(0, 48)}`;
|
|
4991
|
+
let returnedOperation;
|
|
4992
|
+
try {
|
|
4993
|
+
returnedOperation = await canonicalStore.commit({
|
|
4994
|
+
actorId: adoptionActorId,
|
|
4995
|
+
changes,
|
|
4996
|
+
expectedHead: {
|
|
4997
|
+
generation: priorHead.generation,
|
|
4998
|
+
operationSha256: priorHead.operationSha256
|
|
4999
|
+
},
|
|
5000
|
+
maximumOperationBytes: maximumCanonicalOperationBytes,
|
|
5001
|
+
operationId
|
|
5002
|
+
});
|
|
5003
|
+
} catch (error) {
|
|
5004
|
+
if (!(error instanceof OhConflictError))
|
|
5005
|
+
throw error;
|
|
5006
|
+
const actualHead2 = await readPhysicalCanonicalHead();
|
|
5007
|
+
const actualLane = exactHead(actualHead2, priorHead) ? await readLane(canonicalAuthority, "canonical", actualHead2) : await proveCanonicalDescendant(canonicalAuthority, priorHead, actualHead2);
|
|
5008
|
+
const actualDifferences = adoptionDifferences(actualLane.snapshot, request.nomination.closure.records);
|
|
5009
|
+
if (actualDifferences.length === 0) {
|
|
5010
|
+
if (!exactHead(actualHead2, priorHead)) {
|
|
5011
|
+
await installCanonicalHead(actualHead2);
|
|
5012
|
+
}
|
|
5013
|
+
return adoptionReceipt(adoptionActorId, canonicalAuthorityId, canonicalBinding.bindingSha256, request.nomination.nominationSha256, null, priorHead, actualHead2, "already-present");
|
|
5014
|
+
}
|
|
5015
|
+
throw adoptionConflict(request.expectedCanonicalHead, actualHead2, actualDifferences);
|
|
5016
|
+
}
|
|
5017
|
+
const operation = parseOhOperationV1(detachCanonicalData(returnedOperation, "The returned canonical adoption operation", OH_MEMORY_AUTHORITY_LIMITS_V1.canonicalChangeFeedPageBytes).value);
|
|
5018
|
+
if (operation === null || operation.actorId !== adoptionActorId || operation.operationId !== operationId || operation.spaceId !== canonicalBinding.spaceId || operation.parentOperationSha256 !== priorHead.operationSha256 || operation.sequence !== priorHead.sequence + 1 || canonicalJson(operation.changes) !== canonicalJson(changes)) {
|
|
5019
|
+
throw new OhIntegrityError("The canonical authority returned a different adoption operation.");
|
|
5020
|
+
}
|
|
5021
|
+
const head = immutableClone({
|
|
5022
|
+
generation: operation.sequence,
|
|
5023
|
+
graphRevisionSha256: operation.graphRevisionSha256,
|
|
5024
|
+
operationSha256: operation.operationSha256,
|
|
5025
|
+
recordsSha256: operation.recordsSha256,
|
|
5026
|
+
sequence: operation.sequence,
|
|
5027
|
+
v: 1
|
|
5028
|
+
});
|
|
5029
|
+
const actualHead = await readPhysicalCanonicalHead();
|
|
5030
|
+
if (!exactHead(actualHead, head)) {
|
|
5031
|
+
const actualLane = exactHead(actualHead, priorHead) ? await readLane(canonicalAuthority, "canonical", actualHead) : await proveCanonicalDescendant(canonicalAuthority, priorHead, actualHead, head);
|
|
5032
|
+
const actualDifferences = adoptionDifferences(actualLane.snapshot, request.nomination.closure.records);
|
|
5033
|
+
if (actualDifferences.length !== 0) {
|
|
5034
|
+
throw adoptionConflict(request.expectedCanonicalHead, actualHead, actualDifferences);
|
|
5035
|
+
}
|
|
5036
|
+
await installCanonicalHead(actualHead);
|
|
5037
|
+
return adoptionReceipt(adoptionActorId, canonicalAuthorityId, canonicalBinding.bindingSha256, request.nomination.nominationSha256, null, priorHead, actualHead, "already-present");
|
|
5038
|
+
}
|
|
5039
|
+
await installCanonicalHead(actualHead);
|
|
5040
|
+
return adoptionReceipt(adoptionActorId, canonicalAuthorityId, canonicalBinding.bindingSha256, request.nomination.nominationSha256, operation.operationSha256, priorHead, actualHead, "adopted");
|
|
5041
|
+
});
|
|
5042
|
+
};
|
|
5043
|
+
return Object.freeze({ agent, host: Object.freeze({ adoptNomination, advanceCanonical }) });
|
|
5044
|
+
}
|
|
4092
5045
|
export {
|
|
4093
5046
|
renderOhMemoryPageMarkdownV1,
|
|
4094
5047
|
parseOhMemoryPageValueV1,
|
|
4095
5048
|
parseOhMemoryPageRecordV1,
|
|
4096
5049
|
parseOhMemoryPageMarkdownV1,
|
|
5050
|
+
parseOhMemoryNominationV1,
|
|
4097
5051
|
createOhMemoryPageValueV1,
|
|
4098
5052
|
createOhMemoryPageRecordV1,
|
|
5053
|
+
createOhMemoryAuthorityV1,
|
|
4099
5054
|
createOhMemoryAgentV2,
|
|
4100
5055
|
createOhMemoryAgentV1,
|
|
5056
|
+
OhMemoryContinuationError,
|
|
5057
|
+
OhMemoryAdoptionConflictError,
|
|
4101
5058
|
OH_MEMORY_QUERY_LIMITS_V2,
|
|
4102
5059
|
OH_MEMORY_PAGE_RECORD_CODEC_V1,
|
|
4103
5060
|
OH_MEMORY_PAGE_MARKDOWN_EXTENSION_V1,
|
|
@@ -4106,5 +5063,6 @@ export {
|
|
|
4106
5063
|
OH_MEMORY_LIMITS_V1,
|
|
4107
5064
|
OH_MEMORY_FORMAT_VERSION_V1,
|
|
4108
5065
|
OH_MEMORY_CONFLICT_POLICY_V1,
|
|
4109
|
-
OH_MEMORY_COMPOSITE_FACT_EXTRACTOR_V1
|
|
5066
|
+
OH_MEMORY_COMPOSITE_FACT_EXTRACTOR_V1,
|
|
5067
|
+
OH_MEMORY_AUTHORITY_LIMITS_V1
|
|
4110
5068
|
};
|