fix(investigation): persist large results and require graph links
Generic Container CI/CD / test-build-publish (push) Successful in 4m15s
Server CI/CD v2 / build-test-publish-and-deploy (push) Successful in 4m15s

Full-network pressure comparisons exceeded the worker limit before saved-result previews could run. Preserve complete calculation results and keep read-time summaries and pagination.

Incremental graph publication accepted isolated nodes after flow-authoring rules were lost. Require new steps to connect explicitly while retaining historical snapshots and status-only patches. Regression tests reproduce both failures on the previous code.
This commit is contained in:
2026-11-15 13:51:43 +08:00
parent d3a4433386
commit 4b47ae00d1
7 changed files with 173 additions and 5 deletions
+2 -4
View File
@@ -93,8 +93,8 @@ async def execute_child(input_path: str, output_path: str):
category = 'EXECUTION_CONTEXT_UNAVAILABLE'
payload = {'status': 'failed', 'error': {'code': category}}
encoded = json.dumps(payload, allow_nan=False)
if len(encoded.encode()) > 8 * 1024 * 1024:
encoded = json.dumps({'status': 'failed', 'error': {'code': 'RESULT_TOO_LARGE'}})
# Keep the complete calculation for persistence. MCP result reads apply
# their own summary/page budgets after the result has been saved.
Path(output_path).write_text(encoded)
@@ -212,8 +212,6 @@ class CalculationWorker:
terminal = {'status': 'timed_out', 'error': {'code': 'EXECUTION_TIMEOUT'}}
elif process.returncode is not None:
try:
if output_path.stat().st_size > 8 * 1024 * 1024:
raise ValueError('Result exceeds persistence limit')
terminal = json.loads(output_path.read_text())
except (OSError, ValueError):
terminal = {'status': 'failed', 'error': {'code': 'CHILD_PROCESS_FAILED'}}
+21
View File
@@ -39,6 +39,7 @@ def update_investigation_graph(
))
nodes = {node.node_id: node.model_copy(deep=True) for node in previous.node_refs} if previous else {}
saved_ids = set(nodes)
require(len({node.node_id for node in update.nodes}) == len(update.nodes),
"GRAPH_NODE_DUPLICATE", "Graph update contains duplicate node ids", status=422)
for node in update.nodes:
@@ -81,6 +82,26 @@ def update_investigation_graph(
edges.append(edge.model_copy(deep=True))
require(len(nodes) <= 100 and len(edges) <= 200, "GRAPH_CAPACITY_EXCEEDED",
"Investigation graph exceeds 100 nodes or 200 edges; existing paths were retained", status=422)
# Validate only newly added steps. Historical snapshots and status-only
# patches stay valid, even if an earlier publisher omitted relationships.
new_ids = set(nodes) - saved_ids
if new_ids and len(nodes) > 1:
neighbours = {node_id: set() for node_id in nodes}
for edge in edges:
neighbours[edge.source].add(edge.target)
neighbours[edge.target].add(edge.source)
connected = set(saved_ids) or {next(iter(nodes))}
pending = list(connected)
while pending:
for node_id in neighbours[pending.pop()] - connected:
connected.add(node_id)
pending.append(node_id)
missing = sorted(new_ids - connected)
require(not missing, "GRAPH_RELATION_REQUIRED",
"Add explicit edges connecting these new steps to the investigation narrative: "
+ ", ".join(missing)
+ ". Describe branches, parallel findings or retries; edges do not schedule execution.",
status=422)
lines = [source or "flowchart TD"]
for node in nodes.values():
if in_source(node.node_id):