@openagentsinc/conformance-kit 0.2.0-rc.2 → 0.2.0-rc.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -7
- package/package.json +6 -5
- package/src/corpus-composition-laws.test.ts +9 -0
- package/src/corpus-composition-laws.ts +512 -0
- package/src/index.ts +4 -0
package/README.md
CHANGED
|
@@ -23,13 +23,14 @@ is proven to work before anyone points it at a third-party implementation.
|
|
|
23
23
|
|
|
24
24
|
## The suites
|
|
25
25
|
|
|
26
|
-
| Import | Runner
|
|
27
|
-
| ------------------------------------------ |
|
|
28
|
-
| `@openagentsinc/conformance-kit/adapter` | `runAdapterLaws`
|
|
29
|
-
| `@openagentsinc/conformance-kit/event-log` | `runEventLogLaws`
|
|
30
|
-
| `@openagentsinc/conformance-kit/reducer` | `runReducerLaws`
|
|
31
|
-
| `@openagentsinc/conformance-kit/recall` | `runRecallLaws`
|
|
32
|
-
| `@openagentsinc/conformance-kit/rlm` | `runRlmCapLaws`
|
|
26
|
+
| Import | Runner | Implementation under test | Laws |
|
|
27
|
+
| ------------------------------------------ | -------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
28
|
+
| `@openagentsinc/conformance-kit/adapter` | `runAdapterLaws` | `AgentHarness` | turn framing, capability refusal is fail-closed and named, suspend/continue cursor exactness (with honest lossy degradation) |
|
|
29
|
+
| `@openagentsinc/conformance-kit/event-log` | `runEventLogLaws` | `HarnessEventLogStore` | append monotonicity / dup-free, gap-free replay from a cursor, durable replay after process death, rerun boundaries, live attach, single-flight attach |
|
|
30
|
+
| `@openagentsinc/conformance-kit/reducer` | `runReducerLaws` | a progressive UI-message reducer | progressive fold, tool state machine, transient bypass, fail-loud-never-corrupt |
|
|
31
|
+
| `@openagentsinc/conformance-kit/recall` | `runRecallLaws` | a `HistoryRecall` Tier D source | correctness anchor, caps truncate + report, `cost.modelCalls === 0`, coverage-note carry-through, typed invalid input |
|
|
32
|
+
| `@openagentsinc/conformance-kit/rlm` | `runRlmCapLaws` | the RLM engine + corpus source | every cap → honest `Partial`, generous → `Completed`, no laundering, deterministic never touches a model |
|
|
33
|
+
| `@openagentsinc/conformance-kit/corpus` | `runCorpusCompositionLaws` | a constructor and projection | identity, order, policy, scope, freshness, citations, duplicates, planes, coverage, exclusions, bounded access |
|
|
33
34
|
|
|
34
35
|
Everything is re-exported from the package root too
|
|
35
36
|
(`@openagentsinc/conformance-kit`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openagentsinc/conformance-kit",
|
|
3
|
-
"version": "0.2.0-rc.
|
|
3
|
+
"version": "0.2.0-rc.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"files": [
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"./reducer": "./src/reducer-laws.ts",
|
|
16
16
|
"./recall": "./src/recall-laws.ts",
|
|
17
17
|
"./rlm": "./src/rlm-cap-laws.ts",
|
|
18
|
+
"./corpus": "./src/corpus-composition-laws.ts",
|
|
18
19
|
"./fixtures": "./src/fixtures.ts"
|
|
19
20
|
},
|
|
20
21
|
"publishConfig": {
|
|
@@ -22,10 +23,10 @@
|
|
|
22
23
|
},
|
|
23
24
|
"dependencies": {
|
|
24
25
|
"effect": "4.0.0-beta.94",
|
|
25
|
-
"@openagentsinc/agent-
|
|
26
|
-
"@openagentsinc/
|
|
27
|
-
"@openagentsinc/history-corpus": "0.2.0-rc.
|
|
28
|
-
"@openagentsinc/
|
|
26
|
+
"@openagentsinc/agent-harness-contract": "0.2.0-rc.5",
|
|
27
|
+
"@openagentsinc/agent-runtime-schema": "0.2.0-rc.5",
|
|
28
|
+
"@openagentsinc/history-corpus": "0.2.0-rc.5",
|
|
29
|
+
"@openagentsinc/rlm": "0.2.0-rc.5"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
32
|
"@types/node": "24.13.1",
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { buildInMemoryCompositeProjection, makeCompositeCorpusHandle } from "@openagentsinc/rlm";
|
|
2
|
+
|
|
3
|
+
import { runCorpusCompositionLaws } from "./corpus-composition-laws.ts";
|
|
4
|
+
|
|
5
|
+
runCorpusCompositionLaws({
|
|
6
|
+
label: "reference",
|
|
7
|
+
compose: makeCompositeCorpusHandle,
|
|
8
|
+
makeProjection: buildInMemoryCompositeProjection,
|
|
9
|
+
});
|
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
import { Effect, Stream } from "effect";
|
|
2
|
+
import {
|
|
3
|
+
buildInlineCorpusInput,
|
|
4
|
+
citationFromEntry,
|
|
5
|
+
canonicalJson,
|
|
6
|
+
computeCompositeProjectionDigest,
|
|
7
|
+
makeInlineCorpusHandle,
|
|
8
|
+
validateCitations,
|
|
9
|
+
sha256Hex,
|
|
10
|
+
RlmCorpusError,
|
|
11
|
+
type MakeCompositeCorpusHandleInput,
|
|
12
|
+
type RlmCompositeProjection,
|
|
13
|
+
type RlmCorpusEntry,
|
|
14
|
+
type RlmCorpusHandle,
|
|
15
|
+
} from "@openagentsinc/rlm";
|
|
16
|
+
import { describe, expect, test } from "vite-plus/test";
|
|
17
|
+
|
|
18
|
+
export interface CorpusCompositionLawsConfig {
|
|
19
|
+
readonly label: string;
|
|
20
|
+
readonly compose: (
|
|
21
|
+
input: MakeCompositeCorpusHandleInput,
|
|
22
|
+
) => Effect.Effect<RlmCorpusHandle, RlmCorpusError>;
|
|
23
|
+
readonly makeProjection: (
|
|
24
|
+
input: Omit<MakeCompositeCorpusHandleInput, "projection">,
|
|
25
|
+
) => Effect.Effect<RlmCompositeProjection, RlmCorpusError>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const policy = {
|
|
29
|
+
includeVisibilities: ["public"] as const,
|
|
30
|
+
includeRedactionClasses: ["none"] as const,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const childInput = (
|
|
34
|
+
corpusRef: string,
|
|
35
|
+
address: string,
|
|
36
|
+
options?: {
|
|
37
|
+
readonly scopeRef?: string;
|
|
38
|
+
readonly sourcePlane?: RlmCorpusEntry["sourcePlane"];
|
|
39
|
+
readonly exclusions?: ReadonlyArray<{ readonly reason: string; readonly count: number }>;
|
|
40
|
+
},
|
|
41
|
+
) => {
|
|
42
|
+
const scopeRef = options?.scopeRef ?? "scope.conformance";
|
|
43
|
+
return buildInlineCorpusInput({
|
|
44
|
+
corpusRef,
|
|
45
|
+
scopeRef,
|
|
46
|
+
policy,
|
|
47
|
+
...(options?.exclusions === undefined ? {} : { exclusions: options.exclusions }),
|
|
48
|
+
entries: [
|
|
49
|
+
{
|
|
50
|
+
scopeRef,
|
|
51
|
+
sourcePlane: options?.sourcePlane ?? "repository",
|
|
52
|
+
sourceKind: "fixture",
|
|
53
|
+
sourceAddress: { addressSchemaId: "conformance.address.v1", encodedAddress: address },
|
|
54
|
+
text: `content ${address}`,
|
|
55
|
+
visibility: "public",
|
|
56
|
+
redactionClass: "none",
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const makeChild = (input: ReturnType<typeof childInput>) =>
|
|
63
|
+
Effect.runPromise(makeInlineCorpusHandle(input));
|
|
64
|
+
|
|
65
|
+
const composeWithProjection = (
|
|
66
|
+
config: CorpusCompositionLawsConfig,
|
|
67
|
+
input: Omit<MakeCompositeCorpusHandleInput, "projection">,
|
|
68
|
+
) =>
|
|
69
|
+
Effect.gen(function* () {
|
|
70
|
+
const projection = yield* config.makeProjection(input);
|
|
71
|
+
return yield* config.compose({ ...input, projection });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
/** Published laws for an application-authorized RLM composite implementation. */
|
|
75
|
+
export const runCorpusCompositionLaws = (config: CorpusCompositionLawsConfig): void => {
|
|
76
|
+
describe(`[${config.label}] RLM corpus composition`, () => {
|
|
77
|
+
test("same ordered identities and policy produce the same identity", async () => {
|
|
78
|
+
const first = await makeChild(childInput("child.a", "a"));
|
|
79
|
+
const second = await makeChild(childInput("child.b", "b"));
|
|
80
|
+
const input = {
|
|
81
|
+
corpusRef: "composite",
|
|
82
|
+
scopeRef: "scope.conformance",
|
|
83
|
+
policy,
|
|
84
|
+
children: [
|
|
85
|
+
{ expectedIdentity: first.identity, handle: first },
|
|
86
|
+
{ expectedIdentity: second.identity, handle: second },
|
|
87
|
+
],
|
|
88
|
+
} satisfies Omit<MakeCompositeCorpusHandleInput, "projection">;
|
|
89
|
+
const left = await Effect.runPromise(composeWithProjection(config, input));
|
|
90
|
+
const right = await Effect.runPromise(composeWithProjection(config, input));
|
|
91
|
+
expect(left.identity).toEqual(right.identity);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("semantic child reordering changes the composite content digest", async () => {
|
|
95
|
+
const first = await makeChild(childInput("child.a", "a"));
|
|
96
|
+
const second = await makeChild(childInput("child.b", "b"));
|
|
97
|
+
const left = await Effect.runPromise(
|
|
98
|
+
composeWithProjection(config, {
|
|
99
|
+
corpusRef: "left",
|
|
100
|
+
scopeRef: "scope.conformance",
|
|
101
|
+
policy,
|
|
102
|
+
children: [
|
|
103
|
+
{ expectedIdentity: first.identity, handle: first },
|
|
104
|
+
{ expectedIdentity: second.identity, handle: second },
|
|
105
|
+
],
|
|
106
|
+
}),
|
|
107
|
+
);
|
|
108
|
+
const right = await Effect.runPromise(
|
|
109
|
+
composeWithProjection(config, {
|
|
110
|
+
corpusRef: "right",
|
|
111
|
+
scopeRef: "scope.conformance",
|
|
112
|
+
policy,
|
|
113
|
+
children: [
|
|
114
|
+
{ expectedIdentity: second.identity, handle: second },
|
|
115
|
+
{ expectedIdentity: first.identity, handle: first },
|
|
116
|
+
],
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
expect(left.identity.contentDigest).not.toBe(right.identity.contentDigest);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("a composite policy cannot widen a child policy", async () => {
|
|
123
|
+
const child = await makeChild(childInput("child.a", "a"));
|
|
124
|
+
const input = {
|
|
125
|
+
corpusRef: "composite",
|
|
126
|
+
scopeRef: "scope.conformance",
|
|
127
|
+
policy: {
|
|
128
|
+
includeVisibilities: ["public", "private"] as const,
|
|
129
|
+
includeRedactionClasses: ["none"] as const,
|
|
130
|
+
},
|
|
131
|
+
children: [{ expectedIdentity: child.identity, handle: child }],
|
|
132
|
+
};
|
|
133
|
+
const projection = await Effect.runPromise(config.makeProjection({ ...input, policy }));
|
|
134
|
+
const error = await Effect.runPromise(
|
|
135
|
+
config.compose({ ...input, projection }).pipe(Effect.flip),
|
|
136
|
+
);
|
|
137
|
+
expect(error.reason).toBe("policy_widened");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("a composite cannot cross child manifest scopes", async () => {
|
|
141
|
+
const base = await makeChild(childInput("child.scope", "scope"));
|
|
142
|
+
const input = {
|
|
143
|
+
corpusRef: "composite.scope",
|
|
144
|
+
scopeRef: "scope.conformance",
|
|
145
|
+
policy,
|
|
146
|
+
children: [{ expectedIdentity: base.identity, handle: base }],
|
|
147
|
+
};
|
|
148
|
+
const projection = await Effect.runPromise(config.makeProjection(input));
|
|
149
|
+
const foreign: RlmCorpusHandle = {
|
|
150
|
+
...base,
|
|
151
|
+
manifest: { ...base.manifest, scopeRef: "foreign.scope" },
|
|
152
|
+
};
|
|
153
|
+
const error = await Effect.runPromise(
|
|
154
|
+
config
|
|
155
|
+
.compose({
|
|
156
|
+
...input,
|
|
157
|
+
children: [{ expectedIdentity: foreign.identity, handle: foreign }],
|
|
158
|
+
projection,
|
|
159
|
+
})
|
|
160
|
+
.pipe(Effect.flip),
|
|
161
|
+
);
|
|
162
|
+
expect(error.reason).toBe("invalid_inline");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("a stale child or projection fails bounded reads", async () => {
|
|
166
|
+
const base = await makeChild(childInput("child.stale", "stale"));
|
|
167
|
+
let childChanged = false;
|
|
168
|
+
const child: RlmCorpusHandle = {
|
|
169
|
+
...base,
|
|
170
|
+
assertUnchanged: () =>
|
|
171
|
+
childChanged ? Effect.fail(new RlmCorpusError({ reason: "changed" })) : Effect.void,
|
|
172
|
+
};
|
|
173
|
+
const input = {
|
|
174
|
+
corpusRef: "composite.stale",
|
|
175
|
+
scopeRef: "scope.conformance",
|
|
176
|
+
policy,
|
|
177
|
+
children: [{ expectedIdentity: child.identity, handle: child }],
|
|
178
|
+
};
|
|
179
|
+
const projection = await Effect.runPromise(config.makeProjection(input));
|
|
180
|
+
const composite = await Effect.runPromise(config.compose({ ...input, projection }));
|
|
181
|
+
childChanged = true;
|
|
182
|
+
const childError = await Effect.runPromise(
|
|
183
|
+
composite
|
|
184
|
+
.read({ start: 0, endInclusive: 0 }, { maxEntries: 1, maxCharsPerEntry: 20 })
|
|
185
|
+
.pipe(Effect.flip),
|
|
186
|
+
);
|
|
187
|
+
expect(childError.reason).toBe("changed");
|
|
188
|
+
|
|
189
|
+
childChanged = false;
|
|
190
|
+
let projectionChanged = false;
|
|
191
|
+
const wrapped: RlmCompositeProjection = {
|
|
192
|
+
...projection,
|
|
193
|
+
assertUnchanged: () =>
|
|
194
|
+
projectionChanged
|
|
195
|
+
? Effect.fail(new RlmCorpusError({ reason: "changed" }))
|
|
196
|
+
: projection.assertUnchanged(),
|
|
197
|
+
};
|
|
198
|
+
const second = await Effect.runPromise(config.compose({ ...input, projection: wrapped }));
|
|
199
|
+
projectionChanged = true;
|
|
200
|
+
const projectionError = await Effect.runPromise(
|
|
201
|
+
second
|
|
202
|
+
.read({ start: 0, endInclusive: 0 }, { maxEntries: 1, maxCharsPerEntry: 20 })
|
|
203
|
+
.pipe(Effect.flip),
|
|
204
|
+
);
|
|
205
|
+
expect(projectionError.reason).toBe("changed");
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("citations retain and validate the exact child locator", async () => {
|
|
209
|
+
const child = await makeChild(childInput("child.citation", "citation"));
|
|
210
|
+
const composite = await Effect.runPromise(
|
|
211
|
+
composeWithProjection(config, {
|
|
212
|
+
corpusRef: "composite.citation",
|
|
213
|
+
scopeRef: "scope.conformance",
|
|
214
|
+
policy,
|
|
215
|
+
children: [{ expectedIdentity: child.identity, handle: child }],
|
|
216
|
+
}),
|
|
217
|
+
);
|
|
218
|
+
const entry = (
|
|
219
|
+
await Effect.runPromise(
|
|
220
|
+
composite.read({ start: 0, endInclusive: 0 }, { maxEntries: 1, maxCharsPerEntry: 100 }),
|
|
221
|
+
)
|
|
222
|
+
)[0]!;
|
|
223
|
+
const citation = citationFromEntry(composite, entry);
|
|
224
|
+
expect(citation.sourceOrigin.contentDigest).toBe(child.identity.contentDigest);
|
|
225
|
+
const valid = await Effect.runPromise(validateCitations(composite, [citation]));
|
|
226
|
+
expect(valid.invalid).toEqual([]);
|
|
227
|
+
const changedLocators = [
|
|
228
|
+
{ ...citation.sourceOrigin, contentDigest: "changed-child-digest" },
|
|
229
|
+
{ ...citation.sourceOrigin, entryRef: "changed-entry-ref" },
|
|
230
|
+
{ ...citation.sourceOrigin, sourcePlane: "evidence_pack" as const },
|
|
231
|
+
{
|
|
232
|
+
...citation.sourceOrigin,
|
|
233
|
+
sourceAddress: { ...citation.sourceOrigin.sourceAddress, encodedAddress: "changed" },
|
|
234
|
+
},
|
|
235
|
+
];
|
|
236
|
+
for (const sourceOrigin of changedLocators) {
|
|
237
|
+
const invalid = await Effect.runPromise(
|
|
238
|
+
validateCitations(composite, [{ ...citation, sourceOrigin }]),
|
|
239
|
+
);
|
|
240
|
+
expect(invalid.invalid[0]?.reason).toBe("source_mismatch");
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("duplicate addresses and unsupported planes fail closed", async () => {
|
|
245
|
+
const first = await makeChild(childInput("child.dup.a", "same"));
|
|
246
|
+
const second = await makeChild(childInput("child.dup.b", "same"));
|
|
247
|
+
const duplicateInput = {
|
|
248
|
+
corpusRef: "composite.duplicate",
|
|
249
|
+
scopeRef: "scope.conformance",
|
|
250
|
+
policy,
|
|
251
|
+
children: [
|
|
252
|
+
{ expectedIdentity: first.identity, handle: first },
|
|
253
|
+
{ expectedIdentity: second.identity, handle: second },
|
|
254
|
+
],
|
|
255
|
+
};
|
|
256
|
+
const duplicateProjection = await Effect.runPromise(config.makeProjection(duplicateInput));
|
|
257
|
+
const duplicate = await Effect.runPromise(
|
|
258
|
+
config.compose({ ...duplicateInput, projection: duplicateProjection }).pipe(Effect.flip),
|
|
259
|
+
);
|
|
260
|
+
expect(duplicate.reason).toBe("duplicate_source");
|
|
261
|
+
|
|
262
|
+
const extended = await makeChild(
|
|
263
|
+
childInput("child.extension", "extension", {
|
|
264
|
+
sourcePlane: {
|
|
265
|
+
_tag: "Extension",
|
|
266
|
+
schemaId: "openagents.ai.rlm_source_plane_extension.v1",
|
|
267
|
+
registrySchemaId: "unadmitted.registry.v1",
|
|
268
|
+
plane: "unadmitted",
|
|
269
|
+
},
|
|
270
|
+
}),
|
|
271
|
+
);
|
|
272
|
+
const extensionInput = {
|
|
273
|
+
corpusRef: "composite.extension",
|
|
274
|
+
scopeRef: "scope.conformance",
|
|
275
|
+
policy,
|
|
276
|
+
children: [{ expectedIdentity: extended.identity, handle: extended }],
|
|
277
|
+
};
|
|
278
|
+
const extensionProjection = await Effect.runPromise(config.makeProjection(extensionInput));
|
|
279
|
+
const unsupported = await Effect.runPromise(
|
|
280
|
+
config.compose({ ...extensionInput, projection: extensionProjection }).pipe(Effect.flip),
|
|
281
|
+
);
|
|
282
|
+
expect(unsupported.reason).toBe("unsupported_plane");
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test("coverage and exclusion changes change the manifest digest", async () => {
|
|
286
|
+
const child = await makeChild(childInput("child.coverage", "coverage"));
|
|
287
|
+
const input = {
|
|
288
|
+
corpusRef: "composite.coverage",
|
|
289
|
+
scopeRef: "scope.conformance",
|
|
290
|
+
policy,
|
|
291
|
+
children: [{ expectedIdentity: child.identity, handle: child }],
|
|
292
|
+
};
|
|
293
|
+
const projection = await Effect.runPromise(config.makeProjection(input));
|
|
294
|
+
const left = await Effect.runPromise(config.compose({ ...input, projection }));
|
|
295
|
+
const { projectionDigest: _digest, ...summaryWithoutDigest } = projection.summary;
|
|
296
|
+
const changedSummary = {
|
|
297
|
+
...summaryWithoutDigest,
|
|
298
|
+
coverage: {
|
|
299
|
+
...summaryWithoutDigest.coverage,
|
|
300
|
+
exclusions: [{ reason: "fixture_exclusion", count: 1 }],
|
|
301
|
+
},
|
|
302
|
+
exclusions: [
|
|
303
|
+
{
|
|
304
|
+
childCorpusRef: child.identity.corpusRef,
|
|
305
|
+
reason: "fixture_exclusion",
|
|
306
|
+
count: 1,
|
|
307
|
+
},
|
|
308
|
+
],
|
|
309
|
+
};
|
|
310
|
+
const changedProjection: RlmCompositeProjection = {
|
|
311
|
+
...projection,
|
|
312
|
+
summary: {
|
|
313
|
+
projectionDigest: computeCompositeProjectionDigest(changedSummary),
|
|
314
|
+
...changedSummary,
|
|
315
|
+
},
|
|
316
|
+
assertUnchanged: () => Effect.void,
|
|
317
|
+
};
|
|
318
|
+
const right = await Effect.runPromise(
|
|
319
|
+
config.compose({ ...input, projection: changedProjection }),
|
|
320
|
+
);
|
|
321
|
+
expect(left.identity.contentDigest).toBe(right.identity.contentDigest);
|
|
322
|
+
expect(left.identity.manifestDigest).not.toBe(right.identity.manifestDigest);
|
|
323
|
+
|
|
324
|
+
const changedRootSummary = {
|
|
325
|
+
...summaryWithoutDigest,
|
|
326
|
+
pointerIndexDigest: sha256Hex("changed conformance pointer root"),
|
|
327
|
+
};
|
|
328
|
+
const changedRoot: RlmCompositeProjection = {
|
|
329
|
+
...projection,
|
|
330
|
+
summary: {
|
|
331
|
+
projectionDigest: computeCompositeProjectionDigest(changedRootSummary),
|
|
332
|
+
...changedRootSummary,
|
|
333
|
+
},
|
|
334
|
+
assertUnchanged: () => Effect.void,
|
|
335
|
+
};
|
|
336
|
+
const rooted = await Effect.runPromise(config.compose({ ...input, projection: changedRoot }));
|
|
337
|
+
expect(rooted.identity.contentDigest).toBe(left.identity.contentDigest);
|
|
338
|
+
expect(rooted.identity.manifestDigest).not.toBe(left.identity.manifestDigest);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test("incomplete ranges and pointer-child mismatches fail changed", async () => {
|
|
342
|
+
const child = await makeChild(childInput("child.pointer-law", "pointer-law"));
|
|
343
|
+
const input = {
|
|
344
|
+
corpusRef: "composite.pointer-law",
|
|
345
|
+
scopeRef: "scope.conformance",
|
|
346
|
+
policy,
|
|
347
|
+
children: [{ expectedIdentity: child.identity, handle: child }],
|
|
348
|
+
};
|
|
349
|
+
const projection = await Effect.runPromise(config.makeProjection(input));
|
|
350
|
+
const incomplete: RlmCompositeProjection = {
|
|
351
|
+
...projection,
|
|
352
|
+
readPointers: () => Effect.succeed([]),
|
|
353
|
+
scanPointers: () => Stream.empty,
|
|
354
|
+
};
|
|
355
|
+
const incompleteComposite = await Effect.runPromise(
|
|
356
|
+
config.compose({ ...input, projection: incomplete }),
|
|
357
|
+
);
|
|
358
|
+
const readError = await Effect.runPromise(
|
|
359
|
+
incompleteComposite
|
|
360
|
+
.read({ start: 0, endInclusive: 0 }, { maxEntries: 1, maxCharsPerEntry: 20 })
|
|
361
|
+
.pipe(Effect.flip),
|
|
362
|
+
);
|
|
363
|
+
expect(readError.reason).toBe("changed");
|
|
364
|
+
const scanError = await Effect.runPromise(
|
|
365
|
+
incompleteComposite.scan({ maxEntries: 1 }).pipe(Stream.runCollect, Effect.flip),
|
|
366
|
+
);
|
|
367
|
+
expect(scanError.reason).toBe("changed");
|
|
368
|
+
|
|
369
|
+
const pointer = (
|
|
370
|
+
await Effect.runPromise(
|
|
371
|
+
projection.readPointers({ start: 0, endInclusive: 0 }, { maxEntries: 1 }),
|
|
372
|
+
)
|
|
373
|
+
)[0]!;
|
|
374
|
+
const mismatched: RlmCompositeProjection = {
|
|
375
|
+
...projection,
|
|
376
|
+
readPointers: () =>
|
|
377
|
+
Effect.succeed([{ ...pointer, childEntryRef: "different-child-entry" }]),
|
|
378
|
+
};
|
|
379
|
+
const mismatchComposite = await Effect.runPromise(
|
|
380
|
+
config.compose({ ...input, projection: mismatched }),
|
|
381
|
+
);
|
|
382
|
+
const mismatchError = await Effect.runPromise(
|
|
383
|
+
mismatchComposite
|
|
384
|
+
.read({ start: 0, endInclusive: 0 }, { maxEntries: 1, maxCharsPerEntry: 20 })
|
|
385
|
+
.pipe(Effect.flip),
|
|
386
|
+
);
|
|
387
|
+
expect(mismatchError.reason).toBe("changed");
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
test("constructor and one bounded read do not enumerate a large child", async () => {
|
|
391
|
+
const base = await makeChild(childInput("child.large", "0"));
|
|
392
|
+
let reads = 0;
|
|
393
|
+
let scans = 0;
|
|
394
|
+
let materializes = 0;
|
|
395
|
+
let projectionReads = 0;
|
|
396
|
+
const entry = (ordinal: number): RlmCorpusEntry => ({
|
|
397
|
+
ordinal,
|
|
398
|
+
entryRef: `entry.${ordinal}`,
|
|
399
|
+
scopeRef: "scope.conformance",
|
|
400
|
+
sourcePlane: "repository",
|
|
401
|
+
sourceKind: "fixture",
|
|
402
|
+
sourceAddress: {
|
|
403
|
+
addressSchemaId: "conformance.large.v1",
|
|
404
|
+
encodedAddress: String(ordinal),
|
|
405
|
+
},
|
|
406
|
+
text: `entry ${ordinal}`,
|
|
407
|
+
visibility: "public",
|
|
408
|
+
redactionClass: "none",
|
|
409
|
+
});
|
|
410
|
+
const child: RlmCorpusHandle = {
|
|
411
|
+
...base,
|
|
412
|
+
manifest: {
|
|
413
|
+
...base.manifest,
|
|
414
|
+
coverage: { ...base.manifest.coverage, entryCount: 1_000_000 },
|
|
415
|
+
},
|
|
416
|
+
read: (range) =>
|
|
417
|
+
Effect.sync(() => {
|
|
418
|
+
reads += 1;
|
|
419
|
+
return [entry(range.start)];
|
|
420
|
+
}),
|
|
421
|
+
scan: () => {
|
|
422
|
+
scans += 1;
|
|
423
|
+
return Stream.empty;
|
|
424
|
+
},
|
|
425
|
+
materializeAll: () => {
|
|
426
|
+
materializes += 1;
|
|
427
|
+
return Effect.succeed([]);
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
const pointer = {
|
|
431
|
+
globalOrdinal: 0,
|
|
432
|
+
compositeEntryRef: "composite.0.entry.0",
|
|
433
|
+
childIndex: 0,
|
|
434
|
+
childOrdinal: 0,
|
|
435
|
+
childEntryRef: "entry.0",
|
|
436
|
+
sourcePlane: "repository" as const,
|
|
437
|
+
sourceAddress: entry(0).sourceAddress,
|
|
438
|
+
sourceOrigin: {
|
|
439
|
+
sourcePlane: "repository" as const,
|
|
440
|
+
sourceKind: "fixture",
|
|
441
|
+
sourceAddress: entry(0).sourceAddress,
|
|
442
|
+
corpusRef: child.identity.corpusRef,
|
|
443
|
+
contentDigest: child.identity.contentDigest,
|
|
444
|
+
entryRef: "entry.0",
|
|
445
|
+
},
|
|
446
|
+
};
|
|
447
|
+
const summaryWithoutDigest = {
|
|
448
|
+
scopeRef: "scope.conformance",
|
|
449
|
+
orderedChildren: [
|
|
450
|
+
{
|
|
451
|
+
childIndex: 0,
|
|
452
|
+
corpusRef: child.identity.corpusRef,
|
|
453
|
+
contentDigest: child.identity.contentDigest,
|
|
454
|
+
manifestDigest: child.identity.manifestDigest,
|
|
455
|
+
},
|
|
456
|
+
],
|
|
457
|
+
policy,
|
|
458
|
+
orderingRule: "composite_child_then_ordinal" as const,
|
|
459
|
+
coverage: {
|
|
460
|
+
note: "large projection",
|
|
461
|
+
entryCount: 1_000_000,
|
|
462
|
+
encodedBytes: 1_000_000,
|
|
463
|
+
exclusions: [],
|
|
464
|
+
},
|
|
465
|
+
exclusions: [],
|
|
466
|
+
sourcePlanes: ["repository" as const],
|
|
467
|
+
duplicateSourceCount: 0,
|
|
468
|
+
pointerIndexDigest: sha256Hex(
|
|
469
|
+
canonicalJson({ generator: "conformance-single-pointer-v1", size: 1_000_000 }),
|
|
470
|
+
),
|
|
471
|
+
};
|
|
472
|
+
const projection: RlmCompositeProjection = {
|
|
473
|
+
summary: {
|
|
474
|
+
projectionDigest: computeCompositeProjectionDigest(summaryWithoutDigest),
|
|
475
|
+
...summaryWithoutDigest,
|
|
476
|
+
},
|
|
477
|
+
assertUnchanged: () => Effect.void,
|
|
478
|
+
readPointers: () =>
|
|
479
|
+
Effect.sync(() => {
|
|
480
|
+
projectionReads += 1;
|
|
481
|
+
return [pointer];
|
|
482
|
+
}),
|
|
483
|
+
scanPointers: () => Stream.make(pointer),
|
|
484
|
+
lookupAddress: () => Effect.succeed({ _tag: "Unique", pointer }),
|
|
485
|
+
};
|
|
486
|
+
const composite = await Effect.runPromise(
|
|
487
|
+
config.compose({
|
|
488
|
+
corpusRef: "composite.large",
|
|
489
|
+
scopeRef: "scope.conformance",
|
|
490
|
+
policy,
|
|
491
|
+
children: [{ expectedIdentity: child.identity, handle: child }],
|
|
492
|
+
projection,
|
|
493
|
+
}),
|
|
494
|
+
);
|
|
495
|
+
expect({ reads, scans, materializes, projectionReads }).toEqual({
|
|
496
|
+
reads: 0,
|
|
497
|
+
scans: 0,
|
|
498
|
+
materializes: 0,
|
|
499
|
+
projectionReads: 0,
|
|
500
|
+
});
|
|
501
|
+
await Effect.runPromise(
|
|
502
|
+
composite.read({ start: 0, endInclusive: 0 }, { maxEntries: 1, maxCharsPerEntry: 20 }),
|
|
503
|
+
);
|
|
504
|
+
expect({ reads, scans, materializes, projectionReads }).toEqual({
|
|
505
|
+
reads: 1,
|
|
506
|
+
scans: 0,
|
|
507
|
+
materializes: 0,
|
|
508
|
+
projectionReads: 1,
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,10 @@ export { runEventLogLaws, type EventLogLawsConfig } from "./event-log-laws.ts";
|
|
|
13
13
|
export { runReducerLaws, type ReducerLawsConfig } from "./reducer-laws.ts";
|
|
14
14
|
export { runRecallLaws, type RecallLawsConfig, type RecallSource } from "./recall-laws.ts";
|
|
15
15
|
export { runRlmCapLaws, type RlmCapLawsConfig } from "./rlm-cap-laws.ts";
|
|
16
|
+
export {
|
|
17
|
+
runCorpusCompositionLaws,
|
|
18
|
+
type CorpusCompositionLawsConfig,
|
|
19
|
+
} from "./corpus-composition-laws.ts";
|
|
16
20
|
export {
|
|
17
21
|
assertContiguous,
|
|
18
22
|
attempt,
|