1
0
Fork 0
Code Issues Pull requests Projects Releases 2 Packages Wiki Activity Actions Pages

Trace complete directed graph lineage in Flow view

This commit is contained in:
Andraxion 2026-07-25 01:05:45 -04:00
parent 984a4fd993
commit e405326334
3 changed files with 139 additions and 61 deletions

View file

@ -130,7 +130,7 @@ class DocForgeMcpTests(unittest.IsolatedAsyncioTestCase):
visualization = results[11].structuredContent["visualization"]
self.assertTrue(visualization["read_only"])
self.assertTrue(visualization["project_bound"])
self.assertEqual("graph-browser@10", visualization["template"])
self.assertEqual("graph-browser@11", visualization["template"])
self.assertEqual("managed_idle", visualization["lifetime"]["policy"])
self.assertEqual("docforge_stop_visualization", visualization["lifetime"]["stop_tool"])
self.assertTrue(visualization["url"].startswith("http://127.0.0.1:"))

View file

@ -177,47 +177,34 @@ if (centered.width !== 500 || centered.height !== 300) fail("center preserves zo
if (centered.x !== positions.get("child-two").x - 250) fail("center x");
if (centered.y !== positions.get("child-two").y - 150) fail("center y");
if (relationStyle("calls").family !== "Execution") fail("calls family");
if (relationStyle("contains").flow !== "forward") fail("contains flow direction");
if (relationStyle("reads").flow !== "reverse") fail("reads flow direction");
if (relationStyle("documents").flow !== null) fail("documents excluded from flow");
if (relationStyle("unknown_relation").family !== "Other") fail("fallback relation family");
const flowData = {
root: "primary",
depth: 2,
nodes: [
{node_id: "primary"},
{node_id: "caller"},
{node_id: "dependency"},
{node_id: "reader"},
{node_id: "package"},
{node_id: "module"},
{node_id: "test-class"},
{node_id: "test-file"},
{node_id: "document"},
{node_id: "evidence"},
],
edges: [
{source_id: "caller", relation: "calls", target_id: "primary"},
{source_id: "primary", relation: "depends_on", target_id: "dependency"},
{source_id: "primary", relation: "reads", target_id: "reader"},
{source_id: "test-class", relation: "contains", target_id: "primary"},
{source_id: "test-file", relation: "contains", target_id: "test-class"},
{source_id: "document", relation: "documents", target_id: "primary"},
{source_id: "module", relation: "contains", target_id: "test-file"},
{source_id: "package", relation: "contains", target_id: "module"},
{source_id: "evidence", relation: "verifies", target_id: "primary"},
],
};
const flow = buildFlowGraph(flowData);
const flowIds = new Set(flow.nodes.map((node) => node.node_id));
if (!flowIds.has("caller") || !flowIds.has("dependency") || !flowIds.has("reader")
|| !flowIds.has("test-class") || !flowIds.has("test-file")) {
fail("upstream flow membership");
}
if (flowIds.has("document")) fail("evidence leaked into flow");
if (!flowIds.has("package") || !flowIds.has("module") || !flowIds.has("test-class")
|| !flowIds.has("test-file") || !flowIds.has("evidence")) fail("full lineage membership");
const flowPositions = layoutFlow(flow.nodes, flow.root, flow.topology);
if (flowPositions.get("primary").x !== 0) fail("flow destination position");
if (flowPositions.get("caller").x >= flowPositions.get("primary").x) {
if (flowPositions.get("package").x >= flowPositions.get("primary").x) {
fail("flow upstream direction");
}
const dependencyEdge = flow.edges.find((edge) => edge.relation === "depends_on");
if (dependencyEdge.source_id !== "dependency" || dependencyEdge.target_id !== "primary") {
fail("dependency semantic reversal");
}
const containmentEdge = flow.edges.find((edge) => edge.relation === "contains"
&& edge.source_id === "test-class");
if (!containmentEdge || containmentEdge.target_id !== "primary") {
@ -307,9 +294,9 @@ if (!containmentEdge || containmentEdge.target_id !== "primary") {
self.assertIn("relationStyles", html)
self.assertIn("appendRelationMarker", html)
self.assertIn("renderRelationshipKey", html)
self.assertIn("semanticFlowEdge", html)
self.assertIn("buildFlowGraph", html)
self.assertIn("layoutFlow", html)
self.assertIn('api(`${showingFlow ? "lineage" : "node"}?${params}`)', html)
self.assertIn("filterByDescriptor", html)
self.assertIn("api(`filter?${params}`)", html)
self.assertIn('setViewMode("flow")', html)
@ -371,6 +358,22 @@ if (!containmentEdge || containmentEdge.target_id !== "primary") {
self.assertEqual("guide.workflow", node["node"]["node_id"])
self.assertEqual("guide.workflow", node["root"])
lineage_query = urllib.parse.urlencode({"id": "guide.workflow", "limit": 1000})
with urllib.request.urlopen(
f"{base}api/lineage?{lineage_query}", timeout=2
) as response:
lineage = json.load(response)
self.assertTrue(lineage["lineage"])
self.assertEqual("guide.workflow", lineage["root"])
self.assertIn(
{
"source_id": "proof.validation",
"relation": "proves",
"target_id": "guide.workflow",
},
lineage["edges"],
)
wrong_token = f"{first_url.scheme}://{first_url.netloc}/wrong-token/api/overview"
with self.assertRaises(urllib.error.HTTPError) as missing:
urllib.request.urlopen(wrong_token, timeout=2)