@hraness/oh 0.4.1 → 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 +4 -1
- 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 +2 -1
- 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,133 @@
|
|
|
1
|
+
import { Context, Effect, Layer } from "effect";
|
|
2
|
+
import { safeCode } from "./canonical";
|
|
3
|
+
import { OH_OPERATION_MAX_BYTES_V1 } from "./operation";
|
|
4
|
+
import { OhConflictError, OhIntegrityError, OhProfileError,
|
|
5
|
+
type OhHeadV1, type OhStoreV1 } from "./store";
|
|
6
|
+
import {
|
|
7
|
+
authorityId, bindingFor, continuationKeyV2, createOhMemoryAgentV2WithRuntime,
|
|
8
|
+
createOhMemoryRuntimeV2, immutableClone, parseMemoryAuthorityHead,
|
|
9
|
+
resolveExtractors, resolveNominationRoutes, resolveProgramsV2,
|
|
10
|
+
type OhMemoryAuthorityOptionsV1,
|
|
11
|
+
} from "./memory-core";
|
|
12
|
+
|
|
13
|
+
export type MemoryFailure = Readonly<{
|
|
14
|
+
_tag: "MemoryConflict" | "MemoryIntegrity" | "MemoryProfile" | "MemoryValidation" | "MemoryCapacity" | "MemoryForeign";
|
|
15
|
+
cause: unknown;
|
|
16
|
+
}>;
|
|
17
|
+
|
|
18
|
+
export function memoryFailure(cause: unknown): MemoryFailure {
|
|
19
|
+
return { _tag: cause instanceof OhConflictError ? "MemoryConflict"
|
|
20
|
+
: cause instanceof OhIntegrityError ? "MemoryIntegrity"
|
|
21
|
+
: cause instanceof OhProfileError ? "MemoryProfile"
|
|
22
|
+
: cause instanceof TypeError ? "MemoryValidation"
|
|
23
|
+
: cause instanceof RangeError ? "MemoryCapacity" : "MemoryForeign", cause };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Validation may reject hostile values; unrelated generator defects are not caught. */
|
|
27
|
+
export function memoryValue<A>(evaluate: () => A): Effect.Effect<A, MemoryFailure> {
|
|
28
|
+
return Effect.try({ try: evaluate, catch: memoryFailure });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function memoryCall<A>(operation: () => Promise<A>): Effect.Effect<A, MemoryFailure> {
|
|
32
|
+
return Effect.tryPromise({ try: operation, catch: memoryFailure });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readStore(store: OhStoreV1) {
|
|
36
|
+
return {
|
|
37
|
+
binding: store.binding,
|
|
38
|
+
head: memoryCall(() => store.head()),
|
|
39
|
+
snapshot: (options: Parameters<OhStoreV1["snapshot"]>[0]) => memoryCall(() => store.snapshot(options)),
|
|
40
|
+
changesSince: (from: Parameters<OhStoreV1["changesSince"]>[0], options: Parameters<OhStoreV1["changesSince"]>[1]) =>
|
|
41
|
+
memoryCall(() => store.changesSince(from, options)),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Canonical commit is provided only to the trusted host authority composition. */
|
|
46
|
+
export class CanonicalMemoryStore extends Context.Tag("@hraness/oh/CanonicalMemoryStore")<
|
|
47
|
+
CanonicalMemoryStore, ReturnType<typeof canonicalStorePort>
|
|
48
|
+
>() {}
|
|
49
|
+
|
|
50
|
+
function canonicalStorePort(store: OhStoreV1) {
|
|
51
|
+
return { ...readStore(store),
|
|
52
|
+
commit: (input: Parameters<OhStoreV1["commit"]>[0]) => memoryCall(() => store.commit(input)),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type CanonicalMemoryStoreService = ReturnType<typeof canonicalStorePort>;
|
|
57
|
+
|
|
58
|
+
/** The host adoption program borrows only the working closure read capability. */
|
|
59
|
+
export class WorkingMemorySource extends Context.Tag("@hraness/oh/WorkingMemorySource")<
|
|
60
|
+
WorkingMemorySource, {
|
|
61
|
+
readonly exportDependencyClosure: (input: Parameters<OhStoreV1["exportDependencyClosure"]>[0]) =>
|
|
62
|
+
Effect.Effect<Awaited<ReturnType<OhStoreV1["exportDependencyClosure"]>>, MemoryFailure>;
|
|
63
|
+
}
|
|
64
|
+
>() {}
|
|
65
|
+
|
|
66
|
+
function prepareAuthority(options: OhMemoryAuthorityOptionsV1) {
|
|
67
|
+
const maximumCanonicalOperationBytes = options.maximumCanonicalOperationBytes ?? OH_OPERATION_MAX_BYTES_V1;
|
|
68
|
+
if (!Number.isSafeInteger(maximumCanonicalOperationBytes) || maximumCanonicalOperationBytes < 1
|
|
69
|
+
|| maximumCanonicalOperationBytes > OH_OPERATION_MAX_BYTES_V1) {
|
|
70
|
+
throw new TypeError("Invalid canonical memory operation byte bound.");
|
|
71
|
+
}
|
|
72
|
+
const memoryActorId = safeCode(options.actorId, 128);
|
|
73
|
+
const adoptionActorId = safeCode(options.adoptionActorId, 128);
|
|
74
|
+
if (memoryActorId === null || adoptionActorId === null) throw new TypeError("Invalid host-bound memory authority actor ID.");
|
|
75
|
+
const canonicalStore = options.canonical.store;
|
|
76
|
+
const workingStore = options.working.store;
|
|
77
|
+
const canonicalAuthorityId = authorityId(options.canonical.authorityId);
|
|
78
|
+
const workingAuthorityId = authorityId(options.working.authorityId);
|
|
79
|
+
if (canonicalAuthorityId === workingAuthorityId) throw new OhProfileError("Working and canonical memory must be distinct physical authorities.");
|
|
80
|
+
const canonicalBinding = bindingFor(canonicalStore, options.canonical.expectedBindingSha256, "canonical");
|
|
81
|
+
const workingBinding = bindingFor(workingStore, options.working.expectedBindingSha256, "working");
|
|
82
|
+
const initialCanonicalHead = parseMemoryAuthorityHead(options.canonical.expectedHead, "initial canonical");
|
|
83
|
+
const workingCodecs = options.working.codecs;
|
|
84
|
+
const explainCapabilityLifetimeMs = options.explainCapabilityLifetimeMs;
|
|
85
|
+
const monotonicNow = options.monotonicNow;
|
|
86
|
+
const now = options.now;
|
|
87
|
+
const continuationKey = continuationKeyV2(options.continuationKey);
|
|
88
|
+
const programs = Object.freeze([...resolveProgramsV2(options.programs).values()].map((program) =>
|
|
89
|
+
immutableClone({ evaluation: program.evaluation, maximumPageBytes: program.maximumPageBytes,
|
|
90
|
+
maximumRows: program.maximumRows, pageSize: program.pageSize, parameters: program.parameters,
|
|
91
|
+
programId: program.programId, purpose: program.purpose, query: program.query,
|
|
92
|
+
rulePack: program.rulePack, v: 2 as const })));
|
|
93
|
+
const extractors = resolveExtractors(options.extractors ?? []);
|
|
94
|
+
const nominationRoutes = Object.freeze([...resolveNominationRoutes(options.nominationRoutes ?? []).values()]);
|
|
95
|
+
const runtime = createOhMemoryRuntimeV2(options);
|
|
96
|
+
const createAgentAt = (expectedHead: OhHeadV1) => memoryCall(() => createOhMemoryAgentV2WithRuntime({
|
|
97
|
+
actorId: memoryActorId,
|
|
98
|
+
canonical: { authorityId: canonicalAuthorityId, expectedBindingSha256: canonicalBinding.bindingSha256,
|
|
99
|
+
expectedHead, store: canonicalStore },
|
|
100
|
+
continuationKey,
|
|
101
|
+
...(explainCapabilityLifetimeMs === undefined ? {} : { explainCapabilityLifetimeMs }),
|
|
102
|
+
extractors,
|
|
103
|
+
...(monotonicNow === undefined ? {} : { monotonicNow }),
|
|
104
|
+
nominationRoutes,
|
|
105
|
+
...(now === undefined ? {} : { now }),
|
|
106
|
+
programs,
|
|
107
|
+
working: { authorityId: workingAuthorityId, codecs: workingCodecs,
|
|
108
|
+
expectedBindingSha256: workingBinding.bindingSha256, store: workingStore },
|
|
109
|
+
}, runtime));
|
|
110
|
+
return { adoptionActorId, canonicalAuthorityId, workingAuthorityId, canonicalBinding, workingBinding,
|
|
111
|
+
initialCanonicalHead, maximumCanonicalOperationBytes, createAgentAt,
|
|
112
|
+
routesById: new Map(nominationRoutes.map((route) => [route.nominationId, route])),
|
|
113
|
+
canonical: canonicalStorePort(canonicalStore),
|
|
114
|
+
working: { exportDependencyClosure: (input: Parameters<OhStoreV1["exportDependencyClosure"]>[0]) =>
|
|
115
|
+
memoryCall(() => workingStore.exportDependencyClosure(input)) },
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export class MemoryAuthorityConfig extends Context.Tag("@hraness/oh/MemoryAuthorityConfig")<
|
|
120
|
+
MemoryAuthorityConfig, Omit<ReturnType<typeof prepareAuthority>, "canonical" | "working">
|
|
121
|
+
>() {}
|
|
122
|
+
|
|
123
|
+
/** Preparation captures caller-owned options before the first foreign wait. Stores
|
|
124
|
+
* are borrowed; these Layers cannot close or purge either physical authority. */
|
|
125
|
+
export function memoryAuthorityLive(options: OhMemoryAuthorityOptionsV1):
|
|
126
|
+
Layer.Layer<MemoryAuthorityConfig | CanonicalMemoryStore | WorkingMemorySource, MemoryFailure> {
|
|
127
|
+
return Layer.unwrapEffect(memoryValue(() => prepareAuthority(options)).pipe(Effect.map(prepared => {
|
|
128
|
+
const { canonical, working, ...config } = prepared;
|
|
129
|
+
return Layer.mergeAll(Layer.succeed(MemoryAuthorityConfig, config),
|
|
130
|
+
Layer.succeed(CanonicalMemoryStore, canonical),
|
|
131
|
+
Layer.succeed(WorkingMemorySource, working));
|
|
132
|
+
})));
|
|
133
|
+
}
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { Cause, Effect, Exit, Option } from "effect";
|
|
2
|
+
import { canonicalJson, canonicalSha256, isPlainRecord, hasExactKeys } from "./canonical";
|
|
3
|
+
import { canonicalKnowledgeGraphChangesV1, type KnowledgeGraphChangeV1 } from "./graph";
|
|
4
|
+
import { parseOhOperationV1 } from "./operation";
|
|
5
|
+
import { OH_DEPENDENCY_CLOSURE_LIMITS_V1, OhConflictError, OhIntegrityError, OhProfileError,
|
|
6
|
+
parseOhHeadRefV1, parseOhHeadV1, type OhHeadV1, type OhStoreBindingV1 } from "./store";
|
|
7
|
+
import { OH_MEMORY_LIMITS_V1, OH_MEMORY_AUTHORITY_LIMITS_V1, immutableClone, exactHead, headRef,
|
|
8
|
+
parseDetachedStoreSnapshot, datasetForSnapshot, parseMemoryAuthorityHead, detachCanonicalData,
|
|
9
|
+
parseCanonicalAdvanceRequest, parseAdoptionRequest, canonicalAdvanceReceipt, adoptionReceipt,
|
|
10
|
+
adoptionDifferences, unauthorizedAdoptionDifferences, assertAdoptionSnapshotCapacity, adoptionConflict,
|
|
11
|
+
type LaneSnapshot, type OhMemoryCanonicalAdvanceReceiptV1, type OhMemoryAdoptionReceiptV1,
|
|
12
|
+
type OhMemoryAdoptionConflictEntryV1 } from "./memory-core";
|
|
13
|
+
import { CanonicalMemoryStore, WorkingMemorySource, MemoryAuthorityConfig, memoryValue, memoryFailure, type CanonicalMemoryStoreService,
|
|
14
|
+
type MemoryFailure } from "./memory-authority-platform";
|
|
15
|
+
|
|
16
|
+
type CanonicalAuthority = Readonly<{ authorityId: string; binding: OhStoreBindingV1;
|
|
17
|
+
store: CanonicalMemoryStoreService }>;
|
|
18
|
+
|
|
19
|
+
function readLane(authority: CanonicalAuthority, lane: "canonical", expectedHead: OhHeadV1):
|
|
20
|
+
Effect.Effect<LaneSnapshot, MemoryFailure> {
|
|
21
|
+
return Effect.gen(function* () {
|
|
22
|
+
const head = immutableClone(expectedHead);
|
|
23
|
+
const returnedSnapshot = yield* authority.store.snapshot({
|
|
24
|
+
head: { operationSha256: head.operationSha256, sequence: head.sequence },
|
|
25
|
+
maximumRecords: OH_MEMORY_LIMITS_V1.maximumRecordsPerLane,
|
|
26
|
+
});
|
|
27
|
+
return yield* memoryValue(() => {
|
|
28
|
+
const parsed = parseDetachedStoreSnapshot(returnedSnapshot, `The ${lane} store`, head, authority.binding.spaceId);
|
|
29
|
+
const projected = datasetForSnapshot(authority.binding, parsed.snapshot, parsed.projectionSnapshot);
|
|
30
|
+
return Object.freeze({ authorityId: authority.authorityId, binding: authority.binding,
|
|
31
|
+
dataset: projected.dataset, lane, projectionSnapshot: projected.projectionSnapshot, snapshot: parsed.snapshot });
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function proveCanonicalDescendant(
|
|
37
|
+
authority: CanonicalAuthority,
|
|
38
|
+
priorHead: OhHeadV1,
|
|
39
|
+
nextHead: OhHeadV1,
|
|
40
|
+
requiredFirstHead?: OhHeadV1,
|
|
41
|
+
): Effect.Effect<LaneSnapshot, MemoryFailure> {
|
|
42
|
+
return Effect.gen(function* () {
|
|
43
|
+
if (nextHead.sequence <= priorHead.sequence) {
|
|
44
|
+
return yield* Effect.fail(memoryFailure(new OhConflictError("The next canonical memory head is not a descendant of the current pin.")));
|
|
45
|
+
}
|
|
46
|
+
const distance = nextHead.sequence - priorHead.sequence;
|
|
47
|
+
if (distance > OH_MEMORY_AUTHORITY_LIMITS_V1.canonicalAdvanceOperations
|
|
48
|
+
|| Math.ceil(distance / OH_MEMORY_AUTHORITY_LIMITS_V1.canonicalChangeFeedPage)
|
|
49
|
+
> OH_MEMORY_AUTHORITY_LIMITS_V1.canonicalAdvancePages) {
|
|
50
|
+
return yield* Effect.fail(memoryFailure(new RangeError("The canonical memory advance exceeds its total proof bound; advance in host-reviewed chunks.")));
|
|
51
|
+
}
|
|
52
|
+
const through = headRef(nextHead);
|
|
53
|
+
let cursor = headRef(priorHead);
|
|
54
|
+
let pageCount = 0;
|
|
55
|
+
let reachedHead: OhHeadV1 | null = null;
|
|
56
|
+
let firstHead: OhHeadV1 | null = null;
|
|
57
|
+
while (cursor.sequence < through.sequence) {
|
|
58
|
+
if (pageCount >= OH_MEMORY_AUTHORITY_LIMITS_V1.canonicalAdvancePages) {
|
|
59
|
+
return yield* Effect.fail(memoryFailure(new RangeError("The canonical memory advance exceeded its page proof bound; advance in host-reviewed chunks.")));
|
|
60
|
+
}
|
|
61
|
+
pageCount += 1;
|
|
62
|
+
const remaining = through.sequence - cursor.sequence;
|
|
63
|
+
const limit = Math.min(remaining, OH_MEMORY_AUTHORITY_LIMITS_V1.canonicalChangeFeedPage);
|
|
64
|
+
const page = yield* authority.store.changesSince(cursor, { limit, through });
|
|
65
|
+
const returnedData = (yield* memoryValue(() => detachCanonicalData(
|
|
66
|
+
page,
|
|
67
|
+
"The canonical change-feed page",
|
|
68
|
+
OH_MEMORY_AUTHORITY_LIMITS_V1.canonicalChangeFeedPageBytes,
|
|
69
|
+
))).value;
|
|
70
|
+
const returned = returnedData as Record<string, unknown>;
|
|
71
|
+
if (!isPlainRecord(returned) || !hasExactKeys(returned,
|
|
72
|
+
["from", "hasMore", "operations", "through", "to", "v"])
|
|
73
|
+
|| returned.v !== 1 || typeof returned.hasMore !== "boolean"
|
|
74
|
+
|| !Array.isArray(returned.operations) || returned.operations.length > limit) {
|
|
75
|
+
return yield* Effect.fail(memoryFailure(new OhIntegrityError("The canonical change feed returned an invalid page envelope.")));
|
|
76
|
+
}
|
|
77
|
+
const from = parseOhHeadRefV1(returned.from);
|
|
78
|
+
const returnedThrough = parseOhHeadV1(returned.through);
|
|
79
|
+
const returnedTo = parseOhHeadRefV1(returned.to);
|
|
80
|
+
if (from === null || returnedThrough === null || returnedTo === null
|
|
81
|
+
|| canonicalJson(from) !== canonicalJson(cursor)
|
|
82
|
+
|| !exactHead(returnedThrough, nextHead)) {
|
|
83
|
+
return yield* Effect.fail(memoryFailure(new OhIntegrityError("The canonical change feed changed its pinned bounds.")));
|
|
84
|
+
}
|
|
85
|
+
let reached = cursor;
|
|
86
|
+
for (const value of returned.operations) {
|
|
87
|
+
const operation = parseOhOperationV1(value);
|
|
88
|
+
if (operation === null || operation.spaceId !== authority.binding.spaceId
|
|
89
|
+
|| operation.sequence !== reached.sequence + 1
|
|
90
|
+
|| operation.parentOperationSha256 !== reached.operationSha256) {
|
|
91
|
+
return yield* Effect.fail(memoryFailure(new OhIntegrityError("The canonical change feed contains a gap or different authority.")));
|
|
92
|
+
}
|
|
93
|
+
reached = Object.freeze({ operationSha256: operation.operationSha256,
|
|
94
|
+
sequence: operation.sequence });
|
|
95
|
+
reachedHead = immutableClone({ generation: operation.sequence,
|
|
96
|
+
graphRevisionSha256: operation.graphRevisionSha256,
|
|
97
|
+
operationSha256: operation.operationSha256, recordsSha256: operation.recordsSha256,
|
|
98
|
+
sequence: operation.sequence, v: 1 });
|
|
99
|
+
firstHead ??= reachedHead;
|
|
100
|
+
}
|
|
101
|
+
if (canonicalJson(reached) !== canonicalJson(returnedTo)
|
|
102
|
+
|| (returned.hasMore && returned.operations.length === 0)
|
|
103
|
+
|| (returned.hasMore && reached.sequence >= through.sequence)
|
|
104
|
+
|| reached.sequence > through.sequence
|
|
105
|
+
|| (!returned.hasMore && canonicalJson(reached) !== canonicalJson(through))) {
|
|
106
|
+
return yield* Effect.fail(memoryFailure(new OhIntegrityError("The canonical change feed did not prove the requested descendant.")));
|
|
107
|
+
}
|
|
108
|
+
cursor = reached;
|
|
109
|
+
}
|
|
110
|
+
if (reachedHead === null || !exactHead(reachedHead, nextHead)) {
|
|
111
|
+
return yield* Effect.fail(memoryFailure(new OhIntegrityError("The canonical change feed did not prove the requested full head.")));
|
|
112
|
+
}
|
|
113
|
+
if (requiredFirstHead !== undefined
|
|
114
|
+
&& (firstHead === null || !exactHead(firstHead, requiredFirstHead))) {
|
|
115
|
+
return yield* Effect.fail(memoryFailure(new OhIntegrityError("The returned adoption operation is not on the current canonical path.")));
|
|
116
|
+
}
|
|
117
|
+
return yield* readLane(authority, "canonical", nextHead);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** One FIFO for trusted host changes; agent calls retain their captured immutable pin.
|
|
122
|
+
* After admission the operation retains its permit through actual foreign settlement
|
|
123
|
+
* and pin publication. Native commit/CAS remains authoritative; there is no retry. */
|
|
124
|
+
export const makeMemoryAuthority = Effect.gen(function* () {
|
|
125
|
+
const { initialCanonicalHead, createAgentAt, canonicalAuthorityId, workingAuthorityId,
|
|
126
|
+
canonicalBinding, workingBinding, routesById, adoptionActorId, maximumCanonicalOperationBytes } = yield* MemoryAuthorityConfig;
|
|
127
|
+
const canonicalStore = yield* CanonicalMemoryStore;
|
|
128
|
+
const workingStore = yield* WorkingMemorySource;
|
|
129
|
+
const hostOrder = yield* Effect.makeSemaphore(1);
|
|
130
|
+
let activeCanonicalHead = initialCanonicalHead;
|
|
131
|
+
let activeAgent = yield* createAgentAt(activeCanonicalHead);
|
|
132
|
+
const canonicalAuthority = Object.freeze({ authorityId: canonicalAuthorityId, binding: canonicalBinding, store: canonicalStore });
|
|
133
|
+
const installCanonicalHead = (head: OhHeadV1) => Effect.gen(function* () {
|
|
134
|
+
const nextAgent = yield* createAgentAt(head);
|
|
135
|
+
// Publish both references together only after the next snapshot is verified.
|
|
136
|
+
activeAgent = nextAgent;
|
|
137
|
+
activeCanonicalHead = immutableClone(head);
|
|
138
|
+
});
|
|
139
|
+
const readPhysicalCanonicalHead = () => canonicalStore.head.pipe(
|
|
140
|
+
Effect.flatMap(head => memoryValue(() => parseMemoryAuthorityHead(head, "physical canonical"))));
|
|
141
|
+
const advanceCanonical = (request: ReturnType<typeof parseCanonicalAdvanceRequest>): Effect.Effect<OhMemoryCanonicalAdvanceReceiptV1, MemoryFailure> =>
|
|
142
|
+
hostOrder.withPermits(1)(Effect.uninterruptible(Effect.gen(function* () {
|
|
143
|
+
const priorHead = activeCanonicalHead;
|
|
144
|
+
if (!exactHead(request.expectedHead, priorHead)) {
|
|
145
|
+
return yield* Effect.fail(memoryFailure(new OhConflictError("The expected canonical memory head does not match the current pin.")));
|
|
146
|
+
}
|
|
147
|
+
if (exactHead(request.nextHead, priorHead)) {
|
|
148
|
+
return canonicalAdvanceReceipt(canonicalAuthorityId, canonicalBinding.bindingSha256,
|
|
149
|
+
priorHead, priorHead, "unchanged");
|
|
150
|
+
}
|
|
151
|
+
yield* proveCanonicalDescendant(canonicalAuthority, priorHead, request.nextHead);
|
|
152
|
+
yield* installCanonicalHead(request.nextHead);
|
|
153
|
+
return canonicalAdvanceReceipt(canonicalAuthorityId, canonicalBinding.bindingSha256,
|
|
154
|
+
priorHead, request.nextHead, "advanced");
|
|
155
|
+
})));
|
|
156
|
+
|
|
157
|
+
const adoptNomination = (request: ReturnType<typeof parseAdoptionRequest>): Effect.Effect<OhMemoryAdoptionReceiptV1, MemoryFailure> =>
|
|
158
|
+
hostOrder.withPermits(1)(Effect.uninterruptible(Effect.gen(function* () {
|
|
159
|
+
const route = routesById.get(request.nomination.nominationId);
|
|
160
|
+
if (route === undefined || route.destinationPurpose !== request.nomination.destinationPurpose) {
|
|
161
|
+
return yield* Effect.fail(memoryFailure(new OhProfileError("The memory nomination is not bound to this adoption route.")));
|
|
162
|
+
}
|
|
163
|
+
if (request.nomination.source.authorityId !== workingAuthorityId
|
|
164
|
+
|| request.nomination.source.bindingSha256 !== workingBinding.bindingSha256
|
|
165
|
+
|| request.nomination.closure.binding.bindingSha256 !== workingBinding.bindingSha256) {
|
|
166
|
+
return yield* Effect.fail(memoryFailure(new OhProfileError("The memory nomination is not from the bound working authority.")));
|
|
167
|
+
}
|
|
168
|
+
const returnedReexport = yield* workingStore.exportDependencyClosure({
|
|
169
|
+
head: headRef(request.nomination.source.head),
|
|
170
|
+
maximumRecords: OH_DEPENDENCY_CLOSURE_LIMITS_V1.records,
|
|
171
|
+
roots: request.nomination.closure.roots,
|
|
172
|
+
});
|
|
173
|
+
const reexported = yield* memoryValue(() => detachCanonicalData(returnedReexport, "The working re-exported nomination",
|
|
174
|
+
OH_DEPENDENCY_CLOSURE_LIMITS_V1.bytes));
|
|
175
|
+
if (reexported.canonical !== canonicalJson(request.nomination.closure)) {
|
|
176
|
+
return yield* Effect.fail(memoryFailure(new OhIntegrityError("The working authority did not re-export the nominated closure exactly.")));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const priorHead = activeCanonicalHead;
|
|
180
|
+
const physicalHead = yield* readPhysicalCanonicalHead();
|
|
181
|
+
const replacementConflictsAt = (currentLane: LaneSnapshot): Effect.Effect<readonly OhMemoryAdoptionConflictEntryV1[], MemoryFailure> => Effect.gen(function* () {
|
|
182
|
+
if (request.replacements.length === 0) return [];
|
|
183
|
+
const reviewedLane = exactHead(request.expectedCanonicalHead, currentLane.snapshot.head)
|
|
184
|
+
? currentLane
|
|
185
|
+
: yield* readLane(canonicalAuthority, "canonical", request.expectedCanonicalHead);
|
|
186
|
+
return unauthorizedAdoptionDifferences(reviewedLane.snapshot, currentLane.snapshot,
|
|
187
|
+
request.nomination.closure.records, request.replacements);
|
|
188
|
+
});
|
|
189
|
+
if (!exactHead(physicalHead, priorHead)) {
|
|
190
|
+
const physicalLane = yield* proveCanonicalDescendant(canonicalAuthority, priorHead, physicalHead);
|
|
191
|
+
const physicalDifferences = adoptionDifferences(physicalLane.snapshot,
|
|
192
|
+
request.nomination.closure.records);
|
|
193
|
+
if (physicalDifferences.length === 0) {
|
|
194
|
+
const replacementConflicts = yield* replacementConflictsAt(physicalLane);
|
|
195
|
+
if (replacementConflicts.length > 0) {
|
|
196
|
+
return yield* Effect.fail(memoryFailure(adoptionConflict(request.expectedCanonicalHead, physicalHead, replacementConflicts)));
|
|
197
|
+
}
|
|
198
|
+
yield* installCanonicalHead(physicalHead);
|
|
199
|
+
return adoptionReceipt(adoptionActorId, canonicalAuthorityId,
|
|
200
|
+
canonicalBinding.bindingSha256, request.nomination.nominationSha256, null,
|
|
201
|
+
priorHead, physicalHead, "already-present");
|
|
202
|
+
}
|
|
203
|
+
return yield* Effect.fail(memoryFailure(adoptionConflict(request.expectedCanonicalHead, physicalHead, physicalDifferences)));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const lane = yield* readLane(canonicalAuthority, "canonical", priorHead);
|
|
207
|
+
const differences = adoptionDifferences(lane.snapshot, request.nomination.closure.records);
|
|
208
|
+
if (!exactHead(request.expectedCanonicalHead, priorHead)) {
|
|
209
|
+
if (differences.length === 0) {
|
|
210
|
+
const replacementConflicts = yield* replacementConflictsAt(lane);
|
|
211
|
+
if (replacementConflicts.length > 0) {
|
|
212
|
+
return yield* Effect.fail(memoryFailure(adoptionConflict(request.expectedCanonicalHead, priorHead, replacementConflicts)));
|
|
213
|
+
}
|
|
214
|
+
return adoptionReceipt(adoptionActorId, canonicalAuthorityId,
|
|
215
|
+
canonicalBinding.bindingSha256, request.nomination.nominationSha256, null,
|
|
216
|
+
priorHead, priorHead, "already-present");
|
|
217
|
+
}
|
|
218
|
+
return yield* Effect.fail(memoryFailure(adoptionConflict(request.expectedCanonicalHead, priorHead, differences)));
|
|
219
|
+
}
|
|
220
|
+
if (differences.length === 0) {
|
|
221
|
+
const replacementConflicts = yield* replacementConflictsAt(lane);
|
|
222
|
+
if (replacementConflicts.length > 0) {
|
|
223
|
+
return yield* Effect.fail(memoryFailure(adoptionConflict(request.expectedCanonicalHead, priorHead, replacementConflicts)));
|
|
224
|
+
}
|
|
225
|
+
return adoptionReceipt(adoptionActorId, canonicalAuthorityId,
|
|
226
|
+
canonicalBinding.bindingSha256, request.nomination.nominationSha256, null,
|
|
227
|
+
priorHead, priorHead, "already-present");
|
|
228
|
+
}
|
|
229
|
+
const unauthorized = unauthorizedAdoptionDifferences(lane.snapshot, lane.snapshot,
|
|
230
|
+
request.nomination.closure.records, request.replacements);
|
|
231
|
+
if (unauthorized.length > 0) {
|
|
232
|
+
return yield* Effect.fail(memoryFailure(adoptionConflict(request.expectedCanonicalHead, priorHead, unauthorized)));
|
|
233
|
+
}
|
|
234
|
+
const changedKeys = new Set(differences.map(({ key }) => key));
|
|
235
|
+
const changedRecords = request.nomination.closure.records
|
|
236
|
+
.filter(({ key }) => changedKeys.has(key));
|
|
237
|
+
if (changedRecords.length === 0) {
|
|
238
|
+
return adoptionReceipt(adoptionActorId, canonicalAuthorityId,
|
|
239
|
+
canonicalBinding.bindingSha256, request.nomination.nominationSha256, null,
|
|
240
|
+
priorHead, priorHead, "already-present");
|
|
241
|
+
}
|
|
242
|
+
yield* memoryValue(() => assertAdoptionSnapshotCapacity(lane.snapshot, changedRecords));
|
|
243
|
+
const changes = canonicalKnowledgeGraphChangesV1(changedRecords
|
|
244
|
+
.map((record): KnowledgeGraphChangeV1 => ({ kind: "put", record, v: 1 })));
|
|
245
|
+
const operationId = `memory_adopt_${canonicalSha256({ actorId: adoptionActorId,
|
|
246
|
+
bindingSha256: canonicalBinding.bindingSha256,
|
|
247
|
+
nominationSha256: request.nomination.nominationSha256,
|
|
248
|
+
priorHead, v: 1 }).slice(0, 48)}`;
|
|
249
|
+
const committed = yield* Effect.exit(canonicalStore.commit({ actorId: adoptionActorId, changes,
|
|
250
|
+
expectedHead: { generation: priorHead.generation,
|
|
251
|
+
operationSha256: priorHead.operationSha256 },
|
|
252
|
+
maximumOperationBytes: maximumCanonicalOperationBytes,
|
|
253
|
+
operationId }));
|
|
254
|
+
if (Exit.isFailure(committed)) {
|
|
255
|
+
const error = Cause.failureOption(committed.cause);
|
|
256
|
+
if (Option.isNone(error) || error.value._tag !== "MemoryConflict") return yield* Effect.failCause(committed.cause);
|
|
257
|
+
const actualHead = yield* readPhysicalCanonicalHead();
|
|
258
|
+
const actualLane = exactHead(actualHead, priorHead)
|
|
259
|
+
? yield* readLane(canonicalAuthority, "canonical", actualHead)
|
|
260
|
+
: yield* proveCanonicalDescendant(canonicalAuthority, priorHead, actualHead);
|
|
261
|
+
const actualDifferences = adoptionDifferences(actualLane.snapshot,
|
|
262
|
+
request.nomination.closure.records);
|
|
263
|
+
if (actualDifferences.length === 0) {
|
|
264
|
+
if (!exactHead(actualHead, priorHead)) {
|
|
265
|
+
yield* installCanonicalHead(actualHead);
|
|
266
|
+
}
|
|
267
|
+
return adoptionReceipt(adoptionActorId, canonicalAuthorityId,
|
|
268
|
+
canonicalBinding.bindingSha256, request.nomination.nominationSha256, null,
|
|
269
|
+
priorHead, actualHead, "already-present");
|
|
270
|
+
}
|
|
271
|
+
return yield* Effect.fail(memoryFailure(adoptionConflict(request.expectedCanonicalHead, actualHead, actualDifferences)));
|
|
272
|
+
}
|
|
273
|
+
const operation = yield* memoryValue(() => parseOhOperationV1(detachCanonicalData(committed.value,
|
|
274
|
+
"The returned canonical adoption operation",
|
|
275
|
+
OH_MEMORY_AUTHORITY_LIMITS_V1.canonicalChangeFeedPageBytes).value));
|
|
276
|
+
if (operation === null || operation.actorId !== adoptionActorId
|
|
277
|
+
|| operation.operationId !== operationId || operation.spaceId !== canonicalBinding.spaceId
|
|
278
|
+
|| operation.parentOperationSha256 !== priorHead.operationSha256
|
|
279
|
+
|| operation.sequence !== priorHead.sequence + 1
|
|
280
|
+
|| canonicalJson(operation.changes) !== canonicalJson(changes)) {
|
|
281
|
+
return yield* Effect.fail(memoryFailure(new OhIntegrityError("The canonical authority returned a different adoption operation.")));
|
|
282
|
+
}
|
|
283
|
+
const head: OhHeadV1 = immutableClone({ generation: operation.sequence,
|
|
284
|
+
graphRevisionSha256: operation.graphRevisionSha256,
|
|
285
|
+
operationSha256: operation.operationSha256, recordsSha256: operation.recordsSha256,
|
|
286
|
+
sequence: operation.sequence, v: 1 });
|
|
287
|
+
const actualHead = yield* readPhysicalCanonicalHead();
|
|
288
|
+
if (!exactHead(actualHead, head)) {
|
|
289
|
+
const actualLane = exactHead(actualHead, priorHead)
|
|
290
|
+
? yield* readLane(canonicalAuthority, "canonical", actualHead)
|
|
291
|
+
: yield* proveCanonicalDescendant(canonicalAuthority, priorHead, actualHead, head);
|
|
292
|
+
const actualDifferences = adoptionDifferences(actualLane.snapshot,
|
|
293
|
+
request.nomination.closure.records);
|
|
294
|
+
if (actualDifferences.length !== 0) {
|
|
295
|
+
return yield* Effect.fail(memoryFailure(adoptionConflict(request.expectedCanonicalHead, actualHead, actualDifferences)));
|
|
296
|
+
}
|
|
297
|
+
yield* installCanonicalHead(actualHead);
|
|
298
|
+
return adoptionReceipt(adoptionActorId, canonicalAuthorityId,
|
|
299
|
+
canonicalBinding.bindingSha256, request.nomination.nominationSha256, null,
|
|
300
|
+
priorHead, actualHead, "already-present");
|
|
301
|
+
}
|
|
302
|
+
yield* installCanonicalHead(actualHead);
|
|
303
|
+
return adoptionReceipt(adoptionActorId, canonicalAuthorityId,
|
|
304
|
+
canonicalBinding.bindingSha256, request.nomination.nominationSha256,
|
|
305
|
+
operation.operationSha256, priorHead, actualHead, "adopted");
|
|
306
|
+
})));
|
|
307
|
+
|
|
308
|
+
return { get agent() { return activeAgent; }, advanceCanonical, adoptNomination };
|
|
309
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Cause, Effect, Exit, Option } from "effect";
|
|
2
|
+
import { parseAdoptionRequest, parseCanonicalAdvanceRequest,
|
|
3
|
+
type OhMemoryAuthorityOptionsV1, type OhMemoryAuthorityV1 } from "./memory-core";
|
|
4
|
+
import { memoryAuthorityLive, type MemoryFailure } from "./memory-authority-platform";
|
|
5
|
+
import { makeMemoryAuthority } from "./memory-authority-program";
|
|
6
|
+
|
|
7
|
+
function unwrap<A>(exit: Exit.Exit<A, MemoryFailure>): A {
|
|
8
|
+
if (Exit.isSuccess(exit)) return exit.value;
|
|
9
|
+
const expected = Cause.failureOption(exit.cause);
|
|
10
|
+
if (Option.isSome(expected)) throw expected.value.cause;
|
|
11
|
+
throw Cause.squash(exit.cause);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function run<A>(program: Effect.Effect<A, MemoryFailure, never>): Promise<A> {
|
|
15
|
+
return Effect.runPromiseExit(program).then(unwrap);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Existing Promise surface; stores and agent capability lifetimes remain borrowed. */
|
|
19
|
+
export async function createOhMemoryAuthorityV1(options: OhMemoryAuthorityOptionsV1): Promise<OhMemoryAuthorityV1> {
|
|
20
|
+
const authority = await run(makeMemoryAuthority.pipe(Effect.provide(memoryAuthorityLive(options))));
|
|
21
|
+
return Object.freeze({
|
|
22
|
+
agent: Object.freeze({
|
|
23
|
+
explain: (value: unknown) => authority.agent.explain(value),
|
|
24
|
+
nominate: (value: unknown) => authority.agent.nominate(value),
|
|
25
|
+
query: (value: unknown) => authority.agent.query(value),
|
|
26
|
+
remember: (value: unknown) => authority.agent.remember(value),
|
|
27
|
+
}),
|
|
28
|
+
host: Object.freeze({
|
|
29
|
+
advanceCanonical: (value: unknown) => {
|
|
30
|
+
// Detach before enqueueing; caller mutation cannot change a waiting request.
|
|
31
|
+
try { return run(authority.advanceCanonical(parseCanonicalAdvanceRequest(value))); }
|
|
32
|
+
catch (cause) { return Promise.reject(cause); }
|
|
33
|
+
},
|
|
34
|
+
adoptNomination: (value: unknown) => {
|
|
35
|
+
try { return run(authority.adoptNomination(parseAdoptionRequest(value))); }
|
|
36
|
+
catch (cause) { return Promise.reject(cause); }
|
|
37
|
+
},
|
|
38
|
+
}),
|
|
39
|
+
});
|
|
40
|
+
}
|