@hraness/oh 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +598 -0
- package/dist/canonical.d.ts +32 -0
- package/dist/canonical.d.ts.map +1 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +3419 -0
- package/dist/contract.d.ts +33 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/graph.d.ts +67 -0
- package/dist/graph.d.ts.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1988 -0
- package/dist/libsql.d.ts +57 -0
- package/dist/libsql.d.ts.map +1 -0
- package/dist/libsql.js +2662 -0
- package/dist/memory.d.ts +366 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +3650 -0
- package/dist/ontology.d.ts +242 -0
- package/dist/ontology.d.ts.map +1 -0
- package/dist/operation.d.ts +24 -0
- package/dist/operation.d.ts.map +1 -0
- package/dist/projection-public.d.ts +59 -0
- package/dist/projection-public.d.ts.map +1 -0
- package/dist/projection-public.js +1682 -0
- package/dist/projection-suss.d.ts +17 -0
- package/dist/projection-suss.d.ts.map +1 -0
- package/dist/projection-suss.js +1721 -0
- package/dist/projection.d.ts +315 -0
- package/dist/projection.d.ts.map +1 -0
- package/dist/schema.d.ts +45 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/sdk.d.ts +51 -0
- package/dist/sdk.d.ts.map +1 -0
- package/dist/sdk.js +3072 -0
- package/dist/search.d.ts +34 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/semantic.d.ts +83 -0
- package/dist/semantic.d.ts.map +1 -0
- package/dist/semantic.js +706 -0
- package/dist/sqlite/driver.d.ts +6 -0
- package/dist/sqlite/driver.d.ts.map +1 -0
- package/dist/sqlite/index.d.ts +5 -0
- package/dist/sqlite/index.d.ts.map +1 -0
- package/dist/sqlite/index.js +2840 -0
- package/dist/sqlite/migrations.d.ts +10 -0
- package/dist/sqlite/migrations.d.ts.map +1 -0
- package/dist/sqlite/port.d.ts +40 -0
- package/dist/sqlite/port.d.ts.map +1 -0
- package/dist/sqlite/runtime.d.ts +23 -0
- package/dist/sqlite/runtime.d.ts.map +1 -0
- package/dist/sqlite/store.d.ts +85 -0
- package/dist/sqlite/store.d.ts.map +1 -0
- package/dist/store.d.ts +236 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +996 -0
- package/dist/sync.d.ts +57 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +1328 -0
- package/package.json +121 -0
- package/skills/oh/SKILL.md +206 -0
- package/skills/oh/agents/openai.yaml +4 -0
- package/spec/README.md +74 -0
- package/spec/manifest.json +41 -0
- package/spec/v1/canonical-json.md +59 -0
- package/spec/v1/contract.json +28 -0
- package/spec/v1/contract.schema.json +58 -0
- package/spec/v1/embedding-profile.json +11 -0
- package/spec/v1/embedding.md +56 -0
- package/spec/v1/graph.md +87 -0
- package/spec/v1/memory.md +193 -0
- package/spec/v1/migration.md +92 -0
- package/spec/v1/ontology.json +55 -0
- package/spec/v1/ontology.md +80 -0
- package/spec/v1/operation.schema.json +138 -0
- package/spec/v1/projection-identity.schema.json +58 -0
- package/spec/v1/projection-query.schema.json +60 -0
- package/spec/v1/projection-result.schema.json +452 -0
- package/spec/v1/projection-rule-pack.schema.json +182 -0
- package/spec/v1/projection.md +165 -0
- package/spec/v1/record.schema.json +95 -0
- package/spec/v1/schema-evolution.md +51 -0
- package/spec/v1/schema-revision.schema.json +178 -0
- package/spec/v1/storage.md +88 -0
- package/spec/v1/store.md +131 -0
- package/spec/v1/sync-bundle.schema.json +51 -0
- package/spec/v1/sync.md +67 -0
- package/src/canonical.test.ts +46 -0
- package/src/canonical.ts +203 -0
- package/src/cli.test.ts +103 -0
- package/src/cli.ts +308 -0
- package/src/contract.ts +87 -0
- package/src/contracts.test.ts +147 -0
- package/src/graph.ts +248 -0
- package/src/index.ts +8 -0
- package/src/libsql.test.ts +657 -0
- package/src/libsql.ts +1687 -0
- package/src/memory.test.ts +783 -0
- package/src/memory.ts +1684 -0
- package/src/ontology.ts +573 -0
- package/src/operation.ts +80 -0
- package/src/projection-public.ts +53 -0
- package/src/projection-suss.ts +129 -0
- package/src/projection.test.ts +418 -0
- package/src/projection.ts +1457 -0
- package/src/schema.ts +156 -0
- package/src/sdk.ts +96 -0
- package/src/search.ts +66 -0
- package/src/semantic.test.ts +480 -0
- package/src/semantic.ts +333 -0
- package/src/sqlite/driver.ts +47 -0
- package/src/sqlite/index.ts +4 -0
- package/src/sqlite/migrations.test.ts +44 -0
- package/src/sqlite/migrations.ts +178 -0
- package/src/sqlite/port.test.ts +127 -0
- package/src/sqlite/port.ts +120 -0
- package/src/sqlite/runtime.test.ts +68 -0
- package/src/sqlite/runtime.ts +53 -0
- package/src/sqlite/store.test.ts +295 -0
- package/src/sqlite/store.ts +988 -0
- package/src/store.test.ts +121 -0
- package/src/store.ts +701 -0
- package/src/sync.test.ts +117 -0
- package/src/sync.ts +227 -0
|
@@ -0,0 +1,783 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { canonicalJson, canonicalSha256, type JsonValue } from "./canonical";
|
|
4
|
+
import { OhRecordCodecRegistry } from "./contract";
|
|
5
|
+
import { createKnowledgeGraphRecordV1 } from "./graph";
|
|
6
|
+
import {
|
|
7
|
+
createOhMemoryAgentV1,
|
|
8
|
+
createOhMemoryAgentV2,
|
|
9
|
+
OH_MEMORY_COMPOSITE_FACT_EXTRACTOR_V1,
|
|
10
|
+
OH_MEMORY_QUERY_LIMITS_V2,
|
|
11
|
+
type OhMemoryAuthoritySourceV1,
|
|
12
|
+
type OhMemoryProofV1,
|
|
13
|
+
} from "./memory";
|
|
14
|
+
import {
|
|
15
|
+
createOhProjectionLiteralV1,
|
|
16
|
+
createOhProjectionQueryV1,
|
|
17
|
+
createOhProjectionRulePackV1,
|
|
18
|
+
createOhProjectionRuleV1,
|
|
19
|
+
ohProjectionVariableV1,
|
|
20
|
+
} from "./projection";
|
|
21
|
+
import { createOhSqliteStoreAuthorityV1 } from "./sqlite/port";
|
|
22
|
+
import {
|
|
23
|
+
OH_CANONICAL_STORE_PROFILE_V1,
|
|
24
|
+
OH_WORKING_STORE_PROFILE_V1,
|
|
25
|
+
OhIntegrityError,
|
|
26
|
+
OhProfileError,
|
|
27
|
+
type OhStoreV1,
|
|
28
|
+
} from "./store";
|
|
29
|
+
|
|
30
|
+
function entity(key: string, name: string) {
|
|
31
|
+
return createKnowledgeGraphRecordV1({ dependencies: [], key, kind: "entity", v: 1,
|
|
32
|
+
value: { name } });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function put(store: OhStoreV1, key: string, name: string, operationId: string) {
|
|
36
|
+
return await store.commit({ actorId: "test.host", changes: [
|
|
37
|
+
{ kind: "put", record: entity(key, name), v: 1 },
|
|
38
|
+
], expectedHead: await store.head(), instant: "2026-08-29T12:00:00.000Z", operationId });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function entityCodecs() {
|
|
42
|
+
return new OhRecordCodecRegistry().register({ kind: "entity", parse: (value) => {
|
|
43
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)
|
|
44
|
+
|| typeof (value as { name?: unknown }).name !== "string") return null;
|
|
45
|
+
return { name: (value as { name: string }).name } satisfies JsonValue;
|
|
46
|
+
} });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function visibleProgram() {
|
|
50
|
+
const lane = ohProjectionVariableV1("lane");
|
|
51
|
+
const key = ohProjectionVariableV1("key");
|
|
52
|
+
const kind = ohProjectionVariableV1("kind");
|
|
53
|
+
const digest = ohProjectionVariableV1("digest");
|
|
54
|
+
const literal = (relation: string, ...terms: ReturnType<typeof ohProjectionVariableV1>[]) =>
|
|
55
|
+
createOhProjectionLiteralV1({ relation, terms });
|
|
56
|
+
return {
|
|
57
|
+
programId: "memory.visible-records",
|
|
58
|
+
purpose: "answer.research",
|
|
59
|
+
query: createOhProjectionQueryV1({ find: ["lane", "key", "digest"], limit: 50,
|
|
60
|
+
queryId: "memory.visible-records", where: [literal("memory.visible", lane, key, digest)] }),
|
|
61
|
+
rulePack: createOhProjectionRulePackV1({ rulePackId: "memory.visible-records",
|
|
62
|
+
rulePackRevision: 1, rules: [createOhProjectionRuleV1({
|
|
63
|
+
body: [literal("memory.record", lane, key, kind, digest)],
|
|
64
|
+
head: literal("memory.visible", lane, key, digest), ruleId: "memory.visible-records", })] }),
|
|
65
|
+
} as const;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function nameProgram() {
|
|
69
|
+
const lane = ohProjectionVariableV1("lane");
|
|
70
|
+
const key = ohProjectionVariableV1("key");
|
|
71
|
+
const name = ohProjectionVariableV1("name");
|
|
72
|
+
const literal = (relation: string, ...terms: ReturnType<typeof ohProjectionVariableV1>[]) =>
|
|
73
|
+
createOhProjectionLiteralV1({ relation, terms });
|
|
74
|
+
return {
|
|
75
|
+
programId: "memory.domain-names",
|
|
76
|
+
purpose: "answer.domain",
|
|
77
|
+
query: createOhProjectionQueryV1({ find: ["lane", "key", "name"], limit: 50,
|
|
78
|
+
queryId: "memory.domain-names", where: [literal("domain.visible-name", lane, key, name)] }),
|
|
79
|
+
rulePack: createOhProjectionRulePackV1({ rulePackId: "memory.domain-names",
|
|
80
|
+
rulePackRevision: 1, rules: [createOhProjectionRuleV1({
|
|
81
|
+
body: [literal("domain.name", lane, key, name)],
|
|
82
|
+
head: literal("domain.visible-name", lane, key, name), ruleId: "memory.domain-names", })] }),
|
|
83
|
+
} as const;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function chunkProgram(maximumRows = 400, pageSize = 64,
|
|
87
|
+
maximumResultBytes = 8 * 1024 * 1024, maximumPageBytes = 1024 * 1024) {
|
|
88
|
+
const lane = ohProjectionVariableV1("lane");
|
|
89
|
+
const key = ohProjectionVariableV1("key");
|
|
90
|
+
const index = ohProjectionVariableV1("index");
|
|
91
|
+
const chunk = ohProjectionVariableV1("chunk");
|
|
92
|
+
const source = createOhProjectionLiteralV1({ relation: "domain.value-chunk",
|
|
93
|
+
terms: [lane, key, index, chunk] });
|
|
94
|
+
const visible = createOhProjectionLiteralV1({ relation: "domain.visible-value-chunk",
|
|
95
|
+
terms: [lane, key, index, chunk] });
|
|
96
|
+
return {
|
|
97
|
+
evaluation: {
|
|
98
|
+
maximumDerivedTuples: 2_048,
|
|
99
|
+
maximumProofDepth: 16,
|
|
100
|
+
maximumProofNodes: 16,
|
|
101
|
+
maximumResultBytes,
|
|
102
|
+
maximumRounds: 16,
|
|
103
|
+
maximumTotalProofNodes: 8_192,
|
|
104
|
+
maximumWorkUnits: 2_000_000,
|
|
105
|
+
},
|
|
106
|
+
maximumPageBytes,
|
|
107
|
+
maximumRows,
|
|
108
|
+
pageSize,
|
|
109
|
+
parameters: ["key", "lane"],
|
|
110
|
+
programId: "memory.value-chunks",
|
|
111
|
+
purpose: "answer.memory-value",
|
|
112
|
+
query: createOhProjectionQueryV1({ find: ["index", "chunk"], limit: maximumRows,
|
|
113
|
+
queryId: "memory.value-chunks", where: [visible] }),
|
|
114
|
+
rulePack: createOhProjectionRulePackV1({ rulePackId: "memory.value-chunks",
|
|
115
|
+
rulePackRevision: 1, rules: [createOhProjectionRuleV1({ body: [source], head: visible,
|
|
116
|
+
ruleId: "memory.value-chunks" })] }),
|
|
117
|
+
v: 2,
|
|
118
|
+
} as const;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function fixtureV2(maximumRows = 400, pageSize = 64,
|
|
122
|
+
maximumResultBytes = 8 * 1024 * 1024, configuration: Readonly<{
|
|
123
|
+
chunkBytes?: number;
|
|
124
|
+
chunkCount?: number;
|
|
125
|
+
continuationKey?: Uint8Array;
|
|
126
|
+
extractorInvoked?: () => void;
|
|
127
|
+
maximumPageBytes?: number;
|
|
128
|
+
wrapWorkingStore?: (store: OhStoreV1) => OhStoreV1;
|
|
129
|
+
}> = {}) {
|
|
130
|
+
const canonical = createOhSqliteStoreAuthorityV1({ path: ":memory:",
|
|
131
|
+
profile: OH_CANONICAL_STORE_PROFILE_V1, realmId: "realm:v2-canonical", spaceId: "v2-canonical" });
|
|
132
|
+
const working = createOhSqliteStoreAuthorityV1({ path: ":memory:",
|
|
133
|
+
profile: OH_WORKING_STORE_PROFILE_V1, realmId: "realm:v2-working", spaceId: "v2-working" });
|
|
134
|
+
await put(canonical.store, "entity:chunked", "Chunked", "op_v2_chunked");
|
|
135
|
+
const chunkCount = configuration.chunkCount ?? 300;
|
|
136
|
+
const chunkBytes = configuration.chunkBytes ?? 0;
|
|
137
|
+
const baseProgram = chunkProgram(maximumRows, pageSize, maximumResultBytes,
|
|
138
|
+
configuration.maximumPageBytes ?? 1024 * 1024);
|
|
139
|
+
const expectedCanonicalHead = await canonical.store.head();
|
|
140
|
+
const selectedWorkingStore = configuration.wrapWorkingStore?.(working.store) ?? working.store;
|
|
141
|
+
const createAgent = async (continuationKey?: Uint8Array) => await createOhMemoryAgentV2({
|
|
142
|
+
actorId: "test.memory-agent-v2",
|
|
143
|
+
canonical: { authorityId: "authority.v2-canonical",
|
|
144
|
+
expectedBindingSha256: canonical.store.binding.bindingSha256,
|
|
145
|
+
expectedHead: expectedCanonicalHead, store: canonical.store },
|
|
146
|
+
...(continuationKey === undefined ? {} : { continuationKey }),
|
|
147
|
+
extractors: [{ extractorId: "domain.value-chunks",
|
|
148
|
+
extractorSha256: canonicalSha256({ extractor: "domain.value-chunks", revision: 1 }),
|
|
149
|
+
relations: ["domain.value-chunk"],
|
|
150
|
+
extract: ({ lane, record }) => {
|
|
151
|
+
configuration.extractorInvoked?.();
|
|
152
|
+
return Array.from({ length: chunkCount }, (_, index) => ({
|
|
153
|
+
relation: "domain.value-chunk", tuple: [lane, record.key, index,
|
|
154
|
+
`chunk:${index.toString().padStart(3, "0")}${"x".repeat(chunkBytes)}`], v: 1 as const,
|
|
155
|
+
}));
|
|
156
|
+
},
|
|
157
|
+
}],
|
|
158
|
+
monotonicNow: () => 0,
|
|
159
|
+
now: () => new Date("2026-08-29T12:00:00.000Z"),
|
|
160
|
+
programs: [baseProgram, { ...baseProgram, maximumPageBytes: 1024 * 1024,
|
|
161
|
+
programId: "memory.value-chunks-alternate", purpose: "answer.memory-value-alternate" }],
|
|
162
|
+
working: { authorityId: "authority.v2-working", codecs: entityCodecs(),
|
|
163
|
+
expectedBindingSha256: selectedWorkingStore.binding.bindingSha256,
|
|
164
|
+
store: selectedWorkingStore },
|
|
165
|
+
});
|
|
166
|
+
const agent = await createAgent(configuration.continuationKey);
|
|
167
|
+
return { agent, canonical, createAgent, working };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function physicalSources(proofs: readonly OhMemoryProofV1[]) {
|
|
171
|
+
const sources: OhMemoryAuthoritySourceV1[] = [];
|
|
172
|
+
const visit = (proof: OhMemoryProofV1) => {
|
|
173
|
+
if (proof.kind === "fact") sources.push(...proof.sources);
|
|
174
|
+
else if (proof.kind === "derived") proof.premises.forEach(visit);
|
|
175
|
+
};
|
|
176
|
+
proofs.forEach(visit);
|
|
177
|
+
return sources;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function countedWorkingStore(store: OhStoreV1, counter: { reads: number }): OhStoreV1 {
|
|
181
|
+
return new Proxy(store, {
|
|
182
|
+
get(target, property) {
|
|
183
|
+
const value = Reflect.get(target, property, target) as unknown;
|
|
184
|
+
if (typeof value !== "function") return value;
|
|
185
|
+
return (...args: unknown[]) => {
|
|
186
|
+
if (property === "head" || property === "snapshot") counter.reads += 1;
|
|
187
|
+
return Reflect.apply(value, target, args);
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
}) as OhStoreV1;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function fixture() {
|
|
194
|
+
const canonical = createOhSqliteStoreAuthorityV1({ path: ":memory:",
|
|
195
|
+
profile: OH_CANONICAL_STORE_PROFILE_V1, realmId: "realm:canonical", spaceId: "canonical" });
|
|
196
|
+
const working = createOhSqliteStoreAuthorityV1({ path: ":memory:",
|
|
197
|
+
profile: OH_WORKING_STORE_PROFILE_V1, realmId: "realm:working", spaceId: "working" });
|
|
198
|
+
await put(canonical.store, "entity:shared", "Reviewed", "op_canonical_shared");
|
|
199
|
+
await put(canonical.store, "entity:canonical", "Canonical", "op_canonical_only");
|
|
200
|
+
const canonicalHead = await canonical.store.head();
|
|
201
|
+
let now = new Date("2026-08-29T12:00:00.000Z");
|
|
202
|
+
const clockOrigin = now.getTime();
|
|
203
|
+
let monotonicNow = 0;
|
|
204
|
+
const baseProgram = visibleProgram();
|
|
205
|
+
const agent = await createOhMemoryAgentV1({
|
|
206
|
+
actorId: "test.memory-agent",
|
|
207
|
+
canonical: { authorityId: "authority.canonical", expectedBindingSha256: canonical.store.binding.bindingSha256,
|
|
208
|
+
expectedHead: canonicalHead, store: canonical.store },
|
|
209
|
+
now: () => now,
|
|
210
|
+
nominationRoutes: [{ destinationPurpose: "kb.review", nominationId: "kb.review" }],
|
|
211
|
+
monotonicNow: () => monotonicNow,
|
|
212
|
+
programs: [baseProgram, { ...baseProgram, programId: "memory.visible-records-alternate",
|
|
213
|
+
purpose: "answer.alternate" }, { ...baseProgram,
|
|
214
|
+
evaluation: { maximumRounds: 2 }, programId: "memory.visible-records-bounded" }],
|
|
215
|
+
working: { authorityId: "authority.working", codecs: entityCodecs(),
|
|
216
|
+
expectedBindingSha256: working.store.binding.bindingSha256, store: working.store },
|
|
217
|
+
});
|
|
218
|
+
return { agent, canonical, canonicalHead, setNow(value: string) {
|
|
219
|
+
now = new Date(value); monotonicNow = now.getTime() - clockOrigin;
|
|
220
|
+
}, working };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
describe("experimental composite Oh memory", () => {
|
|
224
|
+
test("keeps two physical authorities explicit and makes working conflicts visible", async () => {
|
|
225
|
+
expect(Object.isFrozen(OH_MEMORY_COMPOSITE_FACT_EXTRACTOR_V1.relations)).toBe(true);
|
|
226
|
+
const value = await fixture();
|
|
227
|
+
const emptyWorkingHead = await value.working.store.head();
|
|
228
|
+
const firstRemember = { expectedHead: {
|
|
229
|
+
generation: emptyWorkingHead.generation, operationSha256: emptyWorkingHead.operationSha256,
|
|
230
|
+
}, requestId: "op_working_shared",
|
|
231
|
+
puts: [{ dependencies: [], key: "entity:shared", kind: "entity", v: 1,
|
|
232
|
+
value: { name: "Proposed correction" } }], tombstones: [], v: 1 } as const;
|
|
233
|
+
const receipt = await value.agent.remember(firstRemember);
|
|
234
|
+
expect(receipt).toMatchObject({ actorId: "test.memory-agent", authorityId: "authority.working",
|
|
235
|
+
lane: "working", requestId: "op_working_shared", status: "committed" });
|
|
236
|
+
expect(Object.keys(receipt)).not.toContain("changes");
|
|
237
|
+
expect(await value.agent.remember(firstRemember)).toEqual(receipt);
|
|
238
|
+
const head = await value.working.store.head();
|
|
239
|
+
await value.agent.remember({ expectedHead: {
|
|
240
|
+
generation: head.generation, operationSha256: head.operationSha256,
|
|
241
|
+
}, requestId: "op_working_only",
|
|
242
|
+
puts: [{ dependencies: [], key: "entity:working", kind: "entity", v: 1,
|
|
243
|
+
value: { name: "Working" } }], tombstones: [], v: 1 });
|
|
244
|
+
|
|
245
|
+
const result = await value.agent.query({ programId: "memory.visible-records", v: 1 });
|
|
246
|
+
expect({ memorySha256: String(result.identity.memorySha256), resultSha256: String(result.resultSha256),
|
|
247
|
+
rowSha256: result.rows.map(({ resultRowSha256 }) => String(resultRowSha256)) }).toEqual({
|
|
248
|
+
memorySha256: "b2327cb013af29646ab6beaa953771153c10f076bb8f3e061f106b0a5f234a3a",
|
|
249
|
+
resultSha256: "44e9fb7af61aa2395f6e028acdfdf327f64c07be9dca40b5097f9d5d3248291e",
|
|
250
|
+
rowSha256: [
|
|
251
|
+
"9abe30330d18d8ee271f25f5db9e9a0bad09eaa8a10fb206b2850336f016903d",
|
|
252
|
+
"ccfc0b064abbbea14ddbdb17a03708cb151f166c82c9c028c159c1e07f10bff5",
|
|
253
|
+
"c4f1ce2d57b8324858892d7a1e3362a77b098d5a8e9b794df269f49dd25d1456",
|
|
254
|
+
"6ca55f381d076658aef1d4b7f238fc7730413c55a4474d3a96703912170c7485",
|
|
255
|
+
],
|
|
256
|
+
});
|
|
257
|
+
expect(result.authority).toBe("derived");
|
|
258
|
+
expect(result.rows.map(({ values }) => values.slice(0, 2))).toEqual([
|
|
259
|
+
["canonical", "entity:canonical"], ["canonical", "entity:shared"],
|
|
260
|
+
["working", "entity:shared"], ["working", "entity:working"],
|
|
261
|
+
]);
|
|
262
|
+
expect(result.conflicts).toHaveLength(1);
|
|
263
|
+
expect(result.conflicts[0]?.key).toBe("entity:shared");
|
|
264
|
+
expect(result.identity.canonical.authorityId).toBe("authority.canonical");
|
|
265
|
+
expect(result.identity.working.authorityId).toBe("authority.working");
|
|
266
|
+
expect(result.identity.canonical.head).toEqual(value.canonicalHead);
|
|
267
|
+
expect(result.identity.working.head).toEqual(await value.working.store.head());
|
|
268
|
+
expect(result.identity.purpose).toBe("answer.research");
|
|
269
|
+
expect(Object.isFrozen(result.rows)).toBe(true);
|
|
270
|
+
expect(Object.isFrozen(result.rows[0]?.values)).toBe(true);
|
|
271
|
+
expect(() => (result.rows as unknown as unknown[]).splice(0, 1)).toThrow();
|
|
272
|
+
expect(() => (result.rows[0]!.values as unknown as unknown[]).push("tamper")).toThrow();
|
|
273
|
+
|
|
274
|
+
const workingRow = result.rows.findIndex(({ values }) => values[0] === "working");
|
|
275
|
+
const explanation = await value.agent.explain({ resultSha256: result.resultSha256,
|
|
276
|
+
row: workingRow, token: result.explainCapability.token, v: 1 });
|
|
277
|
+
expect(explanation.premiseAuthority).toBe("working");
|
|
278
|
+
expect(physicalSources(explanation.proofs)).toEqual([expect.objectContaining({
|
|
279
|
+
authorityId: "authority.working", bindingSha256: value.working.store.binding.bindingSha256,
|
|
280
|
+
head: await value.working.store.head(), key: "entity:shared", lane: "working",
|
|
281
|
+
})]);
|
|
282
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test("binds both heads and host-owned program purpose and evaluation into identity", async () => {
|
|
286
|
+
const value = await fixture();
|
|
287
|
+
const first = await value.agent.query({ programId: "memory.visible-records", v: 1 });
|
|
288
|
+
const purposeChanged = await value.agent.query({
|
|
289
|
+
programId: "memory.visible-records-alternate", v: 1 });
|
|
290
|
+
const evaluationChanged = await value.agent.query({
|
|
291
|
+
programId: "memory.visible-records-bounded", v: 1 });
|
|
292
|
+
expect(purposeChanged.identity.purpose).toBe("answer.alternate");
|
|
293
|
+
expect(evaluationChanged.identity.evaluationSha256).not.toBe(first.identity.evaluationSha256);
|
|
294
|
+
expect(new Set([first.identity.memorySha256, purposeChanged.identity.memorySha256,
|
|
295
|
+
evaluationChanged.identity.memorySha256]).size).toBe(3);
|
|
296
|
+
|
|
297
|
+
const workingHead = await value.working.store.head();
|
|
298
|
+
await value.agent.remember({ expectedHead: {
|
|
299
|
+
generation: workingHead.generation, operationSha256: workingHead.operationSha256,
|
|
300
|
+
}, requestId: "op_identity_change",
|
|
301
|
+
puts: [{ dependencies: [], key: "entity:new", kind: "entity", v: 1,
|
|
302
|
+
value: { name: "New" } }], tombstones: [], v: 1 });
|
|
303
|
+
const headChanged = await value.agent.query({ programId: "memory.visible-records", v: 1 });
|
|
304
|
+
expect(headChanged.identity.memorySha256).not.toBe(first.identity.memorySha256);
|
|
305
|
+
expect(headChanged.identity.working.head.sequence).toBe(1);
|
|
306
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test("binds host-owned domain extractors while retaining physical sources", async () => {
|
|
310
|
+
const canonical = createOhSqliteStoreAuthorityV1({ path: ":memory:",
|
|
311
|
+
profile: OH_CANONICAL_STORE_PROFILE_V1, realmId: "realm:domain-c", spaceId: "domain-c" });
|
|
312
|
+
const working = createOhSqliteStoreAuthorityV1({ path: ":memory:",
|
|
313
|
+
profile: OH_WORKING_STORE_PROFILE_V1, realmId: "realm:domain-w", spaceId: "domain-w" });
|
|
314
|
+
await put(canonical.store, "entity:domain", "Domain name", "op_domain_name");
|
|
315
|
+
const agent = await createOhMemoryAgentV1({ actorId: "test.domain-agent",
|
|
316
|
+
canonical: { authorityId: "authority.domain-c",
|
|
317
|
+
expectedBindingSha256: canonical.store.binding.bindingSha256,
|
|
318
|
+
expectedHead: await canonical.store.head(), store: canonical.store }, extractors: [{
|
|
319
|
+
extractorId: "domain.mutator",
|
|
320
|
+
extractorSha256: canonicalSha256({ extractor: "domain.mutator", v: 1 }),
|
|
321
|
+
relations: ["domain.mutation-attempt"],
|
|
322
|
+
extract: ({ lane, record }) => {
|
|
323
|
+
try { (record.value as { name: string }).name = "Mutated"; } catch { /* frozen input */ }
|
|
324
|
+
return [{ relation: "domain.mutation-attempt", tuple: [lane, record.key], v: 1 }];
|
|
325
|
+
},
|
|
326
|
+
}, { extractorId: "domain.names",
|
|
327
|
+
extractorSha256: canonicalSha256({ extractor: "domain.names", v: 1 }),
|
|
328
|
+
relations: ["domain.name"], extract: ({ lane, record }) => [{ relation: "domain.name",
|
|
329
|
+
tuple: [lane, record.key, (record.value as { name: string }).name], v: 1 }],
|
|
330
|
+
}], programs: [nameProgram()], working: { authorityId: "authority.domain-w",
|
|
331
|
+
codecs: entityCodecs(), expectedBindingSha256: working.store.binding.bindingSha256,
|
|
332
|
+
store: working.store } });
|
|
333
|
+
const result = await agent.query({ programId: "memory.domain-names", v: 1 });
|
|
334
|
+
expect(result.rows.map(({ values }) => values)).toEqual([
|
|
335
|
+
["canonical", "entity:domain", "Domain name"],
|
|
336
|
+
]);
|
|
337
|
+
const explanation = await agent.explain({ resultSha256: result.resultSha256, row: 0,
|
|
338
|
+
token: result.explainCapability.token, v: 1 });
|
|
339
|
+
expect(physicalSources(explanation.proofs)[0]).toMatchObject({ key: "entity:domain", lane: "canonical" });
|
|
340
|
+
const topProof = explanation.proofs[0];
|
|
341
|
+
expect(topProof?.kind).toBe("derived");
|
|
342
|
+
if (topProof?.kind !== "derived") throw new Error("Expected a derived domain proof.");
|
|
343
|
+
const domainProof = topProof.premises[0];
|
|
344
|
+
expect(domainProof?.kind).toBe("fact");
|
|
345
|
+
if (domainProof?.kind !== "fact") throw new Error("Expected a domain fact premise.");
|
|
346
|
+
expect(domainProof.factPolicy).toMatchObject({ extractorId: "domain.names", kind: "domain" });
|
|
347
|
+
await canonical.store.close(); await working.store.close();
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test("captures host options and detaches store-owned snapshots before the first await", async () => {
|
|
351
|
+
const canonical = createOhSqliteStoreAuthorityV1({ path: ":memory:",
|
|
352
|
+
profile: OH_CANONICAL_STORE_PROFILE_V1, realmId: "realm:captured-c", spaceId: "captured-c" });
|
|
353
|
+
const working = createOhSqliteStoreAuthorityV1({ path: ":memory:",
|
|
354
|
+
profile: OH_WORKING_STORE_PROFILE_V1, realmId: "realm:captured-w", spaceId: "captured-w" });
|
|
355
|
+
const swapped = createOhSqliteStoreAuthorityV1({ path: ":memory:",
|
|
356
|
+
profile: OH_WORKING_STORE_PROFILE_V1, realmId: "realm:swapped-w", spaceId: "swapped-w" });
|
|
357
|
+
await put(swapped.store, "entity:swapped", "Swapped", "op_swapped");
|
|
358
|
+
let leakedSnapshot: { head: unknown; records: ReturnType<typeof entity>[]; v: 1 } | undefined;
|
|
359
|
+
const canonicalStore = {
|
|
360
|
+
binding: canonical.store.binding,
|
|
361
|
+
head: () => canonical.store.head(),
|
|
362
|
+
snapshot: async (options?: Parameters<OhStoreV1["snapshot"]>[0]) => {
|
|
363
|
+
const snapshot = await canonical.store.snapshot(options);
|
|
364
|
+
leakedSnapshot = { head: { ...snapshot.head }, records: [...snapshot.records], v: 1 };
|
|
365
|
+
return leakedSnapshot;
|
|
366
|
+
},
|
|
367
|
+
} as unknown as OhStoreV1;
|
|
368
|
+
const program = visibleProgram();
|
|
369
|
+
const mutableOptions = { actorId: "test.captured-agent", canonical: {
|
|
370
|
+
authorityId: "authority.captured-c", expectedBindingSha256: canonical.store.binding.bindingSha256,
|
|
371
|
+
expectedHead: await canonical.store.head(), store: canonicalStore },
|
|
372
|
+
nominationRoutes: [{ destinationPurpose: "kb.review", nominationId: "kb.review" }],
|
|
373
|
+
programs: [program], working: { authorityId: "authority.captured-w", codecs: entityCodecs(),
|
|
374
|
+
expectedBindingSha256: working.store.binding.bindingSha256, store: working.store } };
|
|
375
|
+
const pending = createOhMemoryAgentV1(mutableOptions);
|
|
376
|
+
mutableOptions.working.store = swapped.store;
|
|
377
|
+
(mutableOptions.programs[0] as { purpose: string }).purpose = "attacker.relabel";
|
|
378
|
+
const agent = await pending;
|
|
379
|
+
if (leakedSnapshot === undefined) throw new Error("Expected the test store to return a snapshot.");
|
|
380
|
+
leakedSnapshot.records.push(entity("entity:mutated-snapshot", "Mutated"));
|
|
381
|
+
const head = await working.store.head();
|
|
382
|
+
const receipt = await agent.remember({ expectedHead: { generation: head.generation,
|
|
383
|
+
operationSha256: head.operationSha256 }, puts: [{ dependencies: [], key: "entity:bound",
|
|
384
|
+
kind: "entity", v: 1, value: { name: "Bound" } }], requestId: "captured-remember",
|
|
385
|
+
tombstones: [], v: 1 });
|
|
386
|
+
expect(receipt.bindingSha256).toBe(working.store.binding.bindingSha256);
|
|
387
|
+
const result = await agent.query({ programId: "memory.visible-records", v: 1 });
|
|
388
|
+
expect(result.identity.purpose).toBe("answer.research");
|
|
389
|
+
expect(result.rows.map(({ values }) => values.slice(0, 2))).toEqual([
|
|
390
|
+
["working", "entity:bound"],
|
|
391
|
+
]);
|
|
392
|
+
const nomination = await agent.nominate({ nominationId: "kb.review",
|
|
393
|
+
roots: ["entity:bound"], v: 1 });
|
|
394
|
+
expect(nomination.closure.records.map(({ key }) => key)).toEqual(["entity:bound"]);
|
|
395
|
+
await canonical.store.close(); await working.store.close(); await swapped.store.close();
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test("rejects query injection and misbound or expired explanation capabilities", async () => {
|
|
399
|
+
const value = await fixture();
|
|
400
|
+
expect(Object.keys(value.agent).sort()).toEqual(["explain", "nominate", "query", "remember"]);
|
|
401
|
+
await expect(value.agent.query({ programId: "memory.visible-records",
|
|
402
|
+
query: { relation: "attacker" }, v: 1 }))
|
|
403
|
+
.rejects.toThrow(TypeError);
|
|
404
|
+
await expect(value.agent.query({ programId: "memory.visible-records",
|
|
405
|
+
purpose: "attacker.label", v: 1 })).rejects.toThrow(TypeError);
|
|
406
|
+
await expect(value.agent.query({ programId: "unknown", v: 1 })).rejects.toThrow("Unknown named");
|
|
407
|
+
const result = await value.agent.query({ programId: "memory.visible-records", v: 1 });
|
|
408
|
+
await expect(value.agent.explain({ resultSha256: "f".repeat(64), row: 0,
|
|
409
|
+
token: result.explainCapability.token, v: 1 })).rejects.toThrow(OhProfileError);
|
|
410
|
+
await expect(value.agent.explain({ resultSha256: result.resultSha256, row: 0,
|
|
411
|
+
token: "A".repeat(43), v: 1 })).rejects.toThrow(OhProfileError);
|
|
412
|
+
value.setNow("2026-08-29T12:10:00.000Z");
|
|
413
|
+
const later = await value.agent.query({ programId: "memory.visible-records", v: 1 });
|
|
414
|
+
value.setNow("2026-08-29T12:05:00.000Z");
|
|
415
|
+
await expect(value.agent.explain({ resultSha256: later.resultSha256, row: 0,
|
|
416
|
+
token: later.explainCapability.token, v: 1 })).rejects.toThrow("monotonic clock regressed");
|
|
417
|
+
value.setNow("2026-08-29T12:15:00.000Z");
|
|
418
|
+
await expect(value.agent.explain({ resultSha256: result.resultSha256, row: 0,
|
|
419
|
+
token: result.explainCapability.token, v: 1 })).rejects.toThrow(OhProfileError);
|
|
420
|
+
value.setNow("invalid");
|
|
421
|
+
await expect(value.agent.explain({ resultSha256: result.resultSha256, row: 0,
|
|
422
|
+
token: result.explainCapability.token, v: 1 })).rejects.toThrow(TypeError);
|
|
423
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
test("prepares an exact working closure without writing canonical authority", async () => {
|
|
427
|
+
const value = await fixture();
|
|
428
|
+
const beforeCanonical = await value.canonical.store.head();
|
|
429
|
+
const head = await value.working.store.head();
|
|
430
|
+
await value.agent.remember({ expectedHead: {
|
|
431
|
+
generation: head.generation, operationSha256: head.operationSha256,
|
|
432
|
+
}, requestId: "op_nomination",
|
|
433
|
+
puts: [{ dependencies: [], key: "entity:candidate", kind: "entity", v: 1,
|
|
434
|
+
value: { name: "Candidate" } }, { dependencies: [], key: "entity:other", kind: "entity", v: 1,
|
|
435
|
+
value: { name: "Other" } }], tombstones: [], v: 1 });
|
|
436
|
+
const nomination = await value.agent.nominate({ nominationId: "kb.review",
|
|
437
|
+
roots: ["entity:candidate"], v: 1 });
|
|
438
|
+
expect(nomination.status).toBe("prepared");
|
|
439
|
+
expect(nomination.destinationPurpose).toBe("kb.review");
|
|
440
|
+
expect(nomination.source.lane).toBe("working");
|
|
441
|
+
expect(nomination.closure.records.map(({ key }) => key)).toEqual(["entity:candidate"]);
|
|
442
|
+
expect(await value.canonical.store.head()).toEqual(beforeCanonical);
|
|
443
|
+
const exactHead = await value.working.store.head();
|
|
444
|
+
const originalExport = value.working.store.exportDependencyClosure.bind(value.working.store);
|
|
445
|
+
const substituted = await originalExport({ head: { operationSha256: exactHead.operationSha256,
|
|
446
|
+
sequence: exactHead.sequence }, roots: ["entity:other"] });
|
|
447
|
+
(value.working.store as unknown as { exportDependencyClosure: () => Promise<typeof substituted> })
|
|
448
|
+
.exportDependencyClosure = async () => substituted;
|
|
449
|
+
await expect(value.agent.nominate({ nominationId: "kb.review",
|
|
450
|
+
roots: ["entity:candidate"], v: 1 })).rejects.toThrow("substituted different roots");
|
|
451
|
+
await expect(value.agent.nominate({ nominationId: "attacker.destination",
|
|
452
|
+
roots: ["entity:candidate"], v: 1 })).rejects.toThrow("Unknown named");
|
|
453
|
+
await expect(value.agent.nominate({ nominationId: "kb.review",
|
|
454
|
+
roots: Array.from({ length: 1_025 }, (_, index) => `entity:${index}`), v: 1 }))
|
|
455
|
+
.rejects.toThrow(TypeError);
|
|
456
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
test("fails closed on host binding, profile, and pinned-head mistakes", async () => {
|
|
460
|
+
const canonical = createOhSqliteStoreAuthorityV1({ path: ":memory:",
|
|
461
|
+
profile: OH_CANONICAL_STORE_PROFILE_V1, realmId: "realm:c", spaceId: "c" });
|
|
462
|
+
const working = createOhSqliteStoreAuthorityV1({ path: ":memory:",
|
|
463
|
+
profile: OH_WORKING_STORE_PROFILE_V1, realmId: "realm:w", spaceId: "w" });
|
|
464
|
+
const head = await canonical.store.head();
|
|
465
|
+
await expect(createOhMemoryAgentV1({ actorId: "test.agent",
|
|
466
|
+
canonical: { authorityId: "authority.c",
|
|
467
|
+
expectedBindingSha256: working.store.binding.bindingSha256, expectedHead: head,
|
|
468
|
+
store: canonical.store }, programs: [visibleProgram()], working: { authorityId: "authority.w",
|
|
469
|
+
codecs: entityCodecs(), expectedBindingSha256: working.store.binding.bindingSha256,
|
|
470
|
+
store: working.store } })).rejects.toThrow(OhIntegrityError);
|
|
471
|
+
await expect(createOhMemoryAgentV1({ actorId: "test.agent",
|
|
472
|
+
canonical: { authorityId: "authority.c",
|
|
473
|
+
expectedBindingSha256: canonical.store.binding.bindingSha256,
|
|
474
|
+
expectedHead: { ...head, recordsSha256: "f".repeat(64) as typeof head.recordsSha256 },
|
|
475
|
+
store: canonical.store }, programs: [visibleProgram()], working: { authorityId: "authority.w",
|
|
476
|
+
codecs: entityCodecs(), expectedBindingSha256: working.store.binding.bindingSha256,
|
|
477
|
+
store: working.store } })).rejects.toThrow();
|
|
478
|
+
await expect(createOhMemoryAgentV1({ actorId: "test.agent",
|
|
479
|
+
canonical: { authorityId: "same",
|
|
480
|
+
expectedBindingSha256: canonical.store.binding.bindingSha256, expectedHead: head,
|
|
481
|
+
store: canonical.store }, programs: [visibleProgram()], working: { authorityId: "same",
|
|
482
|
+
codecs: entityCodecs(), expectedBindingSha256: working.store.binding.bindingSha256,
|
|
483
|
+
store: working.store } })).rejects.toThrow(OhProfileError);
|
|
484
|
+
await canonical.store.close(); await working.store.close();
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
describe("experimental parameterized and paginated Oh memory V2", () => {
|
|
489
|
+
test("binds host-declared lane and key constants and returns more than 256 value chunks in stable pages",
|
|
490
|
+
async () => {
|
|
491
|
+
const value = await fixtureV2();
|
|
492
|
+
let continuation: string | null = null;
|
|
493
|
+
const rows: (readonly (boolean | null | number | string)[])[] = [];
|
|
494
|
+
const starts: number[] = [];
|
|
495
|
+
let first: Awaited<ReturnType<typeof value.agent.query>> | undefined;
|
|
496
|
+
let later: Awaited<ReturnType<typeof value.agent.query>> | undefined;
|
|
497
|
+
do {
|
|
498
|
+
const result = await value.agent.query({ bindings: {
|
|
499
|
+
key: "entity:chunked", lane: "canonical",
|
|
500
|
+
}, continuation, programId: "memory.value-chunks", v: 2 });
|
|
501
|
+
first ??= result;
|
|
502
|
+
if (result.page.start === 64) later = result;
|
|
503
|
+
starts.push(result.page.start);
|
|
504
|
+
rows.push(...result.rows.map((row) => row.values));
|
|
505
|
+
expect(result.page.truncation).toEqual({ reasons: [], truncated: false, v: 2 });
|
|
506
|
+
expect(result.page.hasMore).toBe(result.continuation !== null);
|
|
507
|
+
expect(result.page.hasMore).toBe(result.continuationSha256 !== null);
|
|
508
|
+
expect(result.page.completeness).toBe(result.page.hasMore ? "partial" : "complete");
|
|
509
|
+
continuation = result.continuation;
|
|
510
|
+
} while (continuation !== null);
|
|
511
|
+
|
|
512
|
+
expect(starts).toEqual([0, 64, 128, 192, 256]);
|
|
513
|
+
expect(rows).toHaveLength(300);
|
|
514
|
+
expect(rows[0]).toEqual([0, "chunk:000"]);
|
|
515
|
+
expect(rows).toContainEqual([299, "chunk:299"]);
|
|
516
|
+
expect(rows.map((row) => row[0] as number).sort((left, right) => left - right))
|
|
517
|
+
.toEqual(Array.from({ length: 300 }, (_, index) => index));
|
|
518
|
+
expect(first?.identity.bindings).toEqual({ key: "entity:chunked", lane: "canonical" });
|
|
519
|
+
expect(first?.identity.bindingsSha256).not.toBe(first?.identity.templateQuerySha256);
|
|
520
|
+
expect(first?.identity.boundQuerySha256).not.toBe(first?.identity.templateQuerySha256);
|
|
521
|
+
expect(Object.isFrozen(first?.identity.bindings)).toBe(true);
|
|
522
|
+
if (first === undefined) throw new Error("Expected a first V2 page.");
|
|
523
|
+
const explanation = await value.agent.explain({ pageRow: 0,
|
|
524
|
+
resultSha256: first.resultSha256, token: first.explainCapability.token, v: 2 });
|
|
525
|
+
expect(explanation.page).toEqual(first.page);
|
|
526
|
+
expect(explanation.resultSha256).toBe(first.resultSha256);
|
|
527
|
+
expect(physicalSources(explanation.proofs)).toEqual([expect.objectContaining({
|
|
528
|
+
key: "entity:chunked", lane: "canonical",
|
|
529
|
+
})]);
|
|
530
|
+
if (later === undefined) throw new Error("Expected a later V2 page.");
|
|
531
|
+
const laterExplanation = await value.agent.explain({ pageRow: 0,
|
|
532
|
+
resultSha256: later.resultSha256, token: later.explainCapability.token, v: 2 });
|
|
533
|
+
expect(laterExplanation.page.start).toBe(64);
|
|
534
|
+
expect(laterExplanation.pageRow).toBe(0);
|
|
535
|
+
expect(laterExplanation.values).toEqual(later.rows[0]!.values);
|
|
536
|
+
expect(physicalSources(laterExplanation.proofs)).toEqual([expect.objectContaining({
|
|
537
|
+
key: "entity:chunked", lane: "canonical",
|
|
538
|
+
})]);
|
|
539
|
+
await expect(value.agent.explain({ pageRow: 0, resultSha256: "f".repeat(64),
|
|
540
|
+
token: first.explainCapability.token, v: 2 })).rejects.toThrow(OhProfileError);
|
|
541
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
test("pins continuations to the exact source, program, bindings, and complete projection", async () => {
|
|
545
|
+
const value = await fixtureV2();
|
|
546
|
+
const first = await value.agent.query({ bindings: {
|
|
547
|
+
key: "entity:chunked", lane: "canonical",
|
|
548
|
+
}, continuation: null, programId: "memory.value-chunks", v: 2 });
|
|
549
|
+
expect(first.continuation).not.toBeNull();
|
|
550
|
+
const continuation = first.continuation!;
|
|
551
|
+
const last = continuation.at(-1);
|
|
552
|
+
const tampered = `${continuation.slice(0, -1)}${last === "A" ? "B" : "A"}`;
|
|
553
|
+
await expect(value.agent.query({ bindings: {
|
|
554
|
+
key: "entity:chunked", lane: "canonical",
|
|
555
|
+
}, continuation: tampered, programId: "memory.value-chunks", v: 2 }))
|
|
556
|
+
.rejects.toThrow();
|
|
557
|
+
const decoded = JSON.parse(Buffer.from(continuation, "base64url").toString("utf8")) as
|
|
558
|
+
Record<string, unknown>;
|
|
559
|
+
const noncanonical = Buffer.from(JSON.stringify(Object.fromEntries(
|
|
560
|
+
Object.entries(decoded).reverse())), "utf8").toString("base64url");
|
|
561
|
+
await expect(value.agent.query({ bindings: {
|
|
562
|
+
key: "entity:chunked", lane: "canonical",
|
|
563
|
+
}, continuation: noncanonical, programId: "memory.value-chunks", v: 2 }))
|
|
564
|
+
.rejects.toThrow("Invalid memory continuation payload");
|
|
565
|
+
await expect(value.agent.query({ bindings: {
|
|
566
|
+
key: "entity:chunked", lane: "canonical",
|
|
567
|
+
}, continuation, programId: "memory.value-chunks-alternate", v: 2 }))
|
|
568
|
+
.rejects.toThrow("exact program, binding, and page identity");
|
|
569
|
+
await expect(value.agent.query({ bindings: {
|
|
570
|
+
key: "entity:other", lane: "canonical",
|
|
571
|
+
}, continuation, programId: "memory.value-chunks", v: 2 }))
|
|
572
|
+
.rejects.toThrow(OhIntegrityError);
|
|
573
|
+
|
|
574
|
+
const head = await value.working.store.head();
|
|
575
|
+
await value.agent.remember({ expectedHead: { generation: head.generation,
|
|
576
|
+
operationSha256: head.operationSha256 }, puts: [{ dependencies: [], key: "entity:head-change",
|
|
577
|
+
kind: "entity", v: 1, value: { name: "Head change" } }], requestId: "v2_head_change",
|
|
578
|
+
tombstones: [], v: 1 });
|
|
579
|
+
await expect(value.agent.query({ bindings: {
|
|
580
|
+
key: "entity:chunked", lane: "canonical",
|
|
581
|
+
}, continuation, programId: "memory.value-chunks", v: 2 }))
|
|
582
|
+
.rejects.toThrow("exact source and projection identity");
|
|
583
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
test("rejects an aligned offset forgery after every public cursor digest is recomputed", async () => {
|
|
587
|
+
const value = await fixtureV2(8, 2, 8 * 1024 * 1024, {
|
|
588
|
+
chunkCount: 8,
|
|
589
|
+
continuationKey: new Uint8Array(32).fill(7),
|
|
590
|
+
});
|
|
591
|
+
const request = { bindings: { key: "entity:chunked", lane: "canonical" },
|
|
592
|
+
continuation: null, programId: "memory.value-chunks", v: 2 as const };
|
|
593
|
+
const first = await value.agent.query(request);
|
|
594
|
+
if (first.continuation === null) throw new Error("Expected an issued V2 continuation.");
|
|
595
|
+
const decoded = JSON.parse(Buffer.from(first.continuation, "base64url").toString("utf8")) as
|
|
596
|
+
Record<string, unknown>;
|
|
597
|
+
expect(decoded.nextOffset).toBe(2);
|
|
598
|
+
decoded.nextOffset = 6;
|
|
599
|
+
const unsigned = Object.fromEntries(Object.entries(decoded).filter(([key]) =>
|
|
600
|
+
key !== "continuationHmacSha256" && key !== "continuationSha256"));
|
|
601
|
+
decoded.continuationSha256 = canonicalSha256(unsigned);
|
|
602
|
+
const forged = Buffer.from(canonicalJson(decoded), "utf8").toString("base64url");
|
|
603
|
+
await expect(value.agent.query({ ...request, continuation: forged }))
|
|
604
|
+
.rejects.toThrow("not an issued capability");
|
|
605
|
+
|
|
606
|
+
const replayOne = await value.agent.query({ ...request, continuation: first.continuation });
|
|
607
|
+
const replayTwo = await value.agent.query({ ...request, continuation: first.continuation });
|
|
608
|
+
expect(replayOne.page.start).toBe(2);
|
|
609
|
+
expect(replayTwo.page).toEqual(replayOne.page);
|
|
610
|
+
expect(replayTwo.rows).toEqual(replayOne.rows);
|
|
611
|
+
expect(replayTwo.resultSha256).toBe(replayOne.resultSha256);
|
|
612
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
test("scopes default cursors to one agent and preserves keyed cursors and public digests", async () => {
|
|
616
|
+
const mutableHostKey = new Uint8Array(32).fill(3);
|
|
617
|
+
const persistedHostKey = mutableHostKey.slice();
|
|
618
|
+
const value = await fixtureV2(400, 64, 8 * 1024 * 1024, {
|
|
619
|
+
continuationKey: mutableHostKey,
|
|
620
|
+
});
|
|
621
|
+
mutableHostKey.fill(9);
|
|
622
|
+
const request = { bindings: { key: "entity:chunked", lane: "canonical" },
|
|
623
|
+
continuation: null, programId: "memory.value-chunks", v: 2 as const };
|
|
624
|
+
const keyed = await value.agent.query(request);
|
|
625
|
+
if (keyed.continuation === null) throw new Error("Expected a keyed continuation.");
|
|
626
|
+
const { continuation: _opaqueContinuation, explainCapability: _explainCapability,
|
|
627
|
+
resultSha256: _resultSha256, ...deterministicResultIdentity } = keyed;
|
|
628
|
+
expect(canonicalSha256(deterministicResultIdentity)).toBe(keyed.resultSha256);
|
|
629
|
+
|
|
630
|
+
const otherKeyAgent = await value.createAgent(new Uint8Array(32).fill(4));
|
|
631
|
+
const otherKey = await otherKeyAgent.query(request);
|
|
632
|
+
expect(otherKey.continuation).not.toBe(keyed.continuation);
|
|
633
|
+
expect(otherKey.continuationSha256).toBe(keyed.continuationSha256);
|
|
634
|
+
expect(otherKey.resultSha256).toBe(keyed.resultSha256);
|
|
635
|
+
await expect(otherKeyAgent.query({ ...request, continuation: keyed.continuation }))
|
|
636
|
+
.rejects.toThrow("not an issued capability");
|
|
637
|
+
|
|
638
|
+
const reconstructed = await value.createAgent(persistedHostKey);
|
|
639
|
+
const resumed = await reconstructed.query({ ...request, continuation: keyed.continuation });
|
|
640
|
+
expect(resumed.page.start).toBe(64);
|
|
641
|
+
|
|
642
|
+
const localOne = await value.createAgent();
|
|
643
|
+
const localFirst = await localOne.query(request);
|
|
644
|
+
if (localFirst.continuation === null) throw new Error("Expected a local continuation.");
|
|
645
|
+
const localTwo = await value.createAgent();
|
|
646
|
+
await expect(localTwo.query({ ...request, continuation: localFirst.continuation }))
|
|
647
|
+
.rejects.toThrow("not an issued capability");
|
|
648
|
+
await expect(value.createAgent(new Uint8Array(
|
|
649
|
+
OH_MEMORY_QUERY_LIMITS_V2.continuationKeyMinimumBytes - 1)))
|
|
650
|
+
.rejects.toThrow("32 through 64 raw bytes");
|
|
651
|
+
await expect(value.createAgent(new Uint8Array(
|
|
652
|
+
OH_MEMORY_QUERY_LIMITS_V2.continuationKeyMaximumBytes + 1)))
|
|
653
|
+
.rejects.toThrow("32 through 64 raw bytes");
|
|
654
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
test("authenticates and statically binds cursors before working reads or extraction", async () => {
|
|
658
|
+
const counter = { extractorInvocations: 0, workingReads: 0 };
|
|
659
|
+
const key = new Uint8Array(32).fill(5);
|
|
660
|
+
const value = await fixtureV2(400, 64, 8 * 1024 * 1024, {
|
|
661
|
+
continuationKey: key,
|
|
662
|
+
extractorInvoked: () => { counter.extractorInvocations += 1; },
|
|
663
|
+
wrapWorkingStore: (store) => countedWorkingStore(store, {
|
|
664
|
+
get reads() { return counter.workingReads; },
|
|
665
|
+
set reads(value) { counter.workingReads = value; },
|
|
666
|
+
}),
|
|
667
|
+
});
|
|
668
|
+
const request = { bindings: { key: "entity:chunked", lane: "canonical" },
|
|
669
|
+
continuation: null, programId: "memory.value-chunks", v: 2 as const };
|
|
670
|
+
const first = await value.agent.query(request);
|
|
671
|
+
if (first.continuation === null) throw new Error("Expected an issued continuation.");
|
|
672
|
+
counter.extractorInvocations = 0;
|
|
673
|
+
counter.workingReads = 0;
|
|
674
|
+
|
|
675
|
+
await expect(value.agent.query({ ...request, continuation: "!" })).rejects.toThrow();
|
|
676
|
+
const invalidMacPayload = JSON.parse(
|
|
677
|
+
Buffer.from(first.continuation, "base64url").toString("utf8"),
|
|
678
|
+
) as Record<string, unknown>;
|
|
679
|
+
invalidMacPayload.continuationHmacSha256 = invalidMacPayload.continuationHmacSha256
|
|
680
|
+
=== "f".repeat(64) ? "e".repeat(64) : "f".repeat(64);
|
|
681
|
+
const invalidMac = Buffer.from(canonicalJson(invalidMacPayload), "utf8").toString("base64url");
|
|
682
|
+
await expect(value.agent.query({ ...request, continuation: invalidMac }))
|
|
683
|
+
.rejects.toThrow("not an issued capability");
|
|
684
|
+
await expect(value.agent.query({ ...request, continuation: first.continuation,
|
|
685
|
+
programId: "memory.value-chunks-alternate" }))
|
|
686
|
+
.rejects.toThrow("exact program, binding, and page identity");
|
|
687
|
+
await expect(value.agent.query({ ...request,
|
|
688
|
+
bindings: { key: "entity:other", lane: "canonical" },
|
|
689
|
+
continuation: first.continuation }))
|
|
690
|
+
.rejects.toThrow("exact program, binding, and page identity");
|
|
691
|
+
expect(counter).toEqual({ extractorInvocations: 0, workingReads: 0 });
|
|
692
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
test("bounds and flattens V2 query input before canonical serialization", async () => {
|
|
696
|
+
const value = await fixtureV2();
|
|
697
|
+
const base = { continuation: null, programId: "memory.value-chunks", v: 2 as const };
|
|
698
|
+
const cyclic: Record<string, unknown> = {};
|
|
699
|
+
cyclic.self = cyclic;
|
|
700
|
+
await expect(value.agent.query({ ...base,
|
|
701
|
+
bindings: { key: "entity:chunked", lane: cyclic } }))
|
|
702
|
+
.rejects.toThrow("JSON primitives");
|
|
703
|
+
await expect(value.agent.query({ ...base,
|
|
704
|
+
bindings: { key: "entity:chunked", lane: "x".repeat(16 * 1024 + 1) } }))
|
|
705
|
+
.rejects.toThrow("atom byte bound");
|
|
706
|
+
await expect(value.agent.query({ ...base, bindings: Object.fromEntries(
|
|
707
|
+
Array.from({ length: OH_MEMORY_QUERY_LIMITS_V2.bindings + 1 }, (_, index) =>
|
|
708
|
+
[`p${index}`, index]),
|
|
709
|
+
) })).rejects.toThrow("too many entries");
|
|
710
|
+
await expect(value.agent.query({ ...base, bindings: Object.fromEntries(
|
|
711
|
+
Array.from({ length: 6 }, (_, index) => [`p${index}`, "x".repeat(14 * 1024)]),
|
|
712
|
+
) })).rejects.toThrow("canonical byte bound");
|
|
713
|
+
await expect(value.agent.query({ ...base, bindings: {},
|
|
714
|
+
continuation: "A".repeat(OH_MEMORY_QUERY_LIMITS_V2.continuationBytes + 1) }))
|
|
715
|
+
.rejects.toThrow("continuation exceeds its byte bound");
|
|
716
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
test("fails closed instead of returning a silently row- or byte-truncated page", async () => {
|
|
720
|
+
const rowBound = await fixtureV2(256, 64);
|
|
721
|
+
await expect(rowBound.agent.query({ bindings: {
|
|
722
|
+
key: "entity:chunked", lane: "canonical",
|
|
723
|
+
}, continuation: null, programId: "memory.value-chunks", v: 2 }))
|
|
724
|
+
.rejects.toThrow("projection was truncated (query-limit)");
|
|
725
|
+
await rowBound.canonical.store.close(); await rowBound.working.store.close();
|
|
726
|
+
|
|
727
|
+
const byteBound = await fixtureV2(400, 64, 64 * 1024);
|
|
728
|
+
await expect(byteBound.agent.query({ bindings: {
|
|
729
|
+
key: "entity:chunked", lane: "canonical",
|
|
730
|
+
}, continuation: null, programId: "memory.value-chunks", v: 2 }))
|
|
731
|
+
.rejects.toThrow("projection was truncated (result-bytes)");
|
|
732
|
+
await byteBound.canonical.store.close(); await byteBound.working.store.close();
|
|
733
|
+
|
|
734
|
+
const pageBound = await fixtureV2(400, 64, 8 * 1024 * 1024,
|
|
735
|
+
{ chunkBytes: 2_048, maximumPageBytes: 64 * 1024 });
|
|
736
|
+
await expect(pageBound.agent.query({ bindings: {
|
|
737
|
+
key: "entity:chunked", lane: "canonical",
|
|
738
|
+
}, continuation: null, programId: "memory.value-chunks", v: 2 }))
|
|
739
|
+
.rejects.toThrow("page exceeds its host-declared canonical byte bound");
|
|
740
|
+
const recovered = await pageBound.agent.query({ bindings: {
|
|
741
|
+
key: "entity:chunked", lane: "canonical",
|
|
742
|
+
}, continuation: null, programId: "memory.value-chunks-alternate", v: 2 });
|
|
743
|
+
const explanation = await pageBound.agent.explain({ pageRow: 0,
|
|
744
|
+
resultSha256: recovered.resultSha256, token: recovered.explainCapability.token, v: 2 });
|
|
745
|
+
expect(explanation.values).toEqual(recovered.rows[0]!.values);
|
|
746
|
+
await pageBound.canonical.store.close(); await pageBound.working.store.close();
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
test("reports zero and exact-page results as complete without a continuation", async () => {
|
|
750
|
+
const value = await fixtureV2(400, 64, 8 * 1024 * 1024, { chunkCount: 64 });
|
|
751
|
+
const zero = await value.agent.query({ bindings: {
|
|
752
|
+
key: "entity:absent", lane: "canonical",
|
|
753
|
+
}, continuation: null, programId: "memory.value-chunks", v: 2 });
|
|
754
|
+
expect(zero.rows).toEqual([]);
|
|
755
|
+
expect(zero.page).toMatchObject({ completeness: "complete", endExclusive: 0,
|
|
756
|
+
hasMore: false, returnedRows: 0, start: 0, totalRows: 0 });
|
|
757
|
+
expect(zero.continuation).toBeNull();
|
|
758
|
+
expect(zero.continuationSha256).toBeNull();
|
|
759
|
+
|
|
760
|
+
const exact = await value.agent.query({ bindings: {
|
|
761
|
+
key: "entity:chunked", lane: "canonical",
|
|
762
|
+
}, continuation: null, programId: "memory.value-chunks", v: 2 });
|
|
763
|
+
expect(exact.rows).toHaveLength(64);
|
|
764
|
+
expect(exact.page).toMatchObject({ completeness: "complete", endExclusive: 64,
|
|
765
|
+
hasMore: false, returnedRows: 64, start: 0, totalRows: 64 });
|
|
766
|
+
expect(exact.continuation).toBeNull();
|
|
767
|
+
expect(exact.continuationSha256).toBeNull();
|
|
768
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
test("accepts only exact primitive bindings and rejects caller-supplied query policy", async () => {
|
|
772
|
+
const value = await fixtureV2();
|
|
773
|
+
await expect(value.agent.query({ bindings: { key: "entity:chunked" }, continuation: null,
|
|
774
|
+
programId: "memory.value-chunks", v: 2 })).rejects.toThrow("exactly match");
|
|
775
|
+
await expect(value.agent.query({ bindings: { key: "entity:chunked", lane: { value: "canonical" } },
|
|
776
|
+
continuation: null, programId: "memory.value-chunks", v: 2 }))
|
|
777
|
+
.rejects.toThrow("JSON primitives");
|
|
778
|
+
await expect(value.agent.query({ bindings: { key: "entity:chunked", lane: "canonical" },
|
|
779
|
+
continuation: null, programId: "memory.value-chunks", query: {}, v: 2 }))
|
|
780
|
+
.rejects.toThrow("Invalid parameterized memory query");
|
|
781
|
+
await value.canonical.store.close(); await value.working.store.close();
|
|
782
|
+
});
|
|
783
|
+
});
|