mirror of
https://github.com/morluto/rea
synced 2026-10-27 06:33:32 -04:00
fix(javascript): index repeated graph observations before final sorting
Retain stable live nodes and observation arrays while avoiding quadratic collection rebuilds. Preserve duplicate handling and canonical output. Fixes #1748 Assisted-by: Suika (gensokyo-skills:suika-scatter-gather-planning)
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { completeApplicationCoverage } from "../../domain/javascript/javascriptApplicationEvidenceSchemas.js";
|
||||
import { createJavaScriptApplicationNode } from "../../domain/javascript/javascriptApplicationGraph.js";
|
||||
import { JavaScriptArtifactGraphAccumulator } from "./JavaScriptArtifactGraphAccumulator.js";
|
||||
import { artifactObservationEvidence } from "./JavaScriptArtifactGraphEvidence.js";
|
||||
|
||||
const nodeInput = (value: string, sha256 = "a".repeat(64)) => ({
|
||||
kind: "javascript-asset",
|
||||
identity: {
|
||||
strategy: "content-digest",
|
||||
stability: "global-exact",
|
||||
sha256,
|
||||
},
|
||||
observations: [
|
||||
{
|
||||
label: value,
|
||||
properties: { value },
|
||||
evidence: artifactObservationEvidence({
|
||||
sha256,
|
||||
path: "main.js",
|
||||
operation: "accumulator-test",
|
||||
coverage: completeApplicationCoverage(),
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
it("retains live references, exact deduplication and canonical output across later additions", () => {
|
||||
const accumulator = new JavaScriptArtifactGraphAccumulator();
|
||||
const inputs = ["雪😀", "alpha", "beta"].map((value) => nodeInput(value));
|
||||
const firstInput = inputs[0];
|
||||
if (firstInput === undefined) throw new Error("Missing first input");
|
||||
const node = accumulator.addNode(firstInput);
|
||||
const observations = node.observations;
|
||||
const firstObservation = observations[0];
|
||||
expect(accumulator.addNode(firstInput)).toBe(node);
|
||||
expect(node.observations).toBe(observations);
|
||||
expect(observations).toHaveLength(1);
|
||||
expect(observations[0]).not.toBe(firstObservation);
|
||||
for (const input of inputs.slice(1))
|
||||
expect(accumulator.addNode(input)).toBe(node);
|
||||
const secondNode = accumulator.addNode(nodeInput("other", "b".repeat(64)));
|
||||
const firstSnapshot = accumulator.nodes();
|
||||
const expectedNode = createJavaScriptApplicationNode({
|
||||
...firstInput,
|
||||
observations: inputs.flatMap((input) => input.observations),
|
||||
});
|
||||
expect(firstSnapshot).toEqual([expectedNode, secondNode]);
|
||||
expect(firstSnapshot[0]).toBe(node);
|
||||
expect(node.observations).toBe(observations);
|
||||
const finalizedObservations = [...observations];
|
||||
expect(accumulator.nodes()).not.toBe(firstSnapshot);
|
||||
expect(accumulator.nodes()).toEqual(firstSnapshot);
|
||||
for (const [index, observation] of finalizedObservations.entries())
|
||||
expect(node.observations[index]).toBe(observation);
|
||||
|
||||
const later = nodeInput("later");
|
||||
expect(accumulator.addNode(later)).toBe(node);
|
||||
// Updating a duplicate after final sorting must use its new array index.
|
||||
expect(accumulator.addNode(firstInput)).toBe(node);
|
||||
expect(accumulator.nodes()).toEqual([
|
||||
createJavaScriptApplicationNode({
|
||||
...firstInput,
|
||||
observations: [...inputs, later].flatMap((input) => input.observations),
|
||||
}),
|
||||
secondNode,
|
||||
]);
|
||||
expect(node.observations).toBe(observations);
|
||||
});
|
||||
|
||||
it("preserves first-input duplicates and retains the last occurrence when later merged", () => {
|
||||
const accumulator = new JavaScriptArtifactGraphAccumulator();
|
||||
const first = nodeInput("same");
|
||||
const distinct = nodeInput("distinct");
|
||||
const node = accumulator.addNode({
|
||||
...first,
|
||||
observations: [...first.observations, ...first.observations],
|
||||
});
|
||||
const observations = node.observations;
|
||||
const lastInitial = observations[1];
|
||||
expect(accumulator.nodes()[0]?.observations).toHaveLength(2);
|
||||
expect(accumulator.addNode(distinct)).toBe(node);
|
||||
expect(accumulator.nodes()).toEqual([
|
||||
createJavaScriptApplicationNode({
|
||||
...first,
|
||||
observations: [...first.observations, ...distinct.observations],
|
||||
}),
|
||||
]);
|
||||
expect(node.observations).toBe(observations);
|
||||
expect(observations).toContain(lastInitial);
|
||||
// A repeated observation inside an incoming batch also collapses to one.
|
||||
accumulator.addNode({
|
||||
...distinct,
|
||||
observations: [...distinct.observations, ...distinct.observations],
|
||||
});
|
||||
expect(accumulator.nodes()[0]?.observations).toHaveLength(2);
|
||||
});
|
||||
Reference in New Issue
Block a user