mirror of
https://github.com/morluto/rea.git
synced 2026-10-30 11:25:39 +00:00
* fix(hopper): report unavailable procedure body coverage * refactor!: simplify runtime tools and remove replay engines Keep direct inspection, capture, and comparison tools. Bind handlers to named contracts and let agents compose experiments with commands and local fixture servers. BREAKING CHANGE: Remove controlled replay, Node characterization, finite replay-machine, managed runtime planning, and redundant string-tracing tools. Retire process replay, shims, reactive scenarios, custom checkpoints, and active browser replay and origin-scope inputs.
294 lines
9.2 KiB
TypeScript
294 lines
9.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
changedBehaviorResultSchema,
|
|
findChangedBehavior,
|
|
type ChangedBehaviorResult,
|
|
} from "./changedBehavior.js";
|
|
import { createEvidence, type Evidence } from "./evidence.js";
|
|
import type { JsonValue } from "./jsonValue.js";
|
|
|
|
const source = (digit: string): Evidence =>
|
|
createEvidence(
|
|
{
|
|
path: `/tmp/source-${digit}`,
|
|
sha256: digit.repeat(64),
|
|
format: "file",
|
|
},
|
|
{ id: "fixture", name: "Fixture", version: "1" },
|
|
{ operation: "fixture", parameters: {}, result: { digit } },
|
|
);
|
|
|
|
const left = source("1");
|
|
const right = source("2");
|
|
|
|
const comparison = (
|
|
operation: "compare_process_captures" | "compare_artifacts",
|
|
result: JsonValue,
|
|
input: { readonly salt?: number; readonly evidenceLinks?: string[] } = {},
|
|
): Evidence => {
|
|
const process = operation === "compare_process_captures";
|
|
return createEvidence(
|
|
undefined,
|
|
{
|
|
id: process ? "rea-process" : "rea-artifact-comparison",
|
|
name: process
|
|
? "REA deterministic process harness"
|
|
: "REA artifact comparison",
|
|
version: operation === "compare_process_captures" ? "3" : "1",
|
|
},
|
|
{
|
|
predicateType: process
|
|
? "rea.process-comparison"
|
|
: "rea.artifact-comparison",
|
|
operation,
|
|
parameters: input.salt === undefined ? {} : { salt: input.salt },
|
|
result,
|
|
confidence: "derived",
|
|
authority: "analyst-inference",
|
|
evidenceLinks: input.evidenceLinks ?? [
|
|
left.evidence_id,
|
|
right.evidence_id,
|
|
],
|
|
},
|
|
);
|
|
};
|
|
|
|
const processResult = (
|
|
overrides: Partial<{
|
|
status: "unchanged" | "changed" | "unknown" | "truncated";
|
|
terminal: "unchanged" | "changed" | "unknown" | "truncated";
|
|
interaction: "unchanged" | "changed" | "unknown" | "truncated";
|
|
exit: "unchanged" | "changed" | "unknown" | "truncated";
|
|
process: "unchanged" | "changed" | "unknown" | "truncated";
|
|
}> = {},
|
|
): JsonValue => ({
|
|
status: overrides.status ?? "unchanged",
|
|
terminal: overrides.terminal ?? "unchanged",
|
|
interaction: overrides.interaction ?? "unchanged",
|
|
exit: overrides.exit ?? "unchanged",
|
|
filesystem: "unchanged",
|
|
process: overrides.process ?? "unchanged",
|
|
first_divergence:
|
|
overrides.status === "changed"
|
|
? {
|
|
status: "found",
|
|
dimension: "terminal",
|
|
index: 0,
|
|
left_at_ms: 0,
|
|
right_at_ms: 0,
|
|
left: "left",
|
|
right: "right",
|
|
}
|
|
: overrides.status === "truncated" || overrides.status === "unknown"
|
|
? { status: "unknown", reason: "Incomplete fixture." }
|
|
: { status: "none" },
|
|
limitations: [],
|
|
});
|
|
|
|
const artifactResult = (
|
|
nestedLinks: string[] = [left.evidence_id, right.evidence_id],
|
|
): JsonValue => ({
|
|
status: "changed",
|
|
left_manifest_id: `agm_${"3".repeat(64)}`,
|
|
right_manifest_id: `agm_${"4".repeat(64)}`,
|
|
summary: {
|
|
unchanged: 0,
|
|
added: 0,
|
|
removed: 0,
|
|
changed: 1,
|
|
unknown: 0,
|
|
},
|
|
changes: [
|
|
{
|
|
classification: "changed",
|
|
logical_path: "main.js",
|
|
dimensions: ["content"],
|
|
left_occurrence_id: `occ_${"5".repeat(64)}`,
|
|
right_occurrence_id: `occ_${"6".repeat(64)}`,
|
|
left_artifact_id: `art_${"7".repeat(64)}`,
|
|
right_artifact_id: `art_${"8".repeat(64)}`,
|
|
evidence_links: nestedLinks,
|
|
},
|
|
],
|
|
limitations: [],
|
|
});
|
|
|
|
const expectInvalidSummary = (result: ChangedBehaviorResult): void => {
|
|
expect(
|
|
changedBehaviorResultSchema.safeParse({
|
|
...result,
|
|
summary: { ...result.summary, observed_changes: 0 },
|
|
}).success,
|
|
).toBe(false);
|
|
};
|
|
|
|
describe("changed behavior", () => {
|
|
it("accepts process comparison Evidence", () => {
|
|
const result = findChangedBehavior([
|
|
comparison("compare_process_captures", processResult()),
|
|
]);
|
|
expect(result.behavior_status).toBe("observed_unchanged");
|
|
});
|
|
|
|
it("reports interaction and process changes", () => {
|
|
const evidence = comparison(
|
|
"compare_process_captures",
|
|
processResult({
|
|
status: "changed",
|
|
interaction: "changed",
|
|
process: "changed",
|
|
}),
|
|
);
|
|
|
|
expect(findChangedBehavior([evidence])).toMatchObject({
|
|
behavior_status: "observed_changed",
|
|
summary: { observed_changes: 2 },
|
|
findings: {
|
|
items: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
dimension: "interaction",
|
|
scope: "runtime",
|
|
}),
|
|
expect.objectContaining({ dimension: "process", scope: "runtime" }),
|
|
]),
|
|
},
|
|
});
|
|
});
|
|
|
|
it("classifies runtime changes as observed and cites both observations", () => {
|
|
const evidence = comparison(
|
|
"compare_process_captures",
|
|
processResult({ status: "changed", terminal: "changed" }),
|
|
);
|
|
const result = findChangedBehavior([evidence]);
|
|
expect(changedBehaviorResultSchema.parse(result)).toMatchObject({
|
|
behavior_status: "observed_changed",
|
|
summary: { observed_changes: 1, static_candidates: 0 },
|
|
findings: { items: expect.any(Array) },
|
|
});
|
|
expectInvalidSummary(result);
|
|
expect(result.findings.items[0]).toMatchObject({
|
|
scope: "runtime",
|
|
dimension: "terminal",
|
|
classification: "observed_change",
|
|
evidence_links: expect.arrayContaining([
|
|
evidence.evidence_id,
|
|
left.evidence_id,
|
|
right.evidence_id,
|
|
]),
|
|
});
|
|
});
|
|
|
|
it("keeps static differences as candidates, never runtime observations", () => {
|
|
const evidence = comparison("compare_artifacts", artifactResult());
|
|
const result = findChangedBehavior([evidence]);
|
|
expect(result).toMatchObject({
|
|
behavior_status: "unknown",
|
|
summary: { observed_changes: 0, static_candidates: 1 },
|
|
});
|
|
expect(result.findings.items[0]).toMatchObject({
|
|
scope: "static_candidate",
|
|
dimension: "artifact:main.js",
|
|
classification: "derived_relationship",
|
|
});
|
|
expect(result.limitations).toContain(
|
|
"Static differences are behavior candidates, not runtime observations.",
|
|
);
|
|
});
|
|
|
|
it("lets incomplete runtime evidence dominate observed changes", () => {
|
|
const changed = comparison(
|
|
"compare_process_captures",
|
|
processResult({ status: "changed", terminal: "changed" }),
|
|
);
|
|
const unknown = comparison(
|
|
"compare_process_captures",
|
|
processResult({ status: "unknown", exit: "unknown" }),
|
|
);
|
|
const result = findChangedBehavior([changed, unknown]);
|
|
expect(result).toMatchObject({
|
|
behavior_status: "unknown",
|
|
findings: { items: expect.any(Array) },
|
|
});
|
|
expect(findChangedBehavior([changed, unknown])).toEqual(result);
|
|
});
|
|
|
|
it("uses complete inline artifact deltas as static candidates", () => {
|
|
const complete = comparison("compare_artifacts", artifactResult());
|
|
expect(findChangedBehavior([complete]).findings.items).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({ dimension: "artifact:main.js" }),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("rejects bundles, duplicate comparisons, and malformed exact results", () => {
|
|
const valid = comparison("compare_process_captures", processResult());
|
|
expect(() => findChangedBehavior([valid, valid])).toThrow(
|
|
/duplicate comparison Evidence/u,
|
|
);
|
|
const bundle = createEvidence(
|
|
undefined,
|
|
{ id: "rea-bundle-comparison", name: "Bundle", version: "1" },
|
|
{
|
|
predicateType: "rea.bundle-comparison",
|
|
operation: "compare_bundles",
|
|
parameters: {},
|
|
result: {},
|
|
confidence: "derived",
|
|
authority: "analyst-inference",
|
|
evidenceLinks: [left.evidence_id, right.evidence_id],
|
|
},
|
|
);
|
|
expect(() => findChangedBehavior([bundle])).toThrow(
|
|
/requires process, artifact, or function/u,
|
|
);
|
|
const malformed = comparison("compare_process_captures", {
|
|
status: "unchanged",
|
|
});
|
|
expect(() => findChangedBehavior([malformed])).toThrow();
|
|
const contradictory = comparison(
|
|
"compare_process_captures",
|
|
processResult({ status: "unchanged", terminal: "changed" }),
|
|
);
|
|
expect(() => findChangedBehavior([contradictory])).toThrow(
|
|
/contradicts its dimensions/u,
|
|
);
|
|
const danglingNested = comparison(
|
|
"compare_artifacts",
|
|
artifactResult([`ev_${"f".repeat(64)}`, right.evidence_id]),
|
|
);
|
|
expect(() => findChangedBehavior([danglingNested])).toThrow(
|
|
/outside its top-level closure/u,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("changed behavior aggregation", () => {
|
|
it("keeps all comparisons and their complete evidence closure", () => {
|
|
const comparisons = Array.from({ length: 101 }, (_, comparisonIndex) =>
|
|
comparison(
|
|
"compare_process_captures",
|
|
processResult({ status: "changed", terminal: "changed" }),
|
|
{
|
|
salt: comparisonIndex,
|
|
evidenceLinks: Array.from(
|
|
{ length: 200 },
|
|
(_, linkIndex) =>
|
|
`ev_${(comparisonIndex * 200 + linkIndex + 1)
|
|
.toString(16)
|
|
.padStart(64, "0")}`,
|
|
),
|
|
},
|
|
),
|
|
);
|
|
|
|
const result = findChangedBehavior(comparisons);
|
|
|
|
expect(result.summary.observed_changes).toBe(comparisons.length);
|
|
expect(result.evidence_links).toHaveLength(comparisons.length * 201);
|
|
expect(result.findings.items[0]?.evidence_links).toHaveLength(201);
|
|
});
|
|
});
|