Skip to content

fix: apply pre-flatten passes to imported subgraphs - #2263

Open
noron12234 wants to merge 1 commit into
TEN-framework:mainfrom
noron12234:fix/subgraph-pre-flatten-passes
Open

fix: apply pre-flatten passes to imported subgraphs#2263
noron12234 wants to merge 1 commit into
TEN-framework:mainfrom
noron12234:fix/subgraph-pre-flatten-passes

Conversation

@noron12234

Copy link
Copy Markdown
Contributor

Problem

Graph::flatten_graph runs four passes over a graph:

  1. expand_names_to_individual_itemsnames: [...] into individual name items
  2. flatten_selectors — selector references into the nodes they match
  3. convert_reversed_connections_to_forward_connectionssource into dest
  4. flatten_subgraphs — inline imported subgraphs, prefixing names with the subgraph name

Passes 1-3 were only ever applied to the top-level graph. process_subgraph_node loads an imported subgraph with load_graph_from_uri, which only deserializes the file, and hands it straight to pass 4. A subgraph that uses names, a selector node, or a reversed source connection therefore reaches the flattening logic unlowered, even though that logic assumes all three passes have already run.

Reproduction

sub.json — inside the subgraph, ext_a sends hello to ext_b, written in reverse form:

{
  "nodes": [
    {"type": "extension", "name": "ext_a", "addon": "addon_a"},
    {"type": "extension", "name": "ext_b", "addon": "addon_b"}
  ],
  "connections": [
    {"extension": "ext_b", "cmd": [{"name": "hello", "source": [{"extension": "ext_a"}]}]}
  ]
}

Main graph:

{"nodes": [{"type": "subgraph", "name": "sg", "graph": {"import_uri": "file://.../sub.json"}}]}

Expected after flattening: sg_ext_a --hello--> sg_ext_b.

Actual on main:

{
  "nodes": [
    {"type": "extension", "name": "sg_ext_a", "addon": "addon_a"},
    {"type": "extension", "name": "sg_ext_b", "addon": "addon_b"}
  ],
  "connections": [
    {"extension": "sg_ext_b", "cmd": [{"name": "hello", "source": [{"extension": "ext_a"}]}]}
  ]
}

The connection is still in reverse form, and its source still names ext_a while the node has been renamed to sg_ext_a. It now points at an extension that does not exist, and nothing reports an error.

Impact per feature

Written inside an imported subgraph Result on main
source (reversed connection) Silently produces a connection referencing a non-existent extension. No error, message never delivered.
names: [...] Reaches flattening unexpanded, where name is expected to be Some and names to be None.
selector node Panics: Unexpected non-extension node in flattened subgraph (subgraph/flatten.rs:493).

The selector case is the most visible one: this code path is reachable from the tman designer HTTP backend and from the C runtime through ten_rust_graph_validate_complete_flatten, so a malformed-but-plausible subgraph file takes down the process instead of returning an error.

Fix

Extract passes 1-3 into Graph::apply_pre_flatten_passes and run them on a subgraph immediately after it is loaded, so an imported subgraph goes through the same lowering as the top-level graph. flatten_graph now calls the same helper, so there is a single definition of what has to happen before flattening.

The other load_graph_from_uri caller (GraphContent::validate_and_complete_and_flatten) already reaches these passes through flatten_graph and needed no change.

Tests

Three tests added to tests/test_case/graph/subgraph.rs, one per feature:

  • test_flatten_subgraph_with_reversed_connection
  • test_flatten_subgraph_with_names_array
  • test_flatten_subgraph_with_selector

Verified they fail on main and pass with this change:

# without the fix
test test_flatten_subgraph_with_names_array ... FAILED
test test_flatten_subgraph_with_reversed_connection ... FAILED
test test_flatten_subgraph_with_selector ... FAILED     # panics at flatten.rs:493
test result: FAILED. 9 passed; 3 failed

# with the fix
test result: ok. 12 passed; 0 failed

The rest of the graph suite is unaffected — graph::reverse (14), graph::flatten_integration (4), graph::names_expansion (6), graph::selector (2), graph::exposed_message (10), graph::graph_info (5), graph::import_uri (6) all still pass.

Possible follow-up

validate_and_complete is also never run on an imported subgraph, so the app-URI rules are not enforced there. I left that out of this PR because it changes which graphs are accepted rather than fixing a mis-compilation. Happy to send a separate PR if you would like that tightened too.

@noron12234
noron12234 requested a review from halajohn as a code owner July 29, 2026 13:54
@noron12234

Copy link
Copy Markdown
Contributor Author

Closing for now — I want to re-check this myself before asking for review. Will reopen once I'm confident. Sorry for the noise.

@noron12234 noron12234 closed this Jul 29, 2026
`load_graph_from_uri` only deserializes a subgraph file, and
`process_subgraph_node` handed the result straight to the flattening
logic. The three passes that `flatten_graph` runs before flattening -
`names` expansion, selector resolution and reversed connection
conversion - were therefore never applied to an imported subgraph, only
to the top-level one.

A subgraph using any of those three features was mis-compiled:

- `source`: the reversed connection was left unconverted and its
  extension name kept its pre-prefix value, so it referenced an
  extension that no longer existed after flattening. No error was
  reported, the connection was silently dropped at runtime.
- `names`: the array reached the flattening logic unexpanded, where
  `name` is expected to be `Some` and `names` to be `None`.
- `selector`: the selector node reached the flattening logic and
  triggered the `Unexpected non-extension node in flattened subgraph`
  panic.

Extract the three passes into `Graph::apply_pre_flatten_passes` and run
them on a subgraph right after it is loaded. The other
`load_graph_from_uri` caller already runs them via `flatten_graph`.
@noron12234 noron12234 reopened this Jul 31, 2026
@noron12234
noron12234 force-pushed the fix/subgraph-pre-flatten-passes branch from 0eed2fb to e0802d1 Compare July 31, 2026 07:59
@noron12234

noron12234 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

Reopened and rebased onto current main.

Verified the bug is still present. Applying only the new tests to main, without the fix:

  • test_flatten_subgraph_with_names_array — fails, names reaches flattening unexpanded
  • test_flatten_subgraph_with_reversed_connection — fails, source still references ext_a after the node was renamed to sg_ext_b
  • test_flatten_subgraph_with_selector — panics at subgraph/flatten.rs:493

With the fix, all 87 tests under graph:: pass.

On the earlier CI failure: it failed at Setup tgn, not in the Rust code. The same workflow also failed on the cleanup branch around the same time, and #2269 (rust toolchain replacement cleanup) has since landed. This branch now includes it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant