@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
package/src/memory.ts
ADDED
|
@@ -0,0 +1,1684 @@
|
|
|
1
|
+
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
canonicalJson,
|
|
5
|
+
canonicalSha256,
|
|
6
|
+
hasExactKeys,
|
|
7
|
+
isPlainRecord,
|
|
8
|
+
parseCanonicalInstantV1,
|
|
9
|
+
parseSha256Hex,
|
|
10
|
+
safeCode,
|
|
11
|
+
utf8ByteLength,
|
|
12
|
+
type JsonPrimitive,
|
|
13
|
+
type Sha256Hex,
|
|
14
|
+
} from "./canonical";
|
|
15
|
+
import { type OhRecordCodecRegistry } from "./contract";
|
|
16
|
+
import {
|
|
17
|
+
createKnowledgeGraphRecordV1,
|
|
18
|
+
knowledgeGraphRecordRefV1,
|
|
19
|
+
type KnowledgeGraphRecordV1,
|
|
20
|
+
} from "./graph";
|
|
21
|
+
import {
|
|
22
|
+
OH_PROJECTION_SEMANTICS_V1,
|
|
23
|
+
OH_PROJECTION_LIMITS_V1,
|
|
24
|
+
createOhProjectionDatasetV1,
|
|
25
|
+
createOhProjectionFactV1,
|
|
26
|
+
createOhProjectionLiteralV1,
|
|
27
|
+
createOhProjectionQueryV1,
|
|
28
|
+
createOhProjectionRecordFactsV1,
|
|
29
|
+
createOhProjectionSnapshotV1,
|
|
30
|
+
evaluateOhProjectionV1,
|
|
31
|
+
ohProjectionConstantV1,
|
|
32
|
+
parseOhProjectionQueryV1,
|
|
33
|
+
parseOhProjectionRulePackV1,
|
|
34
|
+
type OhProjectionAtomV1,
|
|
35
|
+
type OhProjectionDatasetV1,
|
|
36
|
+
type OhProjectionEvaluationOptionsV1,
|
|
37
|
+
type OhProjectionFactV1,
|
|
38
|
+
type OhProjectionProofV1,
|
|
39
|
+
type OhProjectionQueryV1,
|
|
40
|
+
type OhProjectionResultRowV1,
|
|
41
|
+
type OhProjectionRulePackV1,
|
|
42
|
+
type OhProjectionSnapshotV1,
|
|
43
|
+
} from "./projection";
|
|
44
|
+
import {
|
|
45
|
+
OhIntegrityError,
|
|
46
|
+
OH_DEPENDENCY_CLOSURE_LIMITS_V1,
|
|
47
|
+
OhProfileError,
|
|
48
|
+
OhSemanticBundleIngressV1,
|
|
49
|
+
parseOhHeadV1,
|
|
50
|
+
parseOhStoreBindingV1,
|
|
51
|
+
verifyOhDependencyClosureAgainstV1,
|
|
52
|
+
type OhDependencyClosureV1,
|
|
53
|
+
type OhHeadV1,
|
|
54
|
+
type OhSnapshotV1,
|
|
55
|
+
type OhStoreBindingV1,
|
|
56
|
+
type OhStoreV1,
|
|
57
|
+
} from "./store";
|
|
58
|
+
export const OH_MEMORY_FORMAT_VERSION_V1 = 1 as const;
|
|
59
|
+
export const OH_MEMORY_CONFLICT_POLICY_V1 = "visible-conflicts.v1" as const;
|
|
60
|
+
export const OH_MEMORY_LIMITS_V1 = Object.freeze({
|
|
61
|
+
explainCapabilityEntryBytes: 32 * 1024 * 1024,
|
|
62
|
+
explainCapabilities: 256,
|
|
63
|
+
explainCapabilityLifetimeMs: 15 * 60 * 1_000,
|
|
64
|
+
explainCapabilityTotalBytes: 64 * 1024 * 1024,
|
|
65
|
+
factsPerRecordPerExtractor: 512,
|
|
66
|
+
maximumExtractorInvocations: 262_144,
|
|
67
|
+
maximumExtractors: 32,
|
|
68
|
+
maximumNominationRoutes: 64,
|
|
69
|
+
maximumPrograms: 128,
|
|
70
|
+
maximumRecordsPerLane: 8_192,
|
|
71
|
+
maximumSyntheticRecords: 16_384,
|
|
72
|
+
rememberBytes: 8 * 1024 * 1024,
|
|
73
|
+
resultBytes: 32 * 1024 * 1024,
|
|
74
|
+
snapshotBytesPerLane: 32 * 1024 * 1024,
|
|
75
|
+
relationsPerExtractor: 64,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const memoryFactPackPayload = Object.freeze({
|
|
79
|
+
factPackId: "oh.memory.composite-facts",
|
|
80
|
+
factPackRevision: 1,
|
|
81
|
+
relations: Object.freeze(["memory.agreement", "memory.conflict", "memory.dependency", "memory.record"]),
|
|
82
|
+
semantics: OH_PROJECTION_SEMANTICS_V1,
|
|
83
|
+
v: 1 as const,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export const OH_MEMORY_COMPOSITE_FACT_EXTRACTOR_V1 = Object.freeze({
|
|
87
|
+
...memoryFactPackPayload,
|
|
88
|
+
extractorSha256: canonicalSha256(memoryFactPackPayload),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
export type OhMemoryLaneV1 = "canonical" | "working";
|
|
92
|
+
|
|
93
|
+
export type OhMemoryAuthoritySourceV1 = Readonly<{
|
|
94
|
+
authorityId: string;
|
|
95
|
+
bindingSha256: Sha256Hex;
|
|
96
|
+
head: OhHeadV1;
|
|
97
|
+
key: string;
|
|
98
|
+
lane: OhMemoryLaneV1;
|
|
99
|
+
recordSha256: Sha256Hex;
|
|
100
|
+
snapshotSha256: Sha256Hex;
|
|
101
|
+
v: 1;
|
|
102
|
+
}>;
|
|
103
|
+
|
|
104
|
+
export type OhMemoryProofV1 =
|
|
105
|
+
| Readonly<{
|
|
106
|
+
factPolicy: OhMemoryFactPolicyV1;
|
|
107
|
+
kind: "fact";
|
|
108
|
+
relation: string;
|
|
109
|
+
sources: readonly OhMemoryAuthoritySourceV1[];
|
|
110
|
+
tuple: readonly OhProjectionAtomV1[];
|
|
111
|
+
v: 1;
|
|
112
|
+
}>
|
|
113
|
+
| Readonly<{
|
|
114
|
+
kind: "derived";
|
|
115
|
+
premises: readonly OhMemoryProofV1[];
|
|
116
|
+
premisesTruncated: boolean;
|
|
117
|
+
relation: string;
|
|
118
|
+
ruleId: string;
|
|
119
|
+
ruleSha256: Sha256Hex;
|
|
120
|
+
tuple: readonly OhProjectionAtomV1[];
|
|
121
|
+
v: 1;
|
|
122
|
+
}>
|
|
123
|
+
| Readonly<{
|
|
124
|
+
kind: "truncated";
|
|
125
|
+
reason: "cycle" | "depth" | "nodes";
|
|
126
|
+
relation: string;
|
|
127
|
+
tuple: readonly OhProjectionAtomV1[];
|
|
128
|
+
v: 1;
|
|
129
|
+
}>;
|
|
130
|
+
|
|
131
|
+
export type OhMemoryLaneIdentityV1 = Readonly<{
|
|
132
|
+
authorityId: string;
|
|
133
|
+
bindingSha256: Sha256Hex;
|
|
134
|
+
datasetSha256: Sha256Hex;
|
|
135
|
+
head: OhHeadV1;
|
|
136
|
+
lane: OhMemoryLaneV1;
|
|
137
|
+
snapshotSha256: Sha256Hex;
|
|
138
|
+
v: 1;
|
|
139
|
+
}>;
|
|
140
|
+
|
|
141
|
+
export type OhMemoryIdentityV1 = Readonly<{
|
|
142
|
+
canonical: OhMemoryLaneIdentityV1;
|
|
143
|
+
compositeDatasetSha256: Sha256Hex;
|
|
144
|
+
conflictPolicy: typeof OH_MEMORY_CONFLICT_POLICY_V1;
|
|
145
|
+
evaluationSha256: Sha256Hex;
|
|
146
|
+
memorySha256: Sha256Hex;
|
|
147
|
+
programId: string;
|
|
148
|
+
projectionSha256: Sha256Hex;
|
|
149
|
+
purpose: string;
|
|
150
|
+
querySha256: Sha256Hex;
|
|
151
|
+
rulePackSha256: Sha256Hex;
|
|
152
|
+
v: 1;
|
|
153
|
+
working: OhMemoryLaneIdentityV1;
|
|
154
|
+
}>;
|
|
155
|
+
|
|
156
|
+
export type OhMemoryConflictV1 = Readonly<{
|
|
157
|
+
canonicalRecordSha256: Sha256Hex;
|
|
158
|
+
key: string;
|
|
159
|
+
v: 1;
|
|
160
|
+
workingRecordSha256: Sha256Hex;
|
|
161
|
+
}>;
|
|
162
|
+
|
|
163
|
+
export type OhMemoryResultRowV1 = Readonly<{
|
|
164
|
+
premiseAuthority: "canonical" | "unknown" | "working";
|
|
165
|
+
premiseLanes: readonly OhMemoryLaneV1[];
|
|
166
|
+
proofsTruncated: boolean;
|
|
167
|
+
resultRowSha256: Sha256Hex;
|
|
168
|
+
supportCount: number;
|
|
169
|
+
v: 1;
|
|
170
|
+
values: readonly OhProjectionAtomV1[];
|
|
171
|
+
}>;
|
|
172
|
+
|
|
173
|
+
export type OhMemoryQueryResultV1 = Readonly<{
|
|
174
|
+
authority: "derived";
|
|
175
|
+
conflicts: readonly OhMemoryConflictV1[];
|
|
176
|
+
explainCapability: Readonly<{ expiresAt: string; token: string; v: 1 }>;
|
|
177
|
+
identity: OhMemoryIdentityV1;
|
|
178
|
+
projectionResultSha256: Sha256Hex;
|
|
179
|
+
resultSha256: Sha256Hex;
|
|
180
|
+
rows: readonly OhMemoryResultRowV1[];
|
|
181
|
+
v: 1;
|
|
182
|
+
}>;
|
|
183
|
+
|
|
184
|
+
export type OhMemoryRememberReceiptV1 = Readonly<{
|
|
185
|
+
actorId: string;
|
|
186
|
+
authorityId: string;
|
|
187
|
+
bindingSha256: Sha256Hex;
|
|
188
|
+
head: OhHeadV1;
|
|
189
|
+
instant: string;
|
|
190
|
+
lane: "working";
|
|
191
|
+
operationSha256: Sha256Hex;
|
|
192
|
+
receiptSha256: Sha256Hex;
|
|
193
|
+
requestId: string;
|
|
194
|
+
status: "committed";
|
|
195
|
+
v: 1;
|
|
196
|
+
}>;
|
|
197
|
+
|
|
198
|
+
export type OhMemoryExplanationV1 = Readonly<{
|
|
199
|
+
authority: "derived";
|
|
200
|
+
explanationSha256: Sha256Hex;
|
|
201
|
+
identity: OhMemoryIdentityV1;
|
|
202
|
+
premiseAuthority: OhMemoryResultRowV1["premiseAuthority"];
|
|
203
|
+
premiseLanes: readonly OhMemoryLaneV1[];
|
|
204
|
+
proofs: readonly OhMemoryProofV1[];
|
|
205
|
+
proofsTruncated: boolean;
|
|
206
|
+
resultRowSha256: Sha256Hex;
|
|
207
|
+
resultSha256: Sha256Hex;
|
|
208
|
+
supportCount: number;
|
|
209
|
+
v: 1;
|
|
210
|
+
values: readonly OhProjectionAtomV1[];
|
|
211
|
+
}>;
|
|
212
|
+
|
|
213
|
+
export type OhMemoryNominationV1 = Readonly<{
|
|
214
|
+
closure: OhDependencyClosureV1;
|
|
215
|
+
destinationPurpose: string;
|
|
216
|
+
nominationId: string;
|
|
217
|
+
nominationSha256: Sha256Hex;
|
|
218
|
+
source: Readonly<{
|
|
219
|
+
authorityId: string;
|
|
220
|
+
bindingSha256: Sha256Hex;
|
|
221
|
+
head: OhHeadV1;
|
|
222
|
+
lane: "working";
|
|
223
|
+
v: 1;
|
|
224
|
+
}>;
|
|
225
|
+
status: "prepared";
|
|
226
|
+
v: 1;
|
|
227
|
+
}>;
|
|
228
|
+
|
|
229
|
+
export type OhMemoryNamedProgramV1 = Readonly<{
|
|
230
|
+
evaluation?: OhProjectionEvaluationOptionsV1;
|
|
231
|
+
programId: string;
|
|
232
|
+
purpose: string;
|
|
233
|
+
query: OhProjectionQueryV1;
|
|
234
|
+
rulePack: OhProjectionRulePackV1;
|
|
235
|
+
}>;
|
|
236
|
+
|
|
237
|
+
export type OhMemoryNominationRouteV1 = Readonly<{
|
|
238
|
+
destinationPurpose: string;
|
|
239
|
+
nominationId: string;
|
|
240
|
+
}>;
|
|
241
|
+
|
|
242
|
+
export type OhMemoryFactPolicyV1 =
|
|
243
|
+
| Readonly<{
|
|
244
|
+
extractorSha256: Sha256Hex;
|
|
245
|
+
factPackId: string;
|
|
246
|
+
kind: "built-in";
|
|
247
|
+
v: 1;
|
|
248
|
+
}>
|
|
249
|
+
| Readonly<{
|
|
250
|
+
extractorId: string;
|
|
251
|
+
extractorSha256: Sha256Hex;
|
|
252
|
+
kind: "domain";
|
|
253
|
+
v: 1;
|
|
254
|
+
}>;
|
|
255
|
+
|
|
256
|
+
export type OhMemoryFactDeclarationV1 = Readonly<{
|
|
257
|
+
relation: string;
|
|
258
|
+
tuple: readonly JsonPrimitive[];
|
|
259
|
+
v: 1;
|
|
260
|
+
}>;
|
|
261
|
+
|
|
262
|
+
/** Host-owned, digest-identified domain projection; it cannot choose sources. */
|
|
263
|
+
export type OhMemoryFactExtractorV1 = Readonly<{
|
|
264
|
+
extract(input: Readonly<{
|
|
265
|
+
lane: OhMemoryLaneV1;
|
|
266
|
+
record: KnowledgeGraphRecordV1;
|
|
267
|
+
}>): readonly OhMemoryFactDeclarationV1[];
|
|
268
|
+
extractorId: string;
|
|
269
|
+
extractorSha256: Sha256Hex;
|
|
270
|
+
relations: readonly string[];
|
|
271
|
+
}>;
|
|
272
|
+
|
|
273
|
+
export type OhMemoryFacadeOptionsV1 = Readonly<{
|
|
274
|
+
actorId: string;
|
|
275
|
+
canonical: Readonly<{
|
|
276
|
+
authorityId: string;
|
|
277
|
+
expectedBindingSha256: Sha256Hex;
|
|
278
|
+
expectedHead: OhHeadV1;
|
|
279
|
+
store: OhStoreV1;
|
|
280
|
+
}>;
|
|
281
|
+
explainCapabilityLifetimeMs?: number;
|
|
282
|
+
extractors?: readonly OhMemoryFactExtractorV1[];
|
|
283
|
+
monotonicNow?: () => number;
|
|
284
|
+
nominationRoutes?: readonly OhMemoryNominationRouteV1[];
|
|
285
|
+
now?: () => Date;
|
|
286
|
+
programs: readonly OhMemoryNamedProgramV1[];
|
|
287
|
+
working: Readonly<{
|
|
288
|
+
authorityId: string;
|
|
289
|
+
codecs: OhRecordCodecRegistry;
|
|
290
|
+
expectedBindingSha256: Sha256Hex;
|
|
291
|
+
store: OhStoreV1;
|
|
292
|
+
}>;
|
|
293
|
+
}>;
|
|
294
|
+
|
|
295
|
+
export interface OhMemoryAgentV1 {
|
|
296
|
+
explain(value: unknown): Promise<OhMemoryExplanationV1>;
|
|
297
|
+
nominate(value: unknown): Promise<OhMemoryNominationV1>;
|
|
298
|
+
query(value: unknown): Promise<OhMemoryQueryResultV1>;
|
|
299
|
+
remember(value: unknown): Promise<OhMemoryRememberReceiptV1>;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Additive experimental query/pagination limits; V1 contracts are unchanged. */
|
|
303
|
+
export const OH_MEMORY_QUERY_LIMITS_V2 = Object.freeze({
|
|
304
|
+
bindingBytes: 64 * 1024,
|
|
305
|
+
bindings: 32,
|
|
306
|
+
continuationBytes: 4 * 1024,
|
|
307
|
+
continuationKeyMaximumBytes: 64,
|
|
308
|
+
continuationKeyMinimumBytes: 32,
|
|
309
|
+
maximumPageBytes: 8 * 1024 * 1024,
|
|
310
|
+
maximumPageRows: 256,
|
|
311
|
+
maximumProgramRows: OH_PROJECTION_LIMITS_V1.queryResults,
|
|
312
|
+
minimumPageBytes: 64 * 1024,
|
|
313
|
+
requestBytes: 80 * 1024,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
export type OhMemoryEvaluationLimitsV2 = Readonly<{
|
|
317
|
+
maximumDerivedTuples: number;
|
|
318
|
+
maximumProofDepth: number;
|
|
319
|
+
maximumProofNodes: number;
|
|
320
|
+
maximumResultBytes: number;
|
|
321
|
+
maximumRounds: number;
|
|
322
|
+
maximumTotalProofNodes: number;
|
|
323
|
+
maximumWorkUnits: number;
|
|
324
|
+
}>;
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* A host-owned parameterized program. Parameter names refer only to variables
|
|
328
|
+
* in the query body, never to rule variables or projected output variables.
|
|
329
|
+
*/
|
|
330
|
+
export type OhMemoryNamedProgramV2 = Readonly<{
|
|
331
|
+
evaluation: OhMemoryEvaluationLimitsV2;
|
|
332
|
+
maximumPageBytes: number;
|
|
333
|
+
maximumRows: number;
|
|
334
|
+
pageSize: number;
|
|
335
|
+
parameters: readonly string[];
|
|
336
|
+
programId: string;
|
|
337
|
+
purpose: string;
|
|
338
|
+
query: OhProjectionQueryV1;
|
|
339
|
+
rulePack: OhProjectionRulePackV1;
|
|
340
|
+
v: 2;
|
|
341
|
+
}>;
|
|
342
|
+
|
|
343
|
+
export type OhMemoryFacadeOptionsV2 = Readonly<
|
|
344
|
+
Omit<OhMemoryFacadeOptionsV1, "programs"> & Readonly<{
|
|
345
|
+
/** Raw HMAC key for continuations that must survive agent reconstruction. */
|
|
346
|
+
continuationKey?: Uint8Array;
|
|
347
|
+
programs: readonly OhMemoryNamedProgramV2[];
|
|
348
|
+
}>
|
|
349
|
+
>;
|
|
350
|
+
|
|
351
|
+
export type OhMemoryIdentityV2 = Readonly<{
|
|
352
|
+
bindings: Readonly<Record<string, JsonPrimitive>>;
|
|
353
|
+
bindingsSha256: Sha256Hex;
|
|
354
|
+
boundQuerySha256: Sha256Hex;
|
|
355
|
+
canonical: OhMemoryLaneIdentityV1;
|
|
356
|
+
compositeDatasetSha256: Sha256Hex;
|
|
357
|
+
conflictPolicy: typeof OH_MEMORY_CONFLICT_POLICY_V1;
|
|
358
|
+
evaluationSha256: Sha256Hex;
|
|
359
|
+
memorySha256: Sha256Hex;
|
|
360
|
+
programId: string;
|
|
361
|
+
programSha256: Sha256Hex;
|
|
362
|
+
projectionSha256: Sha256Hex;
|
|
363
|
+
purpose: string;
|
|
364
|
+
rulePackSha256: Sha256Hex;
|
|
365
|
+
templateQuerySha256: Sha256Hex;
|
|
366
|
+
v: 2;
|
|
367
|
+
working: OhMemoryLaneIdentityV1;
|
|
368
|
+
}>;
|
|
369
|
+
|
|
370
|
+
export type OhMemoryResultRowV2 = Readonly<{
|
|
371
|
+
premiseAuthority: "canonical" | "unknown" | "working";
|
|
372
|
+
premiseLanes: readonly OhMemoryLaneV1[];
|
|
373
|
+
proofsTruncated: boolean;
|
|
374
|
+
resultRowSha256: Sha256Hex;
|
|
375
|
+
supportCount: number;
|
|
376
|
+
v: 2;
|
|
377
|
+
values: readonly OhProjectionAtomV1[];
|
|
378
|
+
}>;
|
|
379
|
+
|
|
380
|
+
export type OhMemoryPageV2 = Readonly<{
|
|
381
|
+
completeness: "complete" | "partial";
|
|
382
|
+
endExclusive: number;
|
|
383
|
+
hasMore: boolean;
|
|
384
|
+
maximumPageBytes: number;
|
|
385
|
+
pageSize: number;
|
|
386
|
+
returnedRows: number;
|
|
387
|
+
start: number;
|
|
388
|
+
totalRows: number;
|
|
389
|
+
truncation: Readonly<{
|
|
390
|
+
reasons: readonly [];
|
|
391
|
+
truncated: false;
|
|
392
|
+
v: 2;
|
|
393
|
+
}>;
|
|
394
|
+
v: 2;
|
|
395
|
+
}>;
|
|
396
|
+
|
|
397
|
+
export type OhMemoryQueryResultV2 = Readonly<{
|
|
398
|
+
authority: "derived";
|
|
399
|
+
conflicts: Readonly<{ count: number; conflictsSha256: Sha256Hex; v: 2 }>;
|
|
400
|
+
continuation: string | null;
|
|
401
|
+
continuationSha256: Sha256Hex | null;
|
|
402
|
+
explainCapability: Readonly<{ expiresAt: string; token: string; v: 2 }>;
|
|
403
|
+
identity: OhMemoryIdentityV2;
|
|
404
|
+
page: OhMemoryPageV2;
|
|
405
|
+
projectionResultSha256: Sha256Hex;
|
|
406
|
+
resultSha256: Sha256Hex;
|
|
407
|
+
rows: readonly OhMemoryResultRowV2[];
|
|
408
|
+
v: 2;
|
|
409
|
+
}>;
|
|
410
|
+
|
|
411
|
+
export type OhMemoryExplanationV2 = Readonly<{
|
|
412
|
+
authority: "derived";
|
|
413
|
+
explanationSha256: Sha256Hex;
|
|
414
|
+
identity: OhMemoryIdentityV2;
|
|
415
|
+
page: OhMemoryPageV2;
|
|
416
|
+
pageRow: number;
|
|
417
|
+
premiseAuthority: OhMemoryResultRowV2["premiseAuthority"];
|
|
418
|
+
premiseLanes: readonly OhMemoryLaneV1[];
|
|
419
|
+
proofs: readonly OhMemoryProofV1[];
|
|
420
|
+
proofsTruncated: boolean;
|
|
421
|
+
resultRowSha256: Sha256Hex;
|
|
422
|
+
resultSha256: Sha256Hex;
|
|
423
|
+
supportCount: number;
|
|
424
|
+
v: 2;
|
|
425
|
+
values: readonly OhProjectionAtomV1[];
|
|
426
|
+
}>;
|
|
427
|
+
|
|
428
|
+
export interface OhMemoryAgentV2 {
|
|
429
|
+
explain(value: unknown): Promise<OhMemoryExplanationV2>;
|
|
430
|
+
nominate(value: unknown): Promise<OhMemoryNominationV1>;
|
|
431
|
+
query(value: unknown): Promise<OhMemoryQueryResultV2>;
|
|
432
|
+
remember(value: unknown): Promise<OhMemoryRememberReceiptV1>;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
type LaneSnapshot = Readonly<{
|
|
436
|
+
authorityId: string;
|
|
437
|
+
binding: OhStoreBindingV1;
|
|
438
|
+
dataset: OhProjectionDatasetV1;
|
|
439
|
+
lane: OhMemoryLaneV1;
|
|
440
|
+
projectionSnapshot: OhProjectionSnapshotV1;
|
|
441
|
+
snapshot: OhSnapshotV1;
|
|
442
|
+
}>;
|
|
443
|
+
|
|
444
|
+
type SyntheticSource = Readonly<{
|
|
445
|
+
physical: OhMemoryAuthoritySourceV1;
|
|
446
|
+
record: KnowledgeGraphRecordV1;
|
|
447
|
+
}>;
|
|
448
|
+
|
|
449
|
+
type StoredExplanation = Readonly<{
|
|
450
|
+
bytes: number;
|
|
451
|
+
expiresAtMonotonicMs: number;
|
|
452
|
+
identity: OhMemoryIdentityV1;
|
|
453
|
+
proofs: readonly (readonly OhMemoryProofV1[])[];
|
|
454
|
+
resultSha256: Sha256Hex;
|
|
455
|
+
rows: readonly OhMemoryResultRowV1[];
|
|
456
|
+
}>;
|
|
457
|
+
|
|
458
|
+
const builtInFactPolicy: OhMemoryFactPolicyV1 = Object.freeze({
|
|
459
|
+
extractorSha256: OH_MEMORY_COMPOSITE_FACT_EXTRACTOR_V1.extractorSha256,
|
|
460
|
+
factPackId: OH_MEMORY_COMPOSITE_FACT_EXTRACTOR_V1.factPackId,
|
|
461
|
+
kind: "built-in",
|
|
462
|
+
v: 1,
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
function immutableClone<T>(value: T): T {
|
|
466
|
+
if (Array.isArray(value)) {
|
|
467
|
+
return Object.freeze(value.map((item) => immutableClone(item))) as T;
|
|
468
|
+
}
|
|
469
|
+
if (value !== null && typeof value === "object") {
|
|
470
|
+
if (!isPlainRecord(value)) throw new TypeError("Memory output contains a non-JSON object.");
|
|
471
|
+
const cloned: Record<string, unknown> = {};
|
|
472
|
+
for (const key of Object.keys(value)) {
|
|
473
|
+
Object.defineProperty(cloned, key, { configurable: false, enumerable: true,
|
|
474
|
+
value: immutableClone(value[key]), writable: false });
|
|
475
|
+
}
|
|
476
|
+
return Object.freeze(cloned) as T;
|
|
477
|
+
}
|
|
478
|
+
return value;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function compareText(left: string, right: string): number {
|
|
482
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function exactHead(left: OhHeadV1, right: OhHeadV1): boolean {
|
|
486
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function authorityId(value: unknown): string {
|
|
490
|
+
const parsed = safeCode(value, 128);
|
|
491
|
+
if (parsed === null) throw new TypeError("Invalid memory authority ID.");
|
|
492
|
+
return parsed;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function bindingFor(store: OhStoreV1, expected: Sha256Hex, lane: OhMemoryLaneV1): OhStoreBindingV1 {
|
|
496
|
+
const binding = parseOhStoreBindingV1(store.binding);
|
|
497
|
+
if (binding === null || binding.bindingSha256 !== parseSha256Hex(expected)) {
|
|
498
|
+
throw new OhIntegrityError(`The ${lane} store is not the host-bound authority.`);
|
|
499
|
+
}
|
|
500
|
+
if (binding.profile.profileKind !== lane) {
|
|
501
|
+
throw new OhProfileError(`The ${lane} memory lane has the wrong store profile.`);
|
|
502
|
+
}
|
|
503
|
+
return binding;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function laneIdentity(value: LaneSnapshot): OhMemoryLaneIdentityV1 {
|
|
507
|
+
return Object.freeze({
|
|
508
|
+
authorityId: value.authorityId,
|
|
509
|
+
bindingSha256: value.binding.bindingSha256,
|
|
510
|
+
datasetSha256: value.dataset.datasetSha256,
|
|
511
|
+
head: value.snapshot.head,
|
|
512
|
+
lane: value.lane,
|
|
513
|
+
snapshotSha256: value.projectionSnapshot.snapshotSha256,
|
|
514
|
+
v: 1,
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function datasetForSnapshot(binding: OhStoreBindingV1, snapshot: OhSnapshotV1): Readonly<{
|
|
519
|
+
dataset: OhProjectionDatasetV1;
|
|
520
|
+
projectionSnapshot: OhProjectionSnapshotV1;
|
|
521
|
+
}> {
|
|
522
|
+
const projectionSnapshot = createOhProjectionSnapshotV1({
|
|
523
|
+
head: snapshot.head,
|
|
524
|
+
records: snapshot.records,
|
|
525
|
+
spaceId: binding.spaceId,
|
|
526
|
+
});
|
|
527
|
+
const dataset = createOhProjectionDatasetV1({
|
|
528
|
+
extractorSha256: canonicalSha256({ extractor: "oh.memory.lane-structural", v: 1 }),
|
|
529
|
+
factPackId: "oh.memory.lane-structural",
|
|
530
|
+
factPackRevision: 1,
|
|
531
|
+
facts: createOhProjectionRecordFactsV1(snapshot.records),
|
|
532
|
+
snapshot: projectionSnapshot,
|
|
533
|
+
});
|
|
534
|
+
return { dataset, projectionSnapshot };
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
async function readLane(
|
|
538
|
+
authority: Readonly<{ authorityId: string; binding: OhStoreBindingV1; store: OhStoreV1 }>,
|
|
539
|
+
lane: OhMemoryLaneV1,
|
|
540
|
+
expectedHead?: OhHeadV1,
|
|
541
|
+
): Promise<LaneSnapshot> {
|
|
542
|
+
const returnedHead = expectedHead ?? await authority.store.head();
|
|
543
|
+
const head = parseOhHeadV1(immutableClone(returnedHead));
|
|
544
|
+
if (head === null) throw new OhIntegrityError(`The ${lane} store returned an invalid head.`);
|
|
545
|
+
const returnedSnapshot = await authority.store.snapshot({
|
|
546
|
+
head: { operationSha256: head.operationSha256, sequence: head.sequence },
|
|
547
|
+
maximumRecords: OH_MEMORY_LIMITS_V1.maximumRecordsPerLane,
|
|
548
|
+
});
|
|
549
|
+
if (!isPlainRecord(returnedSnapshot)
|
|
550
|
+
|| !hasExactKeys(returnedSnapshot, ["head", "records", "v"])
|
|
551
|
+
|| returnedSnapshot.v !== 1 || !Array.isArray(returnedSnapshot.records)) {
|
|
552
|
+
throw new OhIntegrityError(`The ${lane} store returned an invalid snapshot envelope.`);
|
|
553
|
+
}
|
|
554
|
+
const detached = immutableClone(returnedSnapshot);
|
|
555
|
+
const detachedHead = parseOhHeadV1(detached.head);
|
|
556
|
+
if (detachedHead === null) throw new OhIntegrityError(`The ${lane} store returned an invalid snapshot head.`);
|
|
557
|
+
const snapshot: OhSnapshotV1 = immutableClone({ head: detachedHead,
|
|
558
|
+
records: detached.records, v: 1 });
|
|
559
|
+
if (!exactHead(snapshot.head, head)) {
|
|
560
|
+
throw new OhIntegrityError(`The ${lane} snapshot differs from its pinned head.`);
|
|
561
|
+
}
|
|
562
|
+
if (utf8ByteLength(canonicalJson(snapshot)) > OH_MEMORY_LIMITS_V1.snapshotBytesPerLane) {
|
|
563
|
+
throw new RangeError(`The ${lane} memory snapshot exceeds its canonical byte bound.`);
|
|
564
|
+
}
|
|
565
|
+
const projected = datasetForSnapshot(authority.binding, snapshot);
|
|
566
|
+
return Object.freeze({ authorityId: authority.authorityId, binding: authority.binding,
|
|
567
|
+
dataset: projected.dataset, lane, projectionSnapshot: projected.projectionSnapshot, snapshot });
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function syntheticKey(lane: OhMemoryLaneV1, recordSha256: Sha256Hex): string {
|
|
571
|
+
return `memory-source:${lane}:${recordSha256}`;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function createSyntheticSources(lanes: readonly LaneSnapshot[]): Readonly<{
|
|
575
|
+
records: readonly KnowledgeGraphRecordV1[];
|
|
576
|
+
sources: ReadonlyMap<string, SyntheticSource>;
|
|
577
|
+
}> {
|
|
578
|
+
const sources = new Map<string, SyntheticSource>();
|
|
579
|
+
for (const lane of lanes) {
|
|
580
|
+
for (const physicalRecord of lane.snapshot.records) {
|
|
581
|
+
const key = syntheticKey(lane.lane, physicalRecord.recordSha256);
|
|
582
|
+
const record = createKnowledgeGraphRecordV1({ dependencies: [], key, kind: "view", v: 1,
|
|
583
|
+
value: { authorityId: lane.authorityId, bindingSha256: lane.binding.bindingSha256,
|
|
584
|
+
key: physicalRecord.key, lane: lane.lane, recordSha256: physicalRecord.recordSha256,
|
|
585
|
+
snapshotSha256: lane.projectionSnapshot.snapshotSha256, v: 1 } });
|
|
586
|
+
const physical = Object.freeze({ authorityId: lane.authorityId,
|
|
587
|
+
bindingSha256: lane.binding.bindingSha256,
|
|
588
|
+
head: lane.snapshot.head, key: physicalRecord.key, lane: lane.lane,
|
|
589
|
+
recordSha256: physicalRecord.recordSha256,
|
|
590
|
+
snapshotSha256: lane.projectionSnapshot.snapshotSha256, v: 1 as const });
|
|
591
|
+
if (sources.has(key)) throw new OhIntegrityError("A memory lane contains a duplicate source digest.");
|
|
592
|
+
sources.set(key, Object.freeze({ physical, record }));
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (sources.size > OH_MEMORY_LIMITS_V1.maximumSyntheticRecords) {
|
|
596
|
+
throw new RangeError("The composite memory snapshot has too many records.");
|
|
597
|
+
}
|
|
598
|
+
const records = [...sources.values()].map(({ record }) => record)
|
|
599
|
+
.sort((left, right) => compareText(left.key, right.key));
|
|
600
|
+
return { records, sources };
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function sourceFor(
|
|
604
|
+
sources: ReadonlyMap<string, SyntheticSource>,
|
|
605
|
+
lane: OhMemoryLaneV1,
|
|
606
|
+
record: KnowledgeGraphRecordV1,
|
|
607
|
+
) {
|
|
608
|
+
const source = sources.get(syntheticKey(lane, record.recordSha256));
|
|
609
|
+
if (source === undefined) throw new OhIntegrityError("A composite memory source is missing.");
|
|
610
|
+
return [{ key: source.record.key, recordSha256: source.record.recordSha256, v: 1 as const }];
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function createCompositeDataset(canonical: LaneSnapshot, working: LaneSnapshot,
|
|
614
|
+
extractors: readonly OhMemoryFactExtractorV1[]): Readonly<{
|
|
615
|
+
conflicts: readonly OhMemoryConflictV1[];
|
|
616
|
+
dataset: OhProjectionDatasetV1;
|
|
617
|
+
factPolicies: ReadonlyMap<string, OhMemoryFactPolicyV1>;
|
|
618
|
+
snapshot: OhProjectionSnapshotV1;
|
|
619
|
+
sources: ReadonlyMap<string, SyntheticSource>;
|
|
620
|
+
}> {
|
|
621
|
+
const synthetic = createSyntheticSources([canonical, working]);
|
|
622
|
+
const extractorInvocations = synthetic.records.length * extractors.length;
|
|
623
|
+
if (extractorInvocations > OH_MEMORY_LIMITS_V1.maximumExtractorInvocations) {
|
|
624
|
+
throw new RangeError("The composite memory extractor invocation count exceeds its explicit bound.");
|
|
625
|
+
}
|
|
626
|
+
const facts: OhProjectionFactV1[] = [];
|
|
627
|
+
const factDigests = new Set<Sha256Hex>();
|
|
628
|
+
const factPolicies = new Map<string, OhMemoryFactPolicyV1>();
|
|
629
|
+
const addFact = (fact: OhProjectionFactV1, policy: OhMemoryFactPolicyV1) => {
|
|
630
|
+
if (facts.length >= OH_PROJECTION_LIMITS_V1.facts) {
|
|
631
|
+
throw new RangeError("The composite memory fact set exceeds its explicit bound.");
|
|
632
|
+
}
|
|
633
|
+
if (factDigests.has(fact.factSha256)) {
|
|
634
|
+
throw new OhIntegrityError("A memory fact extractor emitted the same exact fact twice.");
|
|
635
|
+
}
|
|
636
|
+
const priorPolicy = factPolicies.get(fact.relation);
|
|
637
|
+
if (priorPolicy !== undefined && canonicalJson(priorPolicy) !== canonicalJson(policy)) {
|
|
638
|
+
throw new OhIntegrityError("A memory relation has more than one fact policy.");
|
|
639
|
+
}
|
|
640
|
+
facts.push(fact);
|
|
641
|
+
factDigests.add(fact.factSha256);
|
|
642
|
+
factPolicies.set(fact.relation, policy);
|
|
643
|
+
};
|
|
644
|
+
const byLane = new Map<OhMemoryLaneV1, Map<string, KnowledgeGraphRecordV1>>([
|
|
645
|
+
["canonical", new Map(canonical.snapshot.records.map((record) => [record.key, record]))],
|
|
646
|
+
["working", new Map(working.snapshot.records.map((record) => [record.key, record]))],
|
|
647
|
+
]);
|
|
648
|
+
for (const lane of [canonical, working] as const) {
|
|
649
|
+
for (const record of lane.snapshot.records) {
|
|
650
|
+
const extractorRecord = immutableClone(record);
|
|
651
|
+
const source = sourceFor(synthetic.sources, lane.lane, record);
|
|
652
|
+
addFact(createOhProjectionFactV1({ relation: "memory.record", sources: source,
|
|
653
|
+
tuple: [lane.lane, record.key, record.kind, record.recordSha256] }), builtInFactPolicy);
|
|
654
|
+
for (const dependency of record.dependencies) {
|
|
655
|
+
addFact(createOhProjectionFactV1({ relation: "memory.dependency", sources: source,
|
|
656
|
+
tuple: [lane.lane, record.key, dependency] }), builtInFactPolicy);
|
|
657
|
+
}
|
|
658
|
+
for (const extractor of extractors) {
|
|
659
|
+
const declared = extractor.extract(Object.freeze({ lane: lane.lane, record: extractorRecord }));
|
|
660
|
+
if (!Array.isArray(declared)
|
|
661
|
+
|| declared.length > OH_MEMORY_LIMITS_V1.factsPerRecordPerExtractor) {
|
|
662
|
+
throw new RangeError("A memory fact extractor exceeded its per-record bound.");
|
|
663
|
+
}
|
|
664
|
+
for (const fact of declared) {
|
|
665
|
+
if (!isPlainRecord(fact) || !hasExactKeys(fact, ["relation", "tuple", "v"])
|
|
666
|
+
|| fact.v !== 1 || !Array.isArray(fact.tuple) || typeof fact.relation !== "string"
|
|
667
|
+
|| !extractor.relations.includes(fact.relation)) {
|
|
668
|
+
throw new TypeError("A memory fact extractor returned an invalid or reserved fact.");
|
|
669
|
+
}
|
|
670
|
+
addFact(createOhProjectionFactV1({ relation: fact.relation, sources: source, tuple: fact.tuple }),
|
|
671
|
+
Object.freeze({ extractorId: extractor.extractorId,
|
|
672
|
+
extractorSha256: extractor.extractorSha256, kind: "domain", v: 1 }));
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
const conflicts: OhMemoryConflictV1[] = [];
|
|
678
|
+
const canonicalByKey = byLane.get("canonical")!;
|
|
679
|
+
const workingByKey = byLane.get("working")!;
|
|
680
|
+
for (const key of [...canonicalByKey.keys()].filter((candidate) => workingByKey.has(candidate)).sort()) {
|
|
681
|
+
const canonicalRecord = canonicalByKey.get(key)!;
|
|
682
|
+
const workingRecord = workingByKey.get(key)!;
|
|
683
|
+
const sources = [
|
|
684
|
+
...sourceFor(synthetic.sources, "canonical", canonicalRecord),
|
|
685
|
+
...sourceFor(synthetic.sources, "working", workingRecord),
|
|
686
|
+
];
|
|
687
|
+
if (canonicalRecord.recordSha256 === workingRecord.recordSha256) {
|
|
688
|
+
addFact(createOhProjectionFactV1({ relation: "memory.agreement", sources,
|
|
689
|
+
tuple: [key, canonicalRecord.recordSha256] }), builtInFactPolicy);
|
|
690
|
+
} else {
|
|
691
|
+
addFact(createOhProjectionFactV1({ relation: "memory.conflict", sources,
|
|
692
|
+
tuple: [key, canonicalRecord.recordSha256, workingRecord.recordSha256] }), builtInFactPolicy);
|
|
693
|
+
conflicts.push(Object.freeze({ canonicalRecordSha256: canonicalRecord.recordSha256,
|
|
694
|
+
key, v: 1, workingRecordSha256: workingRecord.recordSha256 }));
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
const recordRefs = synthetic.records.map(knowledgeGraphRecordRefV1)
|
|
698
|
+
.sort((left, right) => compareText(left.key, right.key));
|
|
699
|
+
const sourceIdentity = {
|
|
700
|
+
canonical: laneIdentity(canonical),
|
|
701
|
+
conflictPolicy: OH_MEMORY_CONFLICT_POLICY_V1,
|
|
702
|
+
recordRefs,
|
|
703
|
+
v: 1 as const,
|
|
704
|
+
working: laneIdentity(working),
|
|
705
|
+
};
|
|
706
|
+
const head: OhHeadV1 = Object.freeze({ generation: 1,
|
|
707
|
+
graphRevisionSha256: canonicalSha256({ kind: "oh.memory.composite-graph", sourceIdentity }),
|
|
708
|
+
operationSha256: canonicalSha256({ kind: "oh.memory.composite-operation", sourceIdentity }),
|
|
709
|
+
recordsSha256: canonicalSha256(recordRefs), sequence: 1, v: 1 });
|
|
710
|
+
const snapshot = createOhProjectionSnapshotV1({ head, records: synthetic.records,
|
|
711
|
+
spaceId: "oh.memory.composite" });
|
|
712
|
+
const dataset = createOhProjectionDatasetV1({
|
|
713
|
+
extractorSha256: canonicalSha256({
|
|
714
|
+
builtIn: OH_MEMORY_COMPOSITE_FACT_EXTRACTOR_V1.extractorSha256,
|
|
715
|
+
extensions: extractors.map(({ extractorId, extractorSha256, relations }) => ({
|
|
716
|
+
extractorId, extractorSha256, relations,
|
|
717
|
+
})),
|
|
718
|
+
v: 1,
|
|
719
|
+
}),
|
|
720
|
+
factPackId: OH_MEMORY_COMPOSITE_FACT_EXTRACTOR_V1.factPackId,
|
|
721
|
+
factPackRevision: OH_MEMORY_COMPOSITE_FACT_EXTRACTOR_V1.factPackRevision,
|
|
722
|
+
facts,
|
|
723
|
+
snapshot,
|
|
724
|
+
});
|
|
725
|
+
return Object.freeze({ conflicts: Object.freeze(conflicts), dataset, factPolicies, snapshot,
|
|
726
|
+
sources: synthetic.sources });
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function mapProof(proof: OhProjectionProofV1,
|
|
730
|
+
sources: ReadonlyMap<string, SyntheticSource>,
|
|
731
|
+
factPolicies: ReadonlyMap<string, OhMemoryFactPolicyV1>): OhMemoryProofV1 {
|
|
732
|
+
if (proof.kind === "truncated") return Object.freeze({ ...proof });
|
|
733
|
+
if (proof.kind === "derived") {
|
|
734
|
+
return Object.freeze({ ...proof,
|
|
735
|
+
premises: Object.freeze(proof.premises.map((premise) => mapProof(premise, sources, factPolicies))) });
|
|
736
|
+
}
|
|
737
|
+
const physical = proof.sources.map((source) => {
|
|
738
|
+
const mapped = sources.get(source.key);
|
|
739
|
+
if (mapped === undefined || mapped.record.recordSha256 !== source.recordSha256) {
|
|
740
|
+
throw new OhIntegrityError("A projection proof has no exact physical memory source.");
|
|
741
|
+
}
|
|
742
|
+
return mapped.physical;
|
|
743
|
+
}).sort((left, right) => compareText(canonicalJson(left), canonicalJson(right)));
|
|
744
|
+
const factPolicy = factPolicies.get(proof.relation);
|
|
745
|
+
if (factPolicy === undefined) throw new OhIntegrityError("A projection proof has no memory fact policy.");
|
|
746
|
+
return Object.freeze({ factPolicy, kind: "fact", relation: proof.relation,
|
|
747
|
+
sources: Object.freeze(physical), tuple: proof.tuple, v: 1 });
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function collectLanes(proof: OhMemoryProofV1, lanes: Set<OhMemoryLaneV1>): boolean {
|
|
751
|
+
if (proof.kind === "truncated") return true;
|
|
752
|
+
if (proof.kind === "fact") {
|
|
753
|
+
for (const source of proof.sources) lanes.add(source.lane);
|
|
754
|
+
return false;
|
|
755
|
+
}
|
|
756
|
+
let unknown = proof.premisesTruncated;
|
|
757
|
+
for (const premise of proof.premises) unknown = collectLanes(premise, lanes) || unknown;
|
|
758
|
+
return unknown;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function publicRow(row: OhProjectionResultRowV1,
|
|
762
|
+
proofs: readonly OhMemoryProofV1[]): OhMemoryResultRowV1 {
|
|
763
|
+
const lanes = new Set<OhMemoryLaneV1>();
|
|
764
|
+
let unknown = row.proofsTruncated;
|
|
765
|
+
for (const proof of proofs) unknown = collectLanes(proof, lanes) || unknown;
|
|
766
|
+
const premiseLanes = [...lanes].sort() as readonly OhMemoryLaneV1[];
|
|
767
|
+
const premiseAuthority: OhMemoryResultRowV1["premiseAuthority"] = unknown || premiseLanes.length === 0
|
|
768
|
+
? "unknown" : premiseLanes.includes("working") ? "working" : "canonical";
|
|
769
|
+
const payload = { premiseAuthority, premiseLanes, proofsTruncated: row.proofsTruncated,
|
|
770
|
+
supportCount: row.supportCount, v: 1 as const, values: row.values };
|
|
771
|
+
return Object.freeze({ ...payload, resultRowSha256: canonicalSha256(payload) });
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function resolvePrograms(programs: readonly OhMemoryNamedProgramV1[]): ReadonlyMap<string, OhMemoryNamedProgramV1> {
|
|
775
|
+
if (programs.length < 1 || programs.length > OH_MEMORY_LIMITS_V1.maximumPrograms) {
|
|
776
|
+
throw new RangeError("Memory requires a bounded nonempty named program registry.");
|
|
777
|
+
}
|
|
778
|
+
const resolved = new Map<string, OhMemoryNamedProgramV1>();
|
|
779
|
+
for (const program of programs) {
|
|
780
|
+
const programId = safeCode(program.programId, 128);
|
|
781
|
+
const purpose = safeCode(program.purpose, 256);
|
|
782
|
+
const query = parseOhProjectionQueryV1(program.query);
|
|
783
|
+
const rulePack = parseOhProjectionRulePackV1(program.rulePack);
|
|
784
|
+
if (programId === null || purpose === null || query === null || rulePack === null
|
|
785
|
+
|| resolved.has(programId)) {
|
|
786
|
+
throw new TypeError("Invalid or duplicate named memory program.");
|
|
787
|
+
}
|
|
788
|
+
resolved.set(programId, immutableClone({ ...(program.evaluation === undefined ? {}
|
|
789
|
+
: { evaluation: { ...program.evaluation } }), programId, purpose, query, rulePack }));
|
|
790
|
+
}
|
|
791
|
+
return resolved;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function resolveExtractors(extractors: readonly OhMemoryFactExtractorV1[]): readonly OhMemoryFactExtractorV1[] {
|
|
795
|
+
if (extractors.length > OH_MEMORY_LIMITS_V1.maximumExtractors) {
|
|
796
|
+
throw new RangeError("The memory domain extractor registry is too large.");
|
|
797
|
+
}
|
|
798
|
+
const claimedRelations = new Set<string>();
|
|
799
|
+
const resolved = extractors.map((extractor) => {
|
|
800
|
+
const extractorId = safeCode(extractor.extractorId, 128);
|
|
801
|
+
const extractorSha256 = parseSha256Hex(extractor.extractorSha256);
|
|
802
|
+
if (extractorId === null || extractorSha256 === null || typeof extractor.extract !== "function"
|
|
803
|
+
|| !Array.isArray(extractor.relations) || extractor.relations.length < 1
|
|
804
|
+
|| extractor.relations.length > OH_MEMORY_LIMITS_V1.relationsPerExtractor) {
|
|
805
|
+
throw new TypeError("Invalid memory domain fact extractor.");
|
|
806
|
+
}
|
|
807
|
+
const relations = extractor.relations.map((relation) => safeCode(relation, 128)).sort();
|
|
808
|
+
if (relations.some((relation) => relation === null
|
|
809
|
+
|| relation.startsWith("memory.") || relation.startsWith("oh."))
|
|
810
|
+
|| new Set(relations).size !== relations.length) {
|
|
811
|
+
throw new TypeError("A memory domain fact extractor has invalid or reserved relations.");
|
|
812
|
+
}
|
|
813
|
+
for (const relation of relations as string[]) {
|
|
814
|
+
if (claimedRelations.has(relation)) {
|
|
815
|
+
throw new TypeError("Memory domain fact extractor relations must have one owner.");
|
|
816
|
+
}
|
|
817
|
+
claimedRelations.add(relation);
|
|
818
|
+
}
|
|
819
|
+
return Object.freeze({ extract: extractor.extract, extractorId, extractorSha256,
|
|
820
|
+
relations: Object.freeze(relations as string[]) });
|
|
821
|
+
}).sort((left, right) => compareText(left.extractorId, right.extractorId));
|
|
822
|
+
if (new Set(resolved.map(({ extractorId }) => extractorId)).size !== resolved.length) {
|
|
823
|
+
throw new TypeError("Duplicate memory domain fact extractor ID.");
|
|
824
|
+
}
|
|
825
|
+
return Object.freeze(resolved);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function resolveNominationRoutes(routes: readonly OhMemoryNominationRouteV1[]):
|
|
829
|
+
ReadonlyMap<string, OhMemoryNominationRouteV1> {
|
|
830
|
+
if (routes.length > OH_MEMORY_LIMITS_V1.maximumNominationRoutes) {
|
|
831
|
+
throw new RangeError("The memory nomination route registry is too large.");
|
|
832
|
+
}
|
|
833
|
+
const resolved = new Map<string, OhMemoryNominationRouteV1>();
|
|
834
|
+
for (const route of routes) {
|
|
835
|
+
const nominationId = safeCode(route.nominationId, 128);
|
|
836
|
+
const destinationPurpose = safeCode(route.destinationPurpose, 256);
|
|
837
|
+
if (nominationId === null || destinationPurpose === null || resolved.has(nominationId)) {
|
|
838
|
+
throw new TypeError("Invalid or duplicate memory nomination route.");
|
|
839
|
+
}
|
|
840
|
+
resolved.set(nominationId, Object.freeze({ destinationPurpose, nominationId }));
|
|
841
|
+
}
|
|
842
|
+
return resolved;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function parseQueryRequest(value: unknown): Readonly<{ programId: string }> {
|
|
846
|
+
if (!isPlainRecord(value) || !hasExactKeys(value, ["programId", "v"])
|
|
847
|
+
|| value.v !== 1) throw new TypeError("Invalid named memory query.");
|
|
848
|
+
const programId = safeCode(value.programId, 128);
|
|
849
|
+
if (programId === null) throw new TypeError("Invalid named memory query identity.");
|
|
850
|
+
return { programId };
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
function parseExplainRequest(value: unknown): Readonly<{
|
|
854
|
+
row: number;
|
|
855
|
+
resultSha256: Sha256Hex;
|
|
856
|
+
token: string;
|
|
857
|
+
}> {
|
|
858
|
+
if (!isPlainRecord(value) || !hasExactKeys(value, ["resultSha256", "row", "token", "v"])
|
|
859
|
+
|| value.v !== 1 || typeof value.token !== "string" || value.token.length !== 43
|
|
860
|
+
|| !Number.isSafeInteger(value.row) || (value.row as number) < 0) {
|
|
861
|
+
throw new TypeError("Invalid memory explanation request.");
|
|
862
|
+
}
|
|
863
|
+
const resultSha256 = parseSha256Hex(value.resultSha256);
|
|
864
|
+
if (resultSha256 === null) throw new TypeError("Invalid memory explanation result identity.");
|
|
865
|
+
return { resultSha256, row: value.row as number, token: value.token };
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
function parseNominationRequest(value: unknown): Readonly<{
|
|
869
|
+
nominationId: string;
|
|
870
|
+
roots: readonly string[];
|
|
871
|
+
}> {
|
|
872
|
+
if (!isPlainRecord(value) || !hasExactKeys(value, ["nominationId", "roots", "v"])
|
|
873
|
+
|| value.v !== 1 || !Array.isArray(value.roots) || value.roots.length < 1
|
|
874
|
+
|| value.roots.length > OH_DEPENDENCY_CLOSURE_LIMITS_V1.roots) {
|
|
875
|
+
throw new TypeError("Invalid memory nomination request.");
|
|
876
|
+
}
|
|
877
|
+
const nominationId = safeCode(value.nominationId, 128);
|
|
878
|
+
const roots = value.roots.map((root) => safeCode(root, 512)).sort();
|
|
879
|
+
if (nominationId === null || roots.some((root) => root === null)
|
|
880
|
+
|| new Set(roots).size !== roots.length) throw new TypeError("Invalid memory nomination identity.");
|
|
881
|
+
return { nominationId, roots: roots as readonly string[] };
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function isoInstant(date: Date): string {
|
|
885
|
+
const value = date.toISOString();
|
|
886
|
+
if (parseCanonicalInstantV1(value) === null) throw new TypeError("The memory clock returned an invalid instant.");
|
|
887
|
+
return value;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
function clockMilliseconds(now: () => Date): number {
|
|
891
|
+
const milliseconds = now().getTime();
|
|
892
|
+
if (!Number.isFinite(milliseconds)) throw new TypeError("The memory clock returned an invalid date.");
|
|
893
|
+
return milliseconds;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
function monotonicMilliseconds(now: () => number): number {
|
|
897
|
+
const milliseconds = now();
|
|
898
|
+
if (!Number.isFinite(milliseconds) || milliseconds < 0) {
|
|
899
|
+
throw new TypeError("The memory monotonic clock returned an invalid value.");
|
|
900
|
+
}
|
|
901
|
+
return milliseconds;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* Creates a model-facing memory surface over two host-bound physical Oh
|
|
906
|
+
* authorities. The returned object has no store, locator, rule, sync, canonical
|
|
907
|
+
* write, or purge handle.
|
|
908
|
+
*/
|
|
909
|
+
export async function createOhMemoryAgentV1(options: OhMemoryFacadeOptionsV1): Promise<OhMemoryAgentV1> {
|
|
910
|
+
const memoryActorId = safeCode(options.actorId, 128);
|
|
911
|
+
if (memoryActorId === null) throw new TypeError("Invalid host-bound memory actor ID.");
|
|
912
|
+
const canonicalStore = options.canonical.store;
|
|
913
|
+
const workingStore = options.working.store;
|
|
914
|
+
const workingCodecs = options.working.codecs;
|
|
915
|
+
const canonicalAuthorityId = authorityId(options.canonical.authorityId);
|
|
916
|
+
const workingAuthorityId = authorityId(options.working.authorityId);
|
|
917
|
+
if (canonicalAuthorityId === workingAuthorityId) {
|
|
918
|
+
throw new OhProfileError("Working and canonical memory must be distinct physical authorities.");
|
|
919
|
+
}
|
|
920
|
+
const canonicalBinding = bindingFor(canonicalStore,
|
|
921
|
+
options.canonical.expectedBindingSha256, "canonical");
|
|
922
|
+
const workingBinding = bindingFor(workingStore,
|
|
923
|
+
options.working.expectedBindingSha256, "working");
|
|
924
|
+
const expectedCanonicalHead = parseOhHeadV1(options.canonical.expectedHead);
|
|
925
|
+
if (expectedCanonicalHead === null) throw new TypeError("Invalid pinned canonical memory head.");
|
|
926
|
+
const programs = resolvePrograms(options.programs);
|
|
927
|
+
const extractors = resolveExtractors(options.extractors ?? []);
|
|
928
|
+
const nominationRoutes = resolveNominationRoutes(options.nominationRoutes ?? []);
|
|
929
|
+
const ingress = new OhSemanticBundleIngressV1(workingStore, workingCodecs);
|
|
930
|
+
const now = options.now ?? (() => new Date());
|
|
931
|
+
const monotonicNow = options.monotonicNow ?? (() => performance.now());
|
|
932
|
+
const capabilityLifetime = options.explainCapabilityLifetimeMs
|
|
933
|
+
?? OH_MEMORY_LIMITS_V1.explainCapabilityLifetimeMs;
|
|
934
|
+
if (!Number.isSafeInteger(capabilityLifetime) || capabilityLifetime < 1_000
|
|
935
|
+
|| capabilityLifetime > 60 * 60 * 1_000) {
|
|
936
|
+
throw new RangeError("Invalid memory explanation capability lifetime.");
|
|
937
|
+
}
|
|
938
|
+
const canonical = await readLane({ authorityId: canonicalAuthorityId,
|
|
939
|
+
binding: canonicalBinding, store: canonicalStore }, "canonical", expectedCanonicalHead);
|
|
940
|
+
const explanations = new Map<string, StoredExplanation>();
|
|
941
|
+
let explanationBytes = 0;
|
|
942
|
+
let lastMonotonicMs = -1;
|
|
943
|
+
let lastWallClockMs = Number.NEGATIVE_INFINITY;
|
|
944
|
+
const wallClock = () => {
|
|
945
|
+
const milliseconds = clockMilliseconds(now);
|
|
946
|
+
if (milliseconds < lastWallClockMs) throw new OhProfileError("The memory wall clock regressed.");
|
|
947
|
+
lastWallClockMs = milliseconds;
|
|
948
|
+
return milliseconds;
|
|
949
|
+
};
|
|
950
|
+
const monotonicClock = () => {
|
|
951
|
+
const milliseconds = monotonicMilliseconds(monotonicNow);
|
|
952
|
+
if (milliseconds < lastMonotonicMs) throw new OhProfileError("The memory monotonic clock regressed.");
|
|
953
|
+
lastMonotonicMs = milliseconds;
|
|
954
|
+
return milliseconds;
|
|
955
|
+
};
|
|
956
|
+
const deleteExplanation = (token: string) => {
|
|
957
|
+
const stored = explanations.get(token);
|
|
958
|
+
if (stored !== undefined && explanations.delete(token)) explanationBytes -= stored.bytes;
|
|
959
|
+
};
|
|
960
|
+
|
|
961
|
+
const remember = async (value: unknown): Promise<OhMemoryRememberReceiptV1> => {
|
|
962
|
+
if (utf8ByteLength(canonicalJson(value)) > OH_MEMORY_LIMITS_V1.rememberBytes) {
|
|
963
|
+
throw new RangeError("The memory semantic bundle exceeds its canonical byte bound.");
|
|
964
|
+
}
|
|
965
|
+
if (!isPlainRecord(value) || !hasExactKeys(value,
|
|
966
|
+
["expectedHead", "puts", "requestId", "tombstones", "v"]) || value.v !== 1) {
|
|
967
|
+
throw new TypeError("Invalid memory remember request.");
|
|
968
|
+
}
|
|
969
|
+
const requestId = safeCode(value.requestId, 128);
|
|
970
|
+
if (requestId === null) throw new TypeError("Invalid memory remember request identity.");
|
|
971
|
+
const operationId = `memory_${canonicalSha256({ actorId: memoryActorId,
|
|
972
|
+
bindingSha256: workingBinding.bindingSha256, requestId, v: 1 }).slice(0, 48)}`;
|
|
973
|
+
const operation = await ingress.commit({ actorId: memoryActorId,
|
|
974
|
+
expectedHead: value.expectedHead, instant: isoInstant(new Date(wallClock())), operationId,
|
|
975
|
+
puts: value.puts, tombstones: value.tombstones, v: 1 });
|
|
976
|
+
const head: OhHeadV1 = { generation: operation.sequence,
|
|
977
|
+
graphRevisionSha256: operation.graphRevisionSha256,
|
|
978
|
+
operationSha256: operation.operationSha256, recordsSha256: operation.recordsSha256,
|
|
979
|
+
sequence: operation.sequence, v: 1 };
|
|
980
|
+
const payload = { actorId: operation.actorId, authorityId: workingAuthorityId,
|
|
981
|
+
bindingSha256: workingBinding.bindingSha256, head, instant: operation.instant,
|
|
982
|
+
lane: "working" as const, operationSha256: operation.operationSha256, requestId,
|
|
983
|
+
status: "committed" as const, v: 1 as const };
|
|
984
|
+
return immutableClone({ ...payload, receiptSha256: canonicalSha256(payload) });
|
|
985
|
+
};
|
|
986
|
+
|
|
987
|
+
const query = async (value: unknown): Promise<OhMemoryQueryResultV1> => {
|
|
988
|
+
const request = parseQueryRequest(value);
|
|
989
|
+
const program = programs.get(request.programId);
|
|
990
|
+
if (program === undefined) throw new TypeError("Unknown named memory program.");
|
|
991
|
+
const working = await readLane({ authorityId: workingAuthorityId,
|
|
992
|
+
binding: workingBinding, store: workingStore }, "working");
|
|
993
|
+
const composite = createCompositeDataset(canonical, working, extractors);
|
|
994
|
+
const projection = evaluateOhProjectionV1({ dataset: composite.dataset,
|
|
995
|
+
...(program.evaluation === undefined ? {} : { options: program.evaluation }),
|
|
996
|
+
query: program.query, rulePack: program.rulePack, snapshot: composite.snapshot });
|
|
997
|
+
const identityPayload = {
|
|
998
|
+
canonical: laneIdentity(canonical),
|
|
999
|
+
compositeDatasetSha256: composite.dataset.datasetSha256,
|
|
1000
|
+
conflictPolicy: OH_MEMORY_CONFLICT_POLICY_V1,
|
|
1001
|
+
evaluationSha256: projection.identity.evaluationSha256,
|
|
1002
|
+
programId: program.programId,
|
|
1003
|
+
projectionSha256: projection.identity.projectionSha256,
|
|
1004
|
+
purpose: program.purpose,
|
|
1005
|
+
querySha256: program.query.querySha256,
|
|
1006
|
+
rulePackSha256: program.rulePack.rulePackSha256,
|
|
1007
|
+
v: 1 as const,
|
|
1008
|
+
working: laneIdentity(working),
|
|
1009
|
+
};
|
|
1010
|
+
const identity: OhMemoryIdentityV1 = immutableClone({ ...identityPayload,
|
|
1011
|
+
memorySha256: canonicalSha256(identityPayload) });
|
|
1012
|
+
const proofs = immutableClone(projection.rows.map((row) =>
|
|
1013
|
+
row.proofs.map((proof) => mapProof(proof, composite.sources, composite.factPolicies))));
|
|
1014
|
+
const rows = immutableClone(projection.rows.map((row, index) => publicRow(row, proofs[index]!)));
|
|
1015
|
+
const resultPayload = immutableClone({ authority: "derived" as const,
|
|
1016
|
+
conflicts: composite.conflicts, identity, projectionResultSha256: projection.resultSha256,
|
|
1017
|
+
rows, v: 1 as const });
|
|
1018
|
+
const resultSha256 = canonicalSha256(resultPayload);
|
|
1019
|
+
const issuedAt = wallClock();
|
|
1020
|
+
const issuedAtMonotonic = monotonicClock();
|
|
1021
|
+
const expiresAtMs = issuedAt + capabilityLifetime;
|
|
1022
|
+
const expiresAtMonotonicMs = issuedAtMonotonic + capabilityLifetime;
|
|
1023
|
+
const expiresAt = isoInstant(new Date(expiresAtMs));
|
|
1024
|
+
for (const [existingToken, stored] of explanations) {
|
|
1025
|
+
if (issuedAtMonotonic >= stored.expiresAtMonotonicMs) deleteExplanation(existingToken);
|
|
1026
|
+
}
|
|
1027
|
+
const storedPayload = immutableClone({ expiresAtMonotonicMs, identity, proofs, resultSha256, rows });
|
|
1028
|
+
const storedBytes = utf8ByteLength(canonicalJson(storedPayload)) + 128;
|
|
1029
|
+
if (storedBytes > OH_MEMORY_LIMITS_V1.explainCapabilityEntryBytes
|
|
1030
|
+
|| storedBytes > OH_MEMORY_LIMITS_V1.explainCapabilityTotalBytes) {
|
|
1031
|
+
throw new RangeError("The memory explanation exceeds its retained capability bound.");
|
|
1032
|
+
}
|
|
1033
|
+
while (explanations.size >= OH_MEMORY_LIMITS_V1.explainCapabilities
|
|
1034
|
+
|| explanationBytes + storedBytes > OH_MEMORY_LIMITS_V1.explainCapabilityTotalBytes) {
|
|
1035
|
+
const oldest = explanations.keys().next().value as string | undefined;
|
|
1036
|
+
if (oldest === undefined) break;
|
|
1037
|
+
deleteExplanation(oldest);
|
|
1038
|
+
}
|
|
1039
|
+
let token = randomBytes(32).toString("base64url");
|
|
1040
|
+
while (explanations.has(token)) token = randomBytes(32).toString("base64url");
|
|
1041
|
+
explanations.set(token, immutableClone({ ...storedPayload, bytes: storedBytes }));
|
|
1042
|
+
explanationBytes += storedBytes;
|
|
1043
|
+
const result = immutableClone({ ...resultPayload,
|
|
1044
|
+
explainCapability: { expiresAt, token, v: 1 as const }, resultSha256 });
|
|
1045
|
+
if (utf8ByteLength(canonicalJson(result)) > OH_MEMORY_LIMITS_V1.resultBytes) {
|
|
1046
|
+
deleteExplanation(token);
|
|
1047
|
+
throw new RangeError("The composite memory result exceeds its canonical byte bound.");
|
|
1048
|
+
}
|
|
1049
|
+
return result;
|
|
1050
|
+
};
|
|
1051
|
+
|
|
1052
|
+
const explain = async (value: unknown): Promise<OhMemoryExplanationV1> => {
|
|
1053
|
+
const request = parseExplainRequest(value);
|
|
1054
|
+
const stored = explanations.get(request.token);
|
|
1055
|
+
const currentTime = monotonicClock();
|
|
1056
|
+
if (stored === undefined || stored.resultSha256 !== request.resultSha256
|
|
1057
|
+
|| currentTime >= stored.expiresAtMonotonicMs) {
|
|
1058
|
+
deleteExplanation(request.token);
|
|
1059
|
+
throw new OhProfileError("The memory explanation capability is absent, expired, or misbound.");
|
|
1060
|
+
}
|
|
1061
|
+
const row = stored.rows[request.row];
|
|
1062
|
+
const proofs = stored.proofs[request.row];
|
|
1063
|
+
if (row === undefined || proofs === undefined) throw new RangeError("The explanation row is out of bounds.");
|
|
1064
|
+
const payload = { authority: "derived" as const, identity: stored.identity,
|
|
1065
|
+
premiseAuthority: row.premiseAuthority, premiseLanes: row.premiseLanes, proofs,
|
|
1066
|
+
proofsTruncated: row.proofsTruncated, resultRowSha256: row.resultRowSha256,
|
|
1067
|
+
resultSha256: stored.resultSha256, supportCount: row.supportCount, v: 1 as const,
|
|
1068
|
+
values: row.values };
|
|
1069
|
+
return immutableClone({ ...payload, explanationSha256: canonicalSha256(payload) });
|
|
1070
|
+
};
|
|
1071
|
+
|
|
1072
|
+
const nominate = async (value: unknown): Promise<OhMemoryNominationV1> => {
|
|
1073
|
+
const request = parseNominationRequest(value);
|
|
1074
|
+
const route = nominationRoutes.get(request.nominationId);
|
|
1075
|
+
if (route === undefined) throw new TypeError("Unknown named memory nomination route.");
|
|
1076
|
+
const head = parseOhHeadV1(immutableClone(await workingStore.head()));
|
|
1077
|
+
if (head === null) throw new OhIntegrityError("The working nomination store returned an invalid head.");
|
|
1078
|
+
const closure = await workingStore.exportDependencyClosure({ head: {
|
|
1079
|
+
operationSha256: head.operationSha256, sequence: head.sequence }, roots: request.roots });
|
|
1080
|
+
const verified = verifyOhDependencyClosureAgainstV1(closure, { binding: workingBinding, head });
|
|
1081
|
+
if (!verified.ok) throw new OhIntegrityError("The working nomination closure failed exact verification.");
|
|
1082
|
+
if (canonicalJson(verified.closure.roots) !== canonicalJson(request.roots)) {
|
|
1083
|
+
throw new OhIntegrityError("The working nomination closure substituted different roots.");
|
|
1084
|
+
}
|
|
1085
|
+
const source = Object.freeze({ authorityId: workingAuthorityId,
|
|
1086
|
+
bindingSha256: workingBinding.bindingSha256, head, lane: "working" as const, v: 1 as const });
|
|
1087
|
+
const payload = { closure: verified.closure, destinationPurpose: route.destinationPurpose,
|
|
1088
|
+
nominationId: route.nominationId, source, status: "prepared" as const, v: 1 as const };
|
|
1089
|
+
return immutableClone({ ...payload, nominationSha256: canonicalSha256(payload) });
|
|
1090
|
+
};
|
|
1091
|
+
|
|
1092
|
+
return Object.freeze({ explain, nominate, query, remember });
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
type ResolvedMemoryProgramV2 = OhMemoryNamedProgramV2 & Readonly<{
|
|
1096
|
+
programSha256: Sha256Hex;
|
|
1097
|
+
}>;
|
|
1098
|
+
|
|
1099
|
+
type StoredExplanationV2 = Readonly<{
|
|
1100
|
+
bytes: number;
|
|
1101
|
+
expiresAtMonotonicMs: number;
|
|
1102
|
+
identity: OhMemoryIdentityV2;
|
|
1103
|
+
page: OhMemoryPageV2;
|
|
1104
|
+
proofs: readonly (readonly OhMemoryProofV1[])[];
|
|
1105
|
+
resultSha256: Sha256Hex;
|
|
1106
|
+
rows: readonly OhMemoryResultRowV2[];
|
|
1107
|
+
}>;
|
|
1108
|
+
|
|
1109
|
+
type OhMemoryContinuationIdentityV2 = Readonly<{
|
|
1110
|
+
bindingsSha256: Sha256Hex;
|
|
1111
|
+
memorySha256: Sha256Hex;
|
|
1112
|
+
nextOffset: number;
|
|
1113
|
+
pageSize: number;
|
|
1114
|
+
programSha256: Sha256Hex;
|
|
1115
|
+
projectionResultSha256: Sha256Hex;
|
|
1116
|
+
totalRows: number;
|
|
1117
|
+
v: 2;
|
|
1118
|
+
}>;
|
|
1119
|
+
|
|
1120
|
+
type OhMemoryContinuationV2 = OhMemoryContinuationIdentityV2 & Readonly<{
|
|
1121
|
+
continuationSha256: Sha256Hex;
|
|
1122
|
+
}>;
|
|
1123
|
+
|
|
1124
|
+
type OhMemoryContinuationEnvelopeV2 = OhMemoryContinuationV2 & Readonly<{
|
|
1125
|
+
continuationHmacSha256: Sha256Hex;
|
|
1126
|
+
}>;
|
|
1127
|
+
|
|
1128
|
+
function ownDataKeysV2(value: unknown, maximum: number, label: string): readonly string[] {
|
|
1129
|
+
if (!isPlainRecord(value)) throw new TypeError(`${label} must be a plain data object.`);
|
|
1130
|
+
const ownKeys = Reflect.ownKeys(value);
|
|
1131
|
+
if (ownKeys.length > maximum) throw new RangeError(`${label} has too many entries.`);
|
|
1132
|
+
if (ownKeys.some((key) => typeof key !== "string")) {
|
|
1133
|
+
throw new TypeError(`${label} must have only string data properties.`);
|
|
1134
|
+
}
|
|
1135
|
+
const keys = ownKeys as string[];
|
|
1136
|
+
for (const key of keys) {
|
|
1137
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
1138
|
+
if (descriptor === undefined || !descriptor.enumerable || !("value" in descriptor)) {
|
|
1139
|
+
throw new TypeError(`${label} must have only enumerable data properties.`);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
return keys;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
function continuationKeyV2(value: Uint8Array | undefined): Uint8Array {
|
|
1146
|
+
if (value === undefined) return Uint8Array.from(randomBytes(32));
|
|
1147
|
+
if (!(value instanceof Uint8Array)
|
|
1148
|
+
|| value.byteLength < OH_MEMORY_QUERY_LIMITS_V2.continuationKeyMinimumBytes
|
|
1149
|
+
|| value.byteLength > OH_MEMORY_QUERY_LIMITS_V2.continuationKeyMaximumBytes) {
|
|
1150
|
+
throw new RangeError("The V2 memory continuation key must be 32 through 64 raw bytes.");
|
|
1151
|
+
}
|
|
1152
|
+
return Uint8Array.from(value);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
function positiveBounded(value: unknown, maximum: number, label: string, minimum = 1): number {
|
|
1156
|
+
if (!Number.isSafeInteger(value) || (value as number) < minimum || (value as number) > maximum) {
|
|
1157
|
+
throw new RangeError(`${label} must be an integer from ${minimum} through ${maximum}.`);
|
|
1158
|
+
}
|
|
1159
|
+
return value as number;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
function resolveEvaluationV2(value: unknown): OhMemoryEvaluationLimitsV2 {
|
|
1163
|
+
if (!isPlainRecord(value) || !hasExactKeys(value, ["maximumDerivedTuples", "maximumProofDepth",
|
|
1164
|
+
"maximumProofNodes", "maximumResultBytes", "maximumRounds", "maximumTotalProofNodes",
|
|
1165
|
+
"maximumWorkUnits"])) {
|
|
1166
|
+
throw new TypeError("A V2 memory program must declare every projection evaluation limit.");
|
|
1167
|
+
}
|
|
1168
|
+
return Object.freeze({
|
|
1169
|
+
maximumDerivedTuples: positiveBounded(value.maximumDerivedTuples,
|
|
1170
|
+
OH_PROJECTION_LIMITS_V1.derivedTuples, "maximumDerivedTuples"),
|
|
1171
|
+
maximumProofDepth: positiveBounded(value.maximumProofDepth,
|
|
1172
|
+
OH_PROJECTION_LIMITS_V1.proofDepth, "maximumProofDepth"),
|
|
1173
|
+
maximumProofNodes: positiveBounded(value.maximumProofNodes,
|
|
1174
|
+
OH_PROJECTION_LIMITS_V1.proofNodes, "maximumProofNodes"),
|
|
1175
|
+
maximumResultBytes: positiveBounded(value.maximumResultBytes,
|
|
1176
|
+
OH_PROJECTION_LIMITS_V1.resultBytes, "maximumResultBytes", 64 * 1024),
|
|
1177
|
+
maximumRounds: positiveBounded(value.maximumRounds,
|
|
1178
|
+
OH_PROJECTION_LIMITS_V1.rounds, "maximumRounds"),
|
|
1179
|
+
maximumTotalProofNodes: positiveBounded(value.maximumTotalProofNodes,
|
|
1180
|
+
OH_PROJECTION_LIMITS_V1.totalProofNodes, "maximumTotalProofNodes"),
|
|
1181
|
+
maximumWorkUnits: positiveBounded(value.maximumWorkUnits,
|
|
1182
|
+
OH_PROJECTION_LIMITS_V1.workUnits, "maximumWorkUnits"),
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
function resolveProgramsV2(programs: readonly OhMemoryNamedProgramV2[]):
|
|
1187
|
+
ReadonlyMap<string, ResolvedMemoryProgramV2> {
|
|
1188
|
+
if (!Array.isArray(programs) || programs.length < 1
|
|
1189
|
+
|| programs.length > OH_MEMORY_LIMITS_V1.maximumPrograms) {
|
|
1190
|
+
throw new RangeError("Memory requires a bounded nonempty V2 named program registry.");
|
|
1191
|
+
}
|
|
1192
|
+
const resolved = new Map<string, ResolvedMemoryProgramV2>();
|
|
1193
|
+
for (const candidate of programs) {
|
|
1194
|
+
if (!isPlainRecord(candidate) || !hasExactKeys(candidate, ["evaluation", "maximumPageBytes",
|
|
1195
|
+
"maximumRows", "pageSize", "parameters", "programId", "purpose", "query", "rulePack", "v"])
|
|
1196
|
+
|| candidate.v !== 2 || !Array.isArray(candidate.parameters)) {
|
|
1197
|
+
throw new TypeError("Invalid V2 named memory program.");
|
|
1198
|
+
}
|
|
1199
|
+
const programId = safeCode(candidate.programId, 128);
|
|
1200
|
+
const purpose = safeCode(candidate.purpose, 256);
|
|
1201
|
+
const query = parseOhProjectionQueryV1(candidate.query);
|
|
1202
|
+
const rulePack = parseOhProjectionRulePackV1(candidate.rulePack);
|
|
1203
|
+
const evaluation = resolveEvaluationV2(candidate.evaluation);
|
|
1204
|
+
const maximumRows = positiveBounded(candidate.maximumRows,
|
|
1205
|
+
OH_MEMORY_QUERY_LIMITS_V2.maximumProgramRows, "maximumRows");
|
|
1206
|
+
const pageSize = positiveBounded(candidate.pageSize,
|
|
1207
|
+
Math.min(maximumRows, OH_MEMORY_QUERY_LIMITS_V2.maximumPageRows), "pageSize");
|
|
1208
|
+
const maximumPageBytes = positiveBounded(candidate.maximumPageBytes,
|
|
1209
|
+
OH_MEMORY_QUERY_LIMITS_V2.maximumPageBytes, "maximumPageBytes",
|
|
1210
|
+
OH_MEMORY_QUERY_LIMITS_V2.minimumPageBytes);
|
|
1211
|
+
if (programId === null || purpose === null || query === null || rulePack === null
|
|
1212
|
+
|| resolved.has(programId) || query.limit !== maximumRows
|
|
1213
|
+
|| candidate.parameters.length > OH_MEMORY_QUERY_LIMITS_V2.bindings) {
|
|
1214
|
+
throw new TypeError("Invalid or duplicate V2 named memory program.");
|
|
1215
|
+
}
|
|
1216
|
+
const parameters = candidate.parameters.map((parameter) => safeCode(parameter, 128)).sort();
|
|
1217
|
+
if (parameters.some((parameter) => parameter === null)
|
|
1218
|
+
|| new Set(parameters).size !== parameters.length) {
|
|
1219
|
+
throw new TypeError("A V2 memory program has invalid or duplicate parameters.");
|
|
1220
|
+
}
|
|
1221
|
+
const queryVariables = new Set(query.where.flatMap((literal) => literal.terms.flatMap((term) =>
|
|
1222
|
+
term.kind === "variable" ? [term.name] : [])));
|
|
1223
|
+
if ((parameters as readonly string[]).some((parameter) => !queryVariables.has(parameter)
|
|
1224
|
+
|| query.find.includes(parameter))) {
|
|
1225
|
+
throw new TypeError("V2 parameters must be query-body variables that are not projected outputs.");
|
|
1226
|
+
}
|
|
1227
|
+
const detachedParameters = Object.freeze(parameters as string[]);
|
|
1228
|
+
const programPayload = {
|
|
1229
|
+
evaluation,
|
|
1230
|
+
maximumPageBytes,
|
|
1231
|
+
maximumRows,
|
|
1232
|
+
pageSize,
|
|
1233
|
+
parameters: detachedParameters,
|
|
1234
|
+
programId,
|
|
1235
|
+
purpose,
|
|
1236
|
+
querySha256: query.querySha256,
|
|
1237
|
+
rulePackSha256: rulePack.rulePackSha256,
|
|
1238
|
+
v: 2 as const,
|
|
1239
|
+
};
|
|
1240
|
+
const program = immutableClone({ evaluation, maximumPageBytes, maximumRows, pageSize,
|
|
1241
|
+
parameters: detachedParameters, programId, programSha256: canonicalSha256(programPayload),
|
|
1242
|
+
purpose, query, rulePack, v: 2 as const });
|
|
1243
|
+
resolved.set(programId, program);
|
|
1244
|
+
}
|
|
1245
|
+
return resolved;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
function parsePrimitiveBindingV2(value: unknown): JsonPrimitive {
|
|
1249
|
+
if (value !== null && typeof value !== "boolean" && typeof value !== "number"
|
|
1250
|
+
&& typeof value !== "string") throw new TypeError("Memory bindings must be JSON primitives.");
|
|
1251
|
+
if (typeof value === "string" && value.length > OH_PROJECTION_LIMITS_V1.atomBytes) {
|
|
1252
|
+
throw new RangeError("A memory binding exceeds the projection atom byte bound.");
|
|
1253
|
+
}
|
|
1254
|
+
if (typeof value === "number" && (!Number.isFinite(value) || Object.is(value, -0))) {
|
|
1255
|
+
throw new TypeError("Memory bindings must be canonical finite JSON numbers.");
|
|
1256
|
+
}
|
|
1257
|
+
const serialized = canonicalJson(value);
|
|
1258
|
+
if (utf8ByteLength(serialized) > OH_PROJECTION_LIMITS_V1.atomBytes) {
|
|
1259
|
+
throw new RangeError("A memory binding exceeds the projection atom byte bound.");
|
|
1260
|
+
}
|
|
1261
|
+
return value as JsonPrimitive;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
function parseQueryRequestV2(value: unknown): Readonly<{
|
|
1265
|
+
bindingsValue: Readonly<Record<string, JsonPrimitive>>;
|
|
1266
|
+
continuation: string | null;
|
|
1267
|
+
programId: string;
|
|
1268
|
+
}> {
|
|
1269
|
+
let keys: readonly string[];
|
|
1270
|
+
try {
|
|
1271
|
+
keys = ownDataKeysV2(value, 4, "The parameterized memory query");
|
|
1272
|
+
} catch {
|
|
1273
|
+
throw new TypeError("Invalid parameterized memory query.");
|
|
1274
|
+
}
|
|
1275
|
+
if (keys.length !== 4 || !["bindings", "continuation", "programId", "v"]
|
|
1276
|
+
.every((key) => keys.includes(key))) {
|
|
1277
|
+
throw new TypeError("Invalid parameterized memory query.");
|
|
1278
|
+
}
|
|
1279
|
+
const record = value as Record<string, unknown>;
|
|
1280
|
+
if (record.v !== 2 || (record.continuation !== null
|
|
1281
|
+
&& typeof record.continuation !== "string")) {
|
|
1282
|
+
throw new TypeError("Invalid parameterized memory query.");
|
|
1283
|
+
}
|
|
1284
|
+
const programId = safeCode(record.programId, 128);
|
|
1285
|
+
if (programId === null) throw new TypeError("Invalid parameterized memory query identity.");
|
|
1286
|
+
const continuation = record.continuation as string | null;
|
|
1287
|
+
if (typeof continuation === "string" && (continuation.length < 1
|
|
1288
|
+
|| continuation.length > OH_MEMORY_QUERY_LIMITS_V2.continuationBytes
|
|
1289
|
+
|| utf8ByteLength(continuation) > OH_MEMORY_QUERY_LIMITS_V2.continuationBytes)) {
|
|
1290
|
+
throw new RangeError("The memory continuation exceeds its byte bound.");
|
|
1291
|
+
}
|
|
1292
|
+
const bindingKeys = ownDataKeysV2(record.bindings, OH_MEMORY_QUERY_LIMITS_V2.bindings,
|
|
1293
|
+
"The parameterized memory query bindings");
|
|
1294
|
+
const bindingRecord = record.bindings as Record<string, unknown>;
|
|
1295
|
+
const bindings: Record<string, JsonPrimitive> = {};
|
|
1296
|
+
for (const key of bindingKeys) {
|
|
1297
|
+
if (safeCode(key, 128) === null) throw new TypeError("Invalid memory binding name.");
|
|
1298
|
+
bindings[key] = parsePrimitiveBindingV2(bindingRecord[key]);
|
|
1299
|
+
}
|
|
1300
|
+
const boundedRequest = { bindings, continuation, programId, v: 2 as const };
|
|
1301
|
+
if (utf8ByteLength(canonicalJson(boundedRequest)) > OH_MEMORY_QUERY_LIMITS_V2.requestBytes) {
|
|
1302
|
+
throw new RangeError("The parameterized memory query exceeds its canonical byte bound.");
|
|
1303
|
+
}
|
|
1304
|
+
return { bindingsValue: immutableClone(bindings), continuation, programId };
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
function parseBindingsV2(value: Readonly<Record<string, JsonPrimitive>>,
|
|
1308
|
+
parameters: readonly string[]): Readonly<{
|
|
1309
|
+
bindings: Readonly<Record<string, JsonPrimitive>>;
|
|
1310
|
+
bindingsSha256: Sha256Hex;
|
|
1311
|
+
}> {
|
|
1312
|
+
if (!isPlainRecord(value) || !hasExactKeys(value, parameters)) {
|
|
1313
|
+
throw new TypeError("Memory query bindings must exactly match the host-declared parameters.");
|
|
1314
|
+
}
|
|
1315
|
+
const bindings: Record<string, JsonPrimitive> = {};
|
|
1316
|
+
for (const parameter of parameters) bindings[parameter] = value[parameter]!;
|
|
1317
|
+
if (utf8ByteLength(canonicalJson(bindings)) > OH_MEMORY_QUERY_LIMITS_V2.bindingBytes) {
|
|
1318
|
+
throw new RangeError("Memory query bindings exceed their canonical byte bound.");
|
|
1319
|
+
}
|
|
1320
|
+
const detached = immutableClone(bindings);
|
|
1321
|
+
return Object.freeze({ bindings: detached,
|
|
1322
|
+
bindingsSha256: canonicalSha256({ bindings: detached, parameters, v: 2 }) });
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
function bindQueryV2(query: OhProjectionQueryV1,
|
|
1326
|
+
bindings: Readonly<Record<string, JsonPrimitive>>): OhProjectionQueryV1 {
|
|
1327
|
+
const where = query.where.map((literal) => createOhProjectionLiteralV1({
|
|
1328
|
+
relation: literal.relation,
|
|
1329
|
+
terms: literal.terms.map((term) => term.kind === "variable" && Object.hasOwn(bindings, term.name)
|
|
1330
|
+
? ohProjectionConstantV1(bindings[term.name]!) : term),
|
|
1331
|
+
}));
|
|
1332
|
+
return createOhProjectionQueryV1({ find: query.find, limit: query.limit,
|
|
1333
|
+
queryId: query.queryId, where });
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
function publicRowV2(row: OhProjectionResultRowV1,
|
|
1337
|
+
proofs: readonly OhMemoryProofV1[]): OhMemoryResultRowV2 {
|
|
1338
|
+
const lanes = new Set<OhMemoryLaneV1>();
|
|
1339
|
+
let unknown = row.proofsTruncated;
|
|
1340
|
+
for (const proof of proofs) unknown = collectLanes(proof, lanes) || unknown;
|
|
1341
|
+
const premiseLanes = [...lanes].sort() as readonly OhMemoryLaneV1[];
|
|
1342
|
+
const premiseAuthority: OhMemoryResultRowV2["premiseAuthority"] = unknown || premiseLanes.length === 0
|
|
1343
|
+
? "unknown" : premiseLanes.includes("working") ? "working" : "canonical";
|
|
1344
|
+
const payload = { premiseAuthority, premiseLanes, proofsTruncated: row.proofsTruncated,
|
|
1345
|
+
supportCount: row.supportCount, v: 2 as const, values: row.values };
|
|
1346
|
+
return Object.freeze({ ...payload, resultRowSha256: canonicalSha256(payload) });
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
function continuationHmacV2(key: Uint8Array, value: OhMemoryContinuationV2): Buffer {
|
|
1350
|
+
return createHmac("sha256", key).update("oh.memory.continuation.v2\0", "utf8")
|
|
1351
|
+
.update(canonicalJson(value), "utf8").digest();
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
function encodeContinuationV2(value: OhMemoryContinuationIdentityV2,
|
|
1355
|
+
key: Uint8Array): Readonly<{ continuation: string; continuationSha256: Sha256Hex }> {
|
|
1356
|
+
const identity = immutableClone(value);
|
|
1357
|
+
const continuationSha256 = canonicalSha256(identity);
|
|
1358
|
+
const signed = immutableClone({ ...identity, continuationSha256 });
|
|
1359
|
+
const envelope: OhMemoryContinuationEnvelopeV2 = immutableClone({ ...signed,
|
|
1360
|
+
continuationHmacSha256: continuationHmacV2(key, signed).toString("hex") as Sha256Hex });
|
|
1361
|
+
const continuation = Buffer.from(canonicalJson(envelope), "utf8").toString("base64url");
|
|
1362
|
+
if (utf8ByteLength(continuation) > OH_MEMORY_QUERY_LIMITS_V2.continuationBytes) {
|
|
1363
|
+
throw new RangeError("The issued memory continuation exceeds its byte bound.");
|
|
1364
|
+
}
|
|
1365
|
+
return Object.freeze({ continuation, continuationSha256 });
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
function parseContinuationV2(value: string, key: Uint8Array): OhMemoryContinuationV2 {
|
|
1369
|
+
if (value.length < 1 || utf8ByteLength(value) > OH_MEMORY_QUERY_LIMITS_V2.continuationBytes
|
|
1370
|
+
|| !/^[A-Za-z0-9_-]+$/u.test(value)) throw new TypeError("Invalid memory continuation encoding.");
|
|
1371
|
+
const bytes = Buffer.from(value, "base64url");
|
|
1372
|
+
if (bytes.toString("base64url") !== value
|
|
1373
|
+
|| bytes.byteLength > OH_MEMORY_QUERY_LIMITS_V2.continuationBytes) {
|
|
1374
|
+
throw new TypeError("Invalid memory continuation encoding.");
|
|
1375
|
+
}
|
|
1376
|
+
const text = bytes.toString("utf8");
|
|
1377
|
+
let decoded: unknown;
|
|
1378
|
+
try { decoded = JSON.parse(text); } catch { throw new TypeError("Invalid memory continuation JSON."); }
|
|
1379
|
+
if (!isPlainRecord(decoded)
|
|
1380
|
+
|| !hasExactKeys(decoded, ["bindingsSha256", "continuationHmacSha256", "continuationSha256",
|
|
1381
|
+
"memorySha256", "nextOffset", "pageSize", "programSha256", "projectionResultSha256",
|
|
1382
|
+
"totalRows", "v"])
|
|
1383
|
+
|| decoded.v !== 2) throw new TypeError("Invalid memory continuation payload.");
|
|
1384
|
+
const bindingsSha256 = parseSha256Hex(decoded.bindingsSha256);
|
|
1385
|
+
const continuationHmacSha256 = parseSha256Hex(decoded.continuationHmacSha256);
|
|
1386
|
+
const continuationSha256 = parseSha256Hex(decoded.continuationSha256);
|
|
1387
|
+
const memorySha256 = parseSha256Hex(decoded.memorySha256);
|
|
1388
|
+
const programSha256 = parseSha256Hex(decoded.programSha256);
|
|
1389
|
+
const projectionResultSha256 = parseSha256Hex(decoded.projectionResultSha256);
|
|
1390
|
+
if (bindingsSha256 === null || continuationHmacSha256 === null
|
|
1391
|
+
|| continuationSha256 === null || memorySha256 === null || programSha256 === null
|
|
1392
|
+
|| projectionResultSha256 === null
|
|
1393
|
+
|| !Number.isSafeInteger(decoded.nextOffset) || (decoded.nextOffset as number) < 1
|
|
1394
|
+
|| (decoded.nextOffset as number) > OH_MEMORY_QUERY_LIMITS_V2.maximumProgramRows
|
|
1395
|
+
|| !Number.isSafeInteger(decoded.pageSize) || (decoded.pageSize as number) < 1
|
|
1396
|
+
|| (decoded.pageSize as number) > OH_MEMORY_QUERY_LIMITS_V2.maximumPageRows
|
|
1397
|
+
|| !Number.isSafeInteger(decoded.totalRows) || (decoded.totalRows as number) < 1
|
|
1398
|
+
|| (decoded.totalRows as number) > OH_MEMORY_QUERY_LIMITS_V2.maximumProgramRows
|
|
1399
|
+
|| (decoded.nextOffset as number) >= (decoded.totalRows as number)
|
|
1400
|
+
|| (decoded.nextOffset as number) % (decoded.pageSize as number) !== 0) {
|
|
1401
|
+
throw new TypeError("Invalid memory continuation identity.");
|
|
1402
|
+
}
|
|
1403
|
+
const identity: OhMemoryContinuationIdentityV2 = { bindingsSha256, memorySha256,
|
|
1404
|
+
nextOffset: decoded.nextOffset as number,
|
|
1405
|
+
pageSize: decoded.pageSize as number, programSha256, projectionResultSha256,
|
|
1406
|
+
totalRows: decoded.totalRows as number, v: 2 as const };
|
|
1407
|
+
const signed: OhMemoryContinuationV2 = { ...identity, continuationSha256 };
|
|
1408
|
+
const envelope: OhMemoryContinuationEnvelopeV2 = { ...signed, continuationHmacSha256 };
|
|
1409
|
+
if (canonicalJson(envelope) !== text) throw new TypeError("Invalid memory continuation payload.");
|
|
1410
|
+
const expectedHmac = continuationHmacV2(key, signed);
|
|
1411
|
+
const receivedHmac = Buffer.from(continuationHmacSha256, "hex");
|
|
1412
|
+
if (!timingSafeEqual(expectedHmac, receivedHmac)) {
|
|
1413
|
+
throw new OhIntegrityError("The memory continuation is not an issued capability.");
|
|
1414
|
+
}
|
|
1415
|
+
if (canonicalSha256(identity) !== continuationSha256) {
|
|
1416
|
+
throw new OhIntegrityError("The memory continuation digest is invalid.");
|
|
1417
|
+
}
|
|
1418
|
+
return Object.freeze(signed);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
function parseExplainRequestV2(value: unknown): Readonly<{
|
|
1422
|
+
pageRow: number;
|
|
1423
|
+
resultSha256: Sha256Hex;
|
|
1424
|
+
token: string;
|
|
1425
|
+
}> {
|
|
1426
|
+
if (!isPlainRecord(value) || !hasExactKeys(value, ["pageRow", "resultSha256", "token", "v"])
|
|
1427
|
+
|| value.v !== 2 || typeof value.token !== "string" || value.token.length !== 43
|
|
1428
|
+
|| !Number.isSafeInteger(value.pageRow) || (value.pageRow as number) < 0) {
|
|
1429
|
+
throw new TypeError("Invalid V2 memory explanation request.");
|
|
1430
|
+
}
|
|
1431
|
+
const resultSha256 = parseSha256Hex(value.resultSha256);
|
|
1432
|
+
if (resultSha256 === null) throw new TypeError("Invalid V2 memory explanation result identity.");
|
|
1433
|
+
return { pageRow: value.pageRow as number, resultSha256, token: value.token };
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
/**
|
|
1437
|
+
* Creates the additive V2 memory facade. V2 adds only host-declared primitive
|
|
1438
|
+
* bindings and fail-closed stable pagination; V1 request and digest contracts
|
|
1439
|
+
* remain untouched.
|
|
1440
|
+
*/
|
|
1441
|
+
export async function createOhMemoryAgentV2(options: OhMemoryFacadeOptionsV2): Promise<OhMemoryAgentV2> {
|
|
1442
|
+
const memoryActorId = safeCode(options.actorId, 128);
|
|
1443
|
+
if (memoryActorId === null) throw new TypeError("Invalid host-bound memory actor ID.");
|
|
1444
|
+
const continuationKey = continuationKeyV2(options.continuationKey);
|
|
1445
|
+
const canonicalStore = options.canonical.store;
|
|
1446
|
+
const workingStore = options.working.store;
|
|
1447
|
+
const workingCodecs = options.working.codecs;
|
|
1448
|
+
const canonicalAuthorityId = authorityId(options.canonical.authorityId);
|
|
1449
|
+
const workingAuthorityId = authorityId(options.working.authorityId);
|
|
1450
|
+
if (canonicalAuthorityId === workingAuthorityId) {
|
|
1451
|
+
throw new OhProfileError("Working and canonical memory must be distinct physical authorities.");
|
|
1452
|
+
}
|
|
1453
|
+
const canonicalBinding = bindingFor(canonicalStore,
|
|
1454
|
+
options.canonical.expectedBindingSha256, "canonical");
|
|
1455
|
+
const workingBinding = bindingFor(workingStore,
|
|
1456
|
+
options.working.expectedBindingSha256, "working");
|
|
1457
|
+
const expectedCanonicalHead = parseOhHeadV1(options.canonical.expectedHead);
|
|
1458
|
+
if (expectedCanonicalHead === null) throw new TypeError("Invalid pinned canonical memory head.");
|
|
1459
|
+
const programs = resolveProgramsV2(options.programs);
|
|
1460
|
+
const extractors = resolveExtractors(options.extractors ?? []);
|
|
1461
|
+
const nominationRoutes = resolveNominationRoutes(options.nominationRoutes ?? []);
|
|
1462
|
+
const ingress = new OhSemanticBundleIngressV1(workingStore, workingCodecs);
|
|
1463
|
+
const now = options.now ?? (() => new Date());
|
|
1464
|
+
const monotonicNow = options.monotonicNow ?? (() => performance.now());
|
|
1465
|
+
const capabilityLifetime = options.explainCapabilityLifetimeMs
|
|
1466
|
+
?? OH_MEMORY_LIMITS_V1.explainCapabilityLifetimeMs;
|
|
1467
|
+
if (!Number.isSafeInteger(capabilityLifetime) || capabilityLifetime < 1_000
|
|
1468
|
+
|| capabilityLifetime > 60 * 60 * 1_000) {
|
|
1469
|
+
throw new RangeError("Invalid memory explanation capability lifetime.");
|
|
1470
|
+
}
|
|
1471
|
+
const canonical = await readLane({ authorityId: canonicalAuthorityId,
|
|
1472
|
+
binding: canonicalBinding, store: canonicalStore }, "canonical", expectedCanonicalHead);
|
|
1473
|
+
const explanations = new Map<string, StoredExplanationV2>();
|
|
1474
|
+
let explanationBytes = 0;
|
|
1475
|
+
let lastMonotonicMs = -1;
|
|
1476
|
+
let lastWallClockMs = Number.NEGATIVE_INFINITY;
|
|
1477
|
+
const wallClock = () => {
|
|
1478
|
+
const milliseconds = clockMilliseconds(now);
|
|
1479
|
+
if (milliseconds < lastWallClockMs) throw new OhProfileError("The memory wall clock regressed.");
|
|
1480
|
+
lastWallClockMs = milliseconds;
|
|
1481
|
+
return milliseconds;
|
|
1482
|
+
};
|
|
1483
|
+
const monotonicClock = () => {
|
|
1484
|
+
const milliseconds = monotonicMilliseconds(monotonicNow);
|
|
1485
|
+
if (milliseconds < lastMonotonicMs) throw new OhProfileError("The memory monotonic clock regressed.");
|
|
1486
|
+
lastMonotonicMs = milliseconds;
|
|
1487
|
+
return milliseconds;
|
|
1488
|
+
};
|
|
1489
|
+
const deleteExplanation = (token: string) => {
|
|
1490
|
+
const stored = explanations.get(token);
|
|
1491
|
+
if (stored !== undefined && explanations.delete(token)) explanationBytes -= stored.bytes;
|
|
1492
|
+
};
|
|
1493
|
+
|
|
1494
|
+
const remember = async (value: unknown): Promise<OhMemoryRememberReceiptV1> => {
|
|
1495
|
+
if (utf8ByteLength(canonicalJson(value)) > OH_MEMORY_LIMITS_V1.rememberBytes) {
|
|
1496
|
+
throw new RangeError("The memory semantic bundle exceeds its canonical byte bound.");
|
|
1497
|
+
}
|
|
1498
|
+
if (!isPlainRecord(value) || !hasExactKeys(value,
|
|
1499
|
+
["expectedHead", "puts", "requestId", "tombstones", "v"]) || value.v !== 1) {
|
|
1500
|
+
throw new TypeError("Invalid memory remember request.");
|
|
1501
|
+
}
|
|
1502
|
+
const requestId = safeCode(value.requestId, 128);
|
|
1503
|
+
if (requestId === null) throw new TypeError("Invalid memory remember request identity.");
|
|
1504
|
+
const operationId = `memory_${canonicalSha256({ actorId: memoryActorId,
|
|
1505
|
+
bindingSha256: workingBinding.bindingSha256, requestId, v: 1 }).slice(0, 48)}`;
|
|
1506
|
+
const operation = await ingress.commit({ actorId: memoryActorId,
|
|
1507
|
+
expectedHead: value.expectedHead, instant: isoInstant(new Date(wallClock())), operationId,
|
|
1508
|
+
puts: value.puts, tombstones: value.tombstones, v: 1 });
|
|
1509
|
+
const head: OhHeadV1 = { generation: operation.sequence,
|
|
1510
|
+
graphRevisionSha256: operation.graphRevisionSha256,
|
|
1511
|
+
operationSha256: operation.operationSha256, recordsSha256: operation.recordsSha256,
|
|
1512
|
+
sequence: operation.sequence, v: 1 };
|
|
1513
|
+
const payload = { actorId: operation.actorId, authorityId: workingAuthorityId,
|
|
1514
|
+
bindingSha256: workingBinding.bindingSha256, head, instant: operation.instant,
|
|
1515
|
+
lane: "working" as const, operationSha256: operation.operationSha256, requestId,
|
|
1516
|
+
status: "committed" as const, v: 1 as const };
|
|
1517
|
+
return immutableClone({ ...payload, receiptSha256: canonicalSha256(payload) });
|
|
1518
|
+
};
|
|
1519
|
+
|
|
1520
|
+
const query = async (value: unknown): Promise<OhMemoryQueryResultV2> => {
|
|
1521
|
+
const request = parseQueryRequestV2(value);
|
|
1522
|
+
const program = programs.get(request.programId);
|
|
1523
|
+
if (program === undefined) throw new TypeError("Unknown named V2 memory program.");
|
|
1524
|
+
const bound = parseBindingsV2(request.bindingsValue, program.parameters);
|
|
1525
|
+
const requestedContinuation = request.continuation === null
|
|
1526
|
+
? null : parseContinuationV2(request.continuation, continuationKey);
|
|
1527
|
+
if (requestedContinuation !== null
|
|
1528
|
+
&& (requestedContinuation.bindingsSha256 !== bound.bindingsSha256
|
|
1529
|
+
|| requestedContinuation.pageSize !== program.pageSize
|
|
1530
|
+
|| requestedContinuation.programSha256 !== program.programSha256
|
|
1531
|
+
|| requestedContinuation.totalRows > program.maximumRows
|
|
1532
|
+
|| requestedContinuation.nextOffset >= requestedContinuation.totalRows
|
|
1533
|
+
|| requestedContinuation.nextOffset % program.pageSize !== 0)) {
|
|
1534
|
+
throw new OhIntegrityError("The memory continuation does not match this exact program, binding, and page identity.");
|
|
1535
|
+
}
|
|
1536
|
+
const boundQuery = bindQueryV2(program.query, bound.bindings);
|
|
1537
|
+
const working = await readLane({ authorityId: workingAuthorityId,
|
|
1538
|
+
binding: workingBinding, store: workingStore }, "working");
|
|
1539
|
+
const composite = createCompositeDataset(canonical, working, extractors);
|
|
1540
|
+
const projection = evaluateOhProjectionV1({ dataset: composite.dataset,
|
|
1541
|
+
options: program.evaluation, query: boundQuery, rulePack: program.rulePack,
|
|
1542
|
+
snapshot: composite.snapshot });
|
|
1543
|
+
if (projection.stats.truncated) {
|
|
1544
|
+
const reasons = projection.stats.truncationReasons.join(", ");
|
|
1545
|
+
throw new RangeError(`The V2 memory projection was truncated (${reasons}); no page was returned.`);
|
|
1546
|
+
}
|
|
1547
|
+
if (projection.rows.length > program.maximumRows) {
|
|
1548
|
+
throw new RangeError("The V2 memory projection exceeds its host-declared row bound.");
|
|
1549
|
+
}
|
|
1550
|
+
const identityPayload = {
|
|
1551
|
+
bindings: bound.bindings,
|
|
1552
|
+
bindingsSha256: bound.bindingsSha256,
|
|
1553
|
+
boundQuerySha256: boundQuery.querySha256,
|
|
1554
|
+
canonical: laneIdentity(canonical),
|
|
1555
|
+
compositeDatasetSha256: composite.dataset.datasetSha256,
|
|
1556
|
+
conflictPolicy: OH_MEMORY_CONFLICT_POLICY_V1,
|
|
1557
|
+
evaluationSha256: projection.identity.evaluationSha256,
|
|
1558
|
+
programId: program.programId,
|
|
1559
|
+
programSha256: program.programSha256,
|
|
1560
|
+
projectionSha256: projection.identity.projectionSha256,
|
|
1561
|
+
purpose: program.purpose,
|
|
1562
|
+
rulePackSha256: program.rulePack.rulePackSha256,
|
|
1563
|
+
templateQuerySha256: program.query.querySha256,
|
|
1564
|
+
v: 2 as const,
|
|
1565
|
+
working: laneIdentity(working),
|
|
1566
|
+
};
|
|
1567
|
+
const identity: OhMemoryIdentityV2 = immutableClone({ ...identityPayload,
|
|
1568
|
+
memorySha256: canonicalSha256(identityPayload) });
|
|
1569
|
+
if (requestedContinuation !== null
|
|
1570
|
+
&& (requestedContinuation.memorySha256 !== identity.memorySha256
|
|
1571
|
+
|| requestedContinuation.projectionResultSha256 !== projection.resultSha256)) {
|
|
1572
|
+
throw new OhIntegrityError("The memory continuation does not match this exact source and projection identity.");
|
|
1573
|
+
}
|
|
1574
|
+
if (requestedContinuation !== null
|
|
1575
|
+
&& (requestedContinuation.totalRows !== projection.rows.length
|
|
1576
|
+
|| requestedContinuation.nextOffset >= projection.rows.length
|
|
1577
|
+
|| requestedContinuation.nextOffset % program.pageSize !== 0)) {
|
|
1578
|
+
throw new OhIntegrityError("The memory continuation does not match this exact row identity.");
|
|
1579
|
+
}
|
|
1580
|
+
const start = requestedContinuation?.nextOffset ?? 0;
|
|
1581
|
+
const endExclusive = Math.min(start + program.pageSize, projection.rows.length);
|
|
1582
|
+
const projectionRows = projection.rows.slice(start, endExclusive);
|
|
1583
|
+
const proofs = immutableClone(projectionRows.map((row) => row.proofs.map((proof) =>
|
|
1584
|
+
mapProof(proof, composite.sources, composite.factPolicies))));
|
|
1585
|
+
const rows = immutableClone(projectionRows.map((row, index) => publicRowV2(row, proofs[index]!)));
|
|
1586
|
+
const hasMore = endExclusive < projection.rows.length;
|
|
1587
|
+
const page: OhMemoryPageV2 = immutableClone({ completeness: hasMore ? "partial" : "complete",
|
|
1588
|
+
endExclusive, hasMore, maximumPageBytes: program.maximumPageBytes, pageSize: program.pageSize,
|
|
1589
|
+
returnedRows: rows.length, start, totalRows: projection.rows.length,
|
|
1590
|
+
truncation: { reasons: [], truncated: false, v: 2 as const }, v: 2 as const });
|
|
1591
|
+
const issuedContinuation = hasMore ? encodeContinuationV2({ bindingsSha256: bound.bindingsSha256,
|
|
1592
|
+
memorySha256: identity.memorySha256, nextOffset: endExclusive, pageSize: program.pageSize,
|
|
1593
|
+
programSha256: program.programSha256, projectionResultSha256: projection.resultSha256,
|
|
1594
|
+
totalRows: projection.rows.length, v: 2 }, continuationKey) : null;
|
|
1595
|
+
const continuation = issuedContinuation?.continuation ?? null;
|
|
1596
|
+
const continuationSha256 = issuedContinuation?.continuationSha256 ?? null;
|
|
1597
|
+
const conflicts = immutableClone({ count: composite.conflicts.length,
|
|
1598
|
+
conflictsSha256: canonicalSha256(composite.conflicts), v: 2 as const });
|
|
1599
|
+
const resultIdentityPayload = immutableClone({ authority: "derived" as const, conflicts,
|
|
1600
|
+
continuationSha256,
|
|
1601
|
+
identity, page, projectionResultSha256: projection.resultSha256, rows, v: 2 as const });
|
|
1602
|
+
const resultSha256 = canonicalSha256(resultIdentityPayload);
|
|
1603
|
+
const resultPayload = immutableClone({ ...resultIdentityPayload, continuation });
|
|
1604
|
+
const issuedAt = wallClock();
|
|
1605
|
+
const issuedAtMonotonic = monotonicClock();
|
|
1606
|
+
const expiresAtMs = issuedAt + capabilityLifetime;
|
|
1607
|
+
const expiresAtMonotonicMs = issuedAtMonotonic + capabilityLifetime;
|
|
1608
|
+
const expiresAt = isoInstant(new Date(expiresAtMs));
|
|
1609
|
+
const pageBytePreflight = { ...resultPayload,
|
|
1610
|
+
explainCapability: { expiresAt, token: "A".repeat(43), v: 2 as const }, resultSha256 };
|
|
1611
|
+
if (utf8ByteLength(canonicalJson(pageBytePreflight)) > program.maximumPageBytes) {
|
|
1612
|
+
throw new RangeError("The V2 memory page exceeds its host-declared canonical byte bound.");
|
|
1613
|
+
}
|
|
1614
|
+
for (const [existingToken, stored] of explanations) {
|
|
1615
|
+
if (issuedAtMonotonic >= stored.expiresAtMonotonicMs) deleteExplanation(existingToken);
|
|
1616
|
+
}
|
|
1617
|
+
const storedPayload = immutableClone({ expiresAtMonotonicMs, identity, page, proofs,
|
|
1618
|
+
resultSha256, rows });
|
|
1619
|
+
const storedBytes = utf8ByteLength(canonicalJson(storedPayload)) + 128;
|
|
1620
|
+
if (storedBytes > OH_MEMORY_LIMITS_V1.explainCapabilityEntryBytes
|
|
1621
|
+
|| storedBytes > OH_MEMORY_LIMITS_V1.explainCapabilityTotalBytes) {
|
|
1622
|
+
throw new RangeError("The V2 memory explanation exceeds its retained capability bound.");
|
|
1623
|
+
}
|
|
1624
|
+
while (explanations.size >= OH_MEMORY_LIMITS_V1.explainCapabilities
|
|
1625
|
+
|| explanationBytes + storedBytes > OH_MEMORY_LIMITS_V1.explainCapabilityTotalBytes) {
|
|
1626
|
+
const oldest = explanations.keys().next().value as string | undefined;
|
|
1627
|
+
if (oldest === undefined) break;
|
|
1628
|
+
deleteExplanation(oldest);
|
|
1629
|
+
}
|
|
1630
|
+
let token = randomBytes(32).toString("base64url");
|
|
1631
|
+
while (explanations.has(token)) token = randomBytes(32).toString("base64url");
|
|
1632
|
+
explanations.set(token, immutableClone({ ...storedPayload, bytes: storedBytes }));
|
|
1633
|
+
explanationBytes += storedBytes;
|
|
1634
|
+
const result = immutableClone({ ...resultPayload,
|
|
1635
|
+
explainCapability: { expiresAt, token, v: 2 as const }, resultSha256 });
|
|
1636
|
+
if (utf8ByteLength(canonicalJson(result)) > program.maximumPageBytes) {
|
|
1637
|
+
deleteExplanation(token);
|
|
1638
|
+
throw new RangeError("The V2 memory page exceeds its host-declared canonical byte bound.");
|
|
1639
|
+
}
|
|
1640
|
+
return result;
|
|
1641
|
+
};
|
|
1642
|
+
|
|
1643
|
+
const explain = async (value: unknown): Promise<OhMemoryExplanationV2> => {
|
|
1644
|
+
const request = parseExplainRequestV2(value);
|
|
1645
|
+
const stored = explanations.get(request.token);
|
|
1646
|
+
const currentTime = monotonicClock();
|
|
1647
|
+
if (stored === undefined || stored.resultSha256 !== request.resultSha256
|
|
1648
|
+
|| currentTime >= stored.expiresAtMonotonicMs) {
|
|
1649
|
+
deleteExplanation(request.token);
|
|
1650
|
+
throw new OhProfileError("The V2 memory explanation capability is absent, expired, or misbound.");
|
|
1651
|
+
}
|
|
1652
|
+
const row = stored.rows[request.pageRow];
|
|
1653
|
+
const proofs = stored.proofs[request.pageRow];
|
|
1654
|
+
if (row === undefined || proofs === undefined) throw new RangeError("The explanation page row is out of bounds.");
|
|
1655
|
+
const payload = { authority: "derived" as const, identity: stored.identity, page: stored.page,
|
|
1656
|
+
pageRow: request.pageRow, premiseAuthority: row.premiseAuthority,
|
|
1657
|
+
premiseLanes: row.premiseLanes, proofs, proofsTruncated: row.proofsTruncated,
|
|
1658
|
+
resultRowSha256: row.resultRowSha256, resultSha256: stored.resultSha256,
|
|
1659
|
+
supportCount: row.supportCount, v: 2 as const, values: row.values };
|
|
1660
|
+
return immutableClone({ ...payload, explanationSha256: canonicalSha256(payload) });
|
|
1661
|
+
};
|
|
1662
|
+
|
|
1663
|
+
const nominate = async (value: unknown): Promise<OhMemoryNominationV1> => {
|
|
1664
|
+
const request = parseNominationRequest(value);
|
|
1665
|
+
const route = nominationRoutes.get(request.nominationId);
|
|
1666
|
+
if (route === undefined) throw new TypeError("Unknown named memory nomination route.");
|
|
1667
|
+
const head = parseOhHeadV1(immutableClone(await workingStore.head()));
|
|
1668
|
+
if (head === null) throw new OhIntegrityError("The working nomination store returned an invalid head.");
|
|
1669
|
+
const closure = await workingStore.exportDependencyClosure({ head: {
|
|
1670
|
+
operationSha256: head.operationSha256, sequence: head.sequence }, roots: request.roots });
|
|
1671
|
+
const verified = verifyOhDependencyClosureAgainstV1(closure, { binding: workingBinding, head });
|
|
1672
|
+
if (!verified.ok) throw new OhIntegrityError("The working nomination closure failed exact verification.");
|
|
1673
|
+
if (canonicalJson(verified.closure.roots) !== canonicalJson(request.roots)) {
|
|
1674
|
+
throw new OhIntegrityError("The working nomination closure substituted different roots.");
|
|
1675
|
+
}
|
|
1676
|
+
const source = Object.freeze({ authorityId: workingAuthorityId,
|
|
1677
|
+
bindingSha256: workingBinding.bindingSha256, head, lane: "working" as const, v: 1 as const });
|
|
1678
|
+
const payload = { closure: verified.closure, destinationPurpose: route.destinationPurpose,
|
|
1679
|
+
nominationId: route.nominationId, source, status: "prepared" as const, v: 1 as const };
|
|
1680
|
+
return immutableClone({ ...payload, nominationSha256: canonicalSha256(payload) });
|
|
1681
|
+
};
|
|
1682
|
+
|
|
1683
|
+
return Object.freeze({ explain, nominate, query, remember });
|
|
1684
|
+
}
|