@hraness/oh 0.4.0 → 0.4.2
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 +3 -0
- package/README.md +12 -9
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +15590 -1705
- package/dist/index.js +15209 -261
- package/dist/libsql-model.d.ts +75 -0
- package/dist/libsql-model.d.ts.map +1 -0
- package/dist/libsql-platform.d.ts +24 -0
- package/dist/libsql-platform.d.ts.map +1 -0
- package/dist/libsql-program.d.ts +64 -0
- package/dist/libsql-program.d.ts.map +1 -0
- package/dist/libsql-runtime.d.ts +13 -0
- package/dist/libsql-runtime.d.ts.map +1 -0
- package/dist/libsql.d.ts +4 -36
- package/dist/libsql.d.ts.map +1 -1
- package/dist/libsql.js +16583 -1426
- package/dist/memory-authority-platform.d.ts +210 -0
- package/dist/memory-authority-platform.d.ts.map +1 -0
- package/dist/memory-authority-program.d.ts +15 -0
- package/dist/memory-authority-program.d.ts.map +1 -0
- package/dist/memory-authority-runtime.d.ts +4 -0
- package/dist/memory-authority-runtime.d.ts.map +1 -0
- package/dist/memory-core.d.ts +522 -0
- package/dist/memory-core.d.ts.map +1 -0
- package/dist/memory.d.ts +5 -445
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +18232 -4143
- package/dist/sdk.js +15266 -318
- package/dist/semantic-cloud.js +1 -260
- package/dist/semantic-model.d.ts +75 -0
- package/dist/semantic-model.d.ts.map +1 -0
- package/dist/semantic-platform.d.ts +32 -0
- package/dist/semantic-platform.d.ts.map +1 -0
- package/dist/semantic-program.d.ts +18 -0
- package/dist/semantic-program.d.ts.map +1 -0
- package/dist/semantic-runtime.d.ts +6 -0
- package/dist/semantic-runtime.d.ts.map +1 -0
- package/dist/semantic.d.ts +6 -57
- package/dist/semantic.d.ts.map +1 -1
- package/dist/semantic.js +15541 -514
- package/dist/sqlite/index.js +1148 -1378
- package/dist/sqlite/port.d.ts +1 -1
- package/dist/sqlite/port.d.ts.map +1 -1
- package/dist/sync-libsql-program.d.ts +5 -0
- package/dist/sync-libsql-program.d.ts.map +1 -0
- package/dist/sync-model.d.ts +60 -0
- package/dist/sync-model.d.ts.map +1 -0
- package/dist/sync-platform.d.ts +41 -0
- package/dist/sync-platform.d.ts.map +1 -0
- package/dist/sync-program.d.ts +11 -0
- package/dist/sync-program.d.ts.map +1 -0
- package/dist/sync-runtime.d.ts +6 -0
- package/dist/sync-runtime.d.ts.map +1 -0
- package/dist/sync.d.ts +5 -55
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +15174 -226
- package/package.json +7 -5
- package/skills/oh/SKILL.md +3 -2
- package/spec/v1/store.md +14 -0
- package/src/cli.test.ts +35 -0
- package/src/cli.ts +4 -3
- package/src/cloudflare-embedding.ts +1 -1
- package/src/libsql-lifecycle.test.ts +76 -0
- package/src/libsql-model.ts +350 -0
- package/src/libsql-platform.ts +51 -0
- package/src/libsql-program.ts +1583 -0
- package/src/libsql-runtime.ts +89 -0
- package/src/libsql-semantic-v2.ts +1 -1
- package/src/libsql-semantic.ts +1 -1
- package/src/libsql.test.ts +245 -0
- package/src/libsql.ts +26 -1698
- package/src/memory-authority-platform.ts +133 -0
- package/src/memory-authority-program.ts +309 -0
- package/src/memory-authority-runtime.ts +40 -0
- package/src/memory-core.ts +2401 -0
- package/src/memory.test.ts +55 -0
- package/src/memory.ts +68 -2759
- package/src/semantic-model.ts +142 -0
- package/src/semantic-platform.ts +130 -0
- package/src/semantic-program.ts +113 -0
- package/src/semantic-runtime.ts +53 -0
- package/src/semantic.test.ts +115 -1
- package/src/semantic.ts +14 -324
- package/src/sqlite/port.ts +1 -1
- package/src/sync-libsql-program.ts +170 -0
- package/src/sync-lifecycle.test.ts +110 -0
- package/src/sync-model.ts +530 -0
- package/src/sync-platform.ts +87 -0
- package/src/sync-program.ts +117 -0
- package/src/sync-runtime.ts +33 -0
- package/src/sync.ts +10 -765
|
@@ -0,0 +1,2401 @@
|
|
|
1
|
+
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { isProxy } from "node:util/types";
|
|
3
|
+
|
|
4
|
+
export * from "./memory-pages";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
canonicalJson,
|
|
8
|
+
canonicalSha256,
|
|
9
|
+
hasExactKeys,
|
|
10
|
+
isPlainRecord,
|
|
11
|
+
orderedUnique,
|
|
12
|
+
parseCanonicalInstantV1,
|
|
13
|
+
parseSha256Hex,
|
|
14
|
+
safeCode,
|
|
15
|
+
utf8ByteLength,
|
|
16
|
+
type JsonPrimitive,
|
|
17
|
+
type Sha256Hex,
|
|
18
|
+
} from "./canonical";
|
|
19
|
+
import { type OhRecordCodecRegistry } from "./contract";
|
|
20
|
+
import {
|
|
21
|
+
canonicalKnowledgeGraphChangesV1,
|
|
22
|
+
createKnowledgeGraphRecordV1,
|
|
23
|
+
knowledgeGraphRecordRefV1,
|
|
24
|
+
type KnowledgeGraphChangeV1,
|
|
25
|
+
type KnowledgeGraphRecordV1,
|
|
26
|
+
} from "./graph";
|
|
27
|
+
import { OH_OPERATION_MAX_BYTES_V1, parseOhOperationV1 } from "./operation";
|
|
28
|
+
import {
|
|
29
|
+
OH_PROJECTION_SEMANTICS_V1,
|
|
30
|
+
OH_PROJECTION_LIMITS_V1,
|
|
31
|
+
createOhProjectionDatasetV1,
|
|
32
|
+
createOhProjectionFactV1,
|
|
33
|
+
createOhProjectionLiteralV1,
|
|
34
|
+
createOhProjectionQueryV1,
|
|
35
|
+
createOhProjectionRecordFactsV1,
|
|
36
|
+
createOhProjectionSnapshotV1,
|
|
37
|
+
evaluateOhProjectionV1,
|
|
38
|
+
ohProjectionConstantV1,
|
|
39
|
+
parseOhProjectionQueryV1,
|
|
40
|
+
parseOhProjectionRulePackV1,
|
|
41
|
+
type OhProjectionAtomV1,
|
|
42
|
+
type OhProjectionDatasetV1,
|
|
43
|
+
type OhProjectionEvaluationOptionsV1,
|
|
44
|
+
type OhProjectionFactV1,
|
|
45
|
+
type OhProjectionProofV1,
|
|
46
|
+
type OhProjectionQueryV1,
|
|
47
|
+
type OhProjectionResultRowV1,
|
|
48
|
+
type OhProjectionRulePackV1,
|
|
49
|
+
type OhProjectionSnapshotV1,
|
|
50
|
+
} from "./projection";
|
|
51
|
+
import {
|
|
52
|
+
OhConflictError,
|
|
53
|
+
OhIntegrityError,
|
|
54
|
+
OH_DEPENDENCY_CLOSURE_LIMITS_V1,
|
|
55
|
+
OhProfileError,
|
|
56
|
+
OhSemanticBundleIngressV1,
|
|
57
|
+
parseOhDependencyClosureV1,
|
|
58
|
+
parseOhHeadV1,
|
|
59
|
+
parseOhHeadRefV1,
|
|
60
|
+
parseOhStoreBindingV1,
|
|
61
|
+
verifyOhDependencyClosureAgainstV1,
|
|
62
|
+
type OhDependencyClosureV1,
|
|
63
|
+
type OhHeadV1,
|
|
64
|
+
type OhHeadRefV1,
|
|
65
|
+
type OhSnapshotV1,
|
|
66
|
+
type OhStoreBindingV1,
|
|
67
|
+
type OhStoreV1,
|
|
68
|
+
} from "./store";
|
|
69
|
+
export const OH_MEMORY_FORMAT_VERSION_V1 = 1 as const;
|
|
70
|
+
export const OH_MEMORY_CONFLICT_POLICY_V1 = "visible-conflicts.v1" as const;
|
|
71
|
+
export const OH_MEMORY_LIMITS_V1 = Object.freeze({
|
|
72
|
+
detachedCanonicalBreadth: 65_536,
|
|
73
|
+
detachedCanonicalDepth: 128,
|
|
74
|
+
detachedCanonicalNodes: 1_048_576,
|
|
75
|
+
explainCapabilityEntryBytes: 32 * 1024 * 1024,
|
|
76
|
+
explainCapabilities: 256,
|
|
77
|
+
explainCapabilityLifetimeMs: 15 * 60 * 1_000,
|
|
78
|
+
explainCapabilityTotalBytes: 64 * 1024 * 1024,
|
|
79
|
+
factsPerRecordPerExtractor: 512,
|
|
80
|
+
maximumExtractorInvocations: 262_144,
|
|
81
|
+
maximumExtractors: 32,
|
|
82
|
+
maximumNominationRoutes: 64,
|
|
83
|
+
maximumPrograms: 128,
|
|
84
|
+
maximumRecordsPerLane: 8_192,
|
|
85
|
+
maximumSyntheticRecords: 16_384,
|
|
86
|
+
rememberBytes: 8 * 1024 * 1024,
|
|
87
|
+
resultBytes: 32 * 1024 * 1024,
|
|
88
|
+
snapshotBytesPerLane: 32 * 1024 * 1024,
|
|
89
|
+
relationsPerExtractor: 64,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const memoryFactPackPayload = Object.freeze({
|
|
93
|
+
factPackId: "oh.memory.composite-facts",
|
|
94
|
+
factPackRevision: 1,
|
|
95
|
+
relations: Object.freeze(["memory.agreement", "memory.conflict", "memory.dependency", "memory.record"]),
|
|
96
|
+
semantics: OH_PROJECTION_SEMANTICS_V1,
|
|
97
|
+
v: 1 as const,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
export const OH_MEMORY_COMPOSITE_FACT_EXTRACTOR_V1 = Object.freeze({
|
|
101
|
+
...memoryFactPackPayload,
|
|
102
|
+
extractorSha256: canonicalSha256(memoryFactPackPayload),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
export type OhMemoryLaneV1 = "canonical" | "working";
|
|
106
|
+
|
|
107
|
+
export type OhMemoryAuthoritySourceV1 = Readonly<{
|
|
108
|
+
authorityId: string;
|
|
109
|
+
bindingSha256: Sha256Hex;
|
|
110
|
+
head: OhHeadV1;
|
|
111
|
+
key: string;
|
|
112
|
+
lane: OhMemoryLaneV1;
|
|
113
|
+
recordSha256: Sha256Hex;
|
|
114
|
+
snapshotSha256: Sha256Hex;
|
|
115
|
+
v: 1;
|
|
116
|
+
}>;
|
|
117
|
+
|
|
118
|
+
export type OhMemoryProofV1 =
|
|
119
|
+
| Readonly<{
|
|
120
|
+
factPolicy: OhMemoryFactPolicyV1;
|
|
121
|
+
kind: "fact";
|
|
122
|
+
relation: string;
|
|
123
|
+
sources: readonly OhMemoryAuthoritySourceV1[];
|
|
124
|
+
tuple: readonly OhProjectionAtomV1[];
|
|
125
|
+
v: 1;
|
|
126
|
+
}>
|
|
127
|
+
| Readonly<{
|
|
128
|
+
kind: "derived";
|
|
129
|
+
premises: readonly OhMemoryProofV1[];
|
|
130
|
+
premisesTruncated: boolean;
|
|
131
|
+
relation: string;
|
|
132
|
+
ruleId: string;
|
|
133
|
+
ruleSha256: Sha256Hex;
|
|
134
|
+
tuple: readonly OhProjectionAtomV1[];
|
|
135
|
+
v: 1;
|
|
136
|
+
}>
|
|
137
|
+
| Readonly<{
|
|
138
|
+
kind: "truncated";
|
|
139
|
+
reason: "cycle" | "depth" | "nodes";
|
|
140
|
+
relation: string;
|
|
141
|
+
tuple: readonly OhProjectionAtomV1[];
|
|
142
|
+
v: 1;
|
|
143
|
+
}>;
|
|
144
|
+
|
|
145
|
+
export type OhMemoryLaneIdentityV1 = Readonly<{
|
|
146
|
+
authorityId: string;
|
|
147
|
+
bindingSha256: Sha256Hex;
|
|
148
|
+
datasetSha256: Sha256Hex;
|
|
149
|
+
head: OhHeadV1;
|
|
150
|
+
lane: OhMemoryLaneV1;
|
|
151
|
+
snapshotSha256: Sha256Hex;
|
|
152
|
+
v: 1;
|
|
153
|
+
}>;
|
|
154
|
+
|
|
155
|
+
export type OhMemoryIdentityV1 = Readonly<{
|
|
156
|
+
canonical: OhMemoryLaneIdentityV1;
|
|
157
|
+
compositeDatasetSha256: Sha256Hex;
|
|
158
|
+
conflictPolicy: typeof OH_MEMORY_CONFLICT_POLICY_V1;
|
|
159
|
+
evaluationSha256: Sha256Hex;
|
|
160
|
+
memorySha256: Sha256Hex;
|
|
161
|
+
programId: string;
|
|
162
|
+
projectionSha256: Sha256Hex;
|
|
163
|
+
purpose: string;
|
|
164
|
+
querySha256: Sha256Hex;
|
|
165
|
+
rulePackSha256: Sha256Hex;
|
|
166
|
+
v: 1;
|
|
167
|
+
working: OhMemoryLaneIdentityV1;
|
|
168
|
+
}>;
|
|
169
|
+
|
|
170
|
+
export type OhMemoryConflictV1 = Readonly<{
|
|
171
|
+
canonicalRecordSha256: Sha256Hex;
|
|
172
|
+
key: string;
|
|
173
|
+
v: 1;
|
|
174
|
+
workingRecordSha256: Sha256Hex;
|
|
175
|
+
}>;
|
|
176
|
+
|
|
177
|
+
export type OhMemoryResultRowV1 = Readonly<{
|
|
178
|
+
premiseAuthority: "canonical" | "unknown" | "working";
|
|
179
|
+
premiseLanes: readonly OhMemoryLaneV1[];
|
|
180
|
+
proofsTruncated: boolean;
|
|
181
|
+
resultRowSha256: Sha256Hex;
|
|
182
|
+
supportCount: number;
|
|
183
|
+
v: 1;
|
|
184
|
+
values: readonly OhProjectionAtomV1[];
|
|
185
|
+
}>;
|
|
186
|
+
|
|
187
|
+
export type OhMemoryQueryResultV1 = Readonly<{
|
|
188
|
+
authority: "derived";
|
|
189
|
+
conflicts: readonly OhMemoryConflictV1[];
|
|
190
|
+
explainCapability: Readonly<{ expiresAt: string; token: string; v: 1 }>;
|
|
191
|
+
identity: OhMemoryIdentityV1;
|
|
192
|
+
projectionResultSha256: Sha256Hex;
|
|
193
|
+
resultSha256: Sha256Hex;
|
|
194
|
+
rows: readonly OhMemoryResultRowV1[];
|
|
195
|
+
v: 1;
|
|
196
|
+
}>;
|
|
197
|
+
|
|
198
|
+
export type OhMemoryRememberReceiptV1 = Readonly<{
|
|
199
|
+
actorId: string;
|
|
200
|
+
authorityId: string;
|
|
201
|
+
bindingSha256: Sha256Hex;
|
|
202
|
+
head: OhHeadV1;
|
|
203
|
+
instant: string;
|
|
204
|
+
lane: "working";
|
|
205
|
+
operationSha256: Sha256Hex;
|
|
206
|
+
receiptSha256: Sha256Hex;
|
|
207
|
+
requestId: string;
|
|
208
|
+
status: "committed";
|
|
209
|
+
v: 1;
|
|
210
|
+
}>;
|
|
211
|
+
|
|
212
|
+
export type OhMemoryExplanationV1 = Readonly<{
|
|
213
|
+
authority: "derived";
|
|
214
|
+
explanationSha256: Sha256Hex;
|
|
215
|
+
identity: OhMemoryIdentityV1;
|
|
216
|
+
premiseAuthority: OhMemoryResultRowV1["premiseAuthority"];
|
|
217
|
+
premiseLanes: readonly OhMemoryLaneV1[];
|
|
218
|
+
proofs: readonly OhMemoryProofV1[];
|
|
219
|
+
proofsTruncated: boolean;
|
|
220
|
+
resultRowSha256: Sha256Hex;
|
|
221
|
+
resultSha256: Sha256Hex;
|
|
222
|
+
supportCount: number;
|
|
223
|
+
v: 1;
|
|
224
|
+
values: readonly OhProjectionAtomV1[];
|
|
225
|
+
}>;
|
|
226
|
+
|
|
227
|
+
export type OhMemoryNominationV1 = Readonly<{
|
|
228
|
+
closure: OhDependencyClosureV1;
|
|
229
|
+
destinationPurpose: string;
|
|
230
|
+
nominationId: string;
|
|
231
|
+
nominationSha256: Sha256Hex;
|
|
232
|
+
source: Readonly<{
|
|
233
|
+
authorityId: string;
|
|
234
|
+
bindingSha256: Sha256Hex;
|
|
235
|
+
head: OhHeadV1;
|
|
236
|
+
lane: "working";
|
|
237
|
+
v: 1;
|
|
238
|
+
}>;
|
|
239
|
+
status: "prepared";
|
|
240
|
+
v: 1;
|
|
241
|
+
}>;
|
|
242
|
+
|
|
243
|
+
export type OhMemoryNamedProgramV1 = Readonly<{
|
|
244
|
+
evaluation?: OhProjectionEvaluationOptionsV1;
|
|
245
|
+
programId: string;
|
|
246
|
+
purpose: string;
|
|
247
|
+
query: OhProjectionQueryV1;
|
|
248
|
+
rulePack: OhProjectionRulePackV1;
|
|
249
|
+
}>;
|
|
250
|
+
|
|
251
|
+
export type OhMemoryNominationRouteV1 = Readonly<{
|
|
252
|
+
destinationPurpose: string;
|
|
253
|
+
nominationId: string;
|
|
254
|
+
}>;
|
|
255
|
+
|
|
256
|
+
export type OhMemoryFactPolicyV1 =
|
|
257
|
+
| Readonly<{
|
|
258
|
+
extractorSha256: Sha256Hex;
|
|
259
|
+
factPackId: string;
|
|
260
|
+
kind: "built-in";
|
|
261
|
+
v: 1;
|
|
262
|
+
}>
|
|
263
|
+
| Readonly<{
|
|
264
|
+
extractorId: string;
|
|
265
|
+
extractorSha256: Sha256Hex;
|
|
266
|
+
kind: "domain";
|
|
267
|
+
v: 1;
|
|
268
|
+
}>;
|
|
269
|
+
|
|
270
|
+
export type OhMemoryFactDeclarationV1 = Readonly<{
|
|
271
|
+
relation: string;
|
|
272
|
+
tuple: readonly JsonPrimitive[];
|
|
273
|
+
v: 1;
|
|
274
|
+
}>;
|
|
275
|
+
|
|
276
|
+
/** Host-owned, digest-identified domain projection; it cannot choose sources. */
|
|
277
|
+
export type OhMemoryFactExtractorV1 = Readonly<{
|
|
278
|
+
extract(input: Readonly<{
|
|
279
|
+
lane: OhMemoryLaneV1;
|
|
280
|
+
record: KnowledgeGraphRecordV1;
|
|
281
|
+
}>): readonly OhMemoryFactDeclarationV1[];
|
|
282
|
+
extractorId: string;
|
|
283
|
+
extractorSha256: Sha256Hex;
|
|
284
|
+
relations: readonly string[];
|
|
285
|
+
}>;
|
|
286
|
+
|
|
287
|
+
export type OhMemoryFacadeOptionsV1 = Readonly<{
|
|
288
|
+
actorId: string;
|
|
289
|
+
canonical: Readonly<{
|
|
290
|
+
authorityId: string;
|
|
291
|
+
expectedBindingSha256: Sha256Hex;
|
|
292
|
+
expectedHead: OhHeadV1;
|
|
293
|
+
store: OhStoreV1;
|
|
294
|
+
}>;
|
|
295
|
+
explainCapabilityLifetimeMs?: number;
|
|
296
|
+
extractors?: readonly OhMemoryFactExtractorV1[];
|
|
297
|
+
monotonicNow?: () => number;
|
|
298
|
+
nominationRoutes?: readonly OhMemoryNominationRouteV1[];
|
|
299
|
+
now?: () => Date;
|
|
300
|
+
programs: readonly OhMemoryNamedProgramV1[];
|
|
301
|
+
working: Readonly<{
|
|
302
|
+
authorityId: string;
|
|
303
|
+
codecs: OhRecordCodecRegistry;
|
|
304
|
+
expectedBindingSha256: Sha256Hex;
|
|
305
|
+
store: OhStoreV1;
|
|
306
|
+
}>;
|
|
307
|
+
}>;
|
|
308
|
+
|
|
309
|
+
export interface OhMemoryAgentV1 {
|
|
310
|
+
explain(value: unknown): Promise<OhMemoryExplanationV1>;
|
|
311
|
+
nominate(value: unknown): Promise<OhMemoryNominationV1>;
|
|
312
|
+
query(value: unknown): Promise<OhMemoryQueryResultV1>;
|
|
313
|
+
remember(value: unknown): Promise<OhMemoryRememberReceiptV1>;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Additive query and pagination limits; V1 contracts are unchanged. */
|
|
317
|
+
export const OH_MEMORY_QUERY_LIMITS_V2 = Object.freeze({
|
|
318
|
+
bindingBytes: 64 * 1024,
|
|
319
|
+
bindings: 32,
|
|
320
|
+
continuationBytes: 4 * 1024,
|
|
321
|
+
continuationKeyMaximumBytes: 64,
|
|
322
|
+
continuationKeyMinimumBytes: 32,
|
|
323
|
+
maximumPageBytes: 8 * 1024 * 1024,
|
|
324
|
+
maximumPageRows: 256,
|
|
325
|
+
maximumProgramRows: OH_PROJECTION_LIMITS_V1.queryResults,
|
|
326
|
+
minimumPageBytes: 64 * 1024,
|
|
327
|
+
requestBytes: 80 * 1024,
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
export type OhMemoryEvaluationLimitsV2 = Readonly<{
|
|
331
|
+
maximumDerivedTuples: number;
|
|
332
|
+
maximumProofDepth: number;
|
|
333
|
+
maximumProofNodes: number;
|
|
334
|
+
maximumResultBytes: number;
|
|
335
|
+
maximumRounds: number;
|
|
336
|
+
maximumTotalProofNodes: number;
|
|
337
|
+
maximumWorkUnits: number;
|
|
338
|
+
}>;
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* A host-owned parameterized program. Parameter names refer only to variables
|
|
342
|
+
* in the query body, never to rule variables or projected output variables.
|
|
343
|
+
*/
|
|
344
|
+
export type OhMemoryNamedProgramV2 = Readonly<{
|
|
345
|
+
evaluation: OhMemoryEvaluationLimitsV2;
|
|
346
|
+
maximumPageBytes: number;
|
|
347
|
+
maximumRows: number;
|
|
348
|
+
pageSize: number;
|
|
349
|
+
parameters: readonly string[];
|
|
350
|
+
programId: string;
|
|
351
|
+
purpose: string;
|
|
352
|
+
query: OhProjectionQueryV1;
|
|
353
|
+
rulePack: OhProjectionRulePackV1;
|
|
354
|
+
v: 2;
|
|
355
|
+
}>;
|
|
356
|
+
|
|
357
|
+
export type OhMemoryFacadeOptionsV2 = Readonly<
|
|
358
|
+
Omit<OhMemoryFacadeOptionsV1, "programs"> & Readonly<{
|
|
359
|
+
/** Raw HMAC key for continuations that must survive agent reconstruction. */
|
|
360
|
+
continuationKey?: Uint8Array;
|
|
361
|
+
programs: readonly OhMemoryNamedProgramV2[];
|
|
362
|
+
}>
|
|
363
|
+
>;
|
|
364
|
+
|
|
365
|
+
export type OhMemoryIdentityV2 = Readonly<{
|
|
366
|
+
bindings: Readonly<Record<string, JsonPrimitive>>;
|
|
367
|
+
bindingsSha256: Sha256Hex;
|
|
368
|
+
boundQuerySha256: Sha256Hex;
|
|
369
|
+
canonical: OhMemoryLaneIdentityV1;
|
|
370
|
+
compositeDatasetSha256: Sha256Hex;
|
|
371
|
+
conflictPolicy: typeof OH_MEMORY_CONFLICT_POLICY_V1;
|
|
372
|
+
evaluationSha256: Sha256Hex;
|
|
373
|
+
memorySha256: Sha256Hex;
|
|
374
|
+
programId: string;
|
|
375
|
+
programSha256: Sha256Hex;
|
|
376
|
+
projectionSha256: Sha256Hex;
|
|
377
|
+
purpose: string;
|
|
378
|
+
rulePackSha256: Sha256Hex;
|
|
379
|
+
templateQuerySha256: Sha256Hex;
|
|
380
|
+
v: 2;
|
|
381
|
+
working: OhMemoryLaneIdentityV1;
|
|
382
|
+
}>;
|
|
383
|
+
|
|
384
|
+
export type OhMemoryResultRowV2 = Readonly<{
|
|
385
|
+
premiseAuthority: "canonical" | "unknown" | "working";
|
|
386
|
+
premiseLanes: readonly OhMemoryLaneV1[];
|
|
387
|
+
proofsTruncated: boolean;
|
|
388
|
+
resultRowSha256: Sha256Hex;
|
|
389
|
+
supportCount: number;
|
|
390
|
+
v: 2;
|
|
391
|
+
values: readonly OhProjectionAtomV1[];
|
|
392
|
+
}>;
|
|
393
|
+
|
|
394
|
+
export type OhMemoryPageV2 = Readonly<{
|
|
395
|
+
completeness: "complete" | "partial";
|
|
396
|
+
endExclusive: number;
|
|
397
|
+
hasMore: boolean;
|
|
398
|
+
maximumPageBytes: number;
|
|
399
|
+
pageSize: number;
|
|
400
|
+
returnedRows: number;
|
|
401
|
+
start: number;
|
|
402
|
+
totalRows: number;
|
|
403
|
+
truncation: Readonly<{
|
|
404
|
+
reasons: readonly [];
|
|
405
|
+
truncated: false;
|
|
406
|
+
v: 2;
|
|
407
|
+
}>;
|
|
408
|
+
v: 2;
|
|
409
|
+
}>;
|
|
410
|
+
|
|
411
|
+
export type OhMemoryQueryResultV2 = Readonly<{
|
|
412
|
+
authority: "derived";
|
|
413
|
+
conflicts: Readonly<{ count: number; conflictsSha256: Sha256Hex; v: 2 }>;
|
|
414
|
+
continuation: string | null;
|
|
415
|
+
continuationSha256: Sha256Hex | null;
|
|
416
|
+
explainCapability: Readonly<{ expiresAt: string; token: string; v: 2 }>;
|
|
417
|
+
identity: OhMemoryIdentityV2;
|
|
418
|
+
page: OhMemoryPageV2;
|
|
419
|
+
projectionResultSha256: Sha256Hex;
|
|
420
|
+
resultSha256: Sha256Hex;
|
|
421
|
+
rows: readonly OhMemoryResultRowV2[];
|
|
422
|
+
v: 2;
|
|
423
|
+
}>;
|
|
424
|
+
|
|
425
|
+
export type OhMemoryContinuationErrorReasonV2 = "authentication" | "encoding" | "identity";
|
|
426
|
+
|
|
427
|
+
/** A caller-supplied V2 continuation cannot be decoded, authenticated, or rebound exactly. */
|
|
428
|
+
export class OhMemoryContinuationError extends OhIntegrityError {
|
|
429
|
+
declare readonly code: "memory-continuation";
|
|
430
|
+
declare readonly reason: OhMemoryContinuationErrorReasonV2;
|
|
431
|
+
|
|
432
|
+
constructor(reason: OhMemoryContinuationErrorReasonV2, message: string) {
|
|
433
|
+
super(message);
|
|
434
|
+
this.name = "OhMemoryContinuationError";
|
|
435
|
+
Object.defineProperties(this, {
|
|
436
|
+
code: { configurable: false, enumerable: true, value: "memory-continuation", writable: false },
|
|
437
|
+
reason: { configurable: false, enumerable: true, value: reason, writable: false },
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export type OhMemoryExplanationV2 = Readonly<{
|
|
443
|
+
authority: "derived";
|
|
444
|
+
explanationSha256: Sha256Hex;
|
|
445
|
+
identity: OhMemoryIdentityV2;
|
|
446
|
+
page: OhMemoryPageV2;
|
|
447
|
+
pageRow: number;
|
|
448
|
+
premiseAuthority: OhMemoryResultRowV2["premiseAuthority"];
|
|
449
|
+
premiseLanes: readonly OhMemoryLaneV1[];
|
|
450
|
+
proofs: readonly OhMemoryProofV1[];
|
|
451
|
+
proofsTruncated: boolean;
|
|
452
|
+
resultRowSha256: Sha256Hex;
|
|
453
|
+
resultSha256: Sha256Hex;
|
|
454
|
+
supportCount: number;
|
|
455
|
+
v: 2;
|
|
456
|
+
values: readonly OhProjectionAtomV1[];
|
|
457
|
+
}>;
|
|
458
|
+
|
|
459
|
+
export interface OhMemoryAgentV2 {
|
|
460
|
+
explain(value: unknown): Promise<OhMemoryExplanationV2>;
|
|
461
|
+
nominate(value: unknown): Promise<OhMemoryNominationV1>;
|
|
462
|
+
query(value: unknown): Promise<OhMemoryQueryResultV2>;
|
|
463
|
+
remember(value: unknown): Promise<OhMemoryRememberReceiptV1>;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export const OH_MEMORY_AUTHORITY_LIMITS_V1 = Object.freeze({
|
|
467
|
+
adoptionReplacements: 128,
|
|
468
|
+
adoptionRequestBytes: OH_DEPENDENCY_CLOSURE_LIMITS_V1.bytes + 192 * 1024,
|
|
469
|
+
canonicalAdvanceOperations: 16_384,
|
|
470
|
+
canonicalAdvancePages: 64,
|
|
471
|
+
canonicalChangeFeedPage: 1_000,
|
|
472
|
+
canonicalChangeFeedPageBytes: 64 * 1024 * 1024,
|
|
473
|
+
retainedExplanationRoutes: OH_MEMORY_LIMITS_V1.explainCapabilities,
|
|
474
|
+
reportedAdoptionConflicts: 128,
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
export type OhMemoryCanonicalAdvanceReceiptV1 = Readonly<{
|
|
478
|
+
authorityId: string;
|
|
479
|
+
bindingSha256: Sha256Hex;
|
|
480
|
+
head: OhHeadV1;
|
|
481
|
+
priorHead: OhHeadV1;
|
|
482
|
+
receiptSha256: Sha256Hex;
|
|
483
|
+
status: "advanced" | "unchanged";
|
|
484
|
+
v: 1;
|
|
485
|
+
}>;
|
|
486
|
+
|
|
487
|
+
export type OhMemoryAdoptionConflictEntryV1 = Readonly<{
|
|
488
|
+
canonicalRecordSha256: Sha256Hex | null;
|
|
489
|
+
key: string;
|
|
490
|
+
nominatedRecordSha256: Sha256Hex;
|
|
491
|
+
v: 1;
|
|
492
|
+
}>;
|
|
493
|
+
|
|
494
|
+
/** Host-only compare-and-swap evidence for one intentional canonical replacement. */
|
|
495
|
+
export type OhMemoryAdoptionReplacementV1 = Readonly<{
|
|
496
|
+
expectedPriorRecordSha256: Sha256Hex;
|
|
497
|
+
key: string;
|
|
498
|
+
v: 1;
|
|
499
|
+
}>;
|
|
500
|
+
|
|
501
|
+
export type OhMemoryAdoptionConflictV1 = Readonly<{
|
|
502
|
+
actualHead: OhHeadV1;
|
|
503
|
+
conflicts: readonly OhMemoryAdoptionConflictEntryV1[];
|
|
504
|
+
conflictsSha256: Sha256Hex;
|
|
505
|
+
expectedHead: OhHeadV1;
|
|
506
|
+
reportedConflicts: number;
|
|
507
|
+
totalConflicts: number;
|
|
508
|
+
truncated: boolean;
|
|
509
|
+
v: 1;
|
|
510
|
+
}>;
|
|
511
|
+
|
|
512
|
+
export class OhMemoryAdoptionConflictError extends OhConflictError {
|
|
513
|
+
declare readonly conflict: OhMemoryAdoptionConflictV1;
|
|
514
|
+
|
|
515
|
+
constructor(conflict: OhMemoryAdoptionConflictV1) {
|
|
516
|
+
super("The nominated records conflict with the current canonical memory head.");
|
|
517
|
+
this.name = "OhMemoryAdoptionConflictError";
|
|
518
|
+
Object.defineProperty(this, "conflict", { configurable: false, enumerable: true,
|
|
519
|
+
value: immutableClone(conflict), writable: false });
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export type OhMemoryAdoptionReceiptV1 = Readonly<{
|
|
524
|
+
actorId: string;
|
|
525
|
+
authorityId: string;
|
|
526
|
+
bindingSha256: Sha256Hex;
|
|
527
|
+
head: OhHeadV1;
|
|
528
|
+
nominationSha256: Sha256Hex;
|
|
529
|
+
operationSha256: Sha256Hex | null;
|
|
530
|
+
priorHead: OhHeadV1;
|
|
531
|
+
receiptSha256: Sha256Hex;
|
|
532
|
+
status: "adopted" | "already-present";
|
|
533
|
+
v: 1;
|
|
534
|
+
}>;
|
|
535
|
+
|
|
536
|
+
export interface OhMemoryHostControlV1 {
|
|
537
|
+
adoptNomination(value: unknown): Promise<OhMemoryAdoptionReceiptV1>;
|
|
538
|
+
advanceCanonical(value: unknown): Promise<OhMemoryCanonicalAdvanceReceiptV1>;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
export type OhMemoryAuthorityV1 = Readonly<{
|
|
542
|
+
agent: OhMemoryAgentV2;
|
|
543
|
+
host: OhMemoryHostControlV1;
|
|
544
|
+
}>;
|
|
545
|
+
|
|
546
|
+
export type OhMemoryAuthorityOptionsV1 = OhMemoryFacadeOptionsV2 & Readonly<{
|
|
547
|
+
adoptionActorId: string;
|
|
548
|
+
maximumCanonicalOperationBytes?: number;
|
|
549
|
+
}>;
|
|
550
|
+
|
|
551
|
+
export type LaneSnapshot = Readonly<{
|
|
552
|
+
authorityId: string;
|
|
553
|
+
binding: OhStoreBindingV1;
|
|
554
|
+
dataset: OhProjectionDatasetV1;
|
|
555
|
+
lane: OhMemoryLaneV1;
|
|
556
|
+
projectionSnapshot: OhProjectionSnapshotV1;
|
|
557
|
+
snapshot: OhSnapshotV1;
|
|
558
|
+
}>;
|
|
559
|
+
|
|
560
|
+
type SyntheticSource = Readonly<{
|
|
561
|
+
physical: OhMemoryAuthoritySourceV1;
|
|
562
|
+
record: KnowledgeGraphRecordV1;
|
|
563
|
+
}>;
|
|
564
|
+
|
|
565
|
+
type StoredExplanation = Readonly<{
|
|
566
|
+
bytes: number;
|
|
567
|
+
expiresAtMonotonicMs: number;
|
|
568
|
+
identity: OhMemoryIdentityV1;
|
|
569
|
+
proofs: readonly (readonly OhMemoryProofV1[])[];
|
|
570
|
+
resultSha256: Sha256Hex;
|
|
571
|
+
rows: readonly OhMemoryResultRowV1[];
|
|
572
|
+
}>;
|
|
573
|
+
|
|
574
|
+
const builtInFactPolicy: OhMemoryFactPolicyV1 = Object.freeze({
|
|
575
|
+
extractorSha256: OH_MEMORY_COMPOSITE_FACT_EXTRACTOR_V1.extractorSha256,
|
|
576
|
+
factPackId: OH_MEMORY_COMPOSITE_FACT_EXTRACTOR_V1.factPackId,
|
|
577
|
+
kind: "built-in",
|
|
578
|
+
v: 1,
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
export function immutableClone<T>(value: T): T {
|
|
582
|
+
if (Array.isArray(value)) {
|
|
583
|
+
return Object.freeze(value.map((item) => immutableClone(item))) as T;
|
|
584
|
+
}
|
|
585
|
+
if (value !== null && typeof value === "object") {
|
|
586
|
+
if (!isPlainRecord(value)) throw new TypeError("Memory output contains a non-JSON object.");
|
|
587
|
+
const cloned: Record<string, unknown> = {};
|
|
588
|
+
for (const key of Object.keys(value)) {
|
|
589
|
+
Object.defineProperty(cloned, key, { configurable: false, enumerable: true,
|
|
590
|
+
value: immutableClone(value[key]), writable: false });
|
|
591
|
+
}
|
|
592
|
+
return Object.freeze(cloned) as T;
|
|
593
|
+
}
|
|
594
|
+
return value;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
type DetachedCanonicalData = Readonly<{
|
|
598
|
+
canonical: string;
|
|
599
|
+
value: unknown;
|
|
600
|
+
}>;
|
|
601
|
+
|
|
602
|
+
function canonicalStringByteLength(
|
|
603
|
+
value: string,
|
|
604
|
+
label: string,
|
|
605
|
+
path: string,
|
|
606
|
+
maximumBytes: number,
|
|
607
|
+
): number {
|
|
608
|
+
let bytes = 2;
|
|
609
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
610
|
+
const code = value.charCodeAt(index);
|
|
611
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
612
|
+
const next = value.charCodeAt(index + 1);
|
|
613
|
+
if (!(next >= 0xdc00 && next <= 0xdfff)) {
|
|
614
|
+
throw new TypeError(`${label} contains invalid Unicode at ${path}.`);
|
|
615
|
+
}
|
|
616
|
+
bytes += 4;
|
|
617
|
+
index += 1;
|
|
618
|
+
} else if (code >= 0xdc00 && code <= 0xdfff) {
|
|
619
|
+
throw new TypeError(`${label} contains invalid Unicode at ${path}.`);
|
|
620
|
+
} else if (code === 0x22 || code === 0x5c || code === 0x08 || code === 0x09
|
|
621
|
+
|| code === 0x0a || code === 0x0c || code === 0x0d) {
|
|
622
|
+
bytes += 2;
|
|
623
|
+
} else if (code <= 0x1f) {
|
|
624
|
+
bytes += 6;
|
|
625
|
+
} else if (code <= 0x7f) {
|
|
626
|
+
bytes += 1;
|
|
627
|
+
} else if (code <= 0x7ff) {
|
|
628
|
+
bytes += 2;
|
|
629
|
+
} else {
|
|
630
|
+
bytes += 3;
|
|
631
|
+
}
|
|
632
|
+
if (bytes > maximumBytes) {
|
|
633
|
+
throw new RangeError(`${label} exceeds its canonical byte bound.`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return bytes;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Takes one descriptor-based JSON snapshot of an external value. No accessor is
|
|
641
|
+
* invoked, proxies are refused at every depth, and callers validate and execute
|
|
642
|
+
* only the returned frozen graph and its matching canonical bytes.
|
|
643
|
+
*/
|
|
644
|
+
export function detachCanonicalData(
|
|
645
|
+
value: unknown,
|
|
646
|
+
label: string,
|
|
647
|
+
maximumBytes: number,
|
|
648
|
+
): DetachedCanonicalData {
|
|
649
|
+
const ancestors = new Set<object>();
|
|
650
|
+
let bytes = 0;
|
|
651
|
+
let nodes = 0;
|
|
652
|
+
const spendBytes = (count: number): void => {
|
|
653
|
+
bytes += count;
|
|
654
|
+
if (bytes > maximumBytes) throw new RangeError(`${label} exceeds its canonical byte bound.`);
|
|
655
|
+
};
|
|
656
|
+
const detach = (candidate: unknown, path: string, depth: number): unknown => {
|
|
657
|
+
if (depth > OH_MEMORY_LIMITS_V1.detachedCanonicalDepth) {
|
|
658
|
+
throw new RangeError(`${label} exceeds its canonical nesting depth bound.`);
|
|
659
|
+
}
|
|
660
|
+
nodes += 1;
|
|
661
|
+
if (nodes > OH_MEMORY_LIMITS_V1.detachedCanonicalNodes) {
|
|
662
|
+
throw new RangeError(`${label} exceeds its canonical node bound.`);
|
|
663
|
+
}
|
|
664
|
+
if (candidate === null) {
|
|
665
|
+
spendBytes(4);
|
|
666
|
+
return candidate;
|
|
667
|
+
}
|
|
668
|
+
if (typeof candidate === "boolean") {
|
|
669
|
+
spendBytes(candidate ? 4 : 5);
|
|
670
|
+
return candidate;
|
|
671
|
+
}
|
|
672
|
+
if (typeof candidate === "string") {
|
|
673
|
+
spendBytes(canonicalStringByteLength(candidate, label, path, maximumBytes - bytes));
|
|
674
|
+
return candidate;
|
|
675
|
+
}
|
|
676
|
+
if (typeof candidate === "number") {
|
|
677
|
+
if (!Number.isFinite(candidate) || Object.is(candidate, -0)) {
|
|
678
|
+
throw new TypeError(`${label} contains a noncanonical number at ${path}.`);
|
|
679
|
+
}
|
|
680
|
+
spendBytes(utf8ByteLength(canonicalJson(candidate)));
|
|
681
|
+
return candidate;
|
|
682
|
+
}
|
|
683
|
+
if (typeof candidate !== "object") {
|
|
684
|
+
throw new TypeError(`${label} contains a non-JSON value at ${path}.`);
|
|
685
|
+
}
|
|
686
|
+
if (isProxy(candidate)) throw new TypeError(`${label} contains a proxy at ${path}.`);
|
|
687
|
+
if (ancestors.has(candidate)) throw new TypeError(`${label} contains a cycle at ${path}.`);
|
|
688
|
+
ancestors.add(candidate);
|
|
689
|
+
try {
|
|
690
|
+
if (Array.isArray(candidate)) {
|
|
691
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(candidate, "length");
|
|
692
|
+
const length = lengthDescriptor?.value;
|
|
693
|
+
if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0) {
|
|
694
|
+
throw new TypeError(`${label} contains an invalid array at ${path}.`);
|
|
695
|
+
}
|
|
696
|
+
if (length > OH_MEMORY_LIMITS_V1.detachedCanonicalBreadth) {
|
|
697
|
+
throw new RangeError(`${label} exceeds its canonical breadth bound.`);
|
|
698
|
+
}
|
|
699
|
+
if (length > OH_MEMORY_LIMITS_V1.detachedCanonicalNodes - nodes) {
|
|
700
|
+
throw new RangeError(`${label} exceeds its canonical node bound.`);
|
|
701
|
+
}
|
|
702
|
+
spendBytes(2 + Math.max(0, length - 1));
|
|
703
|
+
const keys = Reflect.ownKeys(candidate);
|
|
704
|
+
if (keys.length !== length + 1 || !keys.includes("length")
|
|
705
|
+
|| keys.some((key) => key !== "length" && (typeof key !== "string"
|
|
706
|
+
|| !/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= length))) {
|
|
707
|
+
throw new TypeError(`${label} contains a non-data array at ${path}.`);
|
|
708
|
+
}
|
|
709
|
+
const detached: unknown[] = [];
|
|
710
|
+
for (let index = 0; index < length; index += 1) {
|
|
711
|
+
const descriptor = Object.getOwnPropertyDescriptor(candidate, String(index));
|
|
712
|
+
if (descriptor === undefined || !descriptor.enumerable
|
|
713
|
+
|| descriptor.get !== undefined || descriptor.set !== undefined) {
|
|
714
|
+
throw new TypeError(`${label} contains a non-data array entry at ${path}[${index}].`);
|
|
715
|
+
}
|
|
716
|
+
detached.push(detach(descriptor.value, `${path}[${index}]`, depth + 1));
|
|
717
|
+
}
|
|
718
|
+
return Object.freeze(detached);
|
|
719
|
+
}
|
|
720
|
+
const prototype = Object.getPrototypeOf(candidate);
|
|
721
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
722
|
+
throw new TypeError(`${label} contains a non-plain object at ${path}.`);
|
|
723
|
+
}
|
|
724
|
+
const keys = Reflect.ownKeys(candidate);
|
|
725
|
+
if (keys.some((key) => typeof key !== "string")) {
|
|
726
|
+
throw new TypeError(`${label} contains a symbol property at ${path}.`);
|
|
727
|
+
}
|
|
728
|
+
if (keys.length > OH_MEMORY_LIMITS_V1.detachedCanonicalBreadth) {
|
|
729
|
+
throw new RangeError(`${label} exceeds its canonical breadth bound.`);
|
|
730
|
+
}
|
|
731
|
+
if (keys.length > OH_MEMORY_LIMITS_V1.detachedCanonicalNodes - nodes) {
|
|
732
|
+
throw new RangeError(`${label} exceeds its canonical node bound.`);
|
|
733
|
+
}
|
|
734
|
+
spendBytes(2 + Math.max(0, keys.length - 1));
|
|
735
|
+
const detached: Record<string, unknown> = {};
|
|
736
|
+
for (const key of keys as string[]) {
|
|
737
|
+
spendBytes(canonicalStringByteLength(key, label, `${path}.<key>`,
|
|
738
|
+
maximumBytes - bytes - 1) + 1);
|
|
739
|
+
const descriptor = Object.getOwnPropertyDescriptor(candidate, key);
|
|
740
|
+
if (descriptor === undefined || !descriptor.enumerable
|
|
741
|
+
|| descriptor.get !== undefined || descriptor.set !== undefined) {
|
|
742
|
+
throw new TypeError(`${label} contains a non-data property at ${path}.${key}.`);
|
|
743
|
+
}
|
|
744
|
+
Object.defineProperty(detached, key, { configurable: false, enumerable: true,
|
|
745
|
+
value: detach(descriptor.value, `${path}.${key}`, depth + 1), writable: false });
|
|
746
|
+
}
|
|
747
|
+
return Object.freeze(detached);
|
|
748
|
+
} finally {
|
|
749
|
+
ancestors.delete(candidate);
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
const detached = detach(value, "$root", 0);
|
|
753
|
+
const canonical = canonicalJson(detached);
|
|
754
|
+
if (utf8ByteLength(canonical) !== bytes) {
|
|
755
|
+
throw new OhIntegrityError(`${label} canonical byte accounting did not reproduce its snapshot.`);
|
|
756
|
+
}
|
|
757
|
+
return Object.freeze({ canonical, value: detached });
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function compareText(left: string, right: string): number {
|
|
761
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
export function exactHead(left: OhHeadV1, right: OhHeadV1): boolean {
|
|
765
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
export function authorityId(value: unknown): string {
|
|
769
|
+
const parsed = safeCode(value, 128);
|
|
770
|
+
if (parsed === null) throw new TypeError("Invalid memory authority ID.");
|
|
771
|
+
return parsed;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
export function bindingFor(store: OhStoreV1, expected: Sha256Hex, lane: OhMemoryLaneV1): OhStoreBindingV1 {
|
|
775
|
+
const binding = parseOhStoreBindingV1(store.binding);
|
|
776
|
+
if (binding === null || binding.bindingSha256 !== parseSha256Hex(expected)) {
|
|
777
|
+
throw new OhIntegrityError(`The ${lane} store is not the host-bound authority.`);
|
|
778
|
+
}
|
|
779
|
+
if (binding.profile.profileKind !== lane) {
|
|
780
|
+
throw new OhProfileError(`The ${lane} memory lane has the wrong store profile.`);
|
|
781
|
+
}
|
|
782
|
+
return binding;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
function laneIdentity(value: LaneSnapshot): OhMemoryLaneIdentityV1 {
|
|
786
|
+
return Object.freeze({
|
|
787
|
+
authorityId: value.authorityId,
|
|
788
|
+
bindingSha256: value.binding.bindingSha256,
|
|
789
|
+
datasetSha256: value.dataset.datasetSha256,
|
|
790
|
+
head: value.snapshot.head,
|
|
791
|
+
lane: value.lane,
|
|
792
|
+
snapshotSha256: value.projectionSnapshot.snapshotSha256,
|
|
793
|
+
v: 1,
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
export function parseDetachedStoreHead(value: unknown, label: string): OhHeadV1 {
|
|
798
|
+
const head = parseOhHeadV1(detachCanonicalData(value, `${label} head`, 4 * 1024).value);
|
|
799
|
+
if (head === null) throw new OhIntegrityError(`${label} returned an invalid head.`);
|
|
800
|
+
return immutableClone(head);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
export function parseDetachedStoreSnapshot(
|
|
804
|
+
value: unknown,
|
|
805
|
+
label: string,
|
|
806
|
+
expectedHead: OhHeadV1,
|
|
807
|
+
spaceId: string,
|
|
808
|
+
): Readonly<{ projectionSnapshot: OhProjectionSnapshotV1; snapshot: OhSnapshotV1 }> {
|
|
809
|
+
const detachedSnapshot = detachCanonicalData(value, `${label} snapshot`,
|
|
810
|
+
OH_MEMORY_LIMITS_V1.snapshotBytesPerLane);
|
|
811
|
+
if (!isPlainRecord(detachedSnapshot.value)
|
|
812
|
+
|| !hasExactKeys(detachedSnapshot.value, ["head", "records", "v"])
|
|
813
|
+
|| detachedSnapshot.value.v !== 1 || !Array.isArray(detachedSnapshot.value.records)) {
|
|
814
|
+
throw new OhIntegrityError(`${label} returned an invalid snapshot envelope.`);
|
|
815
|
+
}
|
|
816
|
+
const detached = detachedSnapshot.value as Record<string, unknown>;
|
|
817
|
+
const detachedHead = parseOhHeadV1(detached.head);
|
|
818
|
+
if (detachedHead === null) throw new OhIntegrityError(`${label} returned an invalid snapshot head.`);
|
|
819
|
+
const snapshot: OhSnapshotV1 = immutableClone({ head: detachedHead,
|
|
820
|
+
records: detached.records as OhSnapshotV1["records"], v: 1 });
|
|
821
|
+
if (!exactHead(snapshot.head, expectedHead)) {
|
|
822
|
+
throw new OhIntegrityError(`${label} snapshot differs from its pinned head.`);
|
|
823
|
+
}
|
|
824
|
+
let projectionSnapshot: OhProjectionSnapshotV1;
|
|
825
|
+
try {
|
|
826
|
+
projectionSnapshot = createOhProjectionSnapshotV1({
|
|
827
|
+
head: snapshot.head,
|
|
828
|
+
records: snapshot.records,
|
|
829
|
+
spaceId,
|
|
830
|
+
});
|
|
831
|
+
} catch {
|
|
832
|
+
throw new OhIntegrityError(`${label} returned invalid snapshot records.`);
|
|
833
|
+
}
|
|
834
|
+
return Object.freeze({ projectionSnapshot, snapshot });
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
export function datasetForSnapshot(
|
|
838
|
+
binding: OhStoreBindingV1,
|
|
839
|
+
snapshot: OhSnapshotV1,
|
|
840
|
+
validatedProjectionSnapshot?: OhProjectionSnapshotV1,
|
|
841
|
+
): Readonly<{
|
|
842
|
+
dataset: OhProjectionDatasetV1;
|
|
843
|
+
projectionSnapshot: OhProjectionSnapshotV1;
|
|
844
|
+
}> {
|
|
845
|
+
const projectionSnapshot = validatedProjectionSnapshot ?? createOhProjectionSnapshotV1({
|
|
846
|
+
head: snapshot.head, records: snapshot.records, spaceId: binding.spaceId });
|
|
847
|
+
const dataset = createOhProjectionDatasetV1({
|
|
848
|
+
extractorSha256: canonicalSha256({ extractor: "oh.memory.lane-structural", v: 1 }),
|
|
849
|
+
factPackId: "oh.memory.lane-structural",
|
|
850
|
+
factPackRevision: 1,
|
|
851
|
+
facts: createOhProjectionRecordFactsV1(snapshot.records),
|
|
852
|
+
snapshot: projectionSnapshot,
|
|
853
|
+
});
|
|
854
|
+
return { dataset, projectionSnapshot };
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
async function readLane(
|
|
858
|
+
authority: Readonly<{ authorityId: string; binding: OhStoreBindingV1; store: OhStoreV1 }>,
|
|
859
|
+
lane: OhMemoryLaneV1,
|
|
860
|
+
expectedHead?: OhHeadV1,
|
|
861
|
+
): Promise<LaneSnapshot> {
|
|
862
|
+
const label = `The ${lane} store`;
|
|
863
|
+
const head = expectedHead === undefined
|
|
864
|
+
? parseDetachedStoreHead(await authority.store.head(), label)
|
|
865
|
+
: immutableClone(expectedHead);
|
|
866
|
+
const returnedSnapshot = await authority.store.snapshot({
|
|
867
|
+
head: { operationSha256: head.operationSha256, sequence: head.sequence },
|
|
868
|
+
maximumRecords: OH_MEMORY_LIMITS_V1.maximumRecordsPerLane,
|
|
869
|
+
});
|
|
870
|
+
const parsed = parseDetachedStoreSnapshot(returnedSnapshot, label, head,
|
|
871
|
+
authority.binding.spaceId);
|
|
872
|
+
const projected = datasetForSnapshot(authority.binding, parsed.snapshot,
|
|
873
|
+
parsed.projectionSnapshot);
|
|
874
|
+
return Object.freeze({ authorityId: authority.authorityId, binding: authority.binding,
|
|
875
|
+
dataset: projected.dataset, lane, projectionSnapshot: projected.projectionSnapshot,
|
|
876
|
+
snapshot: parsed.snapshot });
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function syntheticKey(lane: OhMemoryLaneV1, recordSha256: Sha256Hex): string {
|
|
880
|
+
return `memory-source:${lane}:${recordSha256}`;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function createSyntheticSources(lanes: readonly LaneSnapshot[]): Readonly<{
|
|
884
|
+
records: readonly KnowledgeGraphRecordV1[];
|
|
885
|
+
sources: ReadonlyMap<string, SyntheticSource>;
|
|
886
|
+
}> {
|
|
887
|
+
const sources = new Map<string, SyntheticSource>();
|
|
888
|
+
for (const lane of lanes) {
|
|
889
|
+
for (const physicalRecord of lane.snapshot.records) {
|
|
890
|
+
const key = syntheticKey(lane.lane, physicalRecord.recordSha256);
|
|
891
|
+
const record = createKnowledgeGraphRecordV1({ dependencies: [], key, kind: "view", v: 1,
|
|
892
|
+
value: { authorityId: lane.authorityId, bindingSha256: lane.binding.bindingSha256,
|
|
893
|
+
key: physicalRecord.key, lane: lane.lane, recordSha256: physicalRecord.recordSha256,
|
|
894
|
+
snapshotSha256: lane.projectionSnapshot.snapshotSha256, v: 1 } });
|
|
895
|
+
const physical = Object.freeze({ authorityId: lane.authorityId,
|
|
896
|
+
bindingSha256: lane.binding.bindingSha256,
|
|
897
|
+
head: lane.snapshot.head, key: physicalRecord.key, lane: lane.lane,
|
|
898
|
+
recordSha256: physicalRecord.recordSha256,
|
|
899
|
+
snapshotSha256: lane.projectionSnapshot.snapshotSha256, v: 1 as const });
|
|
900
|
+
if (sources.has(key)) throw new OhIntegrityError("A memory lane contains a duplicate source digest.");
|
|
901
|
+
sources.set(key, Object.freeze({ physical, record }));
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
if (sources.size > OH_MEMORY_LIMITS_V1.maximumSyntheticRecords) {
|
|
905
|
+
throw new RangeError("The composite memory snapshot has too many records.");
|
|
906
|
+
}
|
|
907
|
+
const records = [...sources.values()].map(({ record }) => record)
|
|
908
|
+
.sort((left, right) => compareText(left.key, right.key));
|
|
909
|
+
return { records, sources };
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
function sourceFor(
|
|
913
|
+
sources: ReadonlyMap<string, SyntheticSource>,
|
|
914
|
+
lane: OhMemoryLaneV1,
|
|
915
|
+
record: KnowledgeGraphRecordV1,
|
|
916
|
+
) {
|
|
917
|
+
const source = sources.get(syntheticKey(lane, record.recordSha256));
|
|
918
|
+
if (source === undefined) throw new OhIntegrityError("A composite memory source is missing.");
|
|
919
|
+
return [{ key: source.record.key, recordSha256: source.record.recordSha256, v: 1 as const }];
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
function createCompositeDataset(canonical: LaneSnapshot, working: LaneSnapshot,
|
|
923
|
+
extractors: readonly OhMemoryFactExtractorV1[]): Readonly<{
|
|
924
|
+
conflicts: readonly OhMemoryConflictV1[];
|
|
925
|
+
dataset: OhProjectionDatasetV1;
|
|
926
|
+
factPolicies: ReadonlyMap<string, OhMemoryFactPolicyV1>;
|
|
927
|
+
snapshot: OhProjectionSnapshotV1;
|
|
928
|
+
sources: ReadonlyMap<string, SyntheticSource>;
|
|
929
|
+
}> {
|
|
930
|
+
const synthetic = createSyntheticSources([canonical, working]);
|
|
931
|
+
const extractorInvocations = synthetic.records.length * extractors.length;
|
|
932
|
+
if (extractorInvocations > OH_MEMORY_LIMITS_V1.maximumExtractorInvocations) {
|
|
933
|
+
throw new RangeError("The composite memory extractor invocation count exceeds its explicit bound.");
|
|
934
|
+
}
|
|
935
|
+
const facts: OhProjectionFactV1[] = [];
|
|
936
|
+
const factDigests = new Set<Sha256Hex>();
|
|
937
|
+
const factPolicies = new Map<string, OhMemoryFactPolicyV1>();
|
|
938
|
+
const addFact = (fact: OhProjectionFactV1, policy: OhMemoryFactPolicyV1) => {
|
|
939
|
+
if (facts.length >= OH_PROJECTION_LIMITS_V1.facts) {
|
|
940
|
+
throw new RangeError("The composite memory fact set exceeds its explicit bound.");
|
|
941
|
+
}
|
|
942
|
+
if (factDigests.has(fact.factSha256)) {
|
|
943
|
+
throw new OhIntegrityError("A memory fact extractor emitted the same exact fact twice.");
|
|
944
|
+
}
|
|
945
|
+
const priorPolicy = factPolicies.get(fact.relation);
|
|
946
|
+
if (priorPolicy !== undefined && canonicalJson(priorPolicy) !== canonicalJson(policy)) {
|
|
947
|
+
throw new OhIntegrityError("A memory relation has more than one fact policy.");
|
|
948
|
+
}
|
|
949
|
+
facts.push(fact);
|
|
950
|
+
factDigests.add(fact.factSha256);
|
|
951
|
+
factPolicies.set(fact.relation, policy);
|
|
952
|
+
};
|
|
953
|
+
const byLane = new Map<OhMemoryLaneV1, Map<string, KnowledgeGraphRecordV1>>([
|
|
954
|
+
["canonical", new Map(canonical.snapshot.records.map((record) => [record.key, record]))],
|
|
955
|
+
["working", new Map(working.snapshot.records.map((record) => [record.key, record]))],
|
|
956
|
+
]);
|
|
957
|
+
for (const lane of [canonical, working] as const) {
|
|
958
|
+
for (const record of lane.snapshot.records) {
|
|
959
|
+
const extractorRecord = immutableClone(record);
|
|
960
|
+
const source = sourceFor(synthetic.sources, lane.lane, record);
|
|
961
|
+
addFact(createOhProjectionFactV1({ relation: "memory.record", sources: source,
|
|
962
|
+
tuple: [lane.lane, record.key, record.kind, record.recordSha256] }), builtInFactPolicy);
|
|
963
|
+
for (const dependency of record.dependencies) {
|
|
964
|
+
addFact(createOhProjectionFactV1({ relation: "memory.dependency", sources: source,
|
|
965
|
+
tuple: [lane.lane, record.key, dependency] }), builtInFactPolicy);
|
|
966
|
+
}
|
|
967
|
+
for (const extractor of extractors) {
|
|
968
|
+
const declared = extractor.extract(Object.freeze({ lane: lane.lane, record: extractorRecord }));
|
|
969
|
+
if (!Array.isArray(declared)
|
|
970
|
+
|| declared.length > OH_MEMORY_LIMITS_V1.factsPerRecordPerExtractor) {
|
|
971
|
+
throw new RangeError("A memory fact extractor exceeded its per-record bound.");
|
|
972
|
+
}
|
|
973
|
+
for (const fact of declared) {
|
|
974
|
+
if (!isPlainRecord(fact) || !hasExactKeys(fact, ["relation", "tuple", "v"])
|
|
975
|
+
|| fact.v !== 1 || !Array.isArray(fact.tuple) || typeof fact.relation !== "string"
|
|
976
|
+
|| !extractor.relations.includes(fact.relation)) {
|
|
977
|
+
throw new TypeError("A memory fact extractor returned an invalid or reserved fact.");
|
|
978
|
+
}
|
|
979
|
+
addFact(createOhProjectionFactV1({ relation: fact.relation, sources: source, tuple: fact.tuple }),
|
|
980
|
+
Object.freeze({ extractorId: extractor.extractorId,
|
|
981
|
+
extractorSha256: extractor.extractorSha256, kind: "domain", v: 1 }));
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
const conflicts: OhMemoryConflictV1[] = [];
|
|
987
|
+
const canonicalByKey = byLane.get("canonical")!;
|
|
988
|
+
const workingByKey = byLane.get("working")!;
|
|
989
|
+
for (const key of [...canonicalByKey.keys()].filter((candidate) => workingByKey.has(candidate)).sort()) {
|
|
990
|
+
const canonicalRecord = canonicalByKey.get(key)!;
|
|
991
|
+
const workingRecord = workingByKey.get(key)!;
|
|
992
|
+
const sources = [
|
|
993
|
+
...sourceFor(synthetic.sources, "canonical", canonicalRecord),
|
|
994
|
+
...sourceFor(synthetic.sources, "working", workingRecord),
|
|
995
|
+
];
|
|
996
|
+
if (canonicalRecord.recordSha256 === workingRecord.recordSha256) {
|
|
997
|
+
addFact(createOhProjectionFactV1({ relation: "memory.agreement", sources,
|
|
998
|
+
tuple: [key, canonicalRecord.recordSha256] }), builtInFactPolicy);
|
|
999
|
+
} else {
|
|
1000
|
+
addFact(createOhProjectionFactV1({ relation: "memory.conflict", sources,
|
|
1001
|
+
tuple: [key, canonicalRecord.recordSha256, workingRecord.recordSha256] }), builtInFactPolicy);
|
|
1002
|
+
conflicts.push(Object.freeze({ canonicalRecordSha256: canonicalRecord.recordSha256,
|
|
1003
|
+
key, v: 1, workingRecordSha256: workingRecord.recordSha256 }));
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
const recordRefs = synthetic.records.map(knowledgeGraphRecordRefV1)
|
|
1007
|
+
.sort((left, right) => compareText(left.key, right.key));
|
|
1008
|
+
const sourceIdentity = {
|
|
1009
|
+
canonical: laneIdentity(canonical),
|
|
1010
|
+
conflictPolicy: OH_MEMORY_CONFLICT_POLICY_V1,
|
|
1011
|
+
recordRefs,
|
|
1012
|
+
v: 1 as const,
|
|
1013
|
+
working: laneIdentity(working),
|
|
1014
|
+
};
|
|
1015
|
+
const head: OhHeadV1 = Object.freeze({ generation: 1,
|
|
1016
|
+
graphRevisionSha256: canonicalSha256({ kind: "oh.memory.composite-graph", sourceIdentity }),
|
|
1017
|
+
operationSha256: canonicalSha256({ kind: "oh.memory.composite-operation", sourceIdentity }),
|
|
1018
|
+
recordsSha256: canonicalSha256(recordRefs), sequence: 1, v: 1 });
|
|
1019
|
+
const snapshot = createOhProjectionSnapshotV1({ head, records: synthetic.records,
|
|
1020
|
+
spaceId: "oh.memory.composite" });
|
|
1021
|
+
const dataset = createOhProjectionDatasetV1({
|
|
1022
|
+
extractorSha256: canonicalSha256({
|
|
1023
|
+
builtIn: OH_MEMORY_COMPOSITE_FACT_EXTRACTOR_V1.extractorSha256,
|
|
1024
|
+
extensions: extractors.map(({ extractorId, extractorSha256, relations }) => ({
|
|
1025
|
+
extractorId, extractorSha256, relations,
|
|
1026
|
+
})),
|
|
1027
|
+
v: 1,
|
|
1028
|
+
}),
|
|
1029
|
+
factPackId: OH_MEMORY_COMPOSITE_FACT_EXTRACTOR_V1.factPackId,
|
|
1030
|
+
factPackRevision: OH_MEMORY_COMPOSITE_FACT_EXTRACTOR_V1.factPackRevision,
|
|
1031
|
+
facts,
|
|
1032
|
+
snapshot,
|
|
1033
|
+
});
|
|
1034
|
+
return Object.freeze({ conflicts: Object.freeze(conflicts), dataset, factPolicies, snapshot,
|
|
1035
|
+
sources: synthetic.sources });
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
function mapProof(proof: OhProjectionProofV1,
|
|
1039
|
+
sources: ReadonlyMap<string, SyntheticSource>,
|
|
1040
|
+
factPolicies: ReadonlyMap<string, OhMemoryFactPolicyV1>): OhMemoryProofV1 {
|
|
1041
|
+
if (proof.kind === "truncated") return Object.freeze({ ...proof });
|
|
1042
|
+
if (proof.kind === "derived") {
|
|
1043
|
+
return Object.freeze({ ...proof,
|
|
1044
|
+
premises: Object.freeze(proof.premises.map((premise) => mapProof(premise, sources, factPolicies))) });
|
|
1045
|
+
}
|
|
1046
|
+
const physical = proof.sources.map((source) => {
|
|
1047
|
+
const mapped = sources.get(source.key);
|
|
1048
|
+
if (mapped === undefined || mapped.record.recordSha256 !== source.recordSha256) {
|
|
1049
|
+
throw new OhIntegrityError("A projection proof has no exact physical memory source.");
|
|
1050
|
+
}
|
|
1051
|
+
return mapped.physical;
|
|
1052
|
+
}).sort((left, right) => compareText(canonicalJson(left), canonicalJson(right)));
|
|
1053
|
+
const factPolicy = factPolicies.get(proof.relation);
|
|
1054
|
+
if (factPolicy === undefined) throw new OhIntegrityError("A projection proof has no memory fact policy.");
|
|
1055
|
+
return Object.freeze({ factPolicy, kind: "fact", relation: proof.relation,
|
|
1056
|
+
sources: Object.freeze(physical), tuple: proof.tuple, v: 1 });
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
function collectLanes(proof: OhMemoryProofV1, lanes: Set<OhMemoryLaneV1>): boolean {
|
|
1060
|
+
if (proof.kind === "truncated") return true;
|
|
1061
|
+
if (proof.kind === "fact") {
|
|
1062
|
+
for (const source of proof.sources) lanes.add(source.lane);
|
|
1063
|
+
return false;
|
|
1064
|
+
}
|
|
1065
|
+
let unknown = proof.premisesTruncated;
|
|
1066
|
+
for (const premise of proof.premises) unknown = collectLanes(premise, lanes) || unknown;
|
|
1067
|
+
return unknown;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
function publicRow(row: OhProjectionResultRowV1,
|
|
1071
|
+
proofs: readonly OhMemoryProofV1[]): OhMemoryResultRowV1 {
|
|
1072
|
+
const lanes = new Set<OhMemoryLaneV1>();
|
|
1073
|
+
let unknown = row.proofsTruncated;
|
|
1074
|
+
for (const proof of proofs) unknown = collectLanes(proof, lanes) || unknown;
|
|
1075
|
+
const premiseLanes = [...lanes].sort() as readonly OhMemoryLaneV1[];
|
|
1076
|
+
const premiseAuthority: OhMemoryResultRowV1["premiseAuthority"] = unknown || premiseLanes.length === 0
|
|
1077
|
+
? "unknown" : premiseLanes.includes("working") ? "working" : "canonical";
|
|
1078
|
+
const payload = { premiseAuthority, premiseLanes, proofsTruncated: row.proofsTruncated,
|
|
1079
|
+
supportCount: row.supportCount, v: 1 as const, values: row.values };
|
|
1080
|
+
return Object.freeze({ ...payload, resultRowSha256: canonicalSha256(payload) });
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
function resolvePrograms(programs: readonly OhMemoryNamedProgramV1[]): ReadonlyMap<string, OhMemoryNamedProgramV1> {
|
|
1084
|
+
if (programs.length < 1 || programs.length > OH_MEMORY_LIMITS_V1.maximumPrograms) {
|
|
1085
|
+
throw new RangeError("Memory requires a bounded nonempty named program registry.");
|
|
1086
|
+
}
|
|
1087
|
+
const resolved = new Map<string, OhMemoryNamedProgramV1>();
|
|
1088
|
+
for (const program of programs) {
|
|
1089
|
+
const programId = safeCode(program.programId, 128);
|
|
1090
|
+
const purpose = safeCode(program.purpose, 256);
|
|
1091
|
+
const query = parseOhProjectionQueryV1(program.query);
|
|
1092
|
+
const rulePack = parseOhProjectionRulePackV1(program.rulePack);
|
|
1093
|
+
if (programId === null || purpose === null || query === null || rulePack === null
|
|
1094
|
+
|| resolved.has(programId)) {
|
|
1095
|
+
throw new TypeError("Invalid or duplicate named memory program.");
|
|
1096
|
+
}
|
|
1097
|
+
resolved.set(programId, immutableClone({ ...(program.evaluation === undefined ? {}
|
|
1098
|
+
: { evaluation: { ...program.evaluation } }), programId, purpose, query, rulePack }));
|
|
1099
|
+
}
|
|
1100
|
+
return resolved;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
export function resolveExtractors(extractors: readonly OhMemoryFactExtractorV1[]): readonly OhMemoryFactExtractorV1[] {
|
|
1104
|
+
if (extractors.length > OH_MEMORY_LIMITS_V1.maximumExtractors) {
|
|
1105
|
+
throw new RangeError("The memory domain extractor registry is too large.");
|
|
1106
|
+
}
|
|
1107
|
+
const claimedRelations = new Set<string>();
|
|
1108
|
+
const resolved = extractors.map((extractor) => {
|
|
1109
|
+
const extractorId = safeCode(extractor.extractorId, 128);
|
|
1110
|
+
const extractorSha256 = parseSha256Hex(extractor.extractorSha256);
|
|
1111
|
+
if (extractorId === null || extractorSha256 === null || typeof extractor.extract !== "function"
|
|
1112
|
+
|| !Array.isArray(extractor.relations) || extractor.relations.length < 1
|
|
1113
|
+
|| extractor.relations.length > OH_MEMORY_LIMITS_V1.relationsPerExtractor) {
|
|
1114
|
+
throw new TypeError("Invalid memory domain fact extractor.");
|
|
1115
|
+
}
|
|
1116
|
+
const relations = extractor.relations.map((relation) => safeCode(relation, 128)).sort();
|
|
1117
|
+
if (relations.some((relation) => relation === null
|
|
1118
|
+
|| relation.startsWith("memory.") || relation.startsWith("oh."))
|
|
1119
|
+
|| new Set(relations).size !== relations.length) {
|
|
1120
|
+
throw new TypeError("A memory domain fact extractor has invalid or reserved relations.");
|
|
1121
|
+
}
|
|
1122
|
+
for (const relation of relations as string[]) {
|
|
1123
|
+
if (claimedRelations.has(relation)) {
|
|
1124
|
+
throw new TypeError("Memory domain fact extractor relations must have one owner.");
|
|
1125
|
+
}
|
|
1126
|
+
claimedRelations.add(relation);
|
|
1127
|
+
}
|
|
1128
|
+
return Object.freeze({ extract: extractor.extract, extractorId, extractorSha256,
|
|
1129
|
+
relations: Object.freeze(relations as string[]) });
|
|
1130
|
+
}).sort((left, right) => compareText(left.extractorId, right.extractorId));
|
|
1131
|
+
if (new Set(resolved.map(({ extractorId }) => extractorId)).size !== resolved.length) {
|
|
1132
|
+
throw new TypeError("Duplicate memory domain fact extractor ID.");
|
|
1133
|
+
}
|
|
1134
|
+
return Object.freeze(resolved);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
export function resolveNominationRoutes(routes: readonly OhMemoryNominationRouteV1[]):
|
|
1138
|
+
ReadonlyMap<string, OhMemoryNominationRouteV1> {
|
|
1139
|
+
if (routes.length > OH_MEMORY_LIMITS_V1.maximumNominationRoutes) {
|
|
1140
|
+
throw new RangeError("The memory nomination route registry is too large.");
|
|
1141
|
+
}
|
|
1142
|
+
const resolved = new Map<string, OhMemoryNominationRouteV1>();
|
|
1143
|
+
for (const route of routes) {
|
|
1144
|
+
const nominationId = safeCode(route.nominationId, 128);
|
|
1145
|
+
const destinationPurpose = safeCode(route.destinationPurpose, 256);
|
|
1146
|
+
if (nominationId === null || destinationPurpose === null || resolved.has(nominationId)) {
|
|
1147
|
+
throw new TypeError("Invalid or duplicate memory nomination route.");
|
|
1148
|
+
}
|
|
1149
|
+
resolved.set(nominationId, Object.freeze({ destinationPurpose, nominationId }));
|
|
1150
|
+
}
|
|
1151
|
+
return resolved;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
function parseQueryRequest(value: unknown): Readonly<{ programId: string }> {
|
|
1155
|
+
const detached = detachCanonicalData(value, "The named memory query",
|
|
1156
|
+
OH_MEMORY_QUERY_LIMITS_V2.requestBytes).value;
|
|
1157
|
+
if (!isPlainRecord(detached) || !hasExactKeys(detached, ["programId", "v"])
|
|
1158
|
+
|| detached.v !== 1) throw new TypeError("Invalid named memory query.");
|
|
1159
|
+
const programId = safeCode(detached.programId, 128);
|
|
1160
|
+
if (programId === null) throw new TypeError("Invalid named memory query identity.");
|
|
1161
|
+
return { programId };
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
function parseExplainRequest(value: unknown): Readonly<{
|
|
1165
|
+
row: number;
|
|
1166
|
+
resultSha256: Sha256Hex;
|
|
1167
|
+
token: string;
|
|
1168
|
+
}> {
|
|
1169
|
+
const detached = detachCanonicalData(value, "The memory explanation request",
|
|
1170
|
+
OH_MEMORY_QUERY_LIMITS_V2.requestBytes).value;
|
|
1171
|
+
if (!isPlainRecord(detached)
|
|
1172
|
+
|| !hasExactKeys(detached, ["resultSha256", "row", "token", "v"])
|
|
1173
|
+
|| detached.v !== 1 || typeof detached.token !== "string" || detached.token.length !== 43
|
|
1174
|
+
|| !Number.isSafeInteger(detached.row) || (detached.row as number) < 0) {
|
|
1175
|
+
throw new TypeError("Invalid memory explanation request.");
|
|
1176
|
+
}
|
|
1177
|
+
const resultSha256 = parseSha256Hex(detached.resultSha256);
|
|
1178
|
+
if (resultSha256 === null) throw new TypeError("Invalid memory explanation result identity.");
|
|
1179
|
+
return { resultSha256, row: detached.row as number, token: detached.token };
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
function parseNominationRequest(value: unknown): Readonly<{
|
|
1183
|
+
nominationId: string;
|
|
1184
|
+
roots: readonly string[];
|
|
1185
|
+
}> {
|
|
1186
|
+
const detached = detachCanonicalData(value, "The memory nomination request",
|
|
1187
|
+
OH_MEMORY_QUERY_LIMITS_V2.requestBytes * 8).value;
|
|
1188
|
+
if (!isPlainRecord(detached) || !hasExactKeys(detached, ["nominationId", "roots", "v"])
|
|
1189
|
+
|| detached.v !== 1 || !Array.isArray(detached.roots) || detached.roots.length < 1
|
|
1190
|
+
|| detached.roots.length > OH_DEPENDENCY_CLOSURE_LIMITS_V1.roots) {
|
|
1191
|
+
throw new TypeError("Invalid memory nomination request.");
|
|
1192
|
+
}
|
|
1193
|
+
const nominationId = safeCode(detached.nominationId, 128);
|
|
1194
|
+
const roots = detached.roots.map((root) => safeCode(root, 512)).sort();
|
|
1195
|
+
if (nominationId === null || roots.some((root) => root === null)
|
|
1196
|
+
|| new Set(roots).size !== roots.length) throw new TypeError("Invalid memory nomination identity.");
|
|
1197
|
+
return { nominationId, roots: roots as readonly string[] };
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
function parseDetachedMemoryNominationV1(value: unknown): OhMemoryNominationV1 | null {
|
|
1201
|
+
try {
|
|
1202
|
+
const keys = ownDataKeysV2(value, 7, "The memory nomination");
|
|
1203
|
+
if (keys.length !== 7 || !["closure", "destinationPurpose", "nominationId",
|
|
1204
|
+
"nominationSha256", "source", "status", "v"].every((key) => keys.includes(key))) return null;
|
|
1205
|
+
const record = value as Record<string, unknown>;
|
|
1206
|
+
if (record.status !== "prepared" || record.v !== 1) return null;
|
|
1207
|
+
const closure = parseOhDependencyClosureV1(record.closure);
|
|
1208
|
+
const destinationPurpose = safeCode(record.destinationPurpose, 256);
|
|
1209
|
+
const nominationId = safeCode(record.nominationId, 128);
|
|
1210
|
+
const nominationSha256 = parseSha256Hex(record.nominationSha256);
|
|
1211
|
+
if (closure === null || destinationPurpose === null || nominationId === null
|
|
1212
|
+
|| nominationSha256 === null || closure.binding.profile.profileKind !== "working"
|
|
1213
|
+
|| [...ownDataKeysV2(record.source, 5, "The memory nomination source")].sort().join("\0")
|
|
1214
|
+
!== ["authorityId", "bindingSha256", "head", "lane", "v"].sort().join("\0")) return null;
|
|
1215
|
+
const sourceRecord = record.source as Record<string, unknown>;
|
|
1216
|
+
if (sourceRecord.lane !== "working" || sourceRecord.v !== 1) return null;
|
|
1217
|
+
const authorityIdValue = safeCode(sourceRecord.authorityId, 128);
|
|
1218
|
+
const bindingSha256 = parseSha256Hex(sourceRecord.bindingSha256);
|
|
1219
|
+
const head = parseOhHeadV1(sourceRecord.head);
|
|
1220
|
+
if (authorityIdValue === null || bindingSha256 === null || head === null
|
|
1221
|
+
|| bindingSha256 !== closure.binding.bindingSha256
|
|
1222
|
+
|| !exactHead(head, closure.head)) return null;
|
|
1223
|
+
const source = { authorityId: authorityIdValue, bindingSha256, head,
|
|
1224
|
+
lane: "working" as const, v: 1 as const };
|
|
1225
|
+
const payload = { closure, destinationPurpose, nominationId, source,
|
|
1226
|
+
status: "prepared" as const, v: 1 as const };
|
|
1227
|
+
return canonicalSha256(payload) === nominationSha256
|
|
1228
|
+
? immutableClone({ ...payload, nominationSha256 }) : null;
|
|
1229
|
+
} catch {
|
|
1230
|
+
return null;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
export function parseOhMemoryNominationV1(value: unknown): OhMemoryNominationV1 | null {
|
|
1235
|
+
try {
|
|
1236
|
+
const detached = detachCanonicalData(value, "The memory nomination",
|
|
1237
|
+
OH_MEMORY_AUTHORITY_LIMITS_V1.adoptionRequestBytes);
|
|
1238
|
+
return parseDetachedMemoryNominationV1(detached.value);
|
|
1239
|
+
} catch {
|
|
1240
|
+
return null;
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
function isoInstant(date: Date): string {
|
|
1245
|
+
const value = date.toISOString();
|
|
1246
|
+
if (parseCanonicalInstantV1(value) === null) throw new TypeError("The memory clock returned an invalid instant.");
|
|
1247
|
+
return value;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
function clockMilliseconds(now: () => Date): number {
|
|
1251
|
+
const milliseconds = now().getTime();
|
|
1252
|
+
if (!Number.isFinite(milliseconds)) throw new TypeError("The memory clock returned an invalid date.");
|
|
1253
|
+
return milliseconds;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
function monotonicMilliseconds(now: () => number): number {
|
|
1257
|
+
const milliseconds = now();
|
|
1258
|
+
if (!Number.isFinite(milliseconds) || milliseconds < 0) {
|
|
1259
|
+
throw new TypeError("The memory monotonic clock returned an invalid value.");
|
|
1260
|
+
}
|
|
1261
|
+
return milliseconds;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
/**
|
|
1265
|
+
* Creates a model-facing memory surface over two host-bound physical Oh
|
|
1266
|
+
* authorities. The returned object has no store, locator, rule, sync, canonical
|
|
1267
|
+
* write, or purge handle.
|
|
1268
|
+
*/
|
|
1269
|
+
export async function createOhMemoryAgentV1(options: OhMemoryFacadeOptionsV1): Promise<OhMemoryAgentV1> {
|
|
1270
|
+
const memoryActorId = safeCode(options.actorId, 128);
|
|
1271
|
+
if (memoryActorId === null) throw new TypeError("Invalid host-bound memory actor ID.");
|
|
1272
|
+
const canonicalStore = options.canonical.store;
|
|
1273
|
+
const workingStore = options.working.store;
|
|
1274
|
+
const workingCodecs = options.working.codecs;
|
|
1275
|
+
const canonicalAuthorityId = authorityId(options.canonical.authorityId);
|
|
1276
|
+
const workingAuthorityId = authorityId(options.working.authorityId);
|
|
1277
|
+
if (canonicalAuthorityId === workingAuthorityId) {
|
|
1278
|
+
throw new OhProfileError("Working and canonical memory must be distinct physical authorities.");
|
|
1279
|
+
}
|
|
1280
|
+
const canonicalBinding = bindingFor(canonicalStore,
|
|
1281
|
+
options.canonical.expectedBindingSha256, "canonical");
|
|
1282
|
+
const workingBinding = bindingFor(workingStore,
|
|
1283
|
+
options.working.expectedBindingSha256, "working");
|
|
1284
|
+
const expectedCanonicalHead = parseMemoryAuthorityHead(options.canonical.expectedHead,
|
|
1285
|
+
"pinned canonical");
|
|
1286
|
+
const programs = resolvePrograms(options.programs);
|
|
1287
|
+
const extractors = resolveExtractors(options.extractors ?? []);
|
|
1288
|
+
const nominationRoutes = resolveNominationRoutes(options.nominationRoutes ?? []);
|
|
1289
|
+
const ingress = new OhSemanticBundleIngressV1(
|
|
1290
|
+
capacityGuardedWorkingStore(workingStore, workingBinding),
|
|
1291
|
+
workingCodecs,
|
|
1292
|
+
);
|
|
1293
|
+
const now = options.now ?? (() => new Date());
|
|
1294
|
+
const monotonicNow = options.monotonicNow ?? (() => performance.now());
|
|
1295
|
+
const capabilityLifetime = options.explainCapabilityLifetimeMs
|
|
1296
|
+
?? OH_MEMORY_LIMITS_V1.explainCapabilityLifetimeMs;
|
|
1297
|
+
if (!Number.isSafeInteger(capabilityLifetime) || capabilityLifetime < 1_000
|
|
1298
|
+
|| capabilityLifetime > 60 * 60 * 1_000) {
|
|
1299
|
+
throw new RangeError("Invalid memory explanation capability lifetime.");
|
|
1300
|
+
}
|
|
1301
|
+
const canonical = await readLane({ authorityId: canonicalAuthorityId,
|
|
1302
|
+
binding: canonicalBinding, store: canonicalStore }, "canonical", expectedCanonicalHead);
|
|
1303
|
+
const explanations = new Map<string, StoredExplanation>();
|
|
1304
|
+
let explanationBytes = 0;
|
|
1305
|
+
let lastMonotonicMs = -1;
|
|
1306
|
+
let lastWallClockMs = Number.NEGATIVE_INFINITY;
|
|
1307
|
+
const wallClock = () => {
|
|
1308
|
+
const milliseconds = clockMilliseconds(now);
|
|
1309
|
+
if (milliseconds < lastWallClockMs) throw new OhProfileError("The memory wall clock regressed.");
|
|
1310
|
+
lastWallClockMs = milliseconds;
|
|
1311
|
+
return milliseconds;
|
|
1312
|
+
};
|
|
1313
|
+
const monotonicClock = () => {
|
|
1314
|
+
const milliseconds = monotonicMilliseconds(monotonicNow);
|
|
1315
|
+
if (milliseconds < lastMonotonicMs) throw new OhProfileError("The memory monotonic clock regressed.");
|
|
1316
|
+
lastMonotonicMs = milliseconds;
|
|
1317
|
+
return milliseconds;
|
|
1318
|
+
};
|
|
1319
|
+
const deleteExplanation = (token: string) => {
|
|
1320
|
+
const stored = explanations.get(token);
|
|
1321
|
+
if (stored !== undefined && explanations.delete(token)) explanationBytes -= stored.bytes;
|
|
1322
|
+
};
|
|
1323
|
+
|
|
1324
|
+
const remember = async (value: unknown): Promise<OhMemoryRememberReceiptV1> => {
|
|
1325
|
+
const detached = detachCanonicalData(value, "The memory semantic bundle",
|
|
1326
|
+
OH_MEMORY_LIMITS_V1.rememberBytes).value;
|
|
1327
|
+
if (!isPlainRecord(detached) || !hasExactKeys(detached,
|
|
1328
|
+
["expectedHead", "puts", "requestId", "tombstones", "v"]) || detached.v !== 1) {
|
|
1329
|
+
throw new TypeError("Invalid memory remember request.");
|
|
1330
|
+
}
|
|
1331
|
+
const requestId = safeCode(detached.requestId, 128);
|
|
1332
|
+
if (requestId === null) throw new TypeError("Invalid memory remember request identity.");
|
|
1333
|
+
const operationId = `memory_${canonicalSha256({ actorId: memoryActorId,
|
|
1334
|
+
bindingSha256: workingBinding.bindingSha256, requestId, v: 1 }).slice(0, 48)}`;
|
|
1335
|
+
const returnedOperation = await ingress.commit({ actorId: memoryActorId,
|
|
1336
|
+
expectedHead: detached.expectedHead, instant: isoInstant(new Date(wallClock())), operationId,
|
|
1337
|
+
puts: detached.puts, tombstones: detached.tombstones, v: 1 });
|
|
1338
|
+
const operation = parseOhOperationV1(detachCanonicalData(returnedOperation,
|
|
1339
|
+
"The returned working memory operation", OH_MEMORY_LIMITS_V1.rememberBytes * 2).value);
|
|
1340
|
+
if (operation === null || operation.actorId !== memoryActorId
|
|
1341
|
+
|| operation.operationId !== operationId || operation.spaceId !== workingBinding.spaceId) {
|
|
1342
|
+
throw new OhIntegrityError("The working authority returned a different memory operation.");
|
|
1343
|
+
}
|
|
1344
|
+
const head: OhHeadV1 = { generation: operation.sequence,
|
|
1345
|
+
graphRevisionSha256: operation.graphRevisionSha256,
|
|
1346
|
+
operationSha256: operation.operationSha256, recordsSha256: operation.recordsSha256,
|
|
1347
|
+
sequence: operation.sequence, v: 1 };
|
|
1348
|
+
const payload = { actorId: operation.actorId, authorityId: workingAuthorityId,
|
|
1349
|
+
bindingSha256: workingBinding.bindingSha256, head, instant: operation.instant,
|
|
1350
|
+
lane: "working" as const, operationSha256: operation.operationSha256, requestId,
|
|
1351
|
+
status: "committed" as const, v: 1 as const };
|
|
1352
|
+
return immutableClone({ ...payload, receiptSha256: canonicalSha256(payload) });
|
|
1353
|
+
};
|
|
1354
|
+
|
|
1355
|
+
const query = async (value: unknown): Promise<OhMemoryQueryResultV1> => {
|
|
1356
|
+
const request = parseQueryRequest(value);
|
|
1357
|
+
const program = programs.get(request.programId);
|
|
1358
|
+
if (program === undefined) throw new TypeError("Unknown named memory program.");
|
|
1359
|
+
const working = await readLane({ authorityId: workingAuthorityId,
|
|
1360
|
+
binding: workingBinding, store: workingStore }, "working");
|
|
1361
|
+
const composite = createCompositeDataset(canonical, working, extractors);
|
|
1362
|
+
const projection = evaluateOhProjectionV1({ dataset: composite.dataset,
|
|
1363
|
+
...(program.evaluation === undefined ? {} : { options: program.evaluation }),
|
|
1364
|
+
query: program.query, rulePack: program.rulePack, snapshot: composite.snapshot });
|
|
1365
|
+
const identityPayload = {
|
|
1366
|
+
canonical: laneIdentity(canonical),
|
|
1367
|
+
compositeDatasetSha256: composite.dataset.datasetSha256,
|
|
1368
|
+
conflictPolicy: OH_MEMORY_CONFLICT_POLICY_V1,
|
|
1369
|
+
evaluationSha256: projection.identity.evaluationSha256,
|
|
1370
|
+
programId: program.programId,
|
|
1371
|
+
projectionSha256: projection.identity.projectionSha256,
|
|
1372
|
+
purpose: program.purpose,
|
|
1373
|
+
querySha256: program.query.querySha256,
|
|
1374
|
+
rulePackSha256: program.rulePack.rulePackSha256,
|
|
1375
|
+
v: 1 as const,
|
|
1376
|
+
working: laneIdentity(working),
|
|
1377
|
+
};
|
|
1378
|
+
const identity: OhMemoryIdentityV1 = immutableClone({ ...identityPayload,
|
|
1379
|
+
memorySha256: canonicalSha256(identityPayload) });
|
|
1380
|
+
const proofs = immutableClone(projection.rows.map((row) =>
|
|
1381
|
+
row.proofs.map((proof) => mapProof(proof, composite.sources, composite.factPolicies))));
|
|
1382
|
+
const rows = immutableClone(projection.rows.map((row, index) => publicRow(row, proofs[index]!)));
|
|
1383
|
+
const resultPayload = immutableClone({ authority: "derived" as const,
|
|
1384
|
+
conflicts: composite.conflicts, identity, projectionResultSha256: projection.resultSha256,
|
|
1385
|
+
rows, v: 1 as const });
|
|
1386
|
+
const resultSha256 = canonicalSha256(resultPayload);
|
|
1387
|
+
const issuedAt = wallClock();
|
|
1388
|
+
const issuedAtMonotonic = monotonicClock();
|
|
1389
|
+
const expiresAtMs = issuedAt + capabilityLifetime;
|
|
1390
|
+
const expiresAtMonotonicMs = issuedAtMonotonic + capabilityLifetime;
|
|
1391
|
+
const expiresAt = isoInstant(new Date(expiresAtMs));
|
|
1392
|
+
for (const [existingToken, stored] of explanations) {
|
|
1393
|
+
if (issuedAtMonotonic >= stored.expiresAtMonotonicMs) deleteExplanation(existingToken);
|
|
1394
|
+
}
|
|
1395
|
+
const storedPayload = immutableClone({ expiresAtMonotonicMs, identity, proofs, resultSha256, rows });
|
|
1396
|
+
const storedBytes = utf8ByteLength(canonicalJson(storedPayload)) + 128;
|
|
1397
|
+
if (storedBytes > OH_MEMORY_LIMITS_V1.explainCapabilityEntryBytes
|
|
1398
|
+
|| storedBytes > OH_MEMORY_LIMITS_V1.explainCapabilityTotalBytes) {
|
|
1399
|
+
throw new RangeError("The memory explanation exceeds its retained capability bound.");
|
|
1400
|
+
}
|
|
1401
|
+
while (explanations.size >= OH_MEMORY_LIMITS_V1.explainCapabilities
|
|
1402
|
+
|| explanationBytes + storedBytes > OH_MEMORY_LIMITS_V1.explainCapabilityTotalBytes) {
|
|
1403
|
+
const oldest = explanations.keys().next().value as string | undefined;
|
|
1404
|
+
if (oldest === undefined) break;
|
|
1405
|
+
deleteExplanation(oldest);
|
|
1406
|
+
}
|
|
1407
|
+
let token = randomBytes(32).toString("base64url");
|
|
1408
|
+
while (explanations.has(token)) token = randomBytes(32).toString("base64url");
|
|
1409
|
+
explanations.set(token, immutableClone({ ...storedPayload, bytes: storedBytes }));
|
|
1410
|
+
explanationBytes += storedBytes;
|
|
1411
|
+
const result = immutableClone({ ...resultPayload,
|
|
1412
|
+
explainCapability: { expiresAt, token, v: 1 as const }, resultSha256 });
|
|
1413
|
+
if (utf8ByteLength(canonicalJson(result)) > OH_MEMORY_LIMITS_V1.resultBytes) {
|
|
1414
|
+
deleteExplanation(token);
|
|
1415
|
+
throw new RangeError("The composite memory result exceeds its canonical byte bound.");
|
|
1416
|
+
}
|
|
1417
|
+
return result;
|
|
1418
|
+
};
|
|
1419
|
+
|
|
1420
|
+
const explain = async (value: unknown): Promise<OhMemoryExplanationV1> => {
|
|
1421
|
+
const request = parseExplainRequest(value);
|
|
1422
|
+
const stored = explanations.get(request.token);
|
|
1423
|
+
const currentTime = monotonicClock();
|
|
1424
|
+
if (stored === undefined || stored.resultSha256 !== request.resultSha256
|
|
1425
|
+
|| currentTime >= stored.expiresAtMonotonicMs) {
|
|
1426
|
+
deleteExplanation(request.token);
|
|
1427
|
+
throw new OhProfileError("The memory explanation capability is absent, expired, or misbound.");
|
|
1428
|
+
}
|
|
1429
|
+
const row = stored.rows[request.row];
|
|
1430
|
+
const proofs = stored.proofs[request.row];
|
|
1431
|
+
if (row === undefined || proofs === undefined) throw new RangeError("The explanation row is out of bounds.");
|
|
1432
|
+
const payload = { authority: "derived" as const, identity: stored.identity,
|
|
1433
|
+
premiseAuthority: row.premiseAuthority, premiseLanes: row.premiseLanes, proofs,
|
|
1434
|
+
proofsTruncated: row.proofsTruncated, resultRowSha256: row.resultRowSha256,
|
|
1435
|
+
resultSha256: stored.resultSha256, supportCount: row.supportCount, v: 1 as const,
|
|
1436
|
+
values: row.values };
|
|
1437
|
+
return immutableClone({ ...payload, explanationSha256: canonicalSha256(payload) });
|
|
1438
|
+
};
|
|
1439
|
+
|
|
1440
|
+
const nominate = async (value: unknown): Promise<OhMemoryNominationV1> => {
|
|
1441
|
+
const request = parseNominationRequest(value);
|
|
1442
|
+
const route = nominationRoutes.get(request.nominationId);
|
|
1443
|
+
if (route === undefined) throw new TypeError("Unknown named memory nomination route.");
|
|
1444
|
+
const head = parseOhHeadV1(detachCanonicalData(await workingStore.head(),
|
|
1445
|
+
"The working nomination store head", 4 * 1024).value);
|
|
1446
|
+
if (head === null) throw new OhIntegrityError("The working nomination store returned an invalid head.");
|
|
1447
|
+
const returnedClosure = await workingStore.exportDependencyClosure({ head: {
|
|
1448
|
+
operationSha256: head.operationSha256, sequence: head.sequence }, roots: request.roots });
|
|
1449
|
+
const closure = detachCanonicalData(returnedClosure, "The working nomination closure",
|
|
1450
|
+
OH_DEPENDENCY_CLOSURE_LIMITS_V1.bytes).value;
|
|
1451
|
+
const verified = verifyOhDependencyClosureAgainstV1(closure, { binding: workingBinding, head });
|
|
1452
|
+
if (!verified.ok) throw new OhIntegrityError("The working nomination closure failed exact verification.");
|
|
1453
|
+
if (canonicalJson(verified.closure.roots) !== canonicalJson(request.roots)) {
|
|
1454
|
+
throw new OhIntegrityError("The working nomination closure substituted different roots.");
|
|
1455
|
+
}
|
|
1456
|
+
const source = Object.freeze({ authorityId: workingAuthorityId,
|
|
1457
|
+
bindingSha256: workingBinding.bindingSha256, head, lane: "working" as const, v: 1 as const });
|
|
1458
|
+
const payload = { closure: verified.closure, destinationPurpose: route.destinationPurpose,
|
|
1459
|
+
nominationId: route.nominationId, source, status: "prepared" as const, v: 1 as const };
|
|
1460
|
+
return immutableClone({ ...payload, nominationSha256: canonicalSha256(payload) });
|
|
1461
|
+
};
|
|
1462
|
+
|
|
1463
|
+
return Object.freeze({ explain, nominate, query, remember });
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
type ResolvedMemoryProgramV2 = OhMemoryNamedProgramV2 & Readonly<{
|
|
1467
|
+
programSha256: Sha256Hex;
|
|
1468
|
+
}>;
|
|
1469
|
+
|
|
1470
|
+
type StoredExplanationV2 = Readonly<{
|
|
1471
|
+
bytes: number;
|
|
1472
|
+
expiresAtMonotonicMs: number;
|
|
1473
|
+
identity: OhMemoryIdentityV2;
|
|
1474
|
+
page: OhMemoryPageV2;
|
|
1475
|
+
proofs: readonly (readonly OhMemoryProofV1[])[];
|
|
1476
|
+
resultSha256: Sha256Hex;
|
|
1477
|
+
rows: readonly OhMemoryResultRowV2[];
|
|
1478
|
+
}>;
|
|
1479
|
+
|
|
1480
|
+
type OhMemoryRuntimeV2 = {
|
|
1481
|
+
readonly capabilityLifetime: number;
|
|
1482
|
+
explanationBytes: number;
|
|
1483
|
+
readonly explanations: Map<string, StoredExplanationV2>;
|
|
1484
|
+
lastMonotonicMs: number;
|
|
1485
|
+
lastWallClockMs: number;
|
|
1486
|
+
readonly monotonicNow: () => number;
|
|
1487
|
+
readonly now: () => Date;
|
|
1488
|
+
};
|
|
1489
|
+
|
|
1490
|
+
export function createOhMemoryRuntimeV2(options: OhMemoryFacadeOptionsV2): OhMemoryRuntimeV2 {
|
|
1491
|
+
const capabilityLifetime = options.explainCapabilityLifetimeMs
|
|
1492
|
+
?? OH_MEMORY_LIMITS_V1.explainCapabilityLifetimeMs;
|
|
1493
|
+
if (!Number.isSafeInteger(capabilityLifetime) || capabilityLifetime < 1_000
|
|
1494
|
+
|| capabilityLifetime > 60 * 60 * 1_000) {
|
|
1495
|
+
throw new RangeError("Invalid memory explanation capability lifetime.");
|
|
1496
|
+
}
|
|
1497
|
+
return {
|
|
1498
|
+
capabilityLifetime,
|
|
1499
|
+
explanationBytes: 0,
|
|
1500
|
+
explanations: new Map<string, StoredExplanationV2>(),
|
|
1501
|
+
lastMonotonicMs: -1,
|
|
1502
|
+
lastWallClockMs: Number.NEGATIVE_INFINITY,
|
|
1503
|
+
monotonicNow: options.monotonicNow ?? (() => performance.now()),
|
|
1504
|
+
now: options.now ?? (() => new Date()),
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
type OhMemoryContinuationIdentityV2 = Readonly<{
|
|
1509
|
+
bindingsSha256: Sha256Hex;
|
|
1510
|
+
memorySha256: Sha256Hex;
|
|
1511
|
+
nextOffset: number;
|
|
1512
|
+
pageSize: number;
|
|
1513
|
+
programSha256: Sha256Hex;
|
|
1514
|
+
projectionResultSha256: Sha256Hex;
|
|
1515
|
+
totalRows: number;
|
|
1516
|
+
v: 2;
|
|
1517
|
+
}>;
|
|
1518
|
+
|
|
1519
|
+
type OhMemoryContinuationV2 = OhMemoryContinuationIdentityV2 & Readonly<{
|
|
1520
|
+
continuationSha256: Sha256Hex;
|
|
1521
|
+
}>;
|
|
1522
|
+
|
|
1523
|
+
type OhMemoryContinuationEnvelopeV2 = OhMemoryContinuationV2 & Readonly<{
|
|
1524
|
+
continuationHmacSha256: Sha256Hex;
|
|
1525
|
+
}>;
|
|
1526
|
+
|
|
1527
|
+
function ownDataKeysV2(value: unknown, maximum: number, label: string): readonly string[] {
|
|
1528
|
+
if (!isPlainRecord(value)) throw new TypeError(`${label} must be a plain data object.`);
|
|
1529
|
+
const ownKeys = Reflect.ownKeys(value);
|
|
1530
|
+
if (ownKeys.length > maximum) throw new RangeError(`${label} has too many entries.`);
|
|
1531
|
+
if (ownKeys.some((key) => typeof key !== "string")) {
|
|
1532
|
+
throw new TypeError(`${label} must have only string data properties.`);
|
|
1533
|
+
}
|
|
1534
|
+
const keys = ownKeys as string[];
|
|
1535
|
+
for (const key of keys) {
|
|
1536
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
1537
|
+
if (descriptor === undefined || !descriptor.enumerable || !("value" in descriptor)) {
|
|
1538
|
+
throw new TypeError(`${label} must have only enumerable data properties.`);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
return keys;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
export function continuationKeyV2(value: Uint8Array | undefined): Uint8Array {
|
|
1545
|
+
if (value === undefined) return Uint8Array.from(randomBytes(32));
|
|
1546
|
+
if (!(value instanceof Uint8Array)
|
|
1547
|
+
|| value.byteLength < OH_MEMORY_QUERY_LIMITS_V2.continuationKeyMinimumBytes
|
|
1548
|
+
|| value.byteLength > OH_MEMORY_QUERY_LIMITS_V2.continuationKeyMaximumBytes) {
|
|
1549
|
+
throw new RangeError("The V2 memory continuation key must be 32 through 64 raw bytes.");
|
|
1550
|
+
}
|
|
1551
|
+
return Uint8Array.from(value);
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
function positiveBounded(value: unknown, maximum: number, label: string, minimum = 1): number {
|
|
1555
|
+
if (!Number.isSafeInteger(value) || (value as number) < minimum || (value as number) > maximum) {
|
|
1556
|
+
throw new RangeError(`${label} must be an integer from ${minimum} through ${maximum}.`);
|
|
1557
|
+
}
|
|
1558
|
+
return value as number;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
function resolveEvaluationV2(value: unknown): OhMemoryEvaluationLimitsV2 {
|
|
1562
|
+
if (!isPlainRecord(value) || !hasExactKeys(value, ["maximumDerivedTuples", "maximumProofDepth",
|
|
1563
|
+
"maximumProofNodes", "maximumResultBytes", "maximumRounds", "maximumTotalProofNodes",
|
|
1564
|
+
"maximumWorkUnits"])) {
|
|
1565
|
+
throw new TypeError("A V2 memory program must declare every projection evaluation limit.");
|
|
1566
|
+
}
|
|
1567
|
+
return Object.freeze({
|
|
1568
|
+
maximumDerivedTuples: positiveBounded(value.maximumDerivedTuples,
|
|
1569
|
+
OH_PROJECTION_LIMITS_V1.derivedTuples, "maximumDerivedTuples"),
|
|
1570
|
+
maximumProofDepth: positiveBounded(value.maximumProofDepth,
|
|
1571
|
+
OH_PROJECTION_LIMITS_V1.proofDepth, "maximumProofDepth"),
|
|
1572
|
+
maximumProofNodes: positiveBounded(value.maximumProofNodes,
|
|
1573
|
+
OH_PROJECTION_LIMITS_V1.proofNodes, "maximumProofNodes"),
|
|
1574
|
+
maximumResultBytes: positiveBounded(value.maximumResultBytes,
|
|
1575
|
+
OH_PROJECTION_LIMITS_V1.resultBytes, "maximumResultBytes", 64 * 1024),
|
|
1576
|
+
maximumRounds: positiveBounded(value.maximumRounds,
|
|
1577
|
+
OH_PROJECTION_LIMITS_V1.rounds, "maximumRounds"),
|
|
1578
|
+
maximumTotalProofNodes: positiveBounded(value.maximumTotalProofNodes,
|
|
1579
|
+
OH_PROJECTION_LIMITS_V1.totalProofNodes, "maximumTotalProofNodes"),
|
|
1580
|
+
maximumWorkUnits: positiveBounded(value.maximumWorkUnits,
|
|
1581
|
+
OH_PROJECTION_LIMITS_V1.workUnits, "maximumWorkUnits"),
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
export function resolveProgramsV2(programs: readonly OhMemoryNamedProgramV2[]):
|
|
1586
|
+
ReadonlyMap<string, ResolvedMemoryProgramV2> {
|
|
1587
|
+
if (!Array.isArray(programs) || programs.length < 1
|
|
1588
|
+
|| programs.length > OH_MEMORY_LIMITS_V1.maximumPrograms) {
|
|
1589
|
+
throw new RangeError("Memory requires a bounded nonempty V2 named program registry.");
|
|
1590
|
+
}
|
|
1591
|
+
const resolved = new Map<string, ResolvedMemoryProgramV2>();
|
|
1592
|
+
for (const candidate of programs) {
|
|
1593
|
+
if (!isPlainRecord(candidate) || !hasExactKeys(candidate, ["evaluation", "maximumPageBytes",
|
|
1594
|
+
"maximumRows", "pageSize", "parameters", "programId", "purpose", "query", "rulePack", "v"])
|
|
1595
|
+
|| candidate.v !== 2 || !Array.isArray(candidate.parameters)) {
|
|
1596
|
+
throw new TypeError("Invalid V2 named memory program.");
|
|
1597
|
+
}
|
|
1598
|
+
const programId = safeCode(candidate.programId, 128);
|
|
1599
|
+
const purpose = safeCode(candidate.purpose, 256);
|
|
1600
|
+
const query = parseOhProjectionQueryV1(candidate.query);
|
|
1601
|
+
const rulePack = parseOhProjectionRulePackV1(candidate.rulePack);
|
|
1602
|
+
const evaluation = resolveEvaluationV2(candidate.evaluation);
|
|
1603
|
+
const maximumRows = positiveBounded(candidate.maximumRows,
|
|
1604
|
+
OH_MEMORY_QUERY_LIMITS_V2.maximumProgramRows, "maximumRows");
|
|
1605
|
+
const pageSize = positiveBounded(candidate.pageSize,
|
|
1606
|
+
Math.min(maximumRows, OH_MEMORY_QUERY_LIMITS_V2.maximumPageRows), "pageSize");
|
|
1607
|
+
const maximumPageBytes = positiveBounded(candidate.maximumPageBytes,
|
|
1608
|
+
OH_MEMORY_QUERY_LIMITS_V2.maximumPageBytes, "maximumPageBytes",
|
|
1609
|
+
OH_MEMORY_QUERY_LIMITS_V2.minimumPageBytes);
|
|
1610
|
+
if (programId === null || purpose === null || query === null || rulePack === null
|
|
1611
|
+
|| resolved.has(programId) || query.limit !== maximumRows
|
|
1612
|
+
|| candidate.parameters.length > OH_MEMORY_QUERY_LIMITS_V2.bindings) {
|
|
1613
|
+
throw new TypeError("Invalid or duplicate V2 named memory program.");
|
|
1614
|
+
}
|
|
1615
|
+
const parameters = candidate.parameters.map((parameter) => safeCode(parameter, 128)).sort();
|
|
1616
|
+
if (parameters.some((parameter) => parameter === null)
|
|
1617
|
+
|| new Set(parameters).size !== parameters.length) {
|
|
1618
|
+
throw new TypeError("A V2 memory program has invalid or duplicate parameters.");
|
|
1619
|
+
}
|
|
1620
|
+
const queryVariables = new Set(query.where.flatMap((literal) => literal.terms.flatMap((term) =>
|
|
1621
|
+
term.kind === "variable" ? [term.name] : [])));
|
|
1622
|
+
if ((parameters as readonly string[]).some((parameter) => !queryVariables.has(parameter)
|
|
1623
|
+
|| query.find.includes(parameter))) {
|
|
1624
|
+
throw new TypeError("V2 parameters must be query-body variables that are not projected outputs.");
|
|
1625
|
+
}
|
|
1626
|
+
const detachedParameters = Object.freeze(parameters as string[]);
|
|
1627
|
+
const programPayload = {
|
|
1628
|
+
evaluation,
|
|
1629
|
+
maximumPageBytes,
|
|
1630
|
+
maximumRows,
|
|
1631
|
+
pageSize,
|
|
1632
|
+
parameters: detachedParameters,
|
|
1633
|
+
programId,
|
|
1634
|
+
purpose,
|
|
1635
|
+
querySha256: query.querySha256,
|
|
1636
|
+
rulePackSha256: rulePack.rulePackSha256,
|
|
1637
|
+
v: 2 as const,
|
|
1638
|
+
};
|
|
1639
|
+
const program = immutableClone({ evaluation, maximumPageBytes, maximumRows, pageSize,
|
|
1640
|
+
parameters: detachedParameters, programId, programSha256: canonicalSha256(programPayload),
|
|
1641
|
+
purpose, query, rulePack, v: 2 as const });
|
|
1642
|
+
resolved.set(programId, program);
|
|
1643
|
+
}
|
|
1644
|
+
return resolved;
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
function parsePrimitiveBindingV2(value: unknown): JsonPrimitive {
|
|
1648
|
+
if (value !== null && typeof value !== "boolean" && typeof value !== "number"
|
|
1649
|
+
&& typeof value !== "string") throw new TypeError("Memory bindings must be JSON primitives.");
|
|
1650
|
+
if (typeof value === "string" && value.length > OH_PROJECTION_LIMITS_V1.atomBytes) {
|
|
1651
|
+
throw new RangeError("A memory binding exceeds the projection atom byte bound.");
|
|
1652
|
+
}
|
|
1653
|
+
if (typeof value === "number" && (!Number.isFinite(value) || Object.is(value, -0))) {
|
|
1654
|
+
throw new TypeError("Memory bindings must be canonical finite JSON numbers.");
|
|
1655
|
+
}
|
|
1656
|
+
const serialized = canonicalJson(value);
|
|
1657
|
+
if (utf8ByteLength(serialized) > OH_PROJECTION_LIMITS_V1.atomBytes) {
|
|
1658
|
+
throw new RangeError("A memory binding exceeds the projection atom byte bound.");
|
|
1659
|
+
}
|
|
1660
|
+
return value as JsonPrimitive;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
function parseQueryRequestV2(value: unknown): Readonly<{
|
|
1664
|
+
bindingsValue: Readonly<Record<string, JsonPrimitive>>;
|
|
1665
|
+
continuation: string | null;
|
|
1666
|
+
programId: string;
|
|
1667
|
+
}> {
|
|
1668
|
+
let detached: unknown;
|
|
1669
|
+
let detachedCanonical = "";
|
|
1670
|
+
let keys: readonly string[];
|
|
1671
|
+
try {
|
|
1672
|
+
const detachedData = detachCanonicalData(value, "The parameterized memory query",
|
|
1673
|
+
OH_MEMORY_QUERY_LIMITS_V2.requestBytes);
|
|
1674
|
+
detached = detachedData.value;
|
|
1675
|
+
detachedCanonical = detachedData.canonical;
|
|
1676
|
+
} catch (error) {
|
|
1677
|
+
if (error instanceof RangeError) throw error;
|
|
1678
|
+
if (error instanceof Error && error.message.includes("$root.bindings")) {
|
|
1679
|
+
throw new TypeError("Memory bindings must be JSON primitives.");
|
|
1680
|
+
}
|
|
1681
|
+
throw new TypeError("Invalid parameterized memory query.");
|
|
1682
|
+
}
|
|
1683
|
+
try {
|
|
1684
|
+
keys = ownDataKeysV2(detached, 4, "The parameterized memory query");
|
|
1685
|
+
} catch (error) {
|
|
1686
|
+
if (error instanceof Error && error.message.includes("$root.bindings")) {
|
|
1687
|
+
throw new TypeError("Memory bindings must be JSON primitives.");
|
|
1688
|
+
}
|
|
1689
|
+
throw new TypeError("Invalid parameterized memory query.");
|
|
1690
|
+
}
|
|
1691
|
+
if (keys.length !== 4 || !["bindings", "continuation", "programId", "v"]
|
|
1692
|
+
.every((key) => keys.includes(key))) {
|
|
1693
|
+
throw new TypeError("Invalid parameterized memory query.");
|
|
1694
|
+
}
|
|
1695
|
+
if (utf8ByteLength(detachedCanonical) > OH_MEMORY_QUERY_LIMITS_V2.requestBytes) {
|
|
1696
|
+
throw new RangeError("The parameterized memory query exceeds its canonical byte bound.");
|
|
1697
|
+
}
|
|
1698
|
+
const record = detached as Record<string, unknown>;
|
|
1699
|
+
if (record.v !== 2) {
|
|
1700
|
+
throw new TypeError("Invalid parameterized memory query.");
|
|
1701
|
+
}
|
|
1702
|
+
if (record.continuation !== null && typeof record.continuation !== "string") {
|
|
1703
|
+
throw new OhMemoryContinuationError("encoding", "Invalid memory continuation encoding.");
|
|
1704
|
+
}
|
|
1705
|
+
const programId = safeCode(record.programId, 128);
|
|
1706
|
+
if (programId === null) throw new TypeError("Invalid parameterized memory query identity.");
|
|
1707
|
+
const continuation = record.continuation as string | null;
|
|
1708
|
+
if (typeof continuation === "string" && (continuation.length < 1
|
|
1709
|
+
|| continuation.length > OH_MEMORY_QUERY_LIMITS_V2.continuationBytes
|
|
1710
|
+
|| utf8ByteLength(continuation) > OH_MEMORY_QUERY_LIMITS_V2.continuationBytes)) {
|
|
1711
|
+
throw new OhMemoryContinuationError("encoding",
|
|
1712
|
+
"The memory continuation exceeds its byte bound.");
|
|
1713
|
+
}
|
|
1714
|
+
const bindingKeys = ownDataKeysV2(record.bindings, OH_MEMORY_QUERY_LIMITS_V2.bindings,
|
|
1715
|
+
"The parameterized memory query bindings");
|
|
1716
|
+
const bindingRecord = record.bindings as Record<string, unknown>;
|
|
1717
|
+
const bindings: Record<string, JsonPrimitive> = {};
|
|
1718
|
+
for (const key of bindingKeys) {
|
|
1719
|
+
if (safeCode(key, 128) === null) throw new TypeError("Invalid memory binding name.");
|
|
1720
|
+
bindings[key] = parsePrimitiveBindingV2(bindingRecord[key]);
|
|
1721
|
+
}
|
|
1722
|
+
const boundedRequest = { bindings, continuation, programId, v: 2 as const };
|
|
1723
|
+
if (utf8ByteLength(canonicalJson(boundedRequest)) > OH_MEMORY_QUERY_LIMITS_V2.requestBytes) {
|
|
1724
|
+
throw new RangeError("The parameterized memory query exceeds its canonical byte bound.");
|
|
1725
|
+
}
|
|
1726
|
+
return { bindingsValue: immutableClone(bindings), continuation, programId };
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
function parseBindingsV2(value: Readonly<Record<string, JsonPrimitive>>,
|
|
1730
|
+
parameters: readonly string[]): Readonly<{
|
|
1731
|
+
bindings: Readonly<Record<string, JsonPrimitive>>;
|
|
1732
|
+
bindingsSha256: Sha256Hex;
|
|
1733
|
+
}> {
|
|
1734
|
+
if (!isPlainRecord(value) || !hasExactKeys(value, parameters)) {
|
|
1735
|
+
throw new TypeError("Memory query bindings must exactly match the host-declared parameters.");
|
|
1736
|
+
}
|
|
1737
|
+
const bindings: Record<string, JsonPrimitive> = {};
|
|
1738
|
+
for (const parameter of parameters) bindings[parameter] = value[parameter]!;
|
|
1739
|
+
if (utf8ByteLength(canonicalJson(bindings)) > OH_MEMORY_QUERY_LIMITS_V2.bindingBytes) {
|
|
1740
|
+
throw new RangeError("Memory query bindings exceed their canonical byte bound.");
|
|
1741
|
+
}
|
|
1742
|
+
const detached = immutableClone(bindings);
|
|
1743
|
+
return Object.freeze({ bindings: detached,
|
|
1744
|
+
bindingsSha256: canonicalSha256({ bindings: detached, parameters, v: 2 }) });
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
function bindQueryV2(query: OhProjectionQueryV1,
|
|
1748
|
+
bindings: Readonly<Record<string, JsonPrimitive>>): OhProjectionQueryV1 {
|
|
1749
|
+
const where = query.where.map((literal) => createOhProjectionLiteralV1({
|
|
1750
|
+
relation: literal.relation,
|
|
1751
|
+
terms: literal.terms.map((term) => term.kind === "variable" && Object.hasOwn(bindings, term.name)
|
|
1752
|
+
? ohProjectionConstantV1(bindings[term.name]!) : term),
|
|
1753
|
+
}));
|
|
1754
|
+
return createOhProjectionQueryV1({ find: query.find, limit: query.limit,
|
|
1755
|
+
queryId: query.queryId, where });
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
function publicRowV2(row: OhProjectionResultRowV1,
|
|
1759
|
+
proofs: readonly OhMemoryProofV1[]): OhMemoryResultRowV2 {
|
|
1760
|
+
const lanes = new Set<OhMemoryLaneV1>();
|
|
1761
|
+
let unknown = row.proofsTruncated;
|
|
1762
|
+
for (const proof of proofs) unknown = collectLanes(proof, lanes) || unknown;
|
|
1763
|
+
const premiseLanes = [...lanes].sort() as readonly OhMemoryLaneV1[];
|
|
1764
|
+
const premiseAuthority: OhMemoryResultRowV2["premiseAuthority"] = unknown || premiseLanes.length === 0
|
|
1765
|
+
? "unknown" : premiseLanes.includes("working") ? "working" : "canonical";
|
|
1766
|
+
const payload = { premiseAuthority, premiseLanes, proofsTruncated: row.proofsTruncated,
|
|
1767
|
+
supportCount: row.supportCount, v: 2 as const, values: row.values };
|
|
1768
|
+
return Object.freeze({ ...payload, resultRowSha256: canonicalSha256(payload) });
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
function continuationHmacV2(key: Uint8Array, value: OhMemoryContinuationV2): Buffer {
|
|
1772
|
+
return createHmac("sha256", key).update("oh.memory.continuation.v2\0", "utf8")
|
|
1773
|
+
.update(canonicalJson(value), "utf8").digest();
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
function encodeContinuationV2(value: OhMemoryContinuationIdentityV2,
|
|
1777
|
+
key: Uint8Array): Readonly<{ continuation: string; continuationSha256: Sha256Hex }> {
|
|
1778
|
+
const identity = immutableClone(value);
|
|
1779
|
+
const continuationSha256 = canonicalSha256(identity);
|
|
1780
|
+
const signed = immutableClone({ ...identity, continuationSha256 });
|
|
1781
|
+
const envelope: OhMemoryContinuationEnvelopeV2 = immutableClone({ ...signed,
|
|
1782
|
+
continuationHmacSha256: continuationHmacV2(key, signed).toString("hex") as Sha256Hex });
|
|
1783
|
+
const continuation = Buffer.from(canonicalJson(envelope), "utf8").toString("base64url");
|
|
1784
|
+
if (utf8ByteLength(continuation) > OH_MEMORY_QUERY_LIMITS_V2.continuationBytes) {
|
|
1785
|
+
throw new RangeError("The issued memory continuation exceeds its byte bound.");
|
|
1786
|
+
}
|
|
1787
|
+
return Object.freeze({ continuation, continuationSha256 });
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
function parseContinuationV2(value: string, key: Uint8Array): OhMemoryContinuationV2 {
|
|
1791
|
+
if (value.length < 1 || utf8ByteLength(value) > OH_MEMORY_QUERY_LIMITS_V2.continuationBytes
|
|
1792
|
+
|| !/^[A-Za-z0-9_-]+$/u.test(value)) {
|
|
1793
|
+
throw new OhMemoryContinuationError("encoding", "Invalid memory continuation encoding.");
|
|
1794
|
+
}
|
|
1795
|
+
const bytes = Buffer.from(value, "base64url");
|
|
1796
|
+
if (bytes.toString("base64url") !== value
|
|
1797
|
+
|| bytes.byteLength > OH_MEMORY_QUERY_LIMITS_V2.continuationBytes) {
|
|
1798
|
+
throw new OhMemoryContinuationError("encoding", "Invalid memory continuation encoding.");
|
|
1799
|
+
}
|
|
1800
|
+
const text = bytes.toString("utf8");
|
|
1801
|
+
let decoded: unknown;
|
|
1802
|
+
try { decoded = JSON.parse(text); } catch {
|
|
1803
|
+
throw new OhMemoryContinuationError("encoding", "Invalid memory continuation JSON.");
|
|
1804
|
+
}
|
|
1805
|
+
if (!isPlainRecord(decoded)
|
|
1806
|
+
|| !hasExactKeys(decoded, ["bindingsSha256", "continuationHmacSha256", "continuationSha256",
|
|
1807
|
+
"memorySha256", "nextOffset", "pageSize", "programSha256", "projectionResultSha256",
|
|
1808
|
+
"totalRows", "v"])
|
|
1809
|
+
|| decoded.v !== 2) {
|
|
1810
|
+
throw new OhMemoryContinuationError("encoding", "Invalid memory continuation payload.");
|
|
1811
|
+
}
|
|
1812
|
+
const bindingsSha256 = parseSha256Hex(decoded.bindingsSha256);
|
|
1813
|
+
const continuationHmacSha256 = parseSha256Hex(decoded.continuationHmacSha256);
|
|
1814
|
+
const continuationSha256 = parseSha256Hex(decoded.continuationSha256);
|
|
1815
|
+
const memorySha256 = parseSha256Hex(decoded.memorySha256);
|
|
1816
|
+
const programSha256 = parseSha256Hex(decoded.programSha256);
|
|
1817
|
+
const projectionResultSha256 = parseSha256Hex(decoded.projectionResultSha256);
|
|
1818
|
+
if (bindingsSha256 === null || continuationHmacSha256 === null
|
|
1819
|
+
|| continuationSha256 === null || memorySha256 === null || programSha256 === null
|
|
1820
|
+
|| projectionResultSha256 === null
|
|
1821
|
+
|| !Number.isSafeInteger(decoded.nextOffset) || (decoded.nextOffset as number) < 1
|
|
1822
|
+
|| (decoded.nextOffset as number) > OH_MEMORY_QUERY_LIMITS_V2.maximumProgramRows
|
|
1823
|
+
|| !Number.isSafeInteger(decoded.pageSize) || (decoded.pageSize as number) < 1
|
|
1824
|
+
|| (decoded.pageSize as number) > OH_MEMORY_QUERY_LIMITS_V2.maximumPageRows
|
|
1825
|
+
|| !Number.isSafeInteger(decoded.totalRows) || (decoded.totalRows as number) < 1
|
|
1826
|
+
|| (decoded.totalRows as number) > OH_MEMORY_QUERY_LIMITS_V2.maximumProgramRows
|
|
1827
|
+
|| (decoded.nextOffset as number) >= (decoded.totalRows as number)
|
|
1828
|
+
|| (decoded.nextOffset as number) % (decoded.pageSize as number) !== 0) {
|
|
1829
|
+
throw new OhMemoryContinuationError("identity", "Invalid memory continuation identity.");
|
|
1830
|
+
}
|
|
1831
|
+
const identity: OhMemoryContinuationIdentityV2 = { bindingsSha256, memorySha256,
|
|
1832
|
+
nextOffset: decoded.nextOffset as number,
|
|
1833
|
+
pageSize: decoded.pageSize as number, programSha256, projectionResultSha256,
|
|
1834
|
+
totalRows: decoded.totalRows as number, v: 2 as const };
|
|
1835
|
+
const signed: OhMemoryContinuationV2 = { ...identity, continuationSha256 };
|
|
1836
|
+
const envelope: OhMemoryContinuationEnvelopeV2 = { ...signed, continuationHmacSha256 };
|
|
1837
|
+
if (canonicalJson(envelope) !== text) {
|
|
1838
|
+
throw new OhMemoryContinuationError("encoding", "Invalid memory continuation payload.");
|
|
1839
|
+
}
|
|
1840
|
+
const expectedHmac = continuationHmacV2(key, signed);
|
|
1841
|
+
const receivedHmac = Buffer.from(continuationHmacSha256, "hex");
|
|
1842
|
+
if (!timingSafeEqual(expectedHmac, receivedHmac)) {
|
|
1843
|
+
throw new OhMemoryContinuationError("authentication",
|
|
1844
|
+
"The memory continuation is not an issued capability.");
|
|
1845
|
+
}
|
|
1846
|
+
if (canonicalSha256(identity) !== continuationSha256) {
|
|
1847
|
+
throw new OhMemoryContinuationError("identity", "The memory continuation digest is invalid.");
|
|
1848
|
+
}
|
|
1849
|
+
return Object.freeze(signed);
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
function parseExplainRequestV2(value: unknown): Readonly<{
|
|
1853
|
+
pageRow: number;
|
|
1854
|
+
resultSha256: Sha256Hex;
|
|
1855
|
+
token: string;
|
|
1856
|
+
}> {
|
|
1857
|
+
const detached = detachCanonicalData(value, "The V2 memory explanation request",
|
|
1858
|
+
OH_MEMORY_QUERY_LIMITS_V2.requestBytes).value;
|
|
1859
|
+
if (!isPlainRecord(detached)
|
|
1860
|
+
|| !hasExactKeys(detached, ["pageRow", "resultSha256", "token", "v"])
|
|
1861
|
+
|| detached.v !== 2 || typeof detached.token !== "string" || detached.token.length !== 43
|
|
1862
|
+
|| !Number.isSafeInteger(detached.pageRow) || (detached.pageRow as number) < 0) {
|
|
1863
|
+
throw new TypeError("Invalid V2 memory explanation request.");
|
|
1864
|
+
}
|
|
1865
|
+
const resultSha256 = parseSha256Hex(detached.resultSha256);
|
|
1866
|
+
if (resultSha256 === null) throw new TypeError("Invalid V2 memory explanation result identity.");
|
|
1867
|
+
return { pageRow: detached.pageRow as number, resultSha256, token: detached.token };
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
/**
|
|
1871
|
+
* Creates the additive V2 memory facade. V2 adds only host-declared primitive
|
|
1872
|
+
* bindings and fail-closed stable pagination; V1 request and digest contracts
|
|
1873
|
+
* remain untouched.
|
|
1874
|
+
*/
|
|
1875
|
+
export async function createOhMemoryAgentV2WithRuntime(
|
|
1876
|
+
options: OhMemoryFacadeOptionsV2,
|
|
1877
|
+
sharedRuntime?: OhMemoryRuntimeV2,
|
|
1878
|
+
): Promise<OhMemoryAgentV2> {
|
|
1879
|
+
const memoryActorId = safeCode(options.actorId, 128);
|
|
1880
|
+
if (memoryActorId === null) throw new TypeError("Invalid host-bound memory actor ID.");
|
|
1881
|
+
const continuationKey = continuationKeyV2(options.continuationKey);
|
|
1882
|
+
const canonicalStore = options.canonical.store;
|
|
1883
|
+
const workingStore = options.working.store;
|
|
1884
|
+
const workingCodecs = options.working.codecs;
|
|
1885
|
+
const canonicalAuthorityId = authorityId(options.canonical.authorityId);
|
|
1886
|
+
const workingAuthorityId = authorityId(options.working.authorityId);
|
|
1887
|
+
if (canonicalAuthorityId === workingAuthorityId) {
|
|
1888
|
+
throw new OhProfileError("Working and canonical memory must be distinct physical authorities.");
|
|
1889
|
+
}
|
|
1890
|
+
const canonicalBinding = bindingFor(canonicalStore,
|
|
1891
|
+
options.canonical.expectedBindingSha256, "canonical");
|
|
1892
|
+
const workingBinding = bindingFor(workingStore,
|
|
1893
|
+
options.working.expectedBindingSha256, "working");
|
|
1894
|
+
const expectedCanonicalHead = parseMemoryAuthorityHead(options.canonical.expectedHead,
|
|
1895
|
+
"pinned canonical");
|
|
1896
|
+
const programs = resolveProgramsV2(options.programs);
|
|
1897
|
+
const extractors = resolveExtractors(options.extractors ?? []);
|
|
1898
|
+
const nominationRoutes = resolveNominationRoutes(options.nominationRoutes ?? []);
|
|
1899
|
+
const ingress = new OhSemanticBundleIngressV1(
|
|
1900
|
+
capacityGuardedWorkingStore(workingStore, workingBinding),
|
|
1901
|
+
workingCodecs,
|
|
1902
|
+
);
|
|
1903
|
+
const runtime = sharedRuntime ?? createOhMemoryRuntimeV2(options);
|
|
1904
|
+
const canonical = await readLane({ authorityId: canonicalAuthorityId,
|
|
1905
|
+
binding: canonicalBinding, store: canonicalStore }, "canonical", expectedCanonicalHead);
|
|
1906
|
+
const wallClock = () => {
|
|
1907
|
+
const milliseconds = clockMilliseconds(runtime.now);
|
|
1908
|
+
if (milliseconds < runtime.lastWallClockMs) {
|
|
1909
|
+
throw new OhProfileError("The memory wall clock regressed.");
|
|
1910
|
+
}
|
|
1911
|
+
runtime.lastWallClockMs = milliseconds;
|
|
1912
|
+
return milliseconds;
|
|
1913
|
+
};
|
|
1914
|
+
const monotonicClock = () => {
|
|
1915
|
+
const milliseconds = monotonicMilliseconds(runtime.monotonicNow);
|
|
1916
|
+
if (milliseconds < runtime.lastMonotonicMs) {
|
|
1917
|
+
throw new OhProfileError("The memory monotonic clock regressed.");
|
|
1918
|
+
}
|
|
1919
|
+
runtime.lastMonotonicMs = milliseconds;
|
|
1920
|
+
return milliseconds;
|
|
1921
|
+
};
|
|
1922
|
+
const deleteExplanation = (token: string) => {
|
|
1923
|
+
const stored = runtime.explanations.get(token);
|
|
1924
|
+
if (stored !== undefined && runtime.explanations.delete(token)) {
|
|
1925
|
+
runtime.explanationBytes -= stored.bytes;
|
|
1926
|
+
}
|
|
1927
|
+
};
|
|
1928
|
+
|
|
1929
|
+
const remember = async (value: unknown): Promise<OhMemoryRememberReceiptV1> => {
|
|
1930
|
+
const detached = detachCanonicalData(value, "The memory semantic bundle",
|
|
1931
|
+
OH_MEMORY_LIMITS_V1.rememberBytes).value;
|
|
1932
|
+
if (!isPlainRecord(detached) || !hasExactKeys(detached,
|
|
1933
|
+
["expectedHead", "puts", "requestId", "tombstones", "v"]) || detached.v !== 1) {
|
|
1934
|
+
throw new TypeError("Invalid memory remember request.");
|
|
1935
|
+
}
|
|
1936
|
+
const requestId = safeCode(detached.requestId, 128);
|
|
1937
|
+
if (requestId === null) throw new TypeError("Invalid memory remember request identity.");
|
|
1938
|
+
const operationId = `memory_${canonicalSha256({ actorId: memoryActorId,
|
|
1939
|
+
bindingSha256: workingBinding.bindingSha256, requestId, v: 1 }).slice(0, 48)}`;
|
|
1940
|
+
const returnedOperation = await ingress.commit({ actorId: memoryActorId,
|
|
1941
|
+
expectedHead: detached.expectedHead, instant: isoInstant(new Date(wallClock())), operationId,
|
|
1942
|
+
puts: detached.puts, tombstones: detached.tombstones, v: 1 });
|
|
1943
|
+
const operation = parseOhOperationV1(detachCanonicalData(returnedOperation,
|
|
1944
|
+
"The returned working memory operation", OH_MEMORY_LIMITS_V1.rememberBytes * 2).value);
|
|
1945
|
+
if (operation === null || operation.actorId !== memoryActorId
|
|
1946
|
+
|| operation.operationId !== operationId || operation.spaceId !== workingBinding.spaceId) {
|
|
1947
|
+
throw new OhIntegrityError("The working authority returned a different memory operation.");
|
|
1948
|
+
}
|
|
1949
|
+
const head: OhHeadV1 = { generation: operation.sequence,
|
|
1950
|
+
graphRevisionSha256: operation.graphRevisionSha256,
|
|
1951
|
+
operationSha256: operation.operationSha256, recordsSha256: operation.recordsSha256,
|
|
1952
|
+
sequence: operation.sequence, v: 1 };
|
|
1953
|
+
const payload = { actorId: operation.actorId, authorityId: workingAuthorityId,
|
|
1954
|
+
bindingSha256: workingBinding.bindingSha256, head, instant: operation.instant,
|
|
1955
|
+
lane: "working" as const, operationSha256: operation.operationSha256, requestId,
|
|
1956
|
+
status: "committed" as const, v: 1 as const };
|
|
1957
|
+
return immutableClone({ ...payload, receiptSha256: canonicalSha256(payload) });
|
|
1958
|
+
};
|
|
1959
|
+
|
|
1960
|
+
const query = async (value: unknown): Promise<OhMemoryQueryResultV2> => {
|
|
1961
|
+
const request = parseQueryRequestV2(value);
|
|
1962
|
+
const program = programs.get(request.programId);
|
|
1963
|
+
if (program === undefined) throw new TypeError("Unknown named V2 memory program.");
|
|
1964
|
+
const bound = parseBindingsV2(request.bindingsValue, program.parameters);
|
|
1965
|
+
const requestedContinuation = request.continuation === null
|
|
1966
|
+
? null : parseContinuationV2(request.continuation, continuationKey);
|
|
1967
|
+
if (requestedContinuation !== null
|
|
1968
|
+
&& (requestedContinuation.bindingsSha256 !== bound.bindingsSha256
|
|
1969
|
+
|| requestedContinuation.pageSize !== program.pageSize
|
|
1970
|
+
|| requestedContinuation.programSha256 !== program.programSha256
|
|
1971
|
+
|| requestedContinuation.totalRows > program.maximumRows
|
|
1972
|
+
|| requestedContinuation.nextOffset >= requestedContinuation.totalRows
|
|
1973
|
+
|| requestedContinuation.nextOffset % program.pageSize !== 0)) {
|
|
1974
|
+
throw new OhMemoryContinuationError("identity",
|
|
1975
|
+
"The memory continuation does not match this exact program, binding, and page identity.");
|
|
1976
|
+
}
|
|
1977
|
+
const boundQuery = bindQueryV2(program.query, bound.bindings);
|
|
1978
|
+
const working = await readLane({ authorityId: workingAuthorityId,
|
|
1979
|
+
binding: workingBinding, store: workingStore }, "working");
|
|
1980
|
+
const composite = createCompositeDataset(canonical, working, extractors);
|
|
1981
|
+
const projection = evaluateOhProjectionV1({ dataset: composite.dataset,
|
|
1982
|
+
options: program.evaluation, query: boundQuery, rulePack: program.rulePack,
|
|
1983
|
+
snapshot: composite.snapshot });
|
|
1984
|
+
if (projection.stats.truncated) {
|
|
1985
|
+
const reasons = projection.stats.truncationReasons.join(", ");
|
|
1986
|
+
throw new RangeError(`The V2 memory projection was truncated (${reasons}); no page was returned.`);
|
|
1987
|
+
}
|
|
1988
|
+
if (projection.rows.length > program.maximumRows) {
|
|
1989
|
+
throw new RangeError("The V2 memory projection exceeds its host-declared row bound.");
|
|
1990
|
+
}
|
|
1991
|
+
const identityPayload = {
|
|
1992
|
+
bindings: bound.bindings,
|
|
1993
|
+
bindingsSha256: bound.bindingsSha256,
|
|
1994
|
+
boundQuerySha256: boundQuery.querySha256,
|
|
1995
|
+
canonical: laneIdentity(canonical),
|
|
1996
|
+
compositeDatasetSha256: composite.dataset.datasetSha256,
|
|
1997
|
+
conflictPolicy: OH_MEMORY_CONFLICT_POLICY_V1,
|
|
1998
|
+
evaluationSha256: projection.identity.evaluationSha256,
|
|
1999
|
+
programId: program.programId,
|
|
2000
|
+
programSha256: program.programSha256,
|
|
2001
|
+
projectionSha256: projection.identity.projectionSha256,
|
|
2002
|
+
purpose: program.purpose,
|
|
2003
|
+
rulePackSha256: program.rulePack.rulePackSha256,
|
|
2004
|
+
templateQuerySha256: program.query.querySha256,
|
|
2005
|
+
v: 2 as const,
|
|
2006
|
+
working: laneIdentity(working),
|
|
2007
|
+
};
|
|
2008
|
+
const identity: OhMemoryIdentityV2 = immutableClone({ ...identityPayload,
|
|
2009
|
+
memorySha256: canonicalSha256(identityPayload) });
|
|
2010
|
+
if (requestedContinuation !== null
|
|
2011
|
+
&& (requestedContinuation.memorySha256 !== identity.memorySha256
|
|
2012
|
+
|| requestedContinuation.projectionResultSha256 !== projection.resultSha256)) {
|
|
2013
|
+
throw new OhMemoryContinuationError("identity",
|
|
2014
|
+
"The memory continuation does not match this exact source and projection identity.");
|
|
2015
|
+
}
|
|
2016
|
+
if (requestedContinuation !== null
|
|
2017
|
+
&& (requestedContinuation.totalRows !== projection.rows.length
|
|
2018
|
+
|| requestedContinuation.nextOffset >= projection.rows.length
|
|
2019
|
+
|| requestedContinuation.nextOffset % program.pageSize !== 0)) {
|
|
2020
|
+
throw new OhMemoryContinuationError("identity",
|
|
2021
|
+
"The memory continuation does not match this exact row identity.");
|
|
2022
|
+
}
|
|
2023
|
+
const start = requestedContinuation?.nextOffset ?? 0;
|
|
2024
|
+
const endExclusive = Math.min(start + program.pageSize, projection.rows.length);
|
|
2025
|
+
const projectionRows = projection.rows.slice(start, endExclusive);
|
|
2026
|
+
const proofs = immutableClone(projectionRows.map((row) => row.proofs.map((proof) =>
|
|
2027
|
+
mapProof(proof, composite.sources, composite.factPolicies))));
|
|
2028
|
+
const rows = immutableClone(projectionRows.map((row, index) => publicRowV2(row, proofs[index]!)));
|
|
2029
|
+
const hasMore = endExclusive < projection.rows.length;
|
|
2030
|
+
const page: OhMemoryPageV2 = immutableClone({ completeness: hasMore ? "partial" : "complete",
|
|
2031
|
+
endExclusive, hasMore, maximumPageBytes: program.maximumPageBytes, pageSize: program.pageSize,
|
|
2032
|
+
returnedRows: rows.length, start, totalRows: projection.rows.length,
|
|
2033
|
+
truncation: { reasons: [], truncated: false, v: 2 as const }, v: 2 as const });
|
|
2034
|
+
const issuedContinuation = hasMore ? encodeContinuationV2({ bindingsSha256: bound.bindingsSha256,
|
|
2035
|
+
memorySha256: identity.memorySha256, nextOffset: endExclusive, pageSize: program.pageSize,
|
|
2036
|
+
programSha256: program.programSha256, projectionResultSha256: projection.resultSha256,
|
|
2037
|
+
totalRows: projection.rows.length, v: 2 }, continuationKey) : null;
|
|
2038
|
+
const continuation = issuedContinuation?.continuation ?? null;
|
|
2039
|
+
const continuationSha256 = issuedContinuation?.continuationSha256 ?? null;
|
|
2040
|
+
const conflicts = immutableClone({ count: composite.conflicts.length,
|
|
2041
|
+
conflictsSha256: canonicalSha256(composite.conflicts), v: 2 as const });
|
|
2042
|
+
const resultIdentityPayload = immutableClone({ authority: "derived" as const, conflicts,
|
|
2043
|
+
continuationSha256,
|
|
2044
|
+
identity, page, projectionResultSha256: projection.resultSha256, rows, v: 2 as const });
|
|
2045
|
+
const resultSha256 = canonicalSha256(resultIdentityPayload);
|
|
2046
|
+
const resultPayload = immutableClone({ ...resultIdentityPayload, continuation });
|
|
2047
|
+
const issuedAt = wallClock();
|
|
2048
|
+
const issuedAtMonotonic = monotonicClock();
|
|
2049
|
+
const expiresAtMs = issuedAt + runtime.capabilityLifetime;
|
|
2050
|
+
const expiresAtMonotonicMs = issuedAtMonotonic + runtime.capabilityLifetime;
|
|
2051
|
+
const expiresAt = isoInstant(new Date(expiresAtMs));
|
|
2052
|
+
const pageBytePreflight = { ...resultPayload,
|
|
2053
|
+
explainCapability: { expiresAt, token: "A".repeat(43), v: 2 as const }, resultSha256 };
|
|
2054
|
+
if (utf8ByteLength(canonicalJson(pageBytePreflight)) > program.maximumPageBytes) {
|
|
2055
|
+
throw new RangeError("The V2 memory page exceeds its host-declared canonical byte bound.");
|
|
2056
|
+
}
|
|
2057
|
+
for (const [existingToken, stored] of runtime.explanations) {
|
|
2058
|
+
if (issuedAtMonotonic >= stored.expiresAtMonotonicMs) deleteExplanation(existingToken);
|
|
2059
|
+
}
|
|
2060
|
+
const storedPayload = immutableClone({ expiresAtMonotonicMs, identity, page, proofs,
|
|
2061
|
+
resultSha256, rows });
|
|
2062
|
+
const storedBytes = utf8ByteLength(canonicalJson(storedPayload)) + 128;
|
|
2063
|
+
if (storedBytes > OH_MEMORY_LIMITS_V1.explainCapabilityEntryBytes
|
|
2064
|
+
|| storedBytes > OH_MEMORY_LIMITS_V1.explainCapabilityTotalBytes) {
|
|
2065
|
+
throw new RangeError("The V2 memory explanation exceeds its retained capability bound.");
|
|
2066
|
+
}
|
|
2067
|
+
while (runtime.explanations.size >= OH_MEMORY_LIMITS_V1.explainCapabilities
|
|
2068
|
+
|| runtime.explanationBytes + storedBytes > OH_MEMORY_LIMITS_V1.explainCapabilityTotalBytes) {
|
|
2069
|
+
const oldest = runtime.explanations.keys().next().value as string | undefined;
|
|
2070
|
+
if (oldest === undefined) break;
|
|
2071
|
+
deleteExplanation(oldest);
|
|
2072
|
+
}
|
|
2073
|
+
let token = randomBytes(32).toString("base64url");
|
|
2074
|
+
while (runtime.explanations.has(token)) token = randomBytes(32).toString("base64url");
|
|
2075
|
+
runtime.explanations.set(token, immutableClone({ ...storedPayload, bytes: storedBytes }));
|
|
2076
|
+
runtime.explanationBytes += storedBytes;
|
|
2077
|
+
const result = immutableClone({ ...resultPayload,
|
|
2078
|
+
explainCapability: { expiresAt, token, v: 2 as const }, resultSha256 });
|
|
2079
|
+
if (utf8ByteLength(canonicalJson(result)) > program.maximumPageBytes) {
|
|
2080
|
+
deleteExplanation(token);
|
|
2081
|
+
throw new RangeError("The V2 memory page exceeds its host-declared canonical byte bound.");
|
|
2082
|
+
}
|
|
2083
|
+
return result;
|
|
2084
|
+
};
|
|
2085
|
+
|
|
2086
|
+
const explain = async (value: unknown): Promise<OhMemoryExplanationV2> => {
|
|
2087
|
+
const request = parseExplainRequestV2(value);
|
|
2088
|
+
const stored = runtime.explanations.get(request.token);
|
|
2089
|
+
const currentTime = monotonicClock();
|
|
2090
|
+
if (stored === undefined || stored.resultSha256 !== request.resultSha256
|
|
2091
|
+
|| currentTime >= stored.expiresAtMonotonicMs) {
|
|
2092
|
+
deleteExplanation(request.token);
|
|
2093
|
+
throw new OhProfileError("The V2 memory explanation capability is absent, expired, or misbound.");
|
|
2094
|
+
}
|
|
2095
|
+
const row = stored.rows[request.pageRow];
|
|
2096
|
+
const proofs = stored.proofs[request.pageRow];
|
|
2097
|
+
if (row === undefined || proofs === undefined) throw new RangeError("The explanation page row is out of bounds.");
|
|
2098
|
+
const payload = { authority: "derived" as const, identity: stored.identity, page: stored.page,
|
|
2099
|
+
pageRow: request.pageRow, premiseAuthority: row.premiseAuthority,
|
|
2100
|
+
premiseLanes: row.premiseLanes, proofs, proofsTruncated: row.proofsTruncated,
|
|
2101
|
+
resultRowSha256: row.resultRowSha256, resultSha256: stored.resultSha256,
|
|
2102
|
+
supportCount: row.supportCount, v: 2 as const, values: row.values };
|
|
2103
|
+
return immutableClone({ ...payload, explanationSha256: canonicalSha256(payload) });
|
|
2104
|
+
};
|
|
2105
|
+
|
|
2106
|
+
const nominate = async (value: unknown): Promise<OhMemoryNominationV1> => {
|
|
2107
|
+
const request = parseNominationRequest(value);
|
|
2108
|
+
const route = nominationRoutes.get(request.nominationId);
|
|
2109
|
+
if (route === undefined) throw new TypeError("Unknown named memory nomination route.");
|
|
2110
|
+
const head = parseOhHeadV1(detachCanonicalData(await workingStore.head(),
|
|
2111
|
+
"The working nomination store head", 4 * 1024).value);
|
|
2112
|
+
if (head === null) throw new OhIntegrityError("The working nomination store returned an invalid head.");
|
|
2113
|
+
const returnedClosure = await workingStore.exportDependencyClosure({ head: {
|
|
2114
|
+
operationSha256: head.operationSha256, sequence: head.sequence }, roots: request.roots });
|
|
2115
|
+
const closure = detachCanonicalData(returnedClosure, "The working nomination closure",
|
|
2116
|
+
OH_DEPENDENCY_CLOSURE_LIMITS_V1.bytes).value;
|
|
2117
|
+
const verified = verifyOhDependencyClosureAgainstV1(closure, { binding: workingBinding, head });
|
|
2118
|
+
if (!verified.ok) throw new OhIntegrityError("The working nomination closure failed exact verification.");
|
|
2119
|
+
if (canonicalJson(verified.closure.roots) !== canonicalJson(request.roots)) {
|
|
2120
|
+
throw new OhIntegrityError("The working nomination closure substituted different roots.");
|
|
2121
|
+
}
|
|
2122
|
+
const source = Object.freeze({ authorityId: workingAuthorityId,
|
|
2123
|
+
bindingSha256: workingBinding.bindingSha256, head, lane: "working" as const, v: 1 as const });
|
|
2124
|
+
const payload = { closure: verified.closure, destinationPurpose: route.destinationPurpose,
|
|
2125
|
+
nominationId: route.nominationId, source, status: "prepared" as const, v: 1 as const };
|
|
2126
|
+
return immutableClone({ ...payload, nominationSha256: canonicalSha256(payload) });
|
|
2127
|
+
};
|
|
2128
|
+
|
|
2129
|
+
return Object.freeze({ explain, nominate, query, remember });
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
export async function createOhMemoryAgentV2(
|
|
2133
|
+
options: OhMemoryFacadeOptionsV2,
|
|
2134
|
+
): Promise<OhMemoryAgentV2> {
|
|
2135
|
+
return await createOhMemoryAgentV2WithRuntime(options);
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
function parseDetachedMemoryAuthorityHead(value: unknown, label: string): OhHeadV1 {
|
|
2139
|
+
const head = parseOhHeadV1(value);
|
|
2140
|
+
if (head === null) throw new TypeError(`Invalid ${label} memory head.`);
|
|
2141
|
+
return immutableClone(head);
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
export function parseMemoryAuthorityHead(value: unknown, label: string): OhHeadV1 {
|
|
2145
|
+
return parseDetachedMemoryAuthorityHead(detachCanonicalData(value,
|
|
2146
|
+
`The ${label} memory head`, 4 * 1024).value, label);
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
export function parseCanonicalAdvanceRequest(value: unknown): Readonly<{
|
|
2150
|
+
expectedHead: OhHeadV1;
|
|
2151
|
+
nextHead: OhHeadV1;
|
|
2152
|
+
}> {
|
|
2153
|
+
const detached = detachCanonicalData(value, "The canonical memory advance request",
|
|
2154
|
+
OH_MEMORY_QUERY_LIMITS_V2.requestBytes).value;
|
|
2155
|
+
if (!isPlainRecord(detached) || !hasExactKeys(detached, ["expectedHead", "nextHead", "v"])
|
|
2156
|
+
|| detached.v !== 1) throw new TypeError("Invalid canonical memory advance request.");
|
|
2157
|
+
return Object.freeze({
|
|
2158
|
+
expectedHead: parseDetachedMemoryAuthorityHead(detached.expectedHead, "expected canonical"),
|
|
2159
|
+
nextHead: parseDetachedMemoryAuthorityHead(detached.nextHead, "next canonical"),
|
|
2160
|
+
});
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
export function parseAdoptionRequest(value: unknown): Readonly<{
|
|
2164
|
+
expectedCanonicalHead: OhHeadV1;
|
|
2165
|
+
nomination: OhMemoryNominationV1;
|
|
2166
|
+
replacements: readonly OhMemoryAdoptionReplacementV1[];
|
|
2167
|
+
}> {
|
|
2168
|
+
const detached = detachCanonicalData(value, "The memory adoption request",
|
|
2169
|
+
OH_MEMORY_AUTHORITY_LIMITS_V1.adoptionRequestBytes).value;
|
|
2170
|
+
if (!isPlainRecord(detached)
|
|
2171
|
+
|| (!hasExactKeys(detached, ["expectedCanonicalHead", "nomination", "v"])
|
|
2172
|
+
&& !hasExactKeys(detached, ["expectedCanonicalHead", "nomination", "replacements", "v"]))
|
|
2173
|
+
|| detached.v !== 1) throw new TypeError("Invalid memory adoption request.");
|
|
2174
|
+
const nomination = parseDetachedMemoryNominationV1(detached.nomination);
|
|
2175
|
+
if (nomination === null) throw new TypeError("Invalid memory adoption nomination.");
|
|
2176
|
+
const replacementValues = "replacements" in detached ? detached.replacements : [];
|
|
2177
|
+
if (!Array.isArray(replacementValues)
|
|
2178
|
+
|| replacementValues.length > OH_MEMORY_AUTHORITY_LIMITS_V1.adoptionReplacements) {
|
|
2179
|
+
throw new TypeError("Invalid memory adoption replacements.");
|
|
2180
|
+
}
|
|
2181
|
+
const nominatedByKey = new Map(nomination.closure.records.map((record) => [record.key, record]));
|
|
2182
|
+
const replacements = replacementValues.map((replacement) => {
|
|
2183
|
+
if (!isPlainRecord(replacement)
|
|
2184
|
+
|| !hasExactKeys(replacement, ["expectedPriorRecordSha256", "key", "v"])
|
|
2185
|
+
|| replacement.v !== 1) throw new TypeError("Invalid memory adoption replacement.");
|
|
2186
|
+
const key = safeCode(replacement.key, 512);
|
|
2187
|
+
const expectedPriorRecordSha256 = parseSha256Hex(replacement.expectedPriorRecordSha256);
|
|
2188
|
+
const nominated = key === null ? undefined : nominatedByKey.get(key);
|
|
2189
|
+
if (key === null || expectedPriorRecordSha256 === null || nominated === undefined) {
|
|
2190
|
+
throw new TypeError("Invalid memory adoption replacement.");
|
|
2191
|
+
}
|
|
2192
|
+
return { expectedPriorRecordSha256, key, v: 1 as const };
|
|
2193
|
+
}).sort((left, right) => compareText(left.key, right.key));
|
|
2194
|
+
if (!orderedUnique(replacements, (replacement) => replacement.key)) {
|
|
2195
|
+
throw new TypeError("Memory adoption replacement keys must be unique.");
|
|
2196
|
+
}
|
|
2197
|
+
return Object.freeze({
|
|
2198
|
+
expectedCanonicalHead: parseDetachedMemoryAuthorityHead(detached.expectedCanonicalHead,
|
|
2199
|
+
"expected canonical adoption"),
|
|
2200
|
+
nomination,
|
|
2201
|
+
replacements: immutableClone(replacements),
|
|
2202
|
+
});
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
export function headRef(head: OhHeadV1): OhHeadRefV1 {
|
|
2206
|
+
return Object.freeze({ operationSha256: head.operationSha256, sequence: head.sequence });
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
|
|
2210
|
+
export function canonicalAdvanceReceipt(
|
|
2211
|
+
authorityIdValue: string,
|
|
2212
|
+
bindingSha256: Sha256Hex,
|
|
2213
|
+
priorHead: OhHeadV1,
|
|
2214
|
+
head: OhHeadV1,
|
|
2215
|
+
status: OhMemoryCanonicalAdvanceReceiptV1["status"],
|
|
2216
|
+
): OhMemoryCanonicalAdvanceReceiptV1 {
|
|
2217
|
+
const payload = { authorityId: authorityIdValue, bindingSha256, head, priorHead, status,
|
|
2218
|
+
v: 1 as const };
|
|
2219
|
+
return immutableClone({ ...payload, receiptSha256: canonicalSha256(payload) });
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
export function adoptionReceipt(
|
|
2223
|
+
actorId: string,
|
|
2224
|
+
authorityIdValue: string,
|
|
2225
|
+
bindingSha256: Sha256Hex,
|
|
2226
|
+
nominationSha256: Sha256Hex,
|
|
2227
|
+
operationSha256: Sha256Hex | null,
|
|
2228
|
+
priorHead: OhHeadV1,
|
|
2229
|
+
head: OhHeadV1,
|
|
2230
|
+
status: OhMemoryAdoptionReceiptV1["status"],
|
|
2231
|
+
): OhMemoryAdoptionReceiptV1 {
|
|
2232
|
+
const payload = { actorId, authorityId: authorityIdValue, bindingSha256, head,
|
|
2233
|
+
nominationSha256, operationSha256, priorHead, status, v: 1 as const };
|
|
2234
|
+
return immutableClone({ ...payload, receiptSha256: canonicalSha256(payload) });
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
export function adoptionDifferences(
|
|
2238
|
+
snapshot: OhSnapshotV1,
|
|
2239
|
+
records: readonly KnowledgeGraphRecordV1[],
|
|
2240
|
+
): readonly OhMemoryAdoptionConflictEntryV1[] {
|
|
2241
|
+
const canonicalByKey = new Map(snapshot.records.map((record) => [record.key, record]));
|
|
2242
|
+
return immutableClone(records.flatMap((record) => {
|
|
2243
|
+
const canonicalRecord = canonicalByKey.get(record.key);
|
|
2244
|
+
return canonicalRecord?.recordSha256 === record.recordSha256 ? [] : [{
|
|
2245
|
+
canonicalRecordSha256: canonicalRecord?.recordSha256 ?? null,
|
|
2246
|
+
key: record.key,
|
|
2247
|
+
nominatedRecordSha256: record.recordSha256,
|
|
2248
|
+
v: 1 as const,
|
|
2249
|
+
}];
|
|
2250
|
+
}).sort((left, right) => compareText(left.key, right.key)));
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
export function unauthorizedAdoptionDifferences(
|
|
2254
|
+
reviewedSnapshot: OhSnapshotV1,
|
|
2255
|
+
currentSnapshot: OhSnapshotV1,
|
|
2256
|
+
records: readonly KnowledgeGraphRecordV1[],
|
|
2257
|
+
replacements: readonly OhMemoryAdoptionReplacementV1[],
|
|
2258
|
+
): readonly OhMemoryAdoptionConflictEntryV1[] {
|
|
2259
|
+
const reviewedByKey = new Map(reviewedSnapshot.records.map((record) => [record.key, record]));
|
|
2260
|
+
const currentByKey = new Map(currentSnapshot.records.map((record) => [record.key, record]));
|
|
2261
|
+
const replacementByKey = new Map(replacements.map((replacement) => [replacement.key,
|
|
2262
|
+
replacement.expectedPriorRecordSha256]));
|
|
2263
|
+
const conflicts = records.flatMap((nominated) => {
|
|
2264
|
+
const reviewed = reviewedByKey.get(nominated.key);
|
|
2265
|
+
const expectedPriorRecordSha256 = replacementByKey.get(nominated.key);
|
|
2266
|
+
const alreadyEqual = reviewed?.recordSha256 === nominated.recordSha256;
|
|
2267
|
+
const authorized = reviewed === undefined
|
|
2268
|
+
? expectedPriorRecordSha256 === undefined
|
|
2269
|
+
: alreadyEqual
|
|
2270
|
+
? expectedPriorRecordSha256 === undefined
|
|
2271
|
+
|| expectedPriorRecordSha256 === reviewed.recordSha256
|
|
2272
|
+
: expectedPriorRecordSha256 === reviewed.recordSha256;
|
|
2273
|
+
if (authorized) return [];
|
|
2274
|
+
return [{
|
|
2275
|
+
canonicalRecordSha256: currentByKey.get(nominated.key)?.recordSha256 ?? null,
|
|
2276
|
+
key: nominated.key,
|
|
2277
|
+
nominatedRecordSha256: nominated.recordSha256,
|
|
2278
|
+
v: 1 as const,
|
|
2279
|
+
}];
|
|
2280
|
+
});
|
|
2281
|
+
return immutableClone(conflicts
|
|
2282
|
+
.sort((left, right) => compareText(left.key, right.key)));
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
async function assertWorkingCommitCapacity(
|
|
2286
|
+
store: OhStoreV1,
|
|
2287
|
+
binding: OhStoreBindingV1,
|
|
2288
|
+
changes: readonly KnowledgeGraphChangeV1[],
|
|
2289
|
+
head: OhHeadV1,
|
|
2290
|
+
): Promise<void> {
|
|
2291
|
+
const returnedSnapshot = await store.snapshot({
|
|
2292
|
+
head: { operationSha256: head.operationSha256, sequence: head.sequence },
|
|
2293
|
+
maximumRecords: OH_MEMORY_LIMITS_V1.maximumRecordsPerLane,
|
|
2294
|
+
});
|
|
2295
|
+
const { snapshot } = parseDetachedStoreSnapshot(returnedSnapshot,
|
|
2296
|
+
"The working capacity store", head, binding.spaceId);
|
|
2297
|
+
const recordsByKey = new Map(snapshot.records.map((record) => [record.key, record]));
|
|
2298
|
+
for (const change of canonicalKnowledgeGraphChangesV1(changes)) {
|
|
2299
|
+
if (change.kind === "put") recordsByKey.set(change.record.key, change.record);
|
|
2300
|
+
else recordsByKey.delete(change.key);
|
|
2301
|
+
}
|
|
2302
|
+
if (recordsByKey.size > OH_MEMORY_LIMITS_V1.maximumRecordsPerLane) {
|
|
2303
|
+
throw new RangeError("The remembered working memory would exceed its record snapshot bound.");
|
|
2304
|
+
}
|
|
2305
|
+
const nextSequence = snapshot.head.sequence + 1;
|
|
2306
|
+
if (!Number.isSafeInteger(nextSequence)) {
|
|
2307
|
+
throw new RangeError("The remembered working memory would exceed its head sequence bound.");
|
|
2308
|
+
}
|
|
2309
|
+
const records = [...recordsByKey.values()]
|
|
2310
|
+
.sort((left, right) => compareText(left.key, right.key));
|
|
2311
|
+
const placeholderDigest = canonicalSha256({ kind: "oh.memory.remember-capacity", v: 1 });
|
|
2312
|
+
const prospective: OhSnapshotV1 = {
|
|
2313
|
+
head: {
|
|
2314
|
+
generation: nextSequence,
|
|
2315
|
+
graphRevisionSha256: placeholderDigest,
|
|
2316
|
+
operationSha256: placeholderDigest,
|
|
2317
|
+
recordsSha256: canonicalSha256(records.map(knowledgeGraphRecordRefV1)),
|
|
2318
|
+
sequence: nextSequence,
|
|
2319
|
+
v: 1,
|
|
2320
|
+
},
|
|
2321
|
+
records,
|
|
2322
|
+
v: 1,
|
|
2323
|
+
};
|
|
2324
|
+
if (utf8ByteLength(canonicalJson(prospective)) > OH_MEMORY_LIMITS_V1.snapshotBytesPerLane) {
|
|
2325
|
+
throw new RangeError("The remembered working memory would exceed its snapshot byte bound.");
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
function capacityGuardedWorkingStore(store: OhStoreV1, binding: OhStoreBindingV1): OhStoreV1 {
|
|
2330
|
+
const guarded: OhStoreV1 = {
|
|
2331
|
+
binding,
|
|
2332
|
+
changesSince: async (from, options) => await store.changesSince(from, options),
|
|
2333
|
+
close: async () => await store.close(),
|
|
2334
|
+
commit: async (input) => {
|
|
2335
|
+
const current = parseDetachedStoreHead(await store.head(),
|
|
2336
|
+
"The working capacity store");
|
|
2337
|
+
// The store owns exact operation-id replay. A stale expected head may
|
|
2338
|
+
// therefore be either a harmless replay or a conflict. Delegate it
|
|
2339
|
+
// unchanged so a hypothetical reapplication cannot reject a replay.
|
|
2340
|
+
if (
|
|
2341
|
+
current.generation === input.expectedHead.generation
|
|
2342
|
+
&& current.operationSha256 === input.expectedHead.operationSha256
|
|
2343
|
+
) await assertWorkingCommitCapacity(store, binding, input.changes, current);
|
|
2344
|
+
return await store.commit(input);
|
|
2345
|
+
},
|
|
2346
|
+
exportDependencyClosure: async (input) => await store.exportDependencyClosure(input),
|
|
2347
|
+
head: async () => await store.head(),
|
|
2348
|
+
snapshot: async (options) => await store.snapshot(options),
|
|
2349
|
+
verify: async () => await store.verify(),
|
|
2350
|
+
};
|
|
2351
|
+
return Object.freeze(guarded);
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
export function assertAdoptionSnapshotCapacity(
|
|
2355
|
+
snapshot: OhSnapshotV1,
|
|
2356
|
+
changedRecords: readonly KnowledgeGraphRecordV1[],
|
|
2357
|
+
): void {
|
|
2358
|
+
const recordsByKey = new Map(snapshot.records.map((record) => [record.key, record]));
|
|
2359
|
+
for (const record of changedRecords) recordsByKey.set(record.key, record);
|
|
2360
|
+
if (recordsByKey.size > OH_MEMORY_LIMITS_V1.maximumRecordsPerLane) {
|
|
2361
|
+
throw new RangeError("The adopted canonical memory would exceed its record snapshot bound.");
|
|
2362
|
+
}
|
|
2363
|
+
const nextSequence = snapshot.head.sequence + 1;
|
|
2364
|
+
if (!Number.isSafeInteger(nextSequence)) {
|
|
2365
|
+
throw new RangeError("The adopted canonical memory would exceed its head sequence bound.");
|
|
2366
|
+
}
|
|
2367
|
+
const records = [...recordsByKey.values()]
|
|
2368
|
+
.sort((left, right) => compareText(left.key, right.key));
|
|
2369
|
+
const placeholderDigest = canonicalSha256({ kind: "oh.memory.adoption-capacity", v: 1 });
|
|
2370
|
+
const prospective: OhSnapshotV1 = {
|
|
2371
|
+
head: {
|
|
2372
|
+
generation: nextSequence,
|
|
2373
|
+
graphRevisionSha256: placeholderDigest,
|
|
2374
|
+
operationSha256: placeholderDigest,
|
|
2375
|
+
recordsSha256: canonicalSha256(records.map(knowledgeGraphRecordRefV1)),
|
|
2376
|
+
sequence: nextSequence,
|
|
2377
|
+
v: 1,
|
|
2378
|
+
},
|
|
2379
|
+
records,
|
|
2380
|
+
v: 1,
|
|
2381
|
+
};
|
|
2382
|
+
if (utf8ByteLength(canonicalJson(prospective)) > OH_MEMORY_LIMITS_V1.snapshotBytesPerLane) {
|
|
2383
|
+
throw new RangeError("The adopted canonical memory would exceed its canonical snapshot byte bound.");
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
export function adoptionConflict(
|
|
2388
|
+
expectedHead: OhHeadV1,
|
|
2389
|
+
actualHead: OhHeadV1,
|
|
2390
|
+
completeConflicts: readonly OhMemoryAdoptionConflictEntryV1[],
|
|
2391
|
+
): OhMemoryAdoptionConflictError {
|
|
2392
|
+
const sorted = immutableClone([...completeConflicts]
|
|
2393
|
+
.sort((left, right) => compareText(left.key, right.key)));
|
|
2394
|
+
const conflicts = immutableClone(sorted.slice(0,
|
|
2395
|
+
OH_MEMORY_AUTHORITY_LIMITS_V1.reportedAdoptionConflicts));
|
|
2396
|
+
const conflict = immutableClone({ actualHead, conflicts,
|
|
2397
|
+
conflictsSha256: canonicalSha256({ conflicts: sorted, v: 1 }), expectedHead,
|
|
2398
|
+
reportedConflicts: conflicts.length, totalConflicts: sorted.length,
|
|
2399
|
+
truncated: conflicts.length !== sorted.length, v: 1 as const });
|
|
2400
|
+
return new OhMemoryAdoptionConflictError(conflict);
|
|
2401
|
+
}
|