@openagentsinc/conformance-kit 0.2.0-rc.7 → 0.2.1-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -8
- package/package.json +20 -6
- package/src/dse-extraction-laws.ts +273 -0
- package/src/graph-archive-laws.ts +267 -0
- package/src/graph-capability-delete-laws.ts +289 -0
- package/src/graph-fixtures.ts +40 -0
- package/src/graph-identity-laws.ts +267 -0
- package/src/graph-ranking-laws.ts +362 -0
- package/src/graph-rlm-laws.ts +243 -0
- package/src/index.ts +16 -0
- package/src/adapter-laws.test.ts +0 -36
- package/src/corpus-composition-laws.test.ts +0 -9
- package/src/event-log-laws.test.ts +0 -10
- package/src/recall-laws.test.ts +0 -10
- package/src/reducer-laws.test.ts +0 -17
- package/src/rlm-cap-laws.test.ts +0 -11
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { Effect, Schema as S } from "effect";
|
|
2
|
+
import { buildInlineCorpusInput, makeInlineCorpusHandle } from "@openagentsinc/rlm";
|
|
3
|
+
import {
|
|
4
|
+
GraphDeleteRef,
|
|
5
|
+
type buildGraphCorpus,
|
|
6
|
+
type makeCanonicalEntity,
|
|
7
|
+
type makeGraphAdapterCapabilities,
|
|
8
|
+
type makeGraphMention,
|
|
9
|
+
type makeEmbeddingProjectionDescriptor,
|
|
10
|
+
type makeGraphRlmClassificationProjection,
|
|
11
|
+
type makeGraphRlmProjection,
|
|
12
|
+
type makeInMemoryGraphSnapshotHandle,
|
|
13
|
+
} from "@openagentsinc/graph-corpus";
|
|
14
|
+
import {
|
|
15
|
+
GRAPH_RANKING_OPERATION_BINDING_SCHEMA_ID,
|
|
16
|
+
GraphRankingOperationBinding,
|
|
17
|
+
type GraphRankingConfidence,
|
|
18
|
+
type makeGraphRankingSnapshot,
|
|
19
|
+
type makeGraphFeedbackObservation,
|
|
20
|
+
type rankGraphOperationResult,
|
|
21
|
+
type validateGraphUsedElementEvidence,
|
|
22
|
+
type validateGraphRankingSnapshot,
|
|
23
|
+
} from "@openagentsinc/graph-corpus/ranking";
|
|
24
|
+
import { describe, expect, test } from "vite-plus/test";
|
|
25
|
+
|
|
26
|
+
import { graphConformanceDerivation, graphConformancePolicy } from "./graph-fixtures.ts";
|
|
27
|
+
|
|
28
|
+
export interface GraphRankingLawsConfig {
|
|
29
|
+
readonly label: string;
|
|
30
|
+
readonly buildGraphCorpus: typeof buildGraphCorpus;
|
|
31
|
+
readonly makeGraphMention: typeof makeGraphMention;
|
|
32
|
+
readonly makeCanonicalEntity: typeof makeCanonicalEntity;
|
|
33
|
+
readonly makeEmbeddingProjectionDescriptor: typeof makeEmbeddingProjectionDescriptor;
|
|
34
|
+
readonly makeInMemoryGraphSnapshotHandle: typeof makeInMemoryGraphSnapshotHandle;
|
|
35
|
+
readonly makeGraphRlmClassificationProjection: typeof makeGraphRlmClassificationProjection;
|
|
36
|
+
readonly makeGraphAdapterCapabilities: typeof makeGraphAdapterCapabilities;
|
|
37
|
+
readonly makeGraphRlmProjection: typeof makeGraphRlmProjection;
|
|
38
|
+
readonly makeGraphRankingSnapshot: typeof makeGraphRankingSnapshot;
|
|
39
|
+
readonly makeGraphFeedbackObservation: typeof makeGraphFeedbackObservation;
|
|
40
|
+
readonly validateGraphRankingSnapshot: typeof validateGraphRankingSnapshot;
|
|
41
|
+
readonly rankGraphOperationResult: typeof rankGraphOperationResult;
|
|
42
|
+
readonly validateGraphUsedElementEvidence: typeof validateGraphUsedElementEvidence;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Laws for deterministic ranking that cannot mutate graph identity. */
|
|
46
|
+
export const runGraphRankingLaws = (implementation: GraphRankingLawsConfig): void => {
|
|
47
|
+
const fixture = async () => {
|
|
48
|
+
const original = await Effect.runPromise(
|
|
49
|
+
makeInlineCorpusHandle(
|
|
50
|
+
buildInlineCorpusInput({
|
|
51
|
+
corpusRef: "corpus.ranking.conformance",
|
|
52
|
+
scopeRef: "tenant.a",
|
|
53
|
+
policy: graphConformancePolicy,
|
|
54
|
+
entries: ["a", "b"].map((entryRef) => ({
|
|
55
|
+
entryRef,
|
|
56
|
+
scopeRef: "tenant.a",
|
|
57
|
+
sourcePlane: "repository" as const,
|
|
58
|
+
sourceKind: "conformance_fixture",
|
|
59
|
+
sourceAddress: {
|
|
60
|
+
addressSchemaId: "conformance.path.v1",
|
|
61
|
+
encodedAddress: `fixture:${entryRef}`,
|
|
62
|
+
},
|
|
63
|
+
text: `source ${entryRef}`,
|
|
64
|
+
visibility: "private" as const,
|
|
65
|
+
redactionClass: "none" as const,
|
|
66
|
+
})),
|
|
67
|
+
}),
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
const sources = await Promise.all(
|
|
71
|
+
["a", "b"].map((entryRef) =>
|
|
72
|
+
Effect.runPromise(
|
|
73
|
+
original
|
|
74
|
+
.validateSourceAddress({
|
|
75
|
+
addressSchemaId: "conformance.path.v1",
|
|
76
|
+
encodedAddress: `fixture:${entryRef}`,
|
|
77
|
+
})
|
|
78
|
+
.pipe(Effect.map(({ origin }) => origin)),
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
);
|
|
82
|
+
const mentions = sources.map((source, index) =>
|
|
83
|
+
implementation.makeGraphMention({
|
|
84
|
+
identityNamespace: "people",
|
|
85
|
+
canonicalKey: `alex:${index}`,
|
|
86
|
+
identityScopeRef: "tenant.a",
|
|
87
|
+
source,
|
|
88
|
+
derivation: graphConformanceDerivation,
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
91
|
+
const entity = implementation.makeCanonicalEntity({
|
|
92
|
+
identityNamespace: "people",
|
|
93
|
+
canonicalKey: "alex",
|
|
94
|
+
identityScopeRef: "tenant.a",
|
|
95
|
+
mentions,
|
|
96
|
+
derivation: graphConformanceDerivation,
|
|
97
|
+
});
|
|
98
|
+
const descriptor = implementation.makeEmbeddingProjectionDescriptor({
|
|
99
|
+
projectionSchemaId: "graph.embedding.conformance.v1",
|
|
100
|
+
elementKinds: ["mention", "entity"],
|
|
101
|
+
embeddableFields: ["identity.canonicalKey"],
|
|
102
|
+
dimensions: 2,
|
|
103
|
+
});
|
|
104
|
+
const built = await Effect.runPromise(
|
|
105
|
+
implementation.buildGraphCorpus({
|
|
106
|
+
graphRef: "graph.ranking.conformance",
|
|
107
|
+
scopeRef: "tenant.a",
|
|
108
|
+
policy: graphConformancePolicy,
|
|
109
|
+
mentions,
|
|
110
|
+
entities: [entity],
|
|
111
|
+
relations: [],
|
|
112
|
+
embeddingProjections: [descriptor],
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
const handle = await Effect.runPromise(implementation.makeInMemoryGraphSnapshotHandle(built));
|
|
116
|
+
const classification = implementation.makeGraphRlmClassificationProjection(
|
|
117
|
+
handle,
|
|
118
|
+
[...mentions, entity].map(({ elementRef }) => ({
|
|
119
|
+
elementRef,
|
|
120
|
+
visibility: "private" as const,
|
|
121
|
+
redactionClass: "none" as const,
|
|
122
|
+
})),
|
|
123
|
+
[original],
|
|
124
|
+
);
|
|
125
|
+
const projection = await Effect.runPromise(
|
|
126
|
+
implementation.makeGraphRlmProjection({
|
|
127
|
+
handle,
|
|
128
|
+
capabilities: implementation.makeGraphAdapterCapabilities([
|
|
129
|
+
"graph_read",
|
|
130
|
+
"rlm_v2_projection",
|
|
131
|
+
]),
|
|
132
|
+
classification,
|
|
133
|
+
corpusRef: "rlm.ranking.conformance",
|
|
134
|
+
supportingCorpora: [original],
|
|
135
|
+
}),
|
|
136
|
+
);
|
|
137
|
+
const limits = {
|
|
138
|
+
maxDepth: 2,
|
|
139
|
+
maxVisitedElements: 10,
|
|
140
|
+
maxReturnedElements: 10,
|
|
141
|
+
maxSourceAddresses: 10,
|
|
142
|
+
maxCharactersPerResult: 2_048,
|
|
143
|
+
maxObservationCharacters: 10_000,
|
|
144
|
+
};
|
|
145
|
+
const result = await Effect.runPromise(
|
|
146
|
+
projection.operators.neighbors(entity.elementRef, limits),
|
|
147
|
+
);
|
|
148
|
+
if (result._tag !== "Complete")
|
|
149
|
+
throw new Error("The ranking conformance fixture must complete.");
|
|
150
|
+
const binding = S.decodeUnknownSync(GraphRankingOperationBinding)({
|
|
151
|
+
schemaId: GRAPH_RANKING_OPERATION_BINDING_SCHEMA_ID,
|
|
152
|
+
_tag: "Neighbors",
|
|
153
|
+
elementRef: entity.elementRef,
|
|
154
|
+
});
|
|
155
|
+
return { built, projection, result, binding, mentions, entity, descriptor, limits };
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const confidence = (
|
|
159
|
+
value: Awaited<ReturnType<typeof fixture>>,
|
|
160
|
+
elementRef: GraphRankingConfidence["elementRef"],
|
|
161
|
+
confidenceMicros: number,
|
|
162
|
+
): GraphRankingConfidence => ({
|
|
163
|
+
graphRef: value.built.snapshot.graphRef,
|
|
164
|
+
scopeRef: value.built.snapshot.scopeRef,
|
|
165
|
+
graphDigest: value.result.graphDigest,
|
|
166
|
+
manifestDigest: value.result.manifestDigest,
|
|
167
|
+
corpusRef: value.result.corpusRef,
|
|
168
|
+
contentDigest: value.result.contentDigest,
|
|
169
|
+
corpusManifestDigest: value.result.corpusManifestDigest,
|
|
170
|
+
classificationDigest: value.result.classificationDigest,
|
|
171
|
+
elementRef,
|
|
172
|
+
confidenceMicros,
|
|
173
|
+
evidenceRef: S.decodeUnknownSync(GraphDeleteRef)(`evidence.${elementRef}`),
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe(`[${implementation.label}] graph ranking`, () => {
|
|
177
|
+
test("equal graph and ranking inputs produce deterministic order and evidence", async () => {
|
|
178
|
+
const value = await fixture();
|
|
179
|
+
const context = {
|
|
180
|
+
built: value.built,
|
|
181
|
+
projection: value.projection,
|
|
182
|
+
result: value.result,
|
|
183
|
+
expectedOperationDigest: value.result.operationDigest,
|
|
184
|
+
binding: value.binding,
|
|
185
|
+
};
|
|
186
|
+
const inputs = {
|
|
187
|
+
...context,
|
|
188
|
+
confidences: [
|
|
189
|
+
confidence(value, value.mentions[0]!.elementRef, 900_000),
|
|
190
|
+
confidence(value, value.mentions[1]!.elementRef, 100_000),
|
|
191
|
+
],
|
|
192
|
+
};
|
|
193
|
+
const left = await Effect.runPromise(implementation.makeGraphRankingSnapshot(inputs));
|
|
194
|
+
const right = await Effect.runPromise(implementation.makeGraphRankingSnapshot(inputs));
|
|
195
|
+
expect(right).toEqual(left);
|
|
196
|
+
const ranked = await Effect.runPromise(
|
|
197
|
+
implementation.rankGraphOperationResult({ ...context, snapshot: left }),
|
|
198
|
+
);
|
|
199
|
+
expect(ranked._tag).toBe("Ranked");
|
|
200
|
+
expect(ranked.evidence.rankingSnapshotDigest).toBe(left.snapshotDigest);
|
|
201
|
+
expect(ranked.observations[0]?.elementRef).toBe(value.mentions[0]?.elementRef);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("ranking-only changes leave graph and manifest bytes unchanged", async () => {
|
|
205
|
+
const value = await fixture();
|
|
206
|
+
const graphBefore = structuredClone(value.built);
|
|
207
|
+
const resultBefore = structuredClone(value.result);
|
|
208
|
+
const context = {
|
|
209
|
+
built: value.built,
|
|
210
|
+
projection: value.projection,
|
|
211
|
+
result: value.result,
|
|
212
|
+
expectedOperationDigest: value.result.operationDigest,
|
|
213
|
+
binding: value.binding,
|
|
214
|
+
};
|
|
215
|
+
const highA = await Effect.runPromise(
|
|
216
|
+
implementation.makeGraphRankingSnapshot({
|
|
217
|
+
...context,
|
|
218
|
+
confidences: [confidence(value, value.mentions[0]!.elementRef, 900_000)],
|
|
219
|
+
}),
|
|
220
|
+
);
|
|
221
|
+
const highB = await Effect.runPromise(
|
|
222
|
+
implementation.makeGraphRankingSnapshot({
|
|
223
|
+
...context,
|
|
224
|
+
confidences: [confidence(value, value.mentions[1]!.elementRef, 900_000)],
|
|
225
|
+
}),
|
|
226
|
+
);
|
|
227
|
+
expect(highB.snapshotDigest).not.toBe(highA.snapshotDigest);
|
|
228
|
+
const rankedA = await Effect.runPromise(
|
|
229
|
+
implementation.rankGraphOperationResult({ ...context, snapshot: highA }),
|
|
230
|
+
);
|
|
231
|
+
const rankedB = await Effect.runPromise(
|
|
232
|
+
implementation.rankGraphOperationResult({ ...context, snapshot: highB }),
|
|
233
|
+
);
|
|
234
|
+
expect(rankedA.observations.map(({ elementRef }) => elementRef)).not.toEqual(
|
|
235
|
+
rankedB.observations.map(({ elementRef }) => elementRef),
|
|
236
|
+
);
|
|
237
|
+
expect(value.built).toEqual(graphBefore);
|
|
238
|
+
expect(value.built.snapshot.graphDigest).toBe(graphBefore.snapshot.graphDigest);
|
|
239
|
+
expect(value.built.manifest.manifestDigest).toBe(graphBefore.manifest.manifestDigest);
|
|
240
|
+
expect(value.built.snapshot.embeddingProjections).toEqual([value.descriptor]);
|
|
241
|
+
expect(value.result).toEqual(resultBefore);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("disabled and truncated ranking is explicit and never drops observations", async () => {
|
|
245
|
+
const value = await fixture();
|
|
246
|
+
const context = {
|
|
247
|
+
built: value.built,
|
|
248
|
+
projection: value.projection,
|
|
249
|
+
result: value.result,
|
|
250
|
+
expectedOperationDigest: value.result.operationDigest,
|
|
251
|
+
binding: value.binding,
|
|
252
|
+
};
|
|
253
|
+
const unranked = await Effect.runPromise(implementation.rankGraphOperationResult(context));
|
|
254
|
+
expect(unranked).toMatchObject({ _tag: "Unranked", disabledReason: "ranking_disabled" });
|
|
255
|
+
expect(unranked.observations).toEqual(value.result.observations);
|
|
256
|
+
const truncated = await Effect.runPromise(
|
|
257
|
+
value.projection.operators.neighbors(value.entity.elementRef, {
|
|
258
|
+
...value.limits,
|
|
259
|
+
maxReturnedElements: 1,
|
|
260
|
+
}),
|
|
261
|
+
);
|
|
262
|
+
expect(truncated._tag).toBe("Truncated");
|
|
263
|
+
const truncatedOutcome = await Effect.runPromise(
|
|
264
|
+
implementation.rankGraphOperationResult({
|
|
265
|
+
...context,
|
|
266
|
+
result: truncated,
|
|
267
|
+
expectedOperationDigest: truncated.operationDigest,
|
|
268
|
+
}),
|
|
269
|
+
);
|
|
270
|
+
expect(truncatedOutcome).toMatchObject({
|
|
271
|
+
_tag: "Unranked",
|
|
272
|
+
disabledReason: "operation_truncated",
|
|
273
|
+
});
|
|
274
|
+
expect(truncatedOutcome.observations).toEqual(truncated.observations);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test("missing features degrade without dropping data and evidence substitution fails", async () => {
|
|
278
|
+
const value = await fixture();
|
|
279
|
+
const context = {
|
|
280
|
+
built: value.built,
|
|
281
|
+
projection: value.projection,
|
|
282
|
+
result: value.result,
|
|
283
|
+
expectedOperationDigest: value.result.operationDigest,
|
|
284
|
+
binding: value.binding,
|
|
285
|
+
};
|
|
286
|
+
const snapshot = await Effect.runPromise(
|
|
287
|
+
implementation.makeGraphRankingSnapshot({
|
|
288
|
+
...context,
|
|
289
|
+
confidences: [confidence(value, value.mentions[0]!.elementRef, 900_000)],
|
|
290
|
+
}),
|
|
291
|
+
);
|
|
292
|
+
const outcome = await Effect.runPromise(
|
|
293
|
+
implementation.rankGraphOperationResult({ ...context, snapshot }),
|
|
294
|
+
);
|
|
295
|
+
expect(outcome.observations).toHaveLength(value.result.observations.length);
|
|
296
|
+
expect(outcome.missingFeatureElementRefs.length).toBeGreaterThan(0);
|
|
297
|
+
const changed = {
|
|
298
|
+
...outcome.evidence,
|
|
299
|
+
evidenceDigest: "f".repeat(64),
|
|
300
|
+
} as typeof outcome.evidence;
|
|
301
|
+
const error = await Effect.runPromise(
|
|
302
|
+
implementation
|
|
303
|
+
.validateGraphUsedElementEvidence(changed, { ...context, snapshot })
|
|
304
|
+
.pipe(Effect.flip),
|
|
305
|
+
);
|
|
306
|
+
expect(error).toMatchObject({
|
|
307
|
+
_tag: "GraphCorpus.RankingError",
|
|
308
|
+
reason: "invalid_evidence",
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test("feedback for an unknown element is rejected exactly", async () => {
|
|
313
|
+
const value = await fixture();
|
|
314
|
+
const error = await Effect.runPromise(
|
|
315
|
+
implementation
|
|
316
|
+
.makeGraphFeedbackObservation({
|
|
317
|
+
built: value.built,
|
|
318
|
+
projection: value.projection,
|
|
319
|
+
result: value.result,
|
|
320
|
+
expectedOperationDigest: value.result.operationDigest,
|
|
321
|
+
binding: value.binding,
|
|
322
|
+
elementRef: "graph.element.unknown",
|
|
323
|
+
feedbackWeightMicros: 1,
|
|
324
|
+
evidenceRef: "evidence.unknown",
|
|
325
|
+
})
|
|
326
|
+
.pipe(Effect.flip),
|
|
327
|
+
);
|
|
328
|
+
expect(error).toMatchObject({
|
|
329
|
+
_tag: "GraphCorpus.RankingError",
|
|
330
|
+
reason: "unknown_element",
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test("a snapshot bound to another graph fails with stale_graph", async () => {
|
|
335
|
+
const value = await fixture();
|
|
336
|
+
const context = {
|
|
337
|
+
built: value.built,
|
|
338
|
+
projection: value.projection,
|
|
339
|
+
result: value.result,
|
|
340
|
+
expectedOperationDigest: value.result.operationDigest,
|
|
341
|
+
binding: value.binding,
|
|
342
|
+
};
|
|
343
|
+
const snapshot = await Effect.runPromise(implementation.makeGraphRankingSnapshot(context));
|
|
344
|
+
const changed = await Effect.runPromise(
|
|
345
|
+
implementation.buildGraphCorpus({
|
|
346
|
+
graphRef: "graph.ranking.other",
|
|
347
|
+
scopeRef: value.built.snapshot.scopeRef,
|
|
348
|
+
policy: value.built.snapshot.policy,
|
|
349
|
+
mentions: value.built.snapshot.mentions,
|
|
350
|
+
entities: value.built.snapshot.entities,
|
|
351
|
+
relations: value.built.snapshot.relations,
|
|
352
|
+
}),
|
|
353
|
+
);
|
|
354
|
+
const error = await Effect.runPromise(
|
|
355
|
+
implementation
|
|
356
|
+
.validateGraphRankingSnapshot(snapshot, { ...context, built: changed })
|
|
357
|
+
.pipe(Effect.flip),
|
|
358
|
+
);
|
|
359
|
+
expect(error.reason).toBe("stale_graph");
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
};
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import {
|
|
3
|
+
buildInlineCorpusInput,
|
|
4
|
+
citationFromEntry,
|
|
5
|
+
makeInlineCorpusHandle,
|
|
6
|
+
validateCitations,
|
|
7
|
+
RlmCorpusError,
|
|
8
|
+
} from "@openagentsinc/rlm";
|
|
9
|
+
import type {
|
|
10
|
+
buildGraphCorpus,
|
|
11
|
+
makeGraphAdapterCapabilities,
|
|
12
|
+
makeGraphMention,
|
|
13
|
+
makeGraphRlmClassificationProjection,
|
|
14
|
+
makeGraphRlmProjection,
|
|
15
|
+
makeInMemoryGraphSnapshotHandle,
|
|
16
|
+
} from "@openagentsinc/graph-corpus";
|
|
17
|
+
import { describe, expect, test } from "vite-plus/test";
|
|
18
|
+
|
|
19
|
+
import { graphConformanceDerivation, graphConformancePolicy } from "./graph-fixtures.ts";
|
|
20
|
+
|
|
21
|
+
export interface GraphRlmLawsConfig {
|
|
22
|
+
readonly label: string;
|
|
23
|
+
readonly buildGraphCorpus: typeof buildGraphCorpus;
|
|
24
|
+
readonly makeGraphMention: typeof makeGraphMention;
|
|
25
|
+
readonly makeInMemoryGraphSnapshotHandle: typeof makeInMemoryGraphSnapshotHandle;
|
|
26
|
+
readonly makeGraphRlmClassificationProjection: typeof makeGraphRlmClassificationProjection;
|
|
27
|
+
readonly makeGraphAdapterCapabilities: typeof makeGraphAdapterCapabilities;
|
|
28
|
+
readonly makeGraphRlmProjection: typeof makeGraphRlmProjection;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const limits = {
|
|
32
|
+
maxDepth: 2,
|
|
33
|
+
maxVisitedElements: 10,
|
|
34
|
+
maxReturnedElements: 10,
|
|
35
|
+
maxSourceAddresses: 10,
|
|
36
|
+
maxCharactersPerResult: 2_048,
|
|
37
|
+
maxObservationCharacters: 10_000,
|
|
38
|
+
} as const;
|
|
39
|
+
|
|
40
|
+
/** Laws for graph-to-RLM projection, exact citations, freshness, and bounds. */
|
|
41
|
+
export const runGraphRlmLaws = (implementation: GraphRlmLawsConfig): void => {
|
|
42
|
+
const fixture = async () => {
|
|
43
|
+
const original = await Effect.runPromise(
|
|
44
|
+
makeInlineCorpusHandle(
|
|
45
|
+
buildInlineCorpusInput({
|
|
46
|
+
corpusRef: "corpus.graph-rlm.conformance",
|
|
47
|
+
scopeRef: "tenant.a",
|
|
48
|
+
policy: graphConformancePolicy,
|
|
49
|
+
entries: ["a", "b"].map((entryRef) => ({
|
|
50
|
+
entryRef,
|
|
51
|
+
scopeRef: "tenant.a",
|
|
52
|
+
sourcePlane: "repository",
|
|
53
|
+
sourceKind: "conformance_fixture",
|
|
54
|
+
sourceAddress: {
|
|
55
|
+
addressSchemaId: "conformance.path.v1",
|
|
56
|
+
encodedAddress: `fixture:${entryRef}`,
|
|
57
|
+
},
|
|
58
|
+
text: `Alex source ${entryRef}.`,
|
|
59
|
+
visibility: "private",
|
|
60
|
+
redactionClass: "none",
|
|
61
|
+
})),
|
|
62
|
+
}),
|
|
63
|
+
),
|
|
64
|
+
);
|
|
65
|
+
const sources = await Promise.all(
|
|
66
|
+
["a", "b"].map((entryRef) =>
|
|
67
|
+
Effect.runPromise(
|
|
68
|
+
original
|
|
69
|
+
.validateSourceAddress({
|
|
70
|
+
addressSchemaId: "conformance.path.v1",
|
|
71
|
+
encodedAddress: `fixture:${entryRef}`,
|
|
72
|
+
})
|
|
73
|
+
.pipe(Effect.map(({ origin }) => origin)),
|
|
74
|
+
),
|
|
75
|
+
),
|
|
76
|
+
);
|
|
77
|
+
const mentions = sources.map((source, index) =>
|
|
78
|
+
implementation.makeGraphMention({
|
|
79
|
+
identityNamespace: "people",
|
|
80
|
+
canonicalKey: `alex:${index}`,
|
|
81
|
+
identityScopeRef: "tenant.a",
|
|
82
|
+
source,
|
|
83
|
+
derivation: graphConformanceDerivation,
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
const mention = mentions[0]!;
|
|
87
|
+
const built = await Effect.runPromise(
|
|
88
|
+
implementation.buildGraphCorpus({
|
|
89
|
+
graphRef: "graph.rlm.conformance",
|
|
90
|
+
scopeRef: "tenant.a",
|
|
91
|
+
policy: graphConformancePolicy,
|
|
92
|
+
mentions,
|
|
93
|
+
entities: [],
|
|
94
|
+
relations: [],
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
const handle = await Effect.runPromise(implementation.makeInMemoryGraphSnapshotHandle(built));
|
|
98
|
+
const classification = implementation.makeGraphRlmClassificationProjection(
|
|
99
|
+
handle,
|
|
100
|
+
mentions.map(({ elementRef }) => ({
|
|
101
|
+
elementRef,
|
|
102
|
+
visibility: "private" as const,
|
|
103
|
+
redactionClass: "none" as const,
|
|
104
|
+
})),
|
|
105
|
+
[original],
|
|
106
|
+
);
|
|
107
|
+
return { original, source: sources[0]!, mention, mentions, built, handle, classification };
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
describe(`[${implementation.label}] graph RLM`, () => {
|
|
111
|
+
test("projection preserves exact source citations and bounded lookup", async () => {
|
|
112
|
+
const value = await fixture();
|
|
113
|
+
const projection = await Effect.runPromise(
|
|
114
|
+
implementation.makeGraphRlmProjection({
|
|
115
|
+
handle: value.handle,
|
|
116
|
+
capabilities: implementation.makeGraphAdapterCapabilities([
|
|
117
|
+
"graph_read",
|
|
118
|
+
"rlm_v2_projection",
|
|
119
|
+
]),
|
|
120
|
+
classification: value.classification,
|
|
121
|
+
corpusRef: "rlm.graph.conformance",
|
|
122
|
+
supportingCorpora: [value.original],
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
const entries = await Effect.runPromise(projection.corpus.materializeAll());
|
|
126
|
+
expect(entries).toHaveLength(2);
|
|
127
|
+
expect(entries[0]?.supportingSources).toEqual([value.source]);
|
|
128
|
+
const citation = citationFromEntry(projection.corpus, entries[0]!);
|
|
129
|
+
expect(
|
|
130
|
+
(await Effect.runPromise(validateCitations(projection.corpus, [citation]))).invalid,
|
|
131
|
+
).toEqual([]);
|
|
132
|
+
const result = await Effect.runPromise(
|
|
133
|
+
projection.operators.lookup(value.mention.elementRef, limits),
|
|
134
|
+
);
|
|
135
|
+
expect(result).toMatchObject({ _tag: "Complete", visitedElements: 1 });
|
|
136
|
+
const capped = await Effect.runPromise(
|
|
137
|
+
projection.operators.searchText("alex", { ...limits, maxReturnedElements: 1 }),
|
|
138
|
+
);
|
|
139
|
+
expect(capped).toMatchObject({ _tag: "Truncated", hitCaps: ["max_returned_elements"] });
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("missing capability and stale classification fail closed", async () => {
|
|
143
|
+
const value = await fixture();
|
|
144
|
+
const unsupported = await Effect.runPromise(
|
|
145
|
+
implementation
|
|
146
|
+
.makeGraphRlmProjection({
|
|
147
|
+
handle: value.handle,
|
|
148
|
+
capabilities: implementation.makeGraphAdapterCapabilities(["graph_read"]),
|
|
149
|
+
classification: value.classification,
|
|
150
|
+
corpusRef: "rlm.graph.unsupported",
|
|
151
|
+
supportingCorpora: [value.original],
|
|
152
|
+
})
|
|
153
|
+
.pipe(Effect.flip),
|
|
154
|
+
);
|
|
155
|
+
expect(unsupported).toMatchObject({
|
|
156
|
+
reason: "unsupported_operation",
|
|
157
|
+
capability: "rlm_v2_projection",
|
|
158
|
+
});
|
|
159
|
+
const incomplete = implementation.makeGraphRlmClassificationProjection(
|
|
160
|
+
value.handle,
|
|
161
|
+
[],
|
|
162
|
+
[value.original],
|
|
163
|
+
);
|
|
164
|
+
const stale = await Effect.runPromise(
|
|
165
|
+
implementation
|
|
166
|
+
.makeGraphRlmProjection({
|
|
167
|
+
handle: value.handle,
|
|
168
|
+
capabilities: implementation.makeGraphAdapterCapabilities([
|
|
169
|
+
"graph_read",
|
|
170
|
+
"rlm_v2_projection",
|
|
171
|
+
]),
|
|
172
|
+
classification: incomplete,
|
|
173
|
+
corpusRef: "rlm.graph.stale",
|
|
174
|
+
supportingCorpora: [value.original],
|
|
175
|
+
})
|
|
176
|
+
.pipe(Effect.flip),
|
|
177
|
+
);
|
|
178
|
+
expect(stale).toMatchObject({ _tag: "GraphCorpus.RlmError", reason: "invalid_projection" });
|
|
179
|
+
|
|
180
|
+
const widened = implementation.makeGraphRlmClassificationProjection(
|
|
181
|
+
value.handle,
|
|
182
|
+
[{ elementRef: value.mention.elementRef, visibility: "public", redactionClass: "none" }],
|
|
183
|
+
[value.original],
|
|
184
|
+
);
|
|
185
|
+
const policyError = await Effect.runPromise(
|
|
186
|
+
implementation
|
|
187
|
+
.makeGraphRlmProjection({
|
|
188
|
+
handle: value.handle,
|
|
189
|
+
capabilities: implementation.makeGraphAdapterCapabilities([
|
|
190
|
+
"graph_read",
|
|
191
|
+
"rlm_v2_projection",
|
|
192
|
+
]),
|
|
193
|
+
classification: widened,
|
|
194
|
+
corpusRef: "rlm.graph.policy-widened",
|
|
195
|
+
supportingCorpora: [value.original],
|
|
196
|
+
})
|
|
197
|
+
.pipe(Effect.flip),
|
|
198
|
+
);
|
|
199
|
+
expect(policyError.reason).toBe("invalid_projection");
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("a supporting corpus that changes after construction fails closed", async () => {
|
|
203
|
+
const value = await fixture();
|
|
204
|
+
let changed = false;
|
|
205
|
+
const supporting = {
|
|
206
|
+
...value.original,
|
|
207
|
+
assertUnchanged: () =>
|
|
208
|
+
changed
|
|
209
|
+
? Effect.fail(new RlmCorpusError({ reason: "changed" }))
|
|
210
|
+
: value.original.assertUnchanged(),
|
|
211
|
+
};
|
|
212
|
+
const classification = implementation.makeGraphRlmClassificationProjection(
|
|
213
|
+
value.handle,
|
|
214
|
+
value.mentions.map(({ elementRef }) => ({
|
|
215
|
+
elementRef,
|
|
216
|
+
visibility: "private" as const,
|
|
217
|
+
redactionClass: "none" as const,
|
|
218
|
+
})),
|
|
219
|
+
[supporting],
|
|
220
|
+
);
|
|
221
|
+
const projection = await Effect.runPromise(
|
|
222
|
+
implementation.makeGraphRlmProjection({
|
|
223
|
+
handle: value.handle,
|
|
224
|
+
capabilities: implementation.makeGraphAdapterCapabilities([
|
|
225
|
+
"graph_read",
|
|
226
|
+
"rlm_v2_projection",
|
|
227
|
+
]),
|
|
228
|
+
classification,
|
|
229
|
+
corpusRef: "rlm.graph.changing",
|
|
230
|
+
supportingCorpora: [supporting],
|
|
231
|
+
}),
|
|
232
|
+
);
|
|
233
|
+
changed = true;
|
|
234
|
+
const error = await Effect.runPromise(
|
|
235
|
+
projection.operators.lookup(value.mention.elementRef, limits).pipe(Effect.flip),
|
|
236
|
+
);
|
|
237
|
+
expect(error).toMatchObject({
|
|
238
|
+
_tag: "GraphCorpus.RlmError",
|
|
239
|
+
reason: "projection_changed",
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,22 @@ export {
|
|
|
17
17
|
runCorpusCompositionLaws,
|
|
18
18
|
type CorpusCompositionLawsConfig,
|
|
19
19
|
} from "./corpus-composition-laws.ts";
|
|
20
|
+
export { runGraphIdentityLaws, type GraphIdentityLawsConfig } from "./graph-identity-laws.ts";
|
|
21
|
+
export {
|
|
22
|
+
runGraphCapabilityDeleteLaws,
|
|
23
|
+
type GraphCapabilityDeleteLawsConfig,
|
|
24
|
+
} from "./graph-capability-delete-laws.ts";
|
|
25
|
+
export { runDseExtractionLaws, type DseExtractionLawsConfig } from "./dse-extraction-laws.ts";
|
|
26
|
+
export { runGraphRlmLaws, type GraphRlmLawsConfig } from "./graph-rlm-laws.ts";
|
|
27
|
+
export { runGraphRankingLaws, type GraphRankingLawsConfig } from "./graph-ranking-laws.ts";
|
|
28
|
+
export { runGraphArchiveLaws, type GraphArchiveLawsConfig } from "./graph-archive-laws.ts";
|
|
29
|
+
export {
|
|
30
|
+
graphArchiveCorruptionFixtures,
|
|
31
|
+
graphConformanceDerivation,
|
|
32
|
+
graphConformancePolicy,
|
|
33
|
+
graphConformanceSource,
|
|
34
|
+
graphPromptInjectionFixture,
|
|
35
|
+
} from "./graph-fixtures.ts";
|
|
20
36
|
export {
|
|
21
37
|
assertContiguous,
|
|
22
38
|
attempt,
|
package/src/adapter-laws.test.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
2
|
-
import { makeReferenceAdapter } from "@openagentsinc/agent-harness-contract";
|
|
3
|
-
|
|
4
|
-
import { runAdapterLaws } from "./adapter-laws.ts";
|
|
5
|
-
|
|
6
|
-
// The kit run against the in-repo reference adapter proves the kit itself
|
|
7
|
-
// works — a lossless suspend/continue adapter that supports every verb.
|
|
8
|
-
runAdapterLaws({
|
|
9
|
-
label: "reference-adapter",
|
|
10
|
-
makeHarness: () => Effect.sync(() => makeReferenceAdapter({ scriptWords: ["a", "b", "c"] })),
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
// A lossy-continue variant exercises the honest-degradation branch of the
|
|
14
|
-
// suspend/continue law.
|
|
15
|
-
runAdapterLaws({
|
|
16
|
-
label: "reference-adapter (lossy continue)",
|
|
17
|
-
makeHarness: () =>
|
|
18
|
-
Effect.sync(() =>
|
|
19
|
-
makeReferenceAdapter({ scriptWords: ["a", "b", "c"], continueIsLossy: true }),
|
|
20
|
-
),
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
// A verb-refusing variant exercises the fail-closed capability branch: compact,
|
|
24
|
-
// detach, and suspend must each fail with a named CapabilityUnsupported error.
|
|
25
|
-
runAdapterLaws({
|
|
26
|
-
label: "reference-adapter (refuses compact/detach/suspend)",
|
|
27
|
-
makeHarness: () =>
|
|
28
|
-
Effect.sync(() =>
|
|
29
|
-
makeReferenceAdapter({
|
|
30
|
-
scriptWords: ["a", "b", "c"],
|
|
31
|
-
supportsCompact: false,
|
|
32
|
-
supportsDetach: false,
|
|
33
|
-
supportsSuspend: false,
|
|
34
|
-
}),
|
|
35
|
-
),
|
|
36
|
-
});
|
|
@@ -1,9 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
2
|
-
import { makeInMemoryEventLogStore } from "@openagentsinc/agent-harness-contract";
|
|
3
|
-
|
|
4
|
-
import { runEventLogLaws } from "./event-log-laws.ts";
|
|
5
|
-
|
|
6
|
-
// The kit run against the in-repo reference store proves the kit itself works.
|
|
7
|
-
runEventLogLaws({
|
|
8
|
-
label: "in-memory-store",
|
|
9
|
-
makeStore: () => Effect.sync(() => makeInMemoryEventLogStore()),
|
|
10
|
-
});
|
package/src/recall-laws.test.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { recallTierD } from "@openagentsinc/history-corpus";
|
|
2
|
-
|
|
3
|
-
import { runRecallLaws } from "./recall-laws.ts";
|
|
4
|
-
|
|
5
|
-
// The kit run against the in-repo reference recall backend (Tier D) proves the
|
|
6
|
-
// kit itself works.
|
|
7
|
-
runRecallLaws({
|
|
8
|
-
label: "recall-tier-d",
|
|
9
|
-
recall: (params) => recallTierD(params),
|
|
10
|
-
});
|