@hraness/oh 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +164 -114
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +811 -97
- package/dist/errors.d.ts +39 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/graph.d.ts.map +1 -1
- package/dist/index.js +676 -61
- package/dist/libsql.d.ts.map +1 -1
- package/dist/libsql.js +162 -35
- package/dist/memory-page.js +2 -2
- package/dist/memory.d.ts +87 -6
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +1106 -148
- package/dist/operation.d.ts +3 -1
- package/dist/operation.d.ts.map +1 -1
- package/dist/projection-public.js +2 -2
- package/dist/projection-suss.js +2 -2
- package/dist/sdk.js +780 -88
- package/dist/semantic-cloud.js +2 -2
- package/dist/semantic.js +2 -2
- package/dist/sqlite/index.js +1251 -306
- package/dist/sqlite/port.d.ts +31 -3
- package/dist/sqlite/port.d.ts.map +1 -1
- package/dist/sqlite/store.d.ts +18 -2
- package/dist/sqlite/store.d.ts.map +1 -1
- package/dist/store.d.ts +3 -12
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +154 -32
- package/dist/sync.d.ts +7 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +668 -35
- package/package.json +5 -1
- package/skills/oh/SKILL.md +42 -16
- package/spec/README.md +2 -2
- package/spec/v1/memory.md +134 -16
- package/spec/v1/storage.md +8 -5
- package/spec/v1/store.md +20 -0
- package/spec/v1/sync.md +77 -8
- package/src/cli.test.ts +53 -1
- package/src/cli.ts +34 -8
- package/src/errors.test.ts +87 -0
- package/src/errors.ts +185 -0
- package/src/graph.ts +2 -2
- package/src/libsql.test.ts +36 -0
- package/src/libsql.ts +26 -5
- package/src/memory.test.ts +1488 -18
- package/src/memory.ts +1199 -122
- package/src/operation.ts +13 -3
- package/src/sqlite/port.test.ts +209 -0
- package/src/sqlite/port.ts +118 -4
- package/src/sqlite/store.test.ts +106 -1
- package/src/sqlite/store.ts +168 -30
- package/src/store.test.ts +12 -0
- package/src/store.ts +30 -20
- package/src/sync.test.ts +570 -2
- package/src/sync.ts +586 -36
package/src/memory.test.ts
CHANGED
|
@@ -2,12 +2,19 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
import { canonicalJson, canonicalSha256, type JsonValue } from "./canonical";
|
|
4
4
|
import { OhRecordCodecRegistry } from "./contract";
|
|
5
|
-
import { createKnowledgeGraphRecordV1
|
|
5
|
+
import { createKnowledgeGraphRecordV1, knowledgeGraphRecordRefV1,
|
|
6
|
+
type KnowledgeGraphRecordV1 } from "./graph";
|
|
6
7
|
import {
|
|
7
8
|
createOhMemoryAgentV1,
|
|
8
9
|
createOhMemoryAgentV2,
|
|
10
|
+
createOhMemoryAuthorityV1,
|
|
11
|
+
OH_MEMORY_AUTHORITY_LIMITS_V1,
|
|
9
12
|
OH_MEMORY_COMPOSITE_FACT_EXTRACTOR_V1,
|
|
13
|
+
OH_MEMORY_LIMITS_V1,
|
|
10
14
|
OH_MEMORY_QUERY_LIMITS_V2,
|
|
15
|
+
OhMemoryAdoptionConflictError,
|
|
16
|
+
OhMemoryContinuationError,
|
|
17
|
+
parseOhMemoryNominationV1,
|
|
11
18
|
type OhMemoryAuthoritySourceV1,
|
|
12
19
|
type OhMemoryProofV1,
|
|
13
20
|
} from "./memory";
|
|
@@ -22,8 +29,12 @@ import { createOhSqliteStoreAuthorityV1 } from "./sqlite/port";
|
|
|
22
29
|
import {
|
|
23
30
|
OH_CANONICAL_STORE_PROFILE_V1,
|
|
24
31
|
OH_WORKING_STORE_PROFILE_V1,
|
|
32
|
+
OhConflictError,
|
|
25
33
|
OhIntegrityError,
|
|
34
|
+
OhOperationSizeError,
|
|
26
35
|
OhProfileError,
|
|
36
|
+
type OhHeadV1,
|
|
37
|
+
type OhSnapshotV1,
|
|
27
38
|
type OhStoreV1,
|
|
28
39
|
} from "./store";
|
|
29
40
|
|
|
@@ -118,6 +129,173 @@ function chunkProgram(maximumRows = 400, pageSize = 64,
|
|
|
118
129
|
} as const;
|
|
119
130
|
}
|
|
120
131
|
|
|
132
|
+
function stableVisibleProgram(pageSize = 50) {
|
|
133
|
+
const program = visibleProgram();
|
|
134
|
+
return {
|
|
135
|
+
evaluation: {
|
|
136
|
+
maximumDerivedTuples: 2_048,
|
|
137
|
+
maximumProofDepth: 16,
|
|
138
|
+
maximumProofNodes: 64,
|
|
139
|
+
maximumResultBytes: 8 * 1024 * 1024,
|
|
140
|
+
maximumRounds: 16,
|
|
141
|
+
maximumTotalProofNodes: 8_192,
|
|
142
|
+
maximumWorkUnits: 2_000_000,
|
|
143
|
+
},
|
|
144
|
+
maximumPageBytes: 1024 * 1024,
|
|
145
|
+
maximumRows: 50,
|
|
146
|
+
pageSize,
|
|
147
|
+
parameters: [],
|
|
148
|
+
programId: program.programId,
|
|
149
|
+
purpose: program.purpose,
|
|
150
|
+
query: program.query,
|
|
151
|
+
rulePack: program.rulePack,
|
|
152
|
+
v: 2 as const,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function authorityFixture(configuration: Readonly<{
|
|
157
|
+
canonicalStore?: (store: OhStoreV1) => OhStoreV1;
|
|
158
|
+
canonicalRecords?: readonly ReturnType<typeof entity>[];
|
|
159
|
+
explainCapabilityLifetimeMs?: number;
|
|
160
|
+
maximumCanonicalOperationBytes?: number;
|
|
161
|
+
monotonicNow?: () => number;
|
|
162
|
+
now?: () => Date;
|
|
163
|
+
pageSize?: number;
|
|
164
|
+
workingStore?: (store: OhStoreV1) => OhStoreV1;
|
|
165
|
+
}> = {}) {
|
|
166
|
+
const canonical = createOhSqliteStoreAuthorityV1({ path: ":memory:",
|
|
167
|
+
profile: OH_CANONICAL_STORE_PROFILE_V1, realmId: "realm:authority-c", spaceId: "authority-c" });
|
|
168
|
+
const working = createOhSqliteStoreAuthorityV1({ path: ":memory:",
|
|
169
|
+
profile: OH_WORKING_STORE_PROFILE_V1, realmId: "realm:authority-w", spaceId: "authority-w" });
|
|
170
|
+
const records = configuration.canonicalRecords ?? [entity("entity:canonical", "Canonical")];
|
|
171
|
+
if (records.length > 0) {
|
|
172
|
+
await canonical.store.commit({ actorId: "test.canonical-seed",
|
|
173
|
+
changes: records.map((record) => ({ kind: "put" as const, record, v: 1 as const })),
|
|
174
|
+
expectedHead: await canonical.store.head(), operationId: "op_authority_seed" });
|
|
175
|
+
}
|
|
176
|
+
const initialCanonicalHead = await canonical.store.head();
|
|
177
|
+
const selectedCanonicalStore = configuration.canonicalStore?.(canonical.store) ?? canonical.store;
|
|
178
|
+
const selectedWorkingStore = configuration.workingStore?.(working.store) ?? working.store;
|
|
179
|
+
const authority = await createOhMemoryAuthorityV1({
|
|
180
|
+
actorId: "test.memory-agent", adoptionActorId: "test.memory-reviewer",
|
|
181
|
+
canonical: { authorityId: "authority.canonical",
|
|
182
|
+
expectedBindingSha256: selectedCanonicalStore.binding.bindingSha256,
|
|
183
|
+
expectedHead: initialCanonicalHead, store: selectedCanonicalStore },
|
|
184
|
+
continuationKey: Uint8Array.from({ length: 32 }, (_, index) => index),
|
|
185
|
+
...(configuration.explainCapabilityLifetimeMs === undefined ? {} : {
|
|
186
|
+
explainCapabilityLifetimeMs: configuration.explainCapabilityLifetimeMs,
|
|
187
|
+
}),
|
|
188
|
+
...(configuration.maximumCanonicalOperationBytes === undefined ? {} : {
|
|
189
|
+
maximumCanonicalOperationBytes: configuration.maximumCanonicalOperationBytes,
|
|
190
|
+
}),
|
|
191
|
+
...(configuration.monotonicNow === undefined ? {} : {
|
|
192
|
+
monotonicNow: configuration.monotonicNow,
|
|
193
|
+
}),
|
|
194
|
+
nominationRoutes: [{ destinationPurpose: "kb.review", nominationId: "kb.review" }],
|
|
195
|
+
...(configuration.now === undefined ? {} : { now: configuration.now }),
|
|
196
|
+
programs: [stableVisibleProgram(configuration.pageSize)],
|
|
197
|
+
working: { authorityId: "authority.working", codecs: entityCodecs(),
|
|
198
|
+
expectedBindingSha256: selectedWorkingStore.binding.bindingSha256,
|
|
199
|
+
store: selectedWorkingStore },
|
|
200
|
+
});
|
|
201
|
+
return { authority, canonical, initialCanonicalHead, working };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function peerAuthority(
|
|
205
|
+
canonicalStore: OhStoreV1,
|
|
206
|
+
workingStore: OhStoreV1,
|
|
207
|
+
expectedCanonicalHead: OhHeadV1,
|
|
208
|
+
) {
|
|
209
|
+
return await createOhMemoryAuthorityV1({
|
|
210
|
+
actorId: "test.memory-agent", adoptionActorId: "test.memory-reviewer",
|
|
211
|
+
canonical: { authorityId: "authority.canonical",
|
|
212
|
+
expectedBindingSha256: canonicalStore.binding.bindingSha256,
|
|
213
|
+
expectedHead: expectedCanonicalHead, store: canonicalStore },
|
|
214
|
+
continuationKey: Uint8Array.from({ length: 32 }, (_, index) => index),
|
|
215
|
+
nominationRoutes: [{ destinationPurpose: "kb.review", nominationId: "kb.review" }],
|
|
216
|
+
programs: [stableVisibleProgram()],
|
|
217
|
+
working: { authorityId: "authority.working", codecs: entityCodecs(),
|
|
218
|
+
expectedBindingSha256: workingStore.binding.bindingSha256, store: workingStore },
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function twoPartyCanonicalCommitRendezvous() {
|
|
223
|
+
const commitCalls: [number, number] = [0, 0];
|
|
224
|
+
const arrived = new Set<number>();
|
|
225
|
+
let release!: () => void;
|
|
226
|
+
const released = new Promise<void>((resolve) => { release = resolve; });
|
|
227
|
+
const wrap = (store: OhStoreV1, participant: 0 | 1): OhStoreV1 => new Proxy(store, {
|
|
228
|
+
get(target, property) {
|
|
229
|
+
const member = Reflect.get(target, property, target) as unknown;
|
|
230
|
+
if (typeof member !== "function") return member;
|
|
231
|
+
if (property !== "commit") {
|
|
232
|
+
return (...args: unknown[]) => Reflect.apply(member, target, args);
|
|
233
|
+
}
|
|
234
|
+
return async (...args: unknown[]) => {
|
|
235
|
+
commitCalls[participant] += 1;
|
|
236
|
+
arrived.add(participant);
|
|
237
|
+
if (arrived.size === 2) release();
|
|
238
|
+
await released;
|
|
239
|
+
return await Reflect.apply(member, target, args);
|
|
240
|
+
};
|
|
241
|
+
},
|
|
242
|
+
}) as OhStoreV1;
|
|
243
|
+
return { commitCalls, wrap };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function staticSnapshot(records: readonly KnowledgeGraphRecordV1[]): OhSnapshotV1 {
|
|
247
|
+
const sorted = [...records].sort((left, right) => left.key < right.key ? -1 : 1);
|
|
248
|
+
const recordsSha256 = canonicalSha256(sorted.map(knowledgeGraphRecordRefV1));
|
|
249
|
+
return Object.freeze({
|
|
250
|
+
head: Object.freeze({
|
|
251
|
+
generation: 1,
|
|
252
|
+
graphRevisionSha256: canonicalSha256({ kind: "test.static-graph", recordsSha256, v: 1 }),
|
|
253
|
+
operationSha256: canonicalSha256({ kind: "test.static-operation", recordsSha256, v: 1 }),
|
|
254
|
+
recordsSha256,
|
|
255
|
+
sequence: 1,
|
|
256
|
+
v: 1 as const,
|
|
257
|
+
}),
|
|
258
|
+
records: Object.freeze(sorted),
|
|
259
|
+
v: 1 as const,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function capacityAuthority(records: readonly KnowledgeGraphRecordV1[]) {
|
|
264
|
+
const canonical = createOhSqliteStoreAuthorityV1({ path: ":memory:",
|
|
265
|
+
profile: OH_CANONICAL_STORE_PROFILE_V1, realmId: "realm:capacity-c", spaceId: "capacity-c" });
|
|
266
|
+
const working = createOhSqliteStoreAuthorityV1({ path: ":memory:",
|
|
267
|
+
profile: OH_WORKING_STORE_PROFILE_V1, realmId: "realm:capacity-w", spaceId: "capacity-w" });
|
|
268
|
+
const snapshot = staticSnapshot(records);
|
|
269
|
+
let commitCalls = 0;
|
|
270
|
+
const canonicalStore: OhStoreV1 = {
|
|
271
|
+
binding: canonical.store.binding,
|
|
272
|
+
changesSince: canonical.store.changesSince.bind(canonical.store),
|
|
273
|
+
close: canonical.store.close.bind(canonical.store),
|
|
274
|
+
async commit() { commitCalls += 1; throw new Error("capacity preflight must precede CAS"); },
|
|
275
|
+
exportDependencyClosure: canonical.store.exportDependencyClosure.bind(canonical.store),
|
|
276
|
+
async head() { return snapshot.head; },
|
|
277
|
+
async snapshot(options) {
|
|
278
|
+
if (options?.maximumRecords !== undefined && records.length > options.maximumRecords) {
|
|
279
|
+
throw new RangeError("static snapshot record bound");
|
|
280
|
+
}
|
|
281
|
+
return snapshot;
|
|
282
|
+
},
|
|
283
|
+
verify: canonical.store.verify.bind(canonical.store),
|
|
284
|
+
};
|
|
285
|
+
const authority = await createOhMemoryAuthorityV1({
|
|
286
|
+
actorId: "test.capacity-agent", adoptionActorId: "test.capacity-reviewer",
|
|
287
|
+
canonical: { authorityId: "authority.capacity-canonical",
|
|
288
|
+
expectedBindingSha256: canonicalStore.binding.bindingSha256,
|
|
289
|
+
expectedHead: snapshot.head, store: canonicalStore },
|
|
290
|
+
continuationKey: Uint8Array.from({ length: 32 }, (_, index) => index),
|
|
291
|
+
nominationRoutes: [{ destinationPurpose: "kb.review", nominationId: "kb.review" }],
|
|
292
|
+
programs: [stableVisibleProgram()],
|
|
293
|
+
working: { authorityId: "authority.capacity-working", codecs: entityCodecs(),
|
|
294
|
+
expectedBindingSha256: working.store.binding.bindingSha256, store: working.store },
|
|
295
|
+
});
|
|
296
|
+
return { authority, canonical, canonicalStore, commitCalls: () => commitCalls, snapshot, working };
|
|
297
|
+
}
|
|
298
|
+
|
|
121
299
|
async function fixtureV2(maximumRows = 400, pageSize = 64,
|
|
122
300
|
maximumResultBytes = 8 * 1024 * 1024, configuration: Readonly<{
|
|
123
301
|
chunkBytes?: number;
|
|
@@ -190,7 +368,136 @@ function countedWorkingStore(store: OhStoreV1, counter: { reads: number }): OhSt
|
|
|
190
368
|
}) as OhStoreV1;
|
|
191
369
|
}
|
|
192
370
|
|
|
193
|
-
|
|
371
|
+
function frozenForwardingStore(store: OhStoreV1): OhStoreV1 {
|
|
372
|
+
return Object.freeze({
|
|
373
|
+
binding: store.binding,
|
|
374
|
+
changesSince: store.changesSince.bind(store),
|
|
375
|
+
close: store.close.bind(store),
|
|
376
|
+
commit: store.commit.bind(store),
|
|
377
|
+
exportDependencyClosure: store.exportDependencyClosure.bind(store),
|
|
378
|
+
head: store.head.bind(store),
|
|
379
|
+
snapshot: store.snapshot.bind(store),
|
|
380
|
+
verify: store.verify.bind(store),
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function saturatedWorkingStore(
|
|
385
|
+
store: OhStoreV1,
|
|
386
|
+
snapshot: OhSnapshotV1,
|
|
387
|
+
counter: { commits: number },
|
|
388
|
+
): OhStoreV1 {
|
|
389
|
+
return new Proxy(store, {
|
|
390
|
+
get(target, property) {
|
|
391
|
+
const value = Reflect.get(target, property, target) as unknown;
|
|
392
|
+
if (property === "commit") {
|
|
393
|
+
return async () => {
|
|
394
|
+
counter.commits += 1;
|
|
395
|
+
throw new Error("working capacity preflight must precede CAS");
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
if (property === "head") return async () => snapshot.head;
|
|
399
|
+
if (property === "snapshot") return async () => snapshot;
|
|
400
|
+
return typeof value === "function"
|
|
401
|
+
? (...args: unknown[]) => Reflect.apply(value, target, args)
|
|
402
|
+
: value;
|
|
403
|
+
},
|
|
404
|
+
}) as OhStoreV1;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function replayProbeWorkingStore(
|
|
408
|
+
store: OhStoreV1,
|
|
409
|
+
state: { rejectSnapshots: boolean; snapshotReads: number },
|
|
410
|
+
): OhStoreV1 {
|
|
411
|
+
return new Proxy(store, {
|
|
412
|
+
get(target, property) {
|
|
413
|
+
const value = Reflect.get(target, property, target) as unknown;
|
|
414
|
+
if (property === "snapshot") {
|
|
415
|
+
return async (...args: unknown[]) => {
|
|
416
|
+
state.snapshotReads += 1;
|
|
417
|
+
if (state.rejectSnapshots) {
|
|
418
|
+
throw new RangeError("a stale replay must not run a prospective capacity snapshot");
|
|
419
|
+
}
|
|
420
|
+
return await Reflect.apply(value as (...values: unknown[]) => unknown, target, args);
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
return typeof value === "function"
|
|
424
|
+
? (...args: unknown[]) => Reflect.apply(value, target, args)
|
|
425
|
+
: value;
|
|
426
|
+
},
|
|
427
|
+
}) as OhStoreV1;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
type HostileCapacityResponse = "accessor-snapshot" | "mismatched-snapshot"
|
|
431
|
+
| "mutated-head-snapshot" | "oversize-head" | "oversize-snapshot";
|
|
432
|
+
|
|
433
|
+
function hostileCapacityWorkingStore(
|
|
434
|
+
store: OhStoreV1,
|
|
435
|
+
mode: HostileCapacityResponse,
|
|
436
|
+
counter: { accessorReads?: number; commits: number; snapshotReads: number },
|
|
437
|
+
): OhStoreV1 {
|
|
438
|
+
let returnedMutableHead: OhHeadV1 | undefined;
|
|
439
|
+
return new Proxy(store, {
|
|
440
|
+
get(target, property) {
|
|
441
|
+
const value = Reflect.get(target, property, target) as unknown;
|
|
442
|
+
if (property === "commit") {
|
|
443
|
+
return async () => {
|
|
444
|
+
counter.commits += 1;
|
|
445
|
+
throw new Error("a hostile capacity response must fail before CAS");
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
if (property === "head" && mode === "oversize-head") {
|
|
449
|
+
return async () => ({ ...await target.head(), padding: "x".repeat(8 * 1024) });
|
|
450
|
+
}
|
|
451
|
+
if (property === "head" && mode === "mutated-head-snapshot") {
|
|
452
|
+
return async () => {
|
|
453
|
+
returnedMutableHead = { ...await target.head() };
|
|
454
|
+
return returnedMutableHead;
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
if (property === "snapshot") {
|
|
458
|
+
return async (...args: unknown[]) => {
|
|
459
|
+
counter.snapshotReads += 1;
|
|
460
|
+
if (mode === "mismatched-snapshot") return staticSnapshot([]);
|
|
461
|
+
if (mode === "mutated-head-snapshot") {
|
|
462
|
+
if (returnedMutableHead === undefined) throw new Error("Expected a prior head read.");
|
|
463
|
+
const substituted = staticSnapshot([]);
|
|
464
|
+
Object.assign(returnedMutableHead as unknown as Record<string, unknown>,
|
|
465
|
+
substituted.head);
|
|
466
|
+
return { head: returnedMutableHead, records: substituted.records, v: 1 };
|
|
467
|
+
}
|
|
468
|
+
const snapshot = await Reflect.apply(value as (...values: unknown[]) => unknown,
|
|
469
|
+
target, args) as OhSnapshotV1;
|
|
470
|
+
if (mode === "oversize-snapshot") {
|
|
471
|
+
return { ...snapshot, padding: "x".repeat(OH_MEMORY_LIMITS_V1.snapshotBytesPerLane) };
|
|
472
|
+
}
|
|
473
|
+
if (mode === "accessor-snapshot") {
|
|
474
|
+
const response = { head: snapshot.head, v: 1 } as {
|
|
475
|
+
head: OhHeadV1;
|
|
476
|
+
readonly records: readonly KnowledgeGraphRecordV1[];
|
|
477
|
+
v: 1;
|
|
478
|
+
};
|
|
479
|
+
Object.defineProperty(response, "records", {
|
|
480
|
+
enumerable: true,
|
|
481
|
+
get: () => {
|
|
482
|
+
counter.accessorReads = (counter.accessorReads ?? 0) + 1;
|
|
483
|
+
return snapshot.records;
|
|
484
|
+
},
|
|
485
|
+
});
|
|
486
|
+
return response;
|
|
487
|
+
}
|
|
488
|
+
return snapshot;
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
return typeof value === "function"
|
|
492
|
+
? (...args: unknown[]) => Reflect.apply(value, target, args)
|
|
493
|
+
: value;
|
|
494
|
+
},
|
|
495
|
+
}) as OhStoreV1;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
async function fixture(configuration: Readonly<{
|
|
499
|
+
wrapWorkingStore?: (store: OhStoreV1) => OhStoreV1;
|
|
500
|
+
}> = {}) {
|
|
194
501
|
const canonical = createOhSqliteStoreAuthorityV1({ path: ":memory:",
|
|
195
502
|
profile: OH_CANONICAL_STORE_PROFILE_V1, realmId: "realm:canonical", spaceId: "canonical" });
|
|
196
503
|
const working = createOhSqliteStoreAuthorityV1({ path: ":memory:",
|
|
@@ -202,6 +509,7 @@ async function fixture() {
|
|
|
202
509
|
const clockOrigin = now.getTime();
|
|
203
510
|
let monotonicNow = 0;
|
|
204
511
|
const baseProgram = visibleProgram();
|
|
512
|
+
const selectedWorkingStore = configuration.wrapWorkingStore?.(working.store) ?? working.store;
|
|
205
513
|
const agent = await createOhMemoryAgentV1({
|
|
206
514
|
actorId: "test.memory-agent",
|
|
207
515
|
canonical: { authorityId: "authority.canonical", expectedBindingSha256: canonical.store.binding.bindingSha256,
|
|
@@ -213,14 +521,15 @@ async function fixture() {
|
|
|
213
521
|
purpose: "answer.alternate" }, { ...baseProgram,
|
|
214
522
|
evaluation: { maximumRounds: 2 }, programId: "memory.visible-records-bounded" }],
|
|
215
523
|
working: { authorityId: "authority.working", codecs: entityCodecs(),
|
|
216
|
-
expectedBindingSha256:
|
|
524
|
+
expectedBindingSha256: selectedWorkingStore.binding.bindingSha256,
|
|
525
|
+
store: selectedWorkingStore },
|
|
217
526
|
});
|
|
218
527
|
return { agent, canonical, canonicalHead, setNow(value: string) {
|
|
219
528
|
now = new Date(value); monotonicNow = now.getTime() - clockOrigin;
|
|
220
529
|
}, working };
|
|
221
530
|
}
|
|
222
531
|
|
|
223
|
-
describe("
|
|
532
|
+
describe("composite Oh memory V1", () => {
|
|
224
533
|
test("keeps two physical authorities explicit and makes working conflicts visible", async () => {
|
|
225
534
|
expect(Object.isFrozen(OH_MEMORY_COMPOSITE_FACT_EXTRACTOR_V1.relations)).toBe(true);
|
|
226
535
|
const value = await fixture();
|
|
@@ -483,9 +792,150 @@ describe("experimental composite Oh memory", () => {
|
|
|
483
792
|
store: working.store } })).rejects.toThrow(OhProfileError);
|
|
484
793
|
await canonical.store.close(); await working.store.close();
|
|
485
794
|
});
|
|
795
|
+
|
|
796
|
+
test("rejects a V1 remember that would exceed working record capacity before CAS", async () => {
|
|
797
|
+
const snapshot = staticSnapshot(Array.from({
|
|
798
|
+
length: OH_MEMORY_LIMITS_V1.maximumRecordsPerLane,
|
|
799
|
+
}, (_, index) => entity(`entity:v1-capacity-${index.toString().padStart(4, "0")}`, "x")));
|
|
800
|
+
const counter = { commits: 0 };
|
|
801
|
+
const value = await fixture({
|
|
802
|
+
wrapWorkingStore: (store) => saturatedWorkingStore(store, snapshot, counter),
|
|
803
|
+
});
|
|
804
|
+
await expect(value.agent.remember({ expectedHead: {
|
|
805
|
+
generation: snapshot.head.generation,
|
|
806
|
+
operationSha256: snapshot.head.operationSha256,
|
|
807
|
+
}, puts: [{ dependencies: [], key: "entity:v1-capacity-extra", kind: "entity", v: 1,
|
|
808
|
+
value: { name: "Extra" } }], requestId: "v1-capacity-extra", tombstones: [], v: 1 }))
|
|
809
|
+
.rejects.toThrow("working memory would exceed its record snapshot bound");
|
|
810
|
+
expect(counter.commits).toBe(0);
|
|
811
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
test("accepts an immutable host working-store capability", async () => {
|
|
815
|
+
const value = await fixture({ wrapWorkingStore: frozenForwardingStore });
|
|
816
|
+
const head = await value.working.store.head();
|
|
817
|
+
const receipt = await value.agent.remember({
|
|
818
|
+
expectedHead: {
|
|
819
|
+
generation: head.generation,
|
|
820
|
+
operationSha256: head.operationSha256,
|
|
821
|
+
},
|
|
822
|
+
puts: [{
|
|
823
|
+
dependencies: [],
|
|
824
|
+
key: "entity:frozen-host-store",
|
|
825
|
+
kind: "entity",
|
|
826
|
+
v: 1,
|
|
827
|
+
value: { name: "Frozen host store" },
|
|
828
|
+
}],
|
|
829
|
+
requestId: "v1-frozen-host-store",
|
|
830
|
+
tombstones: [],
|
|
831
|
+
v: 1,
|
|
832
|
+
});
|
|
833
|
+
expect(receipt.status).toBe("committed");
|
|
834
|
+
expect((await value.working.store.snapshot()).records.map(({ key }) => key))
|
|
835
|
+
.toContain("entity:frozen-host-store");
|
|
836
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
test("lets the V1 store resolve an exact stale replay after a later write", async () => {
|
|
840
|
+
const state = { rejectSnapshots: false, snapshotReads: 0 };
|
|
841
|
+
const value = await fixture({
|
|
842
|
+
wrapWorkingStore: (store) => replayProbeWorkingStore(store, state),
|
|
843
|
+
});
|
|
844
|
+
const initialHead = await value.working.store.head();
|
|
845
|
+
const request = { expectedHead: { generation: initialHead.generation,
|
|
846
|
+
operationSha256: initialHead.operationSha256 }, puts: [{ dependencies: [],
|
|
847
|
+
key: "entity:v1-replay", kind: "entity", v: 1, value: { name: "Replay" } }],
|
|
848
|
+
requestId: "v1-exact-replay", tombstones: [], v: 1 } as const;
|
|
849
|
+
const receipt = await value.agent.remember(request);
|
|
850
|
+
await put(value.working.store, "entity:v1-later", "Later", "v1_later_write");
|
|
851
|
+
const snapshotReads = state.snapshotReads;
|
|
852
|
+
state.rejectSnapshots = true;
|
|
853
|
+
expect(await value.agent.remember(request)).toEqual(receipt);
|
|
854
|
+
expect(state.snapshotReads).toBe(snapshotReads);
|
|
855
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
test("bounds a host-supplied working head before capacity preflight", async () => {
|
|
859
|
+
const counter = { commits: 0, snapshotReads: 0 };
|
|
860
|
+
const value = await fixture({
|
|
861
|
+
wrapWorkingStore: (store) => hostileCapacityWorkingStore(store, "oversize-head", counter),
|
|
862
|
+
});
|
|
863
|
+
const head = await value.working.store.head();
|
|
864
|
+
await expect(value.agent.remember({ expectedHead: {
|
|
865
|
+
generation: head.generation, operationSha256: head.operationSha256,
|
|
866
|
+
}, puts: [{ dependencies: [], key: "entity:v1-hostile-head", kind: "entity", v: 1,
|
|
867
|
+
value: { name: "Hostile head" } }], requestId: "v1-hostile-head", tombstones: [], v: 1 }))
|
|
868
|
+
.rejects.toThrow("canonical byte bound");
|
|
869
|
+
expect(counter).toEqual({ commits: 0, snapshotReads: 0 });
|
|
870
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
test("rejects a capacity snapshot that is not at the requested current head", async () => {
|
|
874
|
+
const counter = { commits: 0, snapshotReads: 0 };
|
|
875
|
+
const value = await fixture({
|
|
876
|
+
wrapWorkingStore: (store) => hostileCapacityWorkingStore(store,
|
|
877
|
+
"mismatched-snapshot", counter),
|
|
878
|
+
});
|
|
879
|
+
const head = await value.working.store.head();
|
|
880
|
+
await expect(value.agent.remember({ expectedHead: {
|
|
881
|
+
generation: head.generation, operationSha256: head.operationSha256,
|
|
882
|
+
}, puts: [{ dependencies: [], key: "entity:v1-mismatched-snapshot", kind: "entity", v: 1,
|
|
883
|
+
value: { name: "Mismatched snapshot" } }], requestId: "v1-mismatched-snapshot",
|
|
884
|
+
tombstones: [], v: 1 })).rejects.toThrow(OhIntegrityError);
|
|
885
|
+
expect(counter).toEqual({ commits: 0, snapshotReads: 1 });
|
|
886
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
test("detaches the working head before a store mutates and reuses its response", async () => {
|
|
890
|
+
const counter = { commits: 0, snapshotReads: 0 };
|
|
891
|
+
const value = await fixture({
|
|
892
|
+
wrapWorkingStore: (store) => hostileCapacityWorkingStore(store,
|
|
893
|
+
"mutated-head-snapshot", counter),
|
|
894
|
+
});
|
|
895
|
+
const head = await value.working.store.head();
|
|
896
|
+
await expect(value.agent.remember({ expectedHead: {
|
|
897
|
+
generation: head.generation, operationSha256: head.operationSha256,
|
|
898
|
+
}, puts: [{ dependencies: [], key: "entity:v1-mutated-head", kind: "entity", v: 1,
|
|
899
|
+
value: { name: "Mutated head" } }], requestId: "v1-mutated-head",
|
|
900
|
+
tombstones: [], v: 1 })).rejects.toThrow(OhIntegrityError);
|
|
901
|
+
expect(counter).toEqual({ commits: 0, snapshotReads: 1 });
|
|
902
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
test("bounds a host-supplied working snapshot before capacity math", async () => {
|
|
906
|
+
const counter = { commits: 0, snapshotReads: 0 };
|
|
907
|
+
const value = await fixture({
|
|
908
|
+
wrapWorkingStore: (store) => hostileCapacityWorkingStore(store,
|
|
909
|
+
"oversize-snapshot", counter),
|
|
910
|
+
});
|
|
911
|
+
const head = await value.working.store.head();
|
|
912
|
+
await expect(value.agent.remember({ expectedHead: {
|
|
913
|
+
generation: head.generation, operationSha256: head.operationSha256,
|
|
914
|
+
}, puts: [{ dependencies: [], key: "entity:v1-oversize-snapshot", kind: "entity", v: 1,
|
|
915
|
+
value: { name: "Oversize snapshot" } }], requestId: "v1-oversize-snapshot",
|
|
916
|
+
tombstones: [], v: 1 })).rejects.toThrow("canonical byte bound");
|
|
917
|
+
expect(counter).toEqual({ commits: 0, snapshotReads: 1 });
|
|
918
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
test("rejects accessor-bearing working snapshots without invoking them", async () => {
|
|
922
|
+
const counter = { accessorReads: 0, commits: 0, snapshotReads: 0 };
|
|
923
|
+
const value = await fixture({
|
|
924
|
+
wrapWorkingStore: (store) => hostileCapacityWorkingStore(store,
|
|
925
|
+
"accessor-snapshot", counter),
|
|
926
|
+
});
|
|
927
|
+
const head = await value.working.store.head();
|
|
928
|
+
await expect(value.agent.remember({ expectedHead: {
|
|
929
|
+
generation: head.generation, operationSha256: head.operationSha256,
|
|
930
|
+
}, puts: [{ dependencies: [], key: "entity:v1-accessor-snapshot", kind: "entity", v: 1,
|
|
931
|
+
value: { name: "Accessor snapshot" } }], requestId: "v1-accessor-snapshot",
|
|
932
|
+
tombstones: [], v: 1 })).rejects.toThrow("non-data property");
|
|
933
|
+
expect(counter).toEqual({ accessorReads: 0, commits: 0, snapshotReads: 1 });
|
|
934
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
935
|
+
});
|
|
486
936
|
});
|
|
487
937
|
|
|
488
|
-
describe("
|
|
938
|
+
describe("parameterized and paginated Oh memory V2", () => {
|
|
489
939
|
test("binds host-declared lane and key constants and returns more than 256 value chunks in stable pages",
|
|
490
940
|
async () => {
|
|
491
941
|
const value = await fixtureV2();
|
|
@@ -553,7 +1003,7 @@ describe("experimental parameterized and paginated Oh memory V2", () => {
|
|
|
553
1003
|
await expect(value.agent.query({ bindings: {
|
|
554
1004
|
key: "entity:chunked", lane: "canonical",
|
|
555
1005
|
}, continuation: tampered, programId: "memory.value-chunks", v: 2 }))
|
|
556
|
-
.rejects.toThrow();
|
|
1006
|
+
.rejects.toThrow(OhMemoryContinuationError);
|
|
557
1007
|
const decoded = JSON.parse(Buffer.from(continuation, "base64url").toString("utf8")) as
|
|
558
1008
|
Record<string, unknown>;
|
|
559
1009
|
const noncanonical = Buffer.from(JSON.stringify(Object.fromEntries(
|
|
@@ -561,15 +1011,15 @@ describe("experimental parameterized and paginated Oh memory V2", () => {
|
|
|
561
1011
|
await expect(value.agent.query({ bindings: {
|
|
562
1012
|
key: "entity:chunked", lane: "canonical",
|
|
563
1013
|
}, continuation: noncanonical, programId: "memory.value-chunks", v: 2 }))
|
|
564
|
-
.rejects.
|
|
1014
|
+
.rejects.toMatchObject({ code: "memory-continuation", reason: "encoding" });
|
|
565
1015
|
await expect(value.agent.query({ bindings: {
|
|
566
1016
|
key: "entity:chunked", lane: "canonical",
|
|
567
1017
|
}, continuation, programId: "memory.value-chunks-alternate", v: 2 }))
|
|
568
|
-
.rejects.toThrow(
|
|
1018
|
+
.rejects.toThrow(OhMemoryContinuationError);
|
|
569
1019
|
await expect(value.agent.query({ bindings: {
|
|
570
1020
|
key: "entity:other", lane: "canonical",
|
|
571
1021
|
}, continuation, programId: "memory.value-chunks", v: 2 }))
|
|
572
|
-
.rejects.toThrow(
|
|
1022
|
+
.rejects.toThrow(OhMemoryContinuationError);
|
|
573
1023
|
|
|
574
1024
|
const head = await value.working.store.head();
|
|
575
1025
|
await value.agent.remember({ expectedHead: { generation: head.generation,
|
|
@@ -579,7 +1029,7 @@ describe("experimental parameterized and paginated Oh memory V2", () => {
|
|
|
579
1029
|
await expect(value.agent.query({ bindings: {
|
|
580
1030
|
key: "entity:chunked", lane: "canonical",
|
|
581
1031
|
}, continuation, programId: "memory.value-chunks", v: 2 }))
|
|
582
|
-
.rejects.
|
|
1032
|
+
.rejects.toMatchObject({ code: "memory-continuation", reason: "identity" });
|
|
583
1033
|
await value.canonical.store.close(); await value.working.store.close();
|
|
584
1034
|
});
|
|
585
1035
|
|
|
@@ -601,7 +1051,7 @@ describe("experimental parameterized and paginated Oh memory V2", () => {
|
|
|
601
1051
|
decoded.continuationSha256 = canonicalSha256(unsigned);
|
|
602
1052
|
const forged = Buffer.from(canonicalJson(decoded), "utf8").toString("base64url");
|
|
603
1053
|
await expect(value.agent.query({ ...request, continuation: forged }))
|
|
604
|
-
.rejects.
|
|
1054
|
+
.rejects.toMatchObject({ code: "memory-continuation", reason: "authentication" });
|
|
605
1055
|
|
|
606
1056
|
const replayOne = await value.agent.query({ ...request, continuation: first.continuation });
|
|
607
1057
|
const replayTwo = await value.agent.query({ ...request, continuation: first.continuation });
|
|
@@ -633,7 +1083,7 @@ describe("experimental parameterized and paginated Oh memory V2", () => {
|
|
|
633
1083
|
expect(otherKey.continuationSha256).toBe(keyed.continuationSha256);
|
|
634
1084
|
expect(otherKey.resultSha256).toBe(keyed.resultSha256);
|
|
635
1085
|
await expect(otherKeyAgent.query({ ...request, continuation: keyed.continuation }))
|
|
636
|
-
.rejects.toThrow(
|
|
1086
|
+
.rejects.toThrow(OhMemoryContinuationError);
|
|
637
1087
|
|
|
638
1088
|
const reconstructed = await value.createAgent(persistedHostKey);
|
|
639
1089
|
const resumed = await reconstructed.query({ ...request, continuation: keyed.continuation });
|
|
@@ -644,7 +1094,7 @@ describe("experimental parameterized and paginated Oh memory V2", () => {
|
|
|
644
1094
|
if (localFirst.continuation === null) throw new Error("Expected a local continuation.");
|
|
645
1095
|
const localTwo = await value.createAgent();
|
|
646
1096
|
await expect(localTwo.query({ ...request, continuation: localFirst.continuation }))
|
|
647
|
-
.rejects.toThrow(
|
|
1097
|
+
.rejects.toThrow(OhMemoryContinuationError);
|
|
648
1098
|
await expect(value.createAgent(new Uint8Array(
|
|
649
1099
|
OH_MEMORY_QUERY_LIMITS_V2.continuationKeyMinimumBytes - 1)))
|
|
650
1100
|
.rejects.toThrow("32 through 64 raw bytes");
|
|
@@ -672,7 +1122,12 @@ describe("experimental parameterized and paginated Oh memory V2", () => {
|
|
|
672
1122
|
counter.extractorInvocations = 0;
|
|
673
1123
|
counter.workingReads = 0;
|
|
674
1124
|
|
|
675
|
-
|
|
1125
|
+
let encodingFailure: unknown;
|
|
1126
|
+
try { await value.agent.query({ ...request, continuation: "!" }); }
|
|
1127
|
+
catch (error) { encodingFailure = error; }
|
|
1128
|
+
expect(encodingFailure).toBeInstanceOf(OhMemoryContinuationError);
|
|
1129
|
+
expect(encodingFailure).toMatchObject({ code: "memory-continuation", reason: "encoding" });
|
|
1130
|
+
expect(Object.getOwnPropertyDescriptor(encodingFailure, "reason")?.writable).toBe(false);
|
|
676
1131
|
const invalidMacPayload = JSON.parse(
|
|
677
1132
|
Buffer.from(first.continuation, "base64url").toString("utf8"),
|
|
678
1133
|
) as Record<string, unknown>;
|
|
@@ -680,18 +1135,50 @@ describe("experimental parameterized and paginated Oh memory V2", () => {
|
|
|
680
1135
|
=== "f".repeat(64) ? "e".repeat(64) : "f".repeat(64);
|
|
681
1136
|
const invalidMac = Buffer.from(canonicalJson(invalidMacPayload), "utf8").toString("base64url");
|
|
682
1137
|
await expect(value.agent.query({ ...request, continuation: invalidMac }))
|
|
683
|
-
.rejects.
|
|
1138
|
+
.rejects.toMatchObject({ code: "memory-continuation", reason: "authentication" });
|
|
684
1139
|
await expect(value.agent.query({ ...request, continuation: first.continuation,
|
|
685
1140
|
programId: "memory.value-chunks-alternate" }))
|
|
686
|
-
.rejects.
|
|
1141
|
+
.rejects.toMatchObject({ code: "memory-continuation", reason: "identity" });
|
|
687
1142
|
await expect(value.agent.query({ ...request,
|
|
688
1143
|
bindings: { key: "entity:other", lane: "canonical" },
|
|
689
1144
|
continuation: first.continuation }))
|
|
690
|
-
.rejects.toThrow(
|
|
1145
|
+
.rejects.toThrow(OhMemoryContinuationError);
|
|
691
1146
|
expect(counter).toEqual({ extractorInvocations: 0, workingReads: 0 });
|
|
692
1147
|
await value.canonical.store.close(); await value.working.store.close();
|
|
693
1148
|
});
|
|
694
1149
|
|
|
1150
|
+
test("does not relabel store failures as continuation failures", async () => {
|
|
1151
|
+
let failWorkingRead = false;
|
|
1152
|
+
const value = await fixtureV2(8, 2, 8 * 1024 * 1024, {
|
|
1153
|
+
chunkCount: 8,
|
|
1154
|
+
continuationKey: new Uint8Array(32).fill(6),
|
|
1155
|
+
wrapWorkingStore: (store) => new Proxy(store, {
|
|
1156
|
+
get(target, property) {
|
|
1157
|
+
const member = Reflect.get(target, property, target) as unknown;
|
|
1158
|
+
if (property === "head") return async () => {
|
|
1159
|
+
if (failWorkingRead) throw new OhIntegrityError("The working store failed verification.");
|
|
1160
|
+
return await target.head();
|
|
1161
|
+
};
|
|
1162
|
+
return typeof member === "function"
|
|
1163
|
+
? (...args: unknown[]) => Reflect.apply(member, target, args)
|
|
1164
|
+
: member;
|
|
1165
|
+
},
|
|
1166
|
+
}) as OhStoreV1,
|
|
1167
|
+
});
|
|
1168
|
+
const request = { bindings: { key: "entity:chunked", lane: "canonical" },
|
|
1169
|
+
continuation: null, programId: "memory.value-chunks", v: 2 as const };
|
|
1170
|
+
const first = await value.agent.query(request);
|
|
1171
|
+
if (first.continuation === null) throw new Error("Expected an issued continuation.");
|
|
1172
|
+
failWorkingRead = true;
|
|
1173
|
+
let thrown: unknown;
|
|
1174
|
+
try { await value.agent.query({ ...request, continuation: first.continuation }); }
|
|
1175
|
+
catch (error) { thrown = error; }
|
|
1176
|
+
expect(thrown).toBeInstanceOf(OhIntegrityError);
|
|
1177
|
+
expect(thrown).not.toBeInstanceOf(OhMemoryContinuationError);
|
|
1178
|
+
expect(thrown).toHaveProperty("message", "The working store failed verification.");
|
|
1179
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
1180
|
+
});
|
|
1181
|
+
|
|
695
1182
|
test("bounds and flattens V2 query input before canonical serialization", async () => {
|
|
696
1183
|
const value = await fixtureV2();
|
|
697
1184
|
const base = { continuation: null, programId: "memory.value-chunks", v: 2 as const };
|
|
@@ -712,7 +1199,7 @@ describe("experimental parameterized and paginated Oh memory V2", () => {
|
|
|
712
1199
|
) })).rejects.toThrow("canonical byte bound");
|
|
713
1200
|
await expect(value.agent.query({ ...base, bindings: {},
|
|
714
1201
|
continuation: "A".repeat(OH_MEMORY_QUERY_LIMITS_V2.continuationBytes + 1) }))
|
|
715
|
-
.rejects.toThrow(
|
|
1202
|
+
.rejects.toThrow(OhMemoryContinuationError);
|
|
716
1203
|
await value.canonical.store.close(); await value.working.store.close();
|
|
717
1204
|
});
|
|
718
1205
|
|
|
@@ -780,4 +1267,987 @@ describe("experimental parameterized and paginated Oh memory V2", () => {
|
|
|
780
1267
|
.rejects.toThrow("Invalid parameterized memory query");
|
|
781
1268
|
await value.canonical.store.close(); await value.working.store.close();
|
|
782
1269
|
});
|
|
1270
|
+
|
|
1271
|
+
test("rejects a V2 remember that would exceed working byte capacity before CAS", async () => {
|
|
1272
|
+
const largeName = "x".repeat(875_000);
|
|
1273
|
+
const snapshot = staticSnapshot(Array.from({ length: 38 }, (_, index) =>
|
|
1274
|
+
entity(`entity:v2-capacity-${index.toString().padStart(2, "0")}`, largeName)));
|
|
1275
|
+
expect(Buffer.byteLength(canonicalJson(snapshot), "utf8"))
|
|
1276
|
+
.toBeLessThanOrEqual(OH_MEMORY_LIMITS_V1.snapshotBytesPerLane);
|
|
1277
|
+
const counter = { commits: 0 };
|
|
1278
|
+
const value = await fixtureV2(400, 64, 8 * 1024 * 1024, {
|
|
1279
|
+
wrapWorkingStore: (store) => saturatedWorkingStore(store, snapshot, counter),
|
|
1280
|
+
});
|
|
1281
|
+
await expect(value.agent.remember({ expectedHead: {
|
|
1282
|
+
generation: snapshot.head.generation,
|
|
1283
|
+
operationSha256: snapshot.head.operationSha256,
|
|
1284
|
+
}, puts: [{ dependencies: [], key: "entity:v2-capacity-extra", kind: "entity", v: 1,
|
|
1285
|
+
value: { name: largeName } }], requestId: "v2-capacity-extra", tombstones: [], v: 1 }))
|
|
1286
|
+
.rejects.toThrow("working memory would exceed its snapshot byte bound");
|
|
1287
|
+
expect(counter.commits).toBe(0);
|
|
1288
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
test("lets the V2 store resolve an exact stale replay after a later write", async () => {
|
|
1292
|
+
const state = { rejectSnapshots: false, snapshotReads: 0 };
|
|
1293
|
+
const value = await fixtureV2(400, 64, 8 * 1024 * 1024, {
|
|
1294
|
+
wrapWorkingStore: (store) => replayProbeWorkingStore(store, state),
|
|
1295
|
+
});
|
|
1296
|
+
const initialHead = await value.working.store.head();
|
|
1297
|
+
const request = { expectedHead: { generation: initialHead.generation,
|
|
1298
|
+
operationSha256: initialHead.operationSha256 }, puts: [{ dependencies: [],
|
|
1299
|
+
key: "entity:v2-replay", kind: "entity", v: 1, value: { name: "Replay" } }],
|
|
1300
|
+
requestId: "v2-exact-replay", tombstones: [], v: 1 } as const;
|
|
1301
|
+
const receipt = await value.agent.remember(request);
|
|
1302
|
+
await put(value.working.store, "entity:v2-later", "Later", "v2_later_write");
|
|
1303
|
+
const snapshotReads = state.snapshotReads;
|
|
1304
|
+
state.rejectSnapshots = true;
|
|
1305
|
+
expect(await value.agent.remember(request)).toEqual(receipt);
|
|
1306
|
+
expect(state.snapshotReads).toBe(snapshotReads);
|
|
1307
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
1308
|
+
});
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1311
|
+
describe("stable host-bound Oh memory authority", () => {
|
|
1312
|
+
test("strictly parses prepared nominations and separates agent and host authority", async () => {
|
|
1313
|
+
const value = await authorityFixture();
|
|
1314
|
+
expect(Object.keys(value.authority).sort()).toEqual(["agent", "host"]);
|
|
1315
|
+
expect(Object.keys(value.authority.agent).sort()).toEqual(["explain", "nominate", "query", "remember"]);
|
|
1316
|
+
expect(Object.keys(value.authority.host).sort()).toEqual(["adoptNomination", "advanceCanonical"]);
|
|
1317
|
+
expect("host" in value.authority.agent).toBe(false);
|
|
1318
|
+
|
|
1319
|
+
const workingHead = await value.working.store.head();
|
|
1320
|
+
await value.authority.agent.remember({ expectedHead: { generation: workingHead.generation,
|
|
1321
|
+
operationSha256: workingHead.operationSha256 },
|
|
1322
|
+
puts: [{ dependencies: [], key: "entity:candidate", kind: "entity", v: 1,
|
|
1323
|
+
value: { name: "Candidate" } }], requestId: "remember_candidate",
|
|
1324
|
+
tombstones: [], v: 1 });
|
|
1325
|
+
const nomination = await value.authority.agent.nominate({ nominationId: "kb.review",
|
|
1326
|
+
roots: ["entity:candidate"], v: 1 });
|
|
1327
|
+
expect(parseOhMemoryNominationV1(nomination)).toEqual(nomination);
|
|
1328
|
+
expect(parseOhMemoryNominationV1({ ...nomination, extra: true })).toBeNull();
|
|
1329
|
+
expect(parseOhMemoryNominationV1({ ...nomination,
|
|
1330
|
+
nominationSha256: "f".repeat(64) })).toBeNull();
|
|
1331
|
+
expect(parseOhMemoryNominationV1({ ...nomination,
|
|
1332
|
+
source: { ...nomination.source, bindingSha256: "f".repeat(64) } })).toBeNull();
|
|
1333
|
+
expect(Object.isFrozen(parseOhMemoryNominationV1(nomination)?.closure.records)).toBe(true);
|
|
1334
|
+
await expect(value.authority.host.adoptNomination({
|
|
1335
|
+
expectedCanonicalHead: value.initialCanonicalHead, extra: true, nomination, v: 1 }))
|
|
1336
|
+
.rejects.toThrow("Invalid memory adoption request");
|
|
1337
|
+
await expect(value.authority.host.advanceCanonical({
|
|
1338
|
+
expectedHead: value.initialCanonicalHead, extra: true,
|
|
1339
|
+
nextHead: value.initialCanonicalHead, v: 1 }))
|
|
1340
|
+
.rejects.toThrow("Invalid canonical memory advance request");
|
|
1341
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1344
|
+
test("refuses an adopted operation above the host canonical byte bound before commit", async () => {
|
|
1345
|
+
const value = await authorityFixture({ maximumCanonicalOperationBytes: 1 });
|
|
1346
|
+
const workingHead = await value.working.store.head();
|
|
1347
|
+
await value.authority.agent.remember({ expectedHead: {
|
|
1348
|
+
generation: workingHead.generation,
|
|
1349
|
+
operationSha256: workingHead.operationSha256,
|
|
1350
|
+
}, puts: [{ dependencies: [], key: "entity:bounded-adoption", kind: "entity", v: 1,
|
|
1351
|
+
value: { name: "Bounded adoption" } }], requestId: "remember_bounded_adoption",
|
|
1352
|
+
tombstones: [], v: 1 });
|
|
1353
|
+
const nomination = await value.authority.agent.nominate({ nominationId: "kb.review",
|
|
1354
|
+
roots: ["entity:bounded-adoption"], v: 1 });
|
|
1355
|
+
await expect(value.authority.host.adoptNomination({
|
|
1356
|
+
expectedCanonicalHead: value.initialCanonicalHead,
|
|
1357
|
+
nomination,
|
|
1358
|
+
v: 1,
|
|
1359
|
+
})).rejects.toThrow(OhOperationSizeError);
|
|
1360
|
+
expect(await value.canonical.store.head()).toEqual(value.initialCanonicalHead);
|
|
1361
|
+
expect((await value.canonical.store.snapshot()).records.map(({ key }) => key))
|
|
1362
|
+
.not.toContain("entity:bounded-adoption");
|
|
1363
|
+
await value.canonical.store.close();
|
|
1364
|
+
await value.working.store.close();
|
|
1365
|
+
});
|
|
1366
|
+
|
|
1367
|
+
test("descriptor-detaches stable unknown inputs and never executes accessors or proxies", async () => {
|
|
1368
|
+
const value = await authorityFixture();
|
|
1369
|
+
const workingHead = await value.working.store.head();
|
|
1370
|
+
let accessorReads = 0;
|
|
1371
|
+
const hostilePut = { dependencies: [], key: "entity:hostile", kind: "entity", v: 1 } as
|
|
1372
|
+
Record<PropertyKey, unknown>;
|
|
1373
|
+
Object.defineProperty(hostilePut, "value", { enumerable: true,
|
|
1374
|
+
get() { accessorReads += 1; throw new Error("must not execute"); } });
|
|
1375
|
+
await expect(value.authority.agent.remember({
|
|
1376
|
+
expectedHead: { generation: workingHead.generation,
|
|
1377
|
+
operationSha256: workingHead.operationSha256 },
|
|
1378
|
+
puts: [hostilePut], requestId: "remember_accessor", tombstones: [], v: 1,
|
|
1379
|
+
})).rejects.toThrow("non-data property");
|
|
1380
|
+
expect(accessorReads).toBe(0);
|
|
1381
|
+
expect(await value.working.store.head()).toEqual(workingHead);
|
|
1382
|
+
|
|
1383
|
+
let proxyDescriptorReads = 0;
|
|
1384
|
+
const proxyBundle = new Proxy({
|
|
1385
|
+
expectedHead: { generation: workingHead.generation,
|
|
1386
|
+
operationSha256: workingHead.operationSha256 },
|
|
1387
|
+
puts: [{ dependencies: [], key: "entity:proxy", kind: "entity", v: 1,
|
|
1388
|
+
value: { name: "Proxy" } }], requestId: "remember_proxy", tombstones: [], v: 1,
|
|
1389
|
+
}, { getOwnPropertyDescriptor(target, property) {
|
|
1390
|
+
proxyDescriptorReads += 1;
|
|
1391
|
+
return Reflect.getOwnPropertyDescriptor(target, property);
|
|
1392
|
+
} });
|
|
1393
|
+
await expect(value.authority.agent.remember(proxyBundle)).rejects.toThrow("contains a proxy");
|
|
1394
|
+
expect(proxyDescriptorReads).toBe(0);
|
|
1395
|
+
expect(await value.working.store.head()).toEqual(workingHead);
|
|
1396
|
+
|
|
1397
|
+
const hostileBindings = new Proxy({}, {});
|
|
1398
|
+
await expect(value.authority.agent.query({ bindings: hostileBindings, continuation: null,
|
|
1399
|
+
programId: "memory.visible-records", v: 2 })).rejects.toThrow("JSON primitives");
|
|
1400
|
+
const explainPromise = value.authority.agent.explain(new Proxy({}, {}));
|
|
1401
|
+
expect(explainPromise).toBeInstanceOf(Promise);
|
|
1402
|
+
await expect(explainPromise).rejects.toThrow("contains a proxy");
|
|
1403
|
+
|
|
1404
|
+
await value.authority.agent.remember({ expectedHead: { generation: workingHead.generation,
|
|
1405
|
+
operationSha256: workingHead.operationSha256 },
|
|
1406
|
+
puts: [{ dependencies: [], key: "entity:valid", kind: "entity", v: 1,
|
|
1407
|
+
value: { name: "Valid" } }], requestId: "remember_valid_after_hostile",
|
|
1408
|
+
tombstones: [], v: 1 });
|
|
1409
|
+
const nomination = await value.authority.agent.nominate({ nominationId: "kb.review",
|
|
1410
|
+
roots: ["entity:valid"], v: 1 });
|
|
1411
|
+
const hostileNomination = { ...nomination } as Record<PropertyKey, unknown>;
|
|
1412
|
+
Object.defineProperty(hostileNomination, "source", { enumerable: true,
|
|
1413
|
+
get() { accessorReads += 1; throw new Error("must not execute"); } });
|
|
1414
|
+
expect(parseOhMemoryNominationV1(hostileNomination)).toBeNull();
|
|
1415
|
+
expect(accessorReads).toBe(0);
|
|
1416
|
+
const hostileAdoption = { expectedCanonicalHead: value.initialCanonicalHead, v: 1 } as
|
|
1417
|
+
Record<PropertyKey, unknown>;
|
|
1418
|
+
Object.defineProperty(hostileAdoption, "nomination", { enumerable: true,
|
|
1419
|
+
get() { accessorReads += 1; throw new Error("must not execute"); } });
|
|
1420
|
+
const adoptionPromise = value.authority.host.adoptNomination(hostileAdoption);
|
|
1421
|
+
expect(adoptionPromise).toBeInstanceOf(Promise);
|
|
1422
|
+
await expect(adoptionPromise).rejects.toThrow("non-data property");
|
|
1423
|
+
expect(accessorReads).toBe(0);
|
|
1424
|
+
const priorRecord = (await value.canonical.store.snapshot()).records[0]!;
|
|
1425
|
+
const hostileReplacement = { key: priorRecord.key, v: 1 } as Record<PropertyKey, unknown>;
|
|
1426
|
+
Object.defineProperty(hostileReplacement, "expectedPriorRecordSha256", { enumerable: true,
|
|
1427
|
+
get() { accessorReads += 1; throw new Error("must not execute"); } });
|
|
1428
|
+
await expect(value.authority.host.adoptNomination({
|
|
1429
|
+
expectedCanonicalHead: value.initialCanonicalHead, nomination,
|
|
1430
|
+
replacements: [hostileReplacement], v: 1,
|
|
1431
|
+
})).rejects.toThrow("non-data property");
|
|
1432
|
+
expect(accessorReads).toBe(0);
|
|
1433
|
+
expect(await value.canonical.store.head()).toEqual(value.initialCanonicalHead);
|
|
1434
|
+
let deeplyNested: unknown = "f".repeat(64);
|
|
1435
|
+
for (let depth = 0; depth <= OH_MEMORY_LIMITS_V1.detachedCanonicalDepth; depth += 1) {
|
|
1436
|
+
deeplyNested = { value: deeplyNested };
|
|
1437
|
+
}
|
|
1438
|
+
await expect(value.authority.host.adoptNomination({
|
|
1439
|
+
expectedCanonicalHead: value.initialCanonicalHead, nomination,
|
|
1440
|
+
replacements: [{ expectedPriorRecordSha256: deeplyNested,
|
|
1441
|
+
key: priorRecord.key, v: 1 }], v: 1,
|
|
1442
|
+
})).rejects.toThrow("canonical nesting depth bound");
|
|
1443
|
+
const tooBroad = new Array(OH_MEMORY_LIMITS_V1.detachedCanonicalBreadth + 1);
|
|
1444
|
+
await expect(value.authority.host.adoptNomination({
|
|
1445
|
+
expectedCanonicalHead: value.initialCanonicalHead, nomination,
|
|
1446
|
+
replacements: tooBroad, v: 1,
|
|
1447
|
+
})).rejects.toThrow("canonical breadth bound");
|
|
1448
|
+
expect(await value.canonical.store.head()).toEqual(value.initialCanonicalHead);
|
|
1449
|
+
const symbolAdvance = { expectedHead: value.initialCanonicalHead,
|
|
1450
|
+
nextHead: value.initialCanonicalHead, v: 1 } as Record<PropertyKey, unknown>;
|
|
1451
|
+
symbolAdvance[Symbol("extra")] = true;
|
|
1452
|
+
await expect(value.authority.host.advanceCanonical(symbolAdvance))
|
|
1453
|
+
.rejects.toThrow("symbol property");
|
|
1454
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1457
|
+
test("adopts a re-exported closure once and makes a concurrent replay already present", async () => {
|
|
1458
|
+
const value = await authorityFixture();
|
|
1459
|
+
const workingHead = await value.working.store.head();
|
|
1460
|
+
await value.authority.agent.remember({ expectedHead: { generation: workingHead.generation,
|
|
1461
|
+
operationSha256: workingHead.operationSha256 },
|
|
1462
|
+
puts: [{ dependencies: [], key: "entity:parent", kind: "entity", v: 1,
|
|
1463
|
+
value: { name: "Parent" } }, { dependencies: ["entity:parent"],
|
|
1464
|
+
key: "entity:child", kind: "entity", v: 1, value: { name: "Child" } }],
|
|
1465
|
+
requestId: "remember_adoption_closure", tombstones: [], v: 1 });
|
|
1466
|
+
const nomination = await value.authority.agent.nominate({ nominationId: "kb.review",
|
|
1467
|
+
roots: ["entity:child"], v: 1 });
|
|
1468
|
+
const request = { expectedCanonicalHead: value.initialCanonicalHead, nomination, v: 1 } as const;
|
|
1469
|
+
const [first, second] = await Promise.all([
|
|
1470
|
+
value.authority.host.adoptNomination(request),
|
|
1471
|
+
value.authority.host.adoptNomination(request),
|
|
1472
|
+
]);
|
|
1473
|
+
expect([first.status, second.status]).toEqual(["adopted", "already-present"]);
|
|
1474
|
+
expect(first.actorId).toBe("test.memory-reviewer");
|
|
1475
|
+
expect(first.operationSha256).not.toBeNull();
|
|
1476
|
+
expect(second.operationSha256).toBeNull();
|
|
1477
|
+
expect(Object.isFrozen(first.head)).toBe(true);
|
|
1478
|
+
expect(first.receiptSha256).toBe(canonicalSha256({ actorId: first.actorId,
|
|
1479
|
+
authorityId: first.authorityId, bindingSha256: first.bindingSha256, head: first.head,
|
|
1480
|
+
nominationSha256: first.nominationSha256, operationSha256: first.operationSha256,
|
|
1481
|
+
priorHead: first.priorHead, status: first.status, v: 1 }));
|
|
1482
|
+
const canonical = await value.canonical.store.snapshot();
|
|
1483
|
+
expect(canonical.records.map(({ key }) => key)).toEqual([
|
|
1484
|
+
"entity:canonical", "entity:child", "entity:parent",
|
|
1485
|
+
]);
|
|
1486
|
+
const visible = await value.authority.agent.query({ bindings: {}, continuation: null,
|
|
1487
|
+
programId: "memory.visible-records", v: 2 });
|
|
1488
|
+
expect(visible.identity.canonical.head).toEqual(first.head);
|
|
1489
|
+
expect(visible.rows.some(({ values }) => values[0] === "canonical"
|
|
1490
|
+
&& values[1] === "entity:child")).toBe(true);
|
|
1491
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
1492
|
+
});
|
|
1493
|
+
|
|
1494
|
+
test("records one physical adoption under two cross-instance CAS attempts and fails closed",
|
|
1495
|
+
async () => {
|
|
1496
|
+
const sameRendezvous = twoPartyCanonicalCommitRendezvous();
|
|
1497
|
+
let samePrimaryCanonical!: OhStoreV1;
|
|
1498
|
+
const same = await authorityFixture({ canonicalStore: (store) => {
|
|
1499
|
+
samePrimaryCanonical = sameRendezvous.wrap(store, 0);
|
|
1500
|
+
return samePrimaryCanonical;
|
|
1501
|
+
} });
|
|
1502
|
+
const samePeerCanonical = sameRendezvous.wrap(same.canonical.store, 1);
|
|
1503
|
+
expect(samePrimaryCanonical).not.toBe(samePeerCanonical);
|
|
1504
|
+
const samePeer = await peerAuthority(samePeerCanonical, same.working.store,
|
|
1505
|
+
same.initialCanonicalHead);
|
|
1506
|
+
const sameWorkingHead = await same.working.store.head();
|
|
1507
|
+
await same.authority.agent.remember({ expectedHead: {
|
|
1508
|
+
generation: sameWorkingHead.generation,
|
|
1509
|
+
operationSha256: sameWorkingHead.operationSha256,
|
|
1510
|
+
}, puts: [{ dependencies: [], key: "entity:cross-instance-same", kind: "entity", v: 1,
|
|
1511
|
+
value: { name: "Same nomination" } }], requestId: "remember_cross_instance_same",
|
|
1512
|
+
tombstones: [], v: 1 });
|
|
1513
|
+
const sameNomination = await same.authority.agent.nominate({ nominationId: "kb.review",
|
|
1514
|
+
roots: ["entity:cross-instance-same"], v: 1 });
|
|
1515
|
+
const sameRequest = { expectedCanonicalHead: same.initialCanonicalHead,
|
|
1516
|
+
nomination: sameNomination, v: 1 } as const;
|
|
1517
|
+
const sameReceipts = await Promise.all([
|
|
1518
|
+
same.authority.host.adoptNomination(sameRequest),
|
|
1519
|
+
samePeer.host.adoptNomination(sameRequest),
|
|
1520
|
+
]);
|
|
1521
|
+
// These are separate authority/store-capability instances around one durable SQLite store.
|
|
1522
|
+
// The rendezvous proves that both planned from the prior head and each invoked its distinct
|
|
1523
|
+
// commit capability once before either CAS reached that store. The [1, 1] below is two bounded
|
|
1524
|
+
// CAS attempts; the change history separately proves their one-physical-operation outcome.
|
|
1525
|
+
// This does not simulate independent processes or remote transport.
|
|
1526
|
+
expect(sameRendezvous.commitCalls).toEqual([1, 1]);
|
|
1527
|
+
const sameHead = await same.canonical.store.head();
|
|
1528
|
+
expect(sameReceipts.every(({ head }) => canonicalJson(head) === canonicalJson(sameHead)))
|
|
1529
|
+
.toBe(true);
|
|
1530
|
+
// An exact operation replay may return the one durable operation to both callers; a store
|
|
1531
|
+
// that reports the losing CAS instead lets the authority reconcile it as already present.
|
|
1532
|
+
expect(sameReceipts.every(({ status }) => status === "adopted"
|
|
1533
|
+
|| status === "already-present")).toBe(true);
|
|
1534
|
+
expect(sameReceipts.some(({ status }) => status === "adopted")).toBe(true);
|
|
1535
|
+
expect((await same.canonical.store.changesSince(same.initialCanonicalHead,
|
|
1536
|
+
{ through: sameHead })).operations).toHaveLength(1);
|
|
1537
|
+
for (const authority of [same.authority, samePeer]) {
|
|
1538
|
+
const page = await authority.agent.query({ bindings: {}, continuation: null,
|
|
1539
|
+
programId: "memory.visible-records", v: 2 });
|
|
1540
|
+
expect(page.identity.canonical.head).toEqual(sameHead);
|
|
1541
|
+
}
|
|
1542
|
+
await same.canonical.store.close(); await same.working.store.close();
|
|
1543
|
+
|
|
1544
|
+
const original = entity("entity:canonical", "Canonical");
|
|
1545
|
+
const differentRendezvous = twoPartyCanonicalCommitRendezvous();
|
|
1546
|
+
let differentPrimaryCanonical!: OhStoreV1;
|
|
1547
|
+
const different = await authorityFixture({ canonicalRecords: [original],
|
|
1548
|
+
canonicalStore: (store) => {
|
|
1549
|
+
differentPrimaryCanonical = differentRendezvous.wrap(store, 0);
|
|
1550
|
+
return differentPrimaryCanonical;
|
|
1551
|
+
} });
|
|
1552
|
+
const differentPeerCanonical = differentRendezvous.wrap(different.canonical.store, 1);
|
|
1553
|
+
expect(differentPrimaryCanonical).not.toBe(differentPeerCanonical);
|
|
1554
|
+
const differentPeer = await peerAuthority(differentPeerCanonical, different.working.store,
|
|
1555
|
+
different.initialCanonicalHead);
|
|
1556
|
+
const firstWorkingHead = await different.working.store.head();
|
|
1557
|
+
await different.authority.agent.remember({ expectedHead: {
|
|
1558
|
+
generation: firstWorkingHead.generation,
|
|
1559
|
+
operationSha256: firstWorkingHead.operationSha256,
|
|
1560
|
+
}, puts: [{ dependencies: [], key: original.key, kind: "entity", v: 1,
|
|
1561
|
+
value: { name: "Replacement A" } }], requestId: "remember_cross_instance_a",
|
|
1562
|
+
tombstones: [], v: 1 });
|
|
1563
|
+
const nominationA = await different.authority.agent.nominate({ nominationId: "kb.review",
|
|
1564
|
+
roots: [original.key], v: 1 });
|
|
1565
|
+
const secondWorkingHead = await different.working.store.head();
|
|
1566
|
+
await different.authority.agent.remember({ expectedHead: {
|
|
1567
|
+
generation: secondWorkingHead.generation,
|
|
1568
|
+
operationSha256: secondWorkingHead.operationSha256,
|
|
1569
|
+
}, puts: [{ dependencies: [], key: original.key, kind: "entity", v: 1,
|
|
1570
|
+
value: { name: "Replacement B" } }], requestId: "remember_cross_instance_b",
|
|
1571
|
+
tombstones: [], v: 1 });
|
|
1572
|
+
const nominationB = await different.authority.agent.nominate({ nominationId: "kb.review",
|
|
1573
|
+
roots: [original.key], v: 1 });
|
|
1574
|
+
const contenders = [{ authority: different.authority, nomination: nominationA },
|
|
1575
|
+
{ authority: differentPeer, nomination: nominationB }];
|
|
1576
|
+
const outcomes = await Promise.allSettled(contenders.map(({ authority, nomination }) =>
|
|
1577
|
+
authority.host.adoptNomination({ expectedCanonicalHead: different.initialCanonicalHead,
|
|
1578
|
+
nomination, replacements: [{ expectedPriorRecordSha256: original.recordSha256,
|
|
1579
|
+
key: original.key, v: 1 }], v: 1 })));
|
|
1580
|
+
const fulfilled = outcomes.flatMap((outcome, index) => outcome.status === "fulfilled"
|
|
1581
|
+
? [{ index, receipt: outcome.value }] : []);
|
|
1582
|
+
const rejected = outcomes.flatMap((outcome, index) => outcome.status === "rejected"
|
|
1583
|
+
? [{ index, reason: outcome.reason as unknown }] : []);
|
|
1584
|
+
expect(differentRendezvous.commitCalls).toEqual([1, 1]);
|
|
1585
|
+
expect(fulfilled).toHaveLength(1);
|
|
1586
|
+
expect(fulfilled[0]?.receipt.status).toBe("adopted");
|
|
1587
|
+
expect(rejected).toHaveLength(1);
|
|
1588
|
+
expect(rejected[0]?.reason).toBeInstanceOf(OhMemoryAdoptionConflictError);
|
|
1589
|
+
const differentHead = await different.canonical.store.head();
|
|
1590
|
+
const winningNomination = contenders[fulfilled[0]!.index]!.nomination;
|
|
1591
|
+
const winningDigest = winningNomination.closure.records
|
|
1592
|
+
.find(({ key }) => key === original.key)!.recordSha256;
|
|
1593
|
+
expect((await different.canonical.store.snapshot()).records
|
|
1594
|
+
.find(({ key }) => key === original.key)?.recordSha256).toBe(winningDigest);
|
|
1595
|
+
expect((await different.canonical.store.changesSince(different.initialCanonicalHead,
|
|
1596
|
+
{ through: differentHead })).operations).toHaveLength(1);
|
|
1597
|
+
expect((rejected[0]!.reason as OhMemoryAdoptionConflictError).conflict.actualHead)
|
|
1598
|
+
.toEqual(differentHead);
|
|
1599
|
+
const losingPage = await contenders[rejected[0]!.index]!.authority.agent.query({
|
|
1600
|
+
bindings: {}, continuation: null, programId: "memory.visible-records", v: 2,
|
|
1601
|
+
});
|
|
1602
|
+
expect(losingPage.identity.canonical.head).toEqual(different.initialCanonicalHead);
|
|
1603
|
+
const winningPage = await contenders[fulfilled[0]!.index]!.authority.agent.query({
|
|
1604
|
+
bindings: {}, continuation: null, programId: "memory.visible-records", v: 2,
|
|
1605
|
+
});
|
|
1606
|
+
expect(winningPage.identity.canonical.head).toEqual(differentHead);
|
|
1607
|
+
await different.canonical.store.close(); await different.working.store.close();
|
|
1608
|
+
});
|
|
1609
|
+
|
|
1610
|
+
test("replaces canonical records only with exact host-supplied prior digests", async () => {
|
|
1611
|
+
const original = entity("entity:canonical", "Canonical");
|
|
1612
|
+
const equal = entity("entity:equal", "Already equal");
|
|
1613
|
+
const value = await authorityFixture({ canonicalRecords: [original, equal] });
|
|
1614
|
+
const workingHead = await value.working.store.head();
|
|
1615
|
+
await value.authority.agent.remember({ expectedHead: { generation: workingHead.generation,
|
|
1616
|
+
operationSha256: workingHead.operationSha256 }, puts: [{ dependencies: [],
|
|
1617
|
+
key: original.key, kind: "entity", v: 1, value: { name: "Reviewed replacement" } },
|
|
1618
|
+
{ dependencies: [], key: equal.key, kind: "entity", v: 1,
|
|
1619
|
+
value: { name: "Already equal" } },
|
|
1620
|
+
{ dependencies: [], key: "entity:new", kind: "entity", v: 1,
|
|
1621
|
+
value: { name: "New alongside replacement" } }], requestId: "remember_replacement",
|
|
1622
|
+
tombstones: [], v: 1 });
|
|
1623
|
+
const nomination = await value.authority.agent.nominate({ nominationId: "kb.review",
|
|
1624
|
+
roots: [original.key, equal.key, "entity:new"], v: 1 });
|
|
1625
|
+
const request = { expectedCanonicalHead: value.initialCanonicalHead, nomination,
|
|
1626
|
+
replacements: [{ expectedPriorRecordSha256: original.recordSha256,
|
|
1627
|
+
key: original.key, v: 1 }], v: 1 } as const;
|
|
1628
|
+
|
|
1629
|
+
const bogusEqualClaim = canonicalSha256({ kind: "test.bogus-equal-prior", v: 1 });
|
|
1630
|
+
await expect(value.authority.host.adoptNomination({ ...request, replacements: [
|
|
1631
|
+
...request.replacements,
|
|
1632
|
+
{ expectedPriorRecordSha256: bogusEqualClaim, key: equal.key, v: 1 as const },
|
|
1633
|
+
] })).rejects.toMatchObject({ conflict: { conflicts: [{
|
|
1634
|
+
canonicalRecordSha256: equal.recordSha256, key: equal.key,
|
|
1635
|
+
nominatedRecordSha256: equal.recordSha256, v: 1,
|
|
1636
|
+
}] } });
|
|
1637
|
+
expect(await value.canonical.store.head()).toEqual(value.initialCanonicalHead);
|
|
1638
|
+
expect((await value.canonical.store.snapshot()).records.map(({ key }) => key))
|
|
1639
|
+
.toEqual(["entity:canonical", "entity:equal"]);
|
|
1640
|
+
|
|
1641
|
+
const adopted = await value.authority.host.adoptNomination(request);
|
|
1642
|
+
expect(adopted.status).toBe("adopted");
|
|
1643
|
+
expect(adopted.priorHead).toEqual(value.initialCanonicalHead);
|
|
1644
|
+
const snapshot = await value.canonical.store.snapshot();
|
|
1645
|
+
expect(snapshot.records.map(({ key }) => key)).toEqual([
|
|
1646
|
+
"entity:canonical", "entity:equal", "entity:new",
|
|
1647
|
+
]);
|
|
1648
|
+
expect(snapshot.records.find(({ key }) => key === original.key)?.recordSha256)
|
|
1649
|
+
.toBe(nomination.closure.records.find(({ key }) => key === original.key)?.recordSha256);
|
|
1650
|
+
const page = await value.canonical.store.changesSince({
|
|
1651
|
+
operationSha256: value.initialCanonicalHead.operationSha256,
|
|
1652
|
+
sequence: value.initialCanonicalHead.sequence,
|
|
1653
|
+
}, { through: adopted.head });
|
|
1654
|
+
expect(page.operations).toHaveLength(1);
|
|
1655
|
+
expect(page.operations[0]?.operationId).toBe(`memory_adopt_${canonicalSha256({
|
|
1656
|
+
actorId: "test.memory-reviewer",
|
|
1657
|
+
bindingSha256: value.canonical.store.binding.bindingSha256,
|
|
1658
|
+
nominationSha256: nomination.nominationSha256,
|
|
1659
|
+
priorHead: value.initialCanonicalHead,
|
|
1660
|
+
v: 1,
|
|
1661
|
+
}).slice(0, 48)}`);
|
|
1662
|
+
expect(page.operations[0]?.changes.map((change) => change.kind === "put"
|
|
1663
|
+
? change.record.key : change.key)).toEqual(["entity:canonical", "entity:new"]);
|
|
1664
|
+
|
|
1665
|
+
const replay = await value.authority.host.adoptNomination(request);
|
|
1666
|
+
expect(replay.status).toBe("already-present");
|
|
1667
|
+
expect(replay.operationSha256).toBeNull();
|
|
1668
|
+
expect(replay.head).toEqual(adopted.head);
|
|
1669
|
+
expect(await value.canonical.store.head()).toEqual(adopted.head);
|
|
1670
|
+
await expect(value.authority.host.adoptNomination({ ...request, replacements: [
|
|
1671
|
+
...request.replacements,
|
|
1672
|
+
{ expectedPriorRecordSha256: bogusEqualClaim, key: equal.key, v: 1 as const },
|
|
1673
|
+
] })).rejects.toMatchObject({ conflict: { conflicts: [{
|
|
1674
|
+
canonicalRecordSha256: equal.recordSha256, key: equal.key,
|
|
1675
|
+
nominatedRecordSha256: equal.recordSha256, v: 1,
|
|
1676
|
+
}] } });
|
|
1677
|
+
expect(await value.canonical.store.head()).toEqual(adopted.head);
|
|
1678
|
+
|
|
1679
|
+
await value.canonical.store.commit({ actorId: "test.canonical-reviser",
|
|
1680
|
+
changes: [{ kind: "put", record: original, v: 1 }], expectedHead: adopted.head,
|
|
1681
|
+
operationId: "op_restore_prior_canonical" });
|
|
1682
|
+
const restoredHead = await value.canonical.store.head();
|
|
1683
|
+
await value.authority.host.advanceCanonical({ expectedHead: adopted.head,
|
|
1684
|
+
nextHead: restoredHead, v: 1 });
|
|
1685
|
+
const readopted = await value.authority.host.adoptNomination({ ...request,
|
|
1686
|
+
expectedCanonicalHead: restoredHead });
|
|
1687
|
+
expect(readopted.status).toBe("adopted");
|
|
1688
|
+
expect(readopted.operationSha256).not.toBe(adopted.operationSha256);
|
|
1689
|
+
expect((await value.canonical.store.snapshot()).records
|
|
1690
|
+
.find(({ key }) => key === original.key)?.recordSha256)
|
|
1691
|
+
.toBe(nomination.closure.records.find(({ key }) => key === original.key)?.recordSha256);
|
|
1692
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
1693
|
+
});
|
|
1694
|
+
|
|
1695
|
+
test("fails a replacement batch atomically when any prior digest is absent or wrong", async () => {
|
|
1696
|
+
const originals = [entity("entity:first-replacement", "First canonical"),
|
|
1697
|
+
entity("entity:second-replacement", "Second canonical")];
|
|
1698
|
+
const value = await authorityFixture({ canonicalRecords: originals });
|
|
1699
|
+
const workingHead = await value.working.store.head();
|
|
1700
|
+
await value.authority.agent.remember({ expectedHead: { generation: workingHead.generation,
|
|
1701
|
+
operationSha256: workingHead.operationSha256 }, puts: [
|
|
1702
|
+
{ dependencies: [], key: originals[0]!.key, kind: "entity", v: 1,
|
|
1703
|
+
value: { name: "First replacement" } },
|
|
1704
|
+
{ dependencies: [], key: originals[1]!.key, kind: "entity", v: 1,
|
|
1705
|
+
value: { name: "Second replacement" } },
|
|
1706
|
+
{ dependencies: [], key: "entity:would-be-added", kind: "entity", v: 1,
|
|
1707
|
+
value: { name: "New record" } },
|
|
1708
|
+
], requestId: "remember_atomic_replacements", tombstones: [], v: 1 });
|
|
1709
|
+
const nomination = await value.authority.agent.nominate({ nominationId: "kb.review",
|
|
1710
|
+
roots: [originals[0]!.key, originals[1]!.key, "entity:would-be-added"], v: 1 });
|
|
1711
|
+
const wrongDigest = canonicalSha256({ kind: "test.wrong-prior-record", v: 1 });
|
|
1712
|
+
let thrown: unknown;
|
|
1713
|
+
try {
|
|
1714
|
+
await value.authority.host.adoptNomination({
|
|
1715
|
+
expectedCanonicalHead: value.initialCanonicalHead, nomination, replacements: [
|
|
1716
|
+
{ expectedPriorRecordSha256: originals[0]!.recordSha256,
|
|
1717
|
+
key: originals[0]!.key, v: 1 },
|
|
1718
|
+
{ expectedPriorRecordSha256: wrongDigest, key: originals[1]!.key, v: 1 },
|
|
1719
|
+
], v: 1,
|
|
1720
|
+
});
|
|
1721
|
+
} catch (error) { thrown = error; }
|
|
1722
|
+
expect(thrown).toBeInstanceOf(OhMemoryAdoptionConflictError);
|
|
1723
|
+
expect((thrown as OhMemoryAdoptionConflictError).conflict.conflicts).toEqual([{
|
|
1724
|
+
canonicalRecordSha256: originals[1]!.recordSha256,
|
|
1725
|
+
key: originals[1]!.key,
|
|
1726
|
+
nominatedRecordSha256: nomination.closure.records
|
|
1727
|
+
.find(({ key }) => key === originals[1]!.key)!.recordSha256,
|
|
1728
|
+
v: 1,
|
|
1729
|
+
}]);
|
|
1730
|
+
expect(await value.canonical.store.head()).toEqual(value.initialCanonicalHead);
|
|
1731
|
+
expect((await value.canonical.store.snapshot()).records).toEqual(originals);
|
|
1732
|
+
|
|
1733
|
+
const absent = nomination.closure.records.find(({ key }) => key === "entity:would-be-added")!;
|
|
1734
|
+
await expect(value.authority.host.adoptNomination({
|
|
1735
|
+
expectedCanonicalHead: value.initialCanonicalHead, nomination,
|
|
1736
|
+
replacements: [
|
|
1737
|
+
{ expectedPriorRecordSha256: originals[0]!.recordSha256,
|
|
1738
|
+
key: originals[0]!.key, v: 1 },
|
|
1739
|
+
{ expectedPriorRecordSha256: originals[1]!.recordSha256,
|
|
1740
|
+
key: originals[1]!.key, v: 1 },
|
|
1741
|
+
{ expectedPriorRecordSha256: wrongDigest, key: absent.key, v: 1 },
|
|
1742
|
+
], v: 1,
|
|
1743
|
+
})).rejects.toMatchObject({ conflict: { conflicts: [{
|
|
1744
|
+
canonicalRecordSha256: null, key: absent.key,
|
|
1745
|
+
nominatedRecordSha256: absent.recordSha256, v: 1,
|
|
1746
|
+
}] } });
|
|
1747
|
+
expect(await value.canonical.store.head()).toEqual(value.initialCanonicalHead);
|
|
1748
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
1749
|
+
});
|
|
1750
|
+
|
|
1751
|
+
test("rejects stale replacement proof and bounds exact replacement claims", async () => {
|
|
1752
|
+
const original = entity("entity:canonical", "Canonical");
|
|
1753
|
+
const value = await authorityFixture({ canonicalRecords: [original] });
|
|
1754
|
+
const workingHead = await value.working.store.head();
|
|
1755
|
+
await value.authority.agent.remember({ expectedHead: { generation: workingHead.generation,
|
|
1756
|
+
operationSha256: workingHead.operationSha256 }, puts: [{ dependencies: [], key: original.key,
|
|
1757
|
+
kind: "entity", v: 1, value: { name: "Nominated replacement" } }],
|
|
1758
|
+
requestId: "remember_stale_replacement", tombstones: [], v: 1 });
|
|
1759
|
+
const nomination = await value.authority.agent.nominate({ nominationId: "kb.review",
|
|
1760
|
+
roots: [original.key], v: 1 });
|
|
1761
|
+
const current = entity(original.key, "Newer canonical");
|
|
1762
|
+
await value.canonical.store.commit({ actorId: "test.canonical-writer",
|
|
1763
|
+
changes: [{ kind: "put", record: current, v: 1 }],
|
|
1764
|
+
expectedHead: value.initialCanonicalHead, operationId: "op_newer_canonical" });
|
|
1765
|
+
const currentHead = await value.canonical.store.head();
|
|
1766
|
+
await value.authority.host.advanceCanonical({ expectedHead: value.initialCanonicalHead,
|
|
1767
|
+
nextHead: currentHead, v: 1 });
|
|
1768
|
+
await expect(value.authority.host.adoptNomination({ expectedCanonicalHead: currentHead,
|
|
1769
|
+
nomination, replacements: [{ expectedPriorRecordSha256: original.recordSha256,
|
|
1770
|
+
key: original.key, v: 1 }], v: 1 })).rejects.toMatchObject({ conflict: {
|
|
1771
|
+
actualHead: currentHead, conflicts: [{ canonicalRecordSha256: current.recordSha256,
|
|
1772
|
+
key: original.key, v: 1 }],
|
|
1773
|
+
} });
|
|
1774
|
+
await expect(value.authority.host.adoptNomination({
|
|
1775
|
+
expectedCanonicalHead: value.initialCanonicalHead, nomination,
|
|
1776
|
+
replacements: [{ expectedPriorRecordSha256: current.recordSha256,
|
|
1777
|
+
key: original.key, v: 1 }], v: 1,
|
|
1778
|
+
})).rejects.toMatchObject({ conflict: {
|
|
1779
|
+
actualHead: currentHead, expectedHead: value.initialCanonicalHead,
|
|
1780
|
+
} });
|
|
1781
|
+
expect(await value.canonical.store.head()).toEqual(currentHead);
|
|
1782
|
+
|
|
1783
|
+
const tooMany = Array.from({ length: OH_MEMORY_AUTHORITY_LIMITS_V1.adoptionReplacements + 1 },
|
|
1784
|
+
() => ({ expectedPriorRecordSha256: current.recordSha256, key: original.key, v: 1 }));
|
|
1785
|
+
await expect(value.authority.host.adoptNomination({ expectedCanonicalHead: currentHead,
|
|
1786
|
+
nomination, replacements: tooMany, v: 1 })).rejects
|
|
1787
|
+
.toThrow("Invalid memory adoption replacements");
|
|
1788
|
+
await expect(value.authority.host.adoptNomination({ expectedCanonicalHead: currentHead,
|
|
1789
|
+
nomination, replacements: [
|
|
1790
|
+
{ expectedPriorRecordSha256: current.recordSha256, key: original.key, v: 1 },
|
|
1791
|
+
{ expectedPriorRecordSha256: current.recordSha256, key: original.key, v: 1 },
|
|
1792
|
+
], v: 1 })).rejects.toThrow("replacement keys must be unique");
|
|
1793
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
1794
|
+
});
|
|
1795
|
+
|
|
1796
|
+
test("aborts adoption atomically with bounded, complete conflict evidence", async () => {
|
|
1797
|
+
const count = OH_MEMORY_AUTHORITY_LIMITS_V1.reportedAdoptionConflicts + 2;
|
|
1798
|
+
const canonicalRecords = Array.from({ length: count }, (_, index) =>
|
|
1799
|
+
entity(`entity:conflict-${index.toString().padStart(3, "0")}`, `Canonical ${index}`));
|
|
1800
|
+
const value = await authorityFixture({ canonicalRecords });
|
|
1801
|
+
const workingHead = await value.working.store.head();
|
|
1802
|
+
const puts = canonicalRecords.map((record, index) => ({ dependencies: [], key: record.key,
|
|
1803
|
+
kind: "entity" as const, v: 1 as const, value: { name: `Working ${index}` } }));
|
|
1804
|
+
puts.push({ dependencies: [], key: "entity:would-be-inserted", kind: "entity", v: 1,
|
|
1805
|
+
value: { name: "Absent" } });
|
|
1806
|
+
await value.authority.agent.remember({ expectedHead: { generation: workingHead.generation,
|
|
1807
|
+
operationSha256: workingHead.operationSha256 }, puts,
|
|
1808
|
+
requestId: "remember_conflicts", tombstones: [], v: 1 });
|
|
1809
|
+
const roots = puts.map(({ key }) => key);
|
|
1810
|
+
const nomination = await value.authority.agent.nominate({ nominationId: "kb.review", roots, v: 1 });
|
|
1811
|
+
let thrown: unknown;
|
|
1812
|
+
try {
|
|
1813
|
+
await value.authority.host.adoptNomination({
|
|
1814
|
+
expectedCanonicalHead: value.initialCanonicalHead, nomination, v: 1 });
|
|
1815
|
+
} catch (error) { thrown = error; }
|
|
1816
|
+
expect(thrown).toBeInstanceOf(OhMemoryAdoptionConflictError);
|
|
1817
|
+
const conflict = (thrown as OhMemoryAdoptionConflictError).conflict;
|
|
1818
|
+
expect(conflict.totalConflicts).toBe(count);
|
|
1819
|
+
expect(conflict.reportedConflicts).toBe(OH_MEMORY_AUTHORITY_LIMITS_V1.reportedAdoptionConflicts);
|
|
1820
|
+
expect(conflict.truncated).toBe(true);
|
|
1821
|
+
expect(conflict.conflicts.map(({ key }) => key)).toEqual(canonicalRecords
|
|
1822
|
+
.slice(0, OH_MEMORY_AUTHORITY_LIMITS_V1.reportedAdoptionConflicts).map(({ key }) => key));
|
|
1823
|
+
const nominatedByKey = new Map(nomination.closure.records.map((record) => [record.key, record]));
|
|
1824
|
+
const complete = canonicalRecords.map((record) => ({
|
|
1825
|
+
canonicalRecordSha256: record.recordSha256,
|
|
1826
|
+
key: record.key,
|
|
1827
|
+
nominatedRecordSha256: nominatedByKey.get(record.key)!.recordSha256,
|
|
1828
|
+
v: 1 as const,
|
|
1829
|
+
}));
|
|
1830
|
+
expect(conflict.conflictsSha256).toBe(canonicalSha256({ conflicts: complete, v: 1 }));
|
|
1831
|
+
expect(Object.isFrozen(conflict.conflicts)).toBe(true);
|
|
1832
|
+
expect(Object.getOwnPropertyDescriptor(thrown, "conflict")?.writable).toBe(false);
|
|
1833
|
+
expect((await value.canonical.store.snapshot()).records
|
|
1834
|
+
.some(({ key }) => key === "entity:would-be-inserted")).toBe(false);
|
|
1835
|
+
expect(await value.canonical.store.head()).toEqual(value.initialCanonicalHead);
|
|
1836
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
1837
|
+
});
|
|
1838
|
+
|
|
1839
|
+
test("reconciles a duplicate adoption against the later physical head", async () => {
|
|
1840
|
+
const value = await authorityFixture();
|
|
1841
|
+
let returnPinnedHeadOnce = true;
|
|
1842
|
+
const laggingStore = new Proxy(value.canonical.store, {
|
|
1843
|
+
get(target, property) {
|
|
1844
|
+
const member = Reflect.get(target, property, target) as unknown;
|
|
1845
|
+
if (property === "head") {
|
|
1846
|
+
return async () => {
|
|
1847
|
+
if (returnPinnedHeadOnce) {
|
|
1848
|
+
returnPinnedHeadOnce = false;
|
|
1849
|
+
return value.initialCanonicalHead;
|
|
1850
|
+
}
|
|
1851
|
+
return await target.head();
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
return typeof member === "function"
|
|
1855
|
+
? (...args: unknown[]) => Reflect.apply(member, target, args)
|
|
1856
|
+
: member;
|
|
1857
|
+
},
|
|
1858
|
+
}) as OhStoreV1;
|
|
1859
|
+
const replaying = await createOhMemoryAuthorityV1({
|
|
1860
|
+
actorId: "test.memory-agent", adoptionActorId: "test.memory-reviewer",
|
|
1861
|
+
canonical: { authorityId: "authority.canonical",
|
|
1862
|
+
expectedBindingSha256: laggingStore.binding.bindingSha256,
|
|
1863
|
+
expectedHead: value.initialCanonicalHead, store: laggingStore },
|
|
1864
|
+
continuationKey: Uint8Array.from({ length: 32 }, (_, index) => index),
|
|
1865
|
+
nominationRoutes: [{ destinationPurpose: "kb.review", nominationId: "kb.review" }],
|
|
1866
|
+
programs: [stableVisibleProgram()],
|
|
1867
|
+
working: { authorityId: "authority.working", codecs: entityCodecs(),
|
|
1868
|
+
expectedBindingSha256: value.working.store.binding.bindingSha256,
|
|
1869
|
+
store: value.working.store },
|
|
1870
|
+
});
|
|
1871
|
+
const workingHead = await value.working.store.head();
|
|
1872
|
+
await value.authority.agent.remember({ expectedHead: { generation: workingHead.generation,
|
|
1873
|
+
operationSha256: workingHead.operationSha256 },
|
|
1874
|
+
puts: [{ dependencies: [], key: "entity:replayed", kind: "entity", v: 1,
|
|
1875
|
+
value: { name: "Nominated" } }], requestId: "remember_replayed",
|
|
1876
|
+
tombstones: [], v: 1 });
|
|
1877
|
+
const nomination = await value.authority.agent.nominate({ nominationId: "kb.review",
|
|
1878
|
+
roots: ["entity:replayed"], v: 1 });
|
|
1879
|
+
const adopted = await value.authority.host.adoptNomination({
|
|
1880
|
+
expectedCanonicalHead: value.initialCanonicalHead, nomination, v: 1 });
|
|
1881
|
+
const overwrittenRecord = entity("entity:replayed", "Overwritten");
|
|
1882
|
+
await value.canonical.store.commit({ actorId: "test.overwriter",
|
|
1883
|
+
changes: [{ kind: "put", record: overwrittenRecord, v: 1 }],
|
|
1884
|
+
expectedHead: adopted.head, operationId: "op_overwrite_adopted_memory" });
|
|
1885
|
+
const physicalHead = await value.canonical.store.head();
|
|
1886
|
+
let thrown: unknown;
|
|
1887
|
+
try {
|
|
1888
|
+
await replaying.host.adoptNomination({ expectedCanonicalHead: value.initialCanonicalHead,
|
|
1889
|
+
nomination, v: 1 });
|
|
1890
|
+
} catch (error) { thrown = error; }
|
|
1891
|
+
expect(thrown).toBeInstanceOf(OhMemoryAdoptionConflictError);
|
|
1892
|
+
expect((thrown as OhMemoryAdoptionConflictError).conflict.actualHead).toEqual(physicalHead);
|
|
1893
|
+
expect((thrown as OhMemoryAdoptionConflictError).conflict.conflicts[0]).toMatchObject({
|
|
1894
|
+
canonicalRecordSha256: overwrittenRecord.recordSha256,
|
|
1895
|
+
key: "entity:replayed",
|
|
1896
|
+
nominatedRecordSha256: nomination.closure.records[0]!.recordSha256,
|
|
1897
|
+
});
|
|
1898
|
+
const stillPinned = await replaying.agent.query({ bindings: {}, continuation: null,
|
|
1899
|
+
programId: "memory.visible-records", v: 2 });
|
|
1900
|
+
expect(stillPinned.identity.canonical.head).toEqual(value.initialCanonicalHead);
|
|
1901
|
+
expect(stillPinned.identity.canonical.head).not.toEqual(adopted.head);
|
|
1902
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
1903
|
+
});
|
|
1904
|
+
|
|
1905
|
+
test("rejects a nomination when the working authority cannot reproduce its exact bytes", async () => {
|
|
1906
|
+
const value = await authorityFixture();
|
|
1907
|
+
const workingHead = await value.working.store.head();
|
|
1908
|
+
await value.authority.agent.remember({ expectedHead: { generation: workingHead.generation,
|
|
1909
|
+
operationSha256: workingHead.operationSha256 },
|
|
1910
|
+
puts: [{ dependencies: [], key: "entity:first", kind: "entity", v: 1,
|
|
1911
|
+
value: { name: "First" } }, { dependencies: [], key: "entity:second",
|
|
1912
|
+
kind: "entity", v: 1, value: { name: "Second" } }],
|
|
1913
|
+
requestId: "remember_reexport", tombstones: [], v: 1 });
|
|
1914
|
+
const nomination = await value.authority.agent.nominate({ nominationId: "kb.review",
|
|
1915
|
+
roots: ["entity:first"], v: 1 });
|
|
1916
|
+
const sourceHead = await value.working.store.head();
|
|
1917
|
+
const substitute = await value.working.store.exportDependencyClosure({ head: sourceHead,
|
|
1918
|
+
roots: ["entity:second"] });
|
|
1919
|
+
(value.working.store as unknown as { exportDependencyClosure: () => Promise<typeof substitute> })
|
|
1920
|
+
.exportDependencyClosure = async () => substitute;
|
|
1921
|
+
await expect(value.authority.host.adoptNomination({
|
|
1922
|
+
expectedCanonicalHead: value.initialCanonicalHead, nomination, v: 1 }))
|
|
1923
|
+
.rejects.toThrow("did not re-export the nominated closure exactly");
|
|
1924
|
+
expect(await value.canonical.store.head()).toEqual(value.initialCanonicalHead);
|
|
1925
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
1926
|
+
});
|
|
1927
|
+
|
|
1928
|
+
test("performs one adoption CAS and reports a racing canonical write without retrying", async () => {
|
|
1929
|
+
const value = await authorityFixture();
|
|
1930
|
+
const workingHead = await value.working.store.head();
|
|
1931
|
+
await value.authority.agent.remember({ expectedHead: { generation: workingHead.generation,
|
|
1932
|
+
operationSha256: workingHead.operationSha256 },
|
|
1933
|
+
puts: [{ dependencies: [], key: "entity:raced-candidate", kind: "entity", v: 1,
|
|
1934
|
+
value: { name: "Candidate" } }], requestId: "remember_raced_candidate",
|
|
1935
|
+
tombstones: [], v: 1 });
|
|
1936
|
+
const nomination = await value.authority.agent.nominate({ nominationId: "kb.review",
|
|
1937
|
+
roots: ["entity:raced-candidate"], v: 1 });
|
|
1938
|
+
const originalCommit = value.canonical.store.commit.bind(value.canonical.store);
|
|
1939
|
+
let authorityCommitCalls = 0;
|
|
1940
|
+
value.canonical.store.commit = async (input) => {
|
|
1941
|
+
authorityCommitCalls += 1;
|
|
1942
|
+
await originalCommit({ actorId: "test.competing-writer",
|
|
1943
|
+
changes: [{ kind: "put", record: entity("entity:competing", "Competing"), v: 1 }],
|
|
1944
|
+
expectedHead: input.expectedHead, operationId: "op_competing_write" });
|
|
1945
|
+
return await originalCommit(input);
|
|
1946
|
+
};
|
|
1947
|
+
let thrown: unknown;
|
|
1948
|
+
try {
|
|
1949
|
+
await value.authority.host.adoptNomination({
|
|
1950
|
+
expectedCanonicalHead: value.initialCanonicalHead, nomination, v: 1 });
|
|
1951
|
+
} catch (error) { thrown = error; }
|
|
1952
|
+
expect(authorityCommitCalls).toBe(1);
|
|
1953
|
+
expect(thrown).toBeInstanceOf(OhMemoryAdoptionConflictError);
|
|
1954
|
+
expect((thrown as OhMemoryAdoptionConflictError).conflict.conflicts).toEqual([{
|
|
1955
|
+
canonicalRecordSha256: null,
|
|
1956
|
+
key: "entity:raced-candidate",
|
|
1957
|
+
nominatedRecordSha256: nomination.closure.records[0]!.recordSha256,
|
|
1958
|
+
v: 1,
|
|
1959
|
+
}]);
|
|
1960
|
+
const snapshot = await value.canonical.store.snapshot();
|
|
1961
|
+
expect(snapshot.records.map(({ key }) => key)).toEqual(["entity:canonical", "entity:competing"]);
|
|
1962
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
1963
|
+
});
|
|
1964
|
+
|
|
1965
|
+
test("recovers an uncertain successful adoption exactly and rejects a later mismatch", async () => {
|
|
1966
|
+
let throwAfterCommit = true;
|
|
1967
|
+
let authorityCommitCalls = 0;
|
|
1968
|
+
const value = await authorityFixture({
|
|
1969
|
+
canonicalStore: (store) => new Proxy(store, {
|
|
1970
|
+
get(target, property) {
|
|
1971
|
+
const member = Reflect.get(target, property, target) as unknown;
|
|
1972
|
+
if (property === "commit") {
|
|
1973
|
+
return async (...args: unknown[]) => {
|
|
1974
|
+
authorityCommitCalls += 1;
|
|
1975
|
+
const operation = await Reflect.apply(member as (...values: unknown[]) => unknown,
|
|
1976
|
+
target, args);
|
|
1977
|
+
if (throwAfterCommit) {
|
|
1978
|
+
throwAfterCommit = false;
|
|
1979
|
+
throw new Error("simulated transport loss after canonical effect");
|
|
1980
|
+
}
|
|
1981
|
+
return operation;
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
return typeof member === "function"
|
|
1985
|
+
? (...args: unknown[]) => Reflect.apply(member, target, args)
|
|
1986
|
+
: member;
|
|
1987
|
+
},
|
|
1988
|
+
}) as OhStoreV1,
|
|
1989
|
+
});
|
|
1990
|
+
const workingHead = await value.working.store.head();
|
|
1991
|
+
await value.authority.agent.remember({ expectedHead: {
|
|
1992
|
+
generation: workingHead.generation,
|
|
1993
|
+
operationSha256: workingHead.operationSha256,
|
|
1994
|
+
}, puts: [{ dependencies: [], key: "entity:uncertain-adoption", kind: "entity", v: 1,
|
|
1995
|
+
value: { name: "Adopted despite lost response" } }], requestId: "remember_uncertain_adoption",
|
|
1996
|
+
tombstones: [], v: 1 });
|
|
1997
|
+
const nomination = await value.authority.agent.nominate({ nominationId: "kb.review",
|
|
1998
|
+
roots: ["entity:uncertain-adoption"], v: 1 });
|
|
1999
|
+
const request = { expectedCanonicalHead: value.initialCanonicalHead, nomination, v: 1 } as const;
|
|
2000
|
+
|
|
2001
|
+
await expect(value.authority.host.adoptNomination(request))
|
|
2002
|
+
.rejects.toThrow("simulated transport loss after canonical effect");
|
|
2003
|
+
expect(authorityCommitCalls).toBe(1);
|
|
2004
|
+
const effectedHead = await value.canonical.store.head();
|
|
2005
|
+
expect(effectedHead).not.toEqual(value.initialCanonicalHead);
|
|
2006
|
+
expect((await value.canonical.store.snapshot()).records
|
|
2007
|
+
.find(({ key }) => key === "entity:uncertain-adoption")?.recordSha256)
|
|
2008
|
+
.toBe(nomination.closure.records[0]?.recordSha256);
|
|
2009
|
+
const stillPinned = await value.authority.agent.query({ bindings: {}, continuation: null,
|
|
2010
|
+
programId: "memory.visible-records", v: 2 });
|
|
2011
|
+
expect(stillPinned.identity.canonical.head).toEqual(value.initialCanonicalHead);
|
|
2012
|
+
|
|
2013
|
+
const recovered = await value.authority.host.adoptNomination(request);
|
|
2014
|
+
expect(authorityCommitCalls).toBe(1);
|
|
2015
|
+
expect(recovered).toMatchObject({ head: effectedHead, operationSha256: null,
|
|
2016
|
+
priorHead: value.initialCanonicalHead, status: "already-present" });
|
|
2017
|
+
expect((await value.canonical.store.changesSince(value.initialCanonicalHead,
|
|
2018
|
+
{ through: effectedHead })).operations).toHaveLength(1);
|
|
2019
|
+
const recoveredPage = await value.authority.agent.query({ bindings: {}, continuation: null,
|
|
2020
|
+
programId: "memory.visible-records", v: 2 });
|
|
2021
|
+
expect(recoveredPage.identity.canonical.head).toEqual(effectedHead);
|
|
2022
|
+
|
|
2023
|
+
const overwritten = entity("entity:uncertain-adoption", "Later conflicting value");
|
|
2024
|
+
await value.canonical.store.commit({ actorId: "test.later-writer",
|
|
2025
|
+
changes: [{ kind: "put", record: overwritten, v: 1 }], expectedHead: effectedHead,
|
|
2026
|
+
operationId: "op_later_uncertain_mismatch" });
|
|
2027
|
+
const mismatchedHead = await value.canonical.store.head();
|
|
2028
|
+
let thrown: unknown;
|
|
2029
|
+
try { await value.authority.host.adoptNomination(request); }
|
|
2030
|
+
catch (error) { thrown = error; }
|
|
2031
|
+
expect(authorityCommitCalls).toBe(1);
|
|
2032
|
+
expect(thrown).toBeInstanceOf(OhMemoryAdoptionConflictError);
|
|
2033
|
+
expect((thrown as OhMemoryAdoptionConflictError).conflict).toMatchObject({
|
|
2034
|
+
actualHead: mismatchedHead,
|
|
2035
|
+
conflicts: [{ canonicalRecordSha256: overwritten.recordSha256,
|
|
2036
|
+
key: "entity:uncertain-adoption",
|
|
2037
|
+
nominatedRecordSha256: nomination.closure.records[0]?.recordSha256, v: 1 }],
|
|
2038
|
+
expectedHead: value.initialCanonicalHead,
|
|
2039
|
+
});
|
|
2040
|
+
const pinnedAfterMismatch = await value.authority.agent.query({ bindings: {}, continuation: null,
|
|
2041
|
+
programId: "memory.visible-records", v: 2 });
|
|
2042
|
+
expect(pinnedAfterMismatch.identity.canonical.head).toEqual(effectedHead);
|
|
2043
|
+
expect(pinnedAfterMismatch.identity.canonical.head).not.toEqual(mismatchedHead);
|
|
2044
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
2045
|
+
});
|
|
2046
|
+
|
|
2047
|
+
test("captures the old canonical pin for in-flight queries and retains their explanations", async () => {
|
|
2048
|
+
let shouldBlock = false;
|
|
2049
|
+
let releaseSnapshot!: () => void;
|
|
2050
|
+
let enteredSnapshot!: () => void;
|
|
2051
|
+
const released = new Promise<void>((resolve) => { releaseSnapshot = resolve; });
|
|
2052
|
+
const entered = new Promise<void>((resolve) => { enteredSnapshot = resolve; });
|
|
2053
|
+
const wrapWorkingStore = (store: OhStoreV1): OhStoreV1 => new Proxy(store, {
|
|
2054
|
+
get(target, property) {
|
|
2055
|
+
const member = Reflect.get(target, property, target) as unknown;
|
|
2056
|
+
if (typeof member !== "function") return member;
|
|
2057
|
+
if (property !== "snapshot") {
|
|
2058
|
+
return (...args: unknown[]) => Reflect.apply(member, target, args);
|
|
2059
|
+
}
|
|
2060
|
+
return async (...args: unknown[]) => {
|
|
2061
|
+
if (shouldBlock) {
|
|
2062
|
+
shouldBlock = false;
|
|
2063
|
+
enteredSnapshot();
|
|
2064
|
+
await released;
|
|
2065
|
+
}
|
|
2066
|
+
return await Reflect.apply(member, target, args);
|
|
2067
|
+
};
|
|
2068
|
+
},
|
|
2069
|
+
}) as OhStoreV1;
|
|
2070
|
+
const value = await authorityFixture({ canonicalRecords: [
|
|
2071
|
+
entity("entity:first", "First"), entity("entity:second", "Second"),
|
|
2072
|
+
], pageSize: 1, workingStore: wrapWorkingStore });
|
|
2073
|
+
shouldBlock = true;
|
|
2074
|
+
const pending = value.authority.agent.query({ bindings: {}, continuation: null,
|
|
2075
|
+
programId: "memory.visible-records", v: 2 });
|
|
2076
|
+
await entered;
|
|
2077
|
+
await value.canonical.store.commit({ actorId: "test.canonical-writer",
|
|
2078
|
+
changes: [{ kind: "put", record: entity("entity:third", "Third"), v: 1 }],
|
|
2079
|
+
expectedHead: value.initialCanonicalHead, operationId: "op_canonical_rollover" });
|
|
2080
|
+
const nextHead = await value.canonical.store.head();
|
|
2081
|
+
const advance = await value.authority.host.advanceCanonical({
|
|
2082
|
+
expectedHead: value.initialCanonicalHead, nextHead, v: 1 });
|
|
2083
|
+
expect(advance.status).toBe("advanced");
|
|
2084
|
+
releaseSnapshot();
|
|
2085
|
+
const oldPage = await pending;
|
|
2086
|
+
expect(oldPage.identity.canonical.head).toEqual(value.initialCanonicalHead);
|
|
2087
|
+
const explanation = await value.authority.agent.explain({ pageRow: 0,
|
|
2088
|
+
resultSha256: oldPage.resultSha256, token: oldPage.explainCapability.token, v: 2 });
|
|
2089
|
+
expect(explanation.resultSha256).toBe(oldPage.resultSha256);
|
|
2090
|
+
await expect(value.authority.agent.query({ bindings: {},
|
|
2091
|
+
continuation: oldPage.continuation, programId: "memory.visible-records", v: 2 }))
|
|
2092
|
+
.rejects.toThrow("does not match this exact source and projection identity");
|
|
2093
|
+
const newPage = await value.authority.agent.query({ bindings: {}, continuation: null,
|
|
2094
|
+
programId: "memory.visible-records", v: 2 });
|
|
2095
|
+
expect(newPage.identity.canonical.head).toEqual(nextHead);
|
|
2096
|
+
const unchanged = await value.authority.host.advanceCanonical({
|
|
2097
|
+
expectedHead: nextHead, nextHead, v: 1 });
|
|
2098
|
+
expect(unchanged.status).toBe("unchanged");
|
|
2099
|
+
await expect(value.authority.host.advanceCanonical({ expectedHead: value.initialCanonicalHead,
|
|
2100
|
+
nextHead, v: 1 })).rejects.toThrow(OhConflictError);
|
|
2101
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
2102
|
+
});
|
|
2103
|
+
|
|
2104
|
+
test("bounds and exactly validates canonical descendant proofs before rollover", async () => {
|
|
2105
|
+
const value = await authorityFixture();
|
|
2106
|
+
await value.canonical.store.commit({ actorId: "test.canonical-writer",
|
|
2107
|
+
changes: [{ kind: "put", record: entity("entity:next", "Next"), v: 1 }],
|
|
2108
|
+
expectedHead: value.initialCanonicalHead, operationId: "op_advance_validation" });
|
|
2109
|
+
const nextHead = await value.canonical.store.head();
|
|
2110
|
+
const originalChangesSince = value.canonical.store.changesSince.bind(value.canonical.store);
|
|
2111
|
+
let accessorReads = 0;
|
|
2112
|
+
value.canonical.store.changesSince = async (from, options) => {
|
|
2113
|
+
const page = await originalChangesSince(from, options);
|
|
2114
|
+
const hostile = { ...page } as Record<PropertyKey, unknown>;
|
|
2115
|
+
Object.defineProperty(hostile, "operations", { enumerable: true,
|
|
2116
|
+
get() { accessorReads += 1; throw new Error("must not execute"); } });
|
|
2117
|
+
return hostile as never;
|
|
2118
|
+
};
|
|
2119
|
+
await expect(value.authority.host.advanceCanonical({
|
|
2120
|
+
expectedHead: value.initialCanonicalHead, nextHead, v: 1 }))
|
|
2121
|
+
.rejects.toThrow("non-data property");
|
|
2122
|
+
expect(accessorReads).toBe(0);
|
|
2123
|
+
|
|
2124
|
+
value.canonical.store.changesSince = async (from) => ({
|
|
2125
|
+
from, hasMore: true, operations: [], through: nextHead, to: from, v: 1,
|
|
2126
|
+
});
|
|
2127
|
+
await expect(value.authority.host.advanceCanonical({
|
|
2128
|
+
expectedHead: value.initialCanonicalHead, nextHead, v: 1 }))
|
|
2129
|
+
.rejects.toThrow("did not prove the requested descendant");
|
|
2130
|
+
|
|
2131
|
+
value.canonical.store.changesSince = originalChangesSince;
|
|
2132
|
+
const wrongFullHead: OhHeadV1 = { ...nextHead,
|
|
2133
|
+
graphRevisionSha256: canonicalSha256({ kind: "test.wrong-full-head", v: 1 }) };
|
|
2134
|
+
await expect(value.authority.host.advanceCanonical({
|
|
2135
|
+
expectedHead: value.initialCanonicalHead, nextHead: wrongFullHead, v: 1 }))
|
|
2136
|
+
.rejects.toThrow("pinned bounds");
|
|
2137
|
+
|
|
2138
|
+
let feedCalls = 0;
|
|
2139
|
+
value.canonical.store.changesSince = async (...args) => {
|
|
2140
|
+
feedCalls += 1;
|
|
2141
|
+
return await originalChangesSince(...args);
|
|
2142
|
+
};
|
|
2143
|
+
const fork: OhHeadV1 = { ...value.initialCanonicalHead,
|
|
2144
|
+
operationSha256: canonicalSha256({ kind: "test.fork", v: 1 }) };
|
|
2145
|
+
await expect(value.authority.host.advanceCanonical({
|
|
2146
|
+
expectedHead: value.initialCanonicalHead, nextHead: fork, v: 1 }))
|
|
2147
|
+
.rejects.toThrow("not a descendant");
|
|
2148
|
+
const rollback: OhHeadV1 = { generation: 0, graphRevisionSha256: null,
|
|
2149
|
+
operationSha256: null, recordsSha256: canonicalSha256([]), sequence: 0, v: 1 };
|
|
2150
|
+
await expect(value.authority.host.advanceCanonical({
|
|
2151
|
+
expectedHead: value.initialCanonicalHead, nextHead: rollback, v: 1 }))
|
|
2152
|
+
.rejects.toThrow("not a descendant");
|
|
2153
|
+
const distantSequence = value.initialCanonicalHead.sequence
|
|
2154
|
+
+ OH_MEMORY_AUTHORITY_LIMITS_V1.canonicalAdvanceOperations + 1;
|
|
2155
|
+
const tooDistant: OhHeadV1 = { generation: distantSequence,
|
|
2156
|
+
graphRevisionSha256: canonicalSha256({ kind: "test.distant-graph", v: 1 }),
|
|
2157
|
+
operationSha256: canonicalSha256({ kind: "test.distant-operation", v: 1 }),
|
|
2158
|
+
recordsSha256: canonicalSha256({ kind: "test.distant-records", v: 1 }),
|
|
2159
|
+
sequence: distantSequence, v: 1 };
|
|
2160
|
+
await expect(value.authority.host.advanceCanonical({
|
|
2161
|
+
expectedHead: value.initialCanonicalHead, nextHead: tooDistant, v: 1 }))
|
|
2162
|
+
.rejects.toThrow("advance in host-reviewed chunks");
|
|
2163
|
+
expect(feedCalls).toBe(0);
|
|
2164
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
2165
|
+
});
|
|
2166
|
+
|
|
2167
|
+
test("shares explanation eviction and monotonic clock state across canonical generations", async () => {
|
|
2168
|
+
const value = await authorityFixture();
|
|
2169
|
+
const oldPages = [];
|
|
2170
|
+
for (let index = 0; index < OH_MEMORY_LIMITS_V1.explainCapabilities - 1; index += 1) {
|
|
2171
|
+
oldPages.push(await value.authority.agent.query({ bindings: {}, continuation: null,
|
|
2172
|
+
programId: "memory.visible-records", v: 2 }));
|
|
2173
|
+
}
|
|
2174
|
+
await value.canonical.store.commit({ actorId: "test.cache-writer",
|
|
2175
|
+
changes: [{ kind: "put", record: entity("entity:cache-next", "Cache next"), v: 1 }],
|
|
2176
|
+
expectedHead: value.initialCanonicalHead, operationId: "op_cache_rollover" });
|
|
2177
|
+
const nextHead = await value.canonical.store.head();
|
|
2178
|
+
await value.authority.host.advanceCanonical({ expectedHead: value.initialCanonicalHead,
|
|
2179
|
+
nextHead, v: 1 });
|
|
2180
|
+
await value.authority.agent.query({ bindings: {}, continuation: null,
|
|
2181
|
+
programId: "memory.visible-records", v: 2 });
|
|
2182
|
+
await value.authority.agent.query({ bindings: {}, continuation: null,
|
|
2183
|
+
programId: "memory.visible-records", v: 2 });
|
|
2184
|
+
const evicted = oldPages[0]!;
|
|
2185
|
+
await expect(value.authority.agent.explain({ pageRow: 0,
|
|
2186
|
+
resultSha256: evicted.resultSha256, token: evicted.explainCapability.token, v: 2 }))
|
|
2187
|
+
.rejects.toThrow("absent, expired, or misbound");
|
|
2188
|
+
const retained = oldPages[1]!;
|
|
2189
|
+
expect((await value.authority.agent.explain({ pageRow: 0,
|
|
2190
|
+
resultSha256: retained.resultSha256,
|
|
2191
|
+
token: retained.explainCapability.token, v: 2 })).resultSha256)
|
|
2192
|
+
.toBe(retained.resultSha256);
|
|
2193
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
2194
|
+
|
|
2195
|
+
let monotonic = 100;
|
|
2196
|
+
const clocked = await authorityFixture({ monotonicNow: () => monotonic,
|
|
2197
|
+
now: () => new Date("2026-08-29T12:00:00.000Z") });
|
|
2198
|
+
await clocked.authority.agent.query({ bindings: {}, continuation: null,
|
|
2199
|
+
programId: "memory.visible-records", v: 2 });
|
|
2200
|
+
await clocked.canonical.store.commit({ actorId: "test.clock-writer",
|
|
2201
|
+
changes: [{ kind: "put", record: entity("entity:clock-next", "Clock next"), v: 1 }],
|
|
2202
|
+
expectedHead: clocked.initialCanonicalHead, operationId: "op_clock_rollover" });
|
|
2203
|
+
const clockHead = await clocked.canonical.store.head();
|
|
2204
|
+
await clocked.authority.host.advanceCanonical({ expectedHead: clocked.initialCanonicalHead,
|
|
2205
|
+
nextHead: clockHead, v: 1 });
|
|
2206
|
+
monotonic = 50;
|
|
2207
|
+
await expect(clocked.authority.agent.query({ bindings: {}, continuation: null,
|
|
2208
|
+
programId: "memory.visible-records", v: 2 })).rejects.toThrow("monotonic clock regressed");
|
|
2209
|
+
await clocked.canonical.store.close(); await clocked.working.store.close();
|
|
2210
|
+
});
|
|
2211
|
+
|
|
2212
|
+
test("rejects record-count and byte-capacity adoption before the only CAS", async () => {
|
|
2213
|
+
const fullRecords = Array.from({ length: OH_MEMORY_LIMITS_V1.maximumRecordsPerLane },
|
|
2214
|
+
(_, index) => entity(`entity:capacity-${index.toString().padStart(4, "0")}`, "x"));
|
|
2215
|
+
const countBound = await capacityAuthority(fullRecords);
|
|
2216
|
+
const workingHead = await countBound.working.store.head();
|
|
2217
|
+
await countBound.authority.agent.remember({ expectedHead: {
|
|
2218
|
+
generation: workingHead.generation, operationSha256: workingHead.operationSha256 },
|
|
2219
|
+
puts: [{ dependencies: [], key: "entity:capacity-extra", kind: "entity", v: 1,
|
|
2220
|
+
value: { name: "Extra" } }], requestId: "remember_count_capacity",
|
|
2221
|
+
tombstones: [], v: 1 });
|
|
2222
|
+
const countNomination = await countBound.authority.agent.nominate({ nominationId: "kb.review",
|
|
2223
|
+
roots: ["entity:capacity-extra"], v: 1 });
|
|
2224
|
+
await expect(countBound.authority.host.adoptNomination({
|
|
2225
|
+
expectedCanonicalHead: countBound.snapshot.head, nomination: countNomination, v: 1 }))
|
|
2226
|
+
.rejects.toThrow("record snapshot bound");
|
|
2227
|
+
expect(countBound.commitCalls()).toBe(0);
|
|
2228
|
+
expect(await countBound.canonicalStore.head()).toEqual(countBound.snapshot.head);
|
|
2229
|
+
await countBound.canonical.store.close(); await countBound.working.store.close();
|
|
2230
|
+
|
|
2231
|
+
const largeName = "x".repeat(875_000);
|
|
2232
|
+
const byteRecords = Array.from({ length: 38 }, (_, index) =>
|
|
2233
|
+
entity(`entity:bytes-${index.toString().padStart(2, "0")}`, largeName));
|
|
2234
|
+
const byteBound = await capacityAuthority(byteRecords);
|
|
2235
|
+
expect(Buffer.byteLength(canonicalJson(byteBound.snapshot), "utf8"))
|
|
2236
|
+
.toBeLessThanOrEqual(OH_MEMORY_LIMITS_V1.snapshotBytesPerLane);
|
|
2237
|
+
const byteWorkingHead = await byteBound.working.store.head();
|
|
2238
|
+
await byteBound.authority.agent.remember({ expectedHead: {
|
|
2239
|
+
generation: byteWorkingHead.generation,
|
|
2240
|
+
operationSha256: byteWorkingHead.operationSha256 },
|
|
2241
|
+
puts: [{ dependencies: [], key: "entity:bytes-extra", kind: "entity", v: 1,
|
|
2242
|
+
value: { name: largeName } }], requestId: "remember_byte_capacity",
|
|
2243
|
+
tombstones: [], v: 1 });
|
|
2244
|
+
const byteNomination = await byteBound.authority.agent.nominate({ nominationId: "kb.review",
|
|
2245
|
+
roots: ["entity:bytes-extra"], v: 1 });
|
|
2246
|
+
await expect(byteBound.authority.host.adoptNomination({
|
|
2247
|
+
expectedCanonicalHead: byteBound.snapshot.head, nomination: byteNomination, v: 1 }))
|
|
2248
|
+
.rejects.toThrow("canonical snapshot byte bound");
|
|
2249
|
+
expect(byteBound.commitCalls()).toBe(0);
|
|
2250
|
+
expect(await byteBound.canonicalStore.head()).toEqual(byteBound.snapshot.head);
|
|
2251
|
+
await byteBound.canonical.store.close(); await byteBound.working.store.close();
|
|
2252
|
+
});
|
|
783
2253
|
});
|