1362 Commits

Author SHA1 Message Date
Sasha Sobran f5c41186c3 fix(tests): update model to gemini-3.1-pro-preview and handle empty streaming chunks 2026-04-21 19:05:11 +00:00
Sasha Sobran 2b54c4ac37 fix: block RCE vulnerability via nested YAML configurations in ADK
Co-authored-by: Sasha Sobran <asobran@google.com>
PiperOrigin-RevId: 902898232
Change-Id: I98ccedb70a7e3ce1a7f90d1f236acb0fb551e464
2026-04-20 18:50:14 -07:00
George Weale 09119c41ad fix(deps): Declare packaging dep and align ValidationError import
Two ADK source files import third-party packages that are not declared
in [project] dependencies, leaving bare `pip install google-adk` one
transitive resolver change away from breaking on `from google.adk import
Runner`:

* utils/model_name_utils.py and cli/cli_deploy.py do
  `from packaging.version import …` at module top level. Reachable from
  the canonical import chain (runners.py -> telemetry.tracing ->
  utils.model_name_utils) and from `adk --help`. Reproduced today by
  uninstalling packaging from a fresh venv.
* tools/environment_simulation/environment_simulation_config.py does
  `from pydantic_core import ValidationError`. pydantic-core ships with
  pydantic, so this works at runtime, but the dep is undeclared. pydantic
  re-exports ValidationError, so swap the import.

Adds packaging>=21.0 to main deps and switches the env_simulation import
to pydantic. Includes guard tests in tests/unittests/test_release_
dependencies.py that pin the contract.

Change-Id: Idab2bffe8ae1d66aa049801fbfff9569af7b7687
2026-04-20 18:17:51 -07:00
Xuan Yang 2be47e1a04 test(events): Prevent accidental introduction of snake_case keys in Event serialization
Added a new unit test test_event_serialization_always_camel_case in test_workflow_events.py. The test recursively verifies that all keys in a serialized Event model use camelCase, ensuring consistency with frontend expectations and catching accidental additions of snake_case fields.

Change-Id: I12ff59079b59ad5fd421e659c80450cf10c15bbb
2026-04-20 15:32:49 -07:00
Xuan Yang 0b3d7c49e1 fix(events): Support camelCase in RequestInput
Add alias_generator and populate_by_name to RequestInput model_config to match frontend naming conventions and other event models.

Change-Id: I5eb4a55185d0113d86407c119050bb9885a7962b
2026-04-20 14:33:19 -07:00
George Weale 47deb94880 fix: enable Python 3.10 support for workflow engine
Replace asyncio.timeout() (3.11+) with asyncio.wait_for() so node
timeouts work on Python 3.10. Fix typing.Self import in telemetry
tests with a 3.10-compatible fallback. Update pyproject.toml to
declare >=3.10 and add the 3.10 classifier.

Change-Id: Ibe1dcf4a1d44014891aa487aac6e160203c38a58
2026-04-19 18:47:09 -07:00
George Weale 3cdb5f1dd9 fix(workflow): enforce output_schema validation in BaseNode.run()
BaseNode.run() wrapped raw yields into Event(output=item) without
calling _validate_output_data(), so output_schema was never enforced
by the runtime for custom nodes. Moved validation into run() so it
applies centrally to all yields — both raw values and Event objects
with output. Updated tests to remove manual _validate_output_data()
calls and added tests proving runtime enforcement.

Change-Id: I4a984e71ae0027daa5cdfb9e3ba7936688743488
2026-04-17 16:37:42 -07:00
Swapnil Agarwal 2af93fd461 use @node decorator to cleanup dynamic node tests
Change-Id: I024c95e3c9988eec787baca76a2a23b55d831197
2026-04-17 15:47:48 -07:00
Swapnil Agarwal 809d7e5c3b Add hitl plus multi-trigger scenario
- Will add fix later in rehydration refactor.

Change-Id: Ibd5a18a8f0e8df8486d950506c45535ca9734db9
2026-04-17 14:38:59 -07:00
George Weale 497f3dd100 fix: resolve test failures from main cherry-picks on v2 branch
- Remove stale base_agent.tracer reference in test_node_functional.py
- Fix expected span names in test_functional.py (v2 uses invoke_agent, not invocation)

Change-Id: Ia47e54d0d811adcafda9b19fe14cf0ab16e2c6b0
2026-04-17 14:24:50 -07:00
Xuan Yang ea0bf27435 refactor(tests): Refactor tests to explicitly handle JSON_SCHEMA_FOR_FUNC_DECL feature flag
Co-authored-by: Xuan Yang <xygoogle@google.com>
PiperOrigin-RevId: 901366486
Change-Id: I2e43d0341acb5846c032c9c4d31b846f2a3bb4f2
2026-04-17 14:24:32 -07:00
Liang Wu 15c4ab7e14 feat(live): Add save_live_blob query parameter to /run_live endpoint
This change introduces a new query parameter `save_live_blob` to the `/run_live` WebSocket endpoint in the ADK web server. When set to true, this parameter enables saving of live blobs during the session. The default value is false. Unit tests are updated to cover this new parameter.

Closes issue #4707

Co-authored-by: Liang Wu <wuliang@google.com>
PiperOrigin-RevId: 901280577
Change-Id: I1359cb831c5bb7ddfa11e5d1d4ebed18080d00f2
2026-04-17 14:24:18 -07:00
Google Team Member 529af898e8 feat: add native OpenTelemetry agentic metrics
New metrics added:
- gen_ai.agent.invocation.duration
- gen_ai.tool.execution.duration
- gen_ai.agent.request.size
- gen_ai.agent.response.size
- gen_ai.agent.workflow.steps

New metrics and attributes are in line with OTel semantic conventions where possible, and will be formally to the semconv later.

PiperOrigin-RevId: 901156576
Change-Id: I0d6afc50287e643245d6fc7690d9841d9f22eaab
2026-04-17 14:23:34 -07:00
Mimi Sun 8ad2d8ead0 feat: Add an option to prevent the SaveFilesAsArtifactsPlugin from attaching reference file parts to the message
Default is True to match existing behavior. Setting it to False prevents the model from loading artifact directly from a provided canonical URI, and makes the behavior consistent between InMemoryArtifactService and other services with canonical URIs.

PiperOrigin-RevId: 901061080
Change-Id: I3079a3cd57e4517c38b62c8e8a7bc1278ae1936e
2026-04-17 14:23:21 -07:00
BowedMars2 1b89fc0538 test: adds missing unit tests for example_util (Issue #4623)
Merge https://github.com/google/adk-python/pull/4802

**Please ensure you have read the [contribution guide](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) before creating a pull request.**

### Link to Issue or Description of Change

- Closes: #4623

**Problem:**
example_util.py was completely missing unit tests.

**Solution:**
I added unit tests for a variety of situations:

- Converting a text-only example to a string
- Converting a text-only example _with multiple Parts_ to a string
- Converting a text-only example to a string when the output contains a conversation between the user and model
- Converting another text-only example with a conversational output, but with clumped messages from each
- Converting an empty list to a string
- Converting an example including a function call to a string
- Converting an example including a function _response_ to a string
- Converting an example including both a function call _and_ a function response to a string
- Converting an example with text, a function call, and a function response to a string
- Building System Information from a list of examples
- Building System Information from an example provider

### Testing Plan

I ran the tests personally, and they successfully passed.

**Unit Tests:**

- [✓] I have added or updated unit tests for my change.
- [✓] All unit tests pass locally.

33 passed in 2.55s

**Manual End-to-End (E2E) Tests:**

To test my change, simply run the unit tests and they should pass. If you want to verify the effectiveness of my tests, try modifying the input given to my unit tests and see if they fail.

### Checklist

- [✓] I have read the [CONTRIBUTING.md](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) document.
- [✓] I have performed a self-review of my own code.
- [✓] I have commented my code, particularly in hard-to-understand areas.
- [✓] I have added tests that prove my fix is effective or that my feature works.
- [✓] New and existing unit tests pass locally with my changes.
- [✓] I have manually tested my changes end-to-end.
- [✓] Any dependent changes have been merged and published in downstream modules.

### Additional context

- I noticed build_example_si takes an argument for query even though it's not used if you pass in a list rather than a BaseExampleProvider. Is this expected behavior that is there for a reason, or should I open an issue for this?
- I also noticed that when model is None, it defaults to Gemini 2. This seems like it could cause issues on the user end. Is there a reason for this functionality?

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/4802 from BowedMars374878:Issue-#4623 efa090e264b4cc155d4937ea2f2d8d90d7753ae6
PiperOrigin-RevId: 901025765

Change-Id: Ief9c2163e8d975f231a67a311967753a46c71d29
2026-04-17 14:23:07 -07:00
Liang Wu c0d6dbd3a0 fix(live): mark all agents' Event as from other agents
This will allow agent transfer to work smoothly in live workflow. See the comment added for details.

Closes issue #5238

Co-authored-by: Liang Wu <wuliang@google.com>
PiperOrigin-RevId: 900993889
Change-Id: I34c5f768482bc71351278dc0a2466885799dad1a
2026-04-17 14:22:40 -07:00
Liang Wu 59c92cbf88 fix(live): treat input transcription as user message
During agent transfer, the input transcription is represented as user input. Also, the previous implementation is incorrect in that the LlmResponse could never have content.role == 'user'. Modified the docstring to match the behavior.

Co-authored-by: Liang Wu <wuliang@google.com>
PiperOrigin-RevId: 900980704
Change-Id: Ia48057c7f02b10be63f6d0e255c07fab177e759d
2026-04-17 14:22:29 -07:00
Morgan Roux 17f3a8a929 chore: Replace old model gemini-2.0 references by 2.5
Merge https://github.com/google/adk-python/pull/4705

### Link to Issue or Description of Change

- Closes: #4666

**Problem:**
Gemini-2.0-flash model is still used in integration tests, but disabled for new users

**Solution:**
Replace all occurrences of Gemini-2.0-flash by Gemini-2.5-flash, in integration tests and docstrings.
Because gemini-2.0 is still enabled for some users, this PR doesn't clean the model from unit tests.

### Testing Plan
Run already existing unittests and integration tests

### Checklist

- [x] I have read the [CONTRIBUTING.md](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) document.
- [x] I have performed a self-review of my own code.
- [x] I have commented my code, particularly in hard-to-understand areas.
- [x] I have added tests that prove my fix is effective or that my feature works.
- [x] New and existing unit tests pass locally with my changes.
- [x] I have manually tested my changes end-to-end.
- [x] Any dependent changes have been merged and published in downstream modules.

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/4705 from morganroux:fix/issues/4666 3c390a2ed3abb7b2fc7bd99cfb801e8a2b1f5343
PiperOrigin-RevId: 900973552

Change-Id: I1d2dd4c749522b529537d5863e4a56865927da95
2026-04-17 14:22:21 -07:00
Enjoy Kumawat 9a962c90c6 fix: handle None state values in skill_toolset after session rewind
Merge https://github.com/google/adk-python/pull/5204

Co-authored-by: Xuan Yang <xygoogle@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/5204 from enjoykumawat:fix/load-skill-tool-none-guard ad027fc1d282be4ae28f7154ea7fcd3610a62e8c
PiperOrigin-RevId: 900930037

Change-Id: I9d7b06d87ffd95fd8926c497b226e2694ac1e578
2026-04-17 14:21:55 -07:00
George Weale 1237e01c8b feat: Add support for memories.ingest_events in VertexAiMemoryBankService
This change introduces `_add_events_to_memory_via_ingest` in `VertexAiMemoryBankService` to support adding events to memory using the `memories.ingest_events` API. This allows passing additional configurations such as `stream_id`, `force_flush`, and `generation_trigger_config`.

This path is triggered when `custom_metadata` contains `use_ingest` or `generation_trigger_config`.

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 900924805
Change-Id: Id1ae9f5050d38753c5d4791acc6f5ab3332a2f9f
2026-04-17 14:21:41 -07:00
Kathy Wu d3d0bdaf72 fix: Pass in auth headers with header provider instead of connection params
This allows headers to refresh on each request. Also only add auth headers if no auth_scheme or auth_credential is specified

Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 900900323
Change-Id: Idf2ec208616528612a428a5260ea0455dd986174
2026-04-17 14:21:13 -07:00
Shangjie Chen 87d3bf5355 test(workflow): Add test for nested workflow timeout
Verify that a nested workflow timeout raises NodeTimeoutError.

Change-Id: Ice80d167bd2edc81ea82ffb5d30a479df283c6df
2026-04-17 12:59:05 -07:00
Yifan Wang 421f99e003 add v2 flag for installing v2 dependecy when dpeloy to AE
Change-Id: Ic53d4121a1ef9ddc74809d897f5081a083826668
2026-04-17 11:21:54 -07:00
Bo Yang 745d861395 fix(tools): Resolve string annotations in FunctionTool to fix type conversion
When from __future__ import annotations is used, type hints are stored as strings. This caused is_list_of_basemodel to fail in FunctionTool, preventing dictionaries from being automatically converted back to Pydantic models when passed to tools. Used get_type_hints to evaluate them.

Also updated 10_burgers.json test trace to reflect the successful execution after this fix.

Change-Id: I92a708dd17ef76fc222779cd28e4a2d551325ce8
2026-04-16 22:08:11 -07:00
Xuan Yang 549c9379dc fix: Prevent LoopAgent from resetting sub-agent state on pause
The LoopAgent now only resets the state of its sub-agents and increments the loop count if the inner loop completes without being interrupted by an exit or pause signal. This ensures that when an invocation is paused, the sub-agent states are preserved for resumption.

Also cleanup dead code in base_agent.py.

Change-Id: Iab1285f44558d1334389920707b9a0c53d076428
2026-04-16 19:06:41 -07:00
Wei (Jack) Sun 4ff2722f0c feat(agents): Enforce request_task and suppress transfer for task agents
- Automatically wrap `mode='task'` sub-agents as tools in `LlmAgent`.
- Exclude `task` mode agents from valid transfer targets in `agent_transfer.py`.
- Suppress transfer instructions entirely for agents in `task` or `single_turn` mode.

Change-Id: I59d84f75915eb4f6258896656c93d05b2a4519cb
2026-04-16 17:30:16 -07:00
Bo Yang 6f46c656e5 feat(agents): Allow custom live model overrides for streaming sessions
Enables setting a default live model at the class level or overriding it, facilitating the use of custom or specialized models in live streaming scenarios.

Change-Id: I061556e1c0af95e25c307ab4ff63137939890406
2026-04-16 15:59:09 -07:00
Bo Yang 81a226ee61 refactor: rename is_parallel to use_sub_branch throughout workflow execution logic
Change-Id: I34eb070973dec2752338c454e4c4bd110466c925
2026-04-16 15:52:31 -07:00
Bo Yang b5ff5d7aec refactor(workflow): Centralize context derivation to simplify NodeRunner
Move node path and scheduler derivation logic into Context.__init__, removing duplicate path building logic in NodeRunner. This simplifies NodeRunner and ensures consistent path generation.

Also fixes a bug where root context paths were incorrectly prefixed with @1, causing test failures.

Change-Id: I55a2fa290221aa6fce87c8a2c476aa97788783a4
2026-04-16 15:36:28 -07:00
Shangjie Chen 1d09bb2101 perf(workflow): Isolate invocations during rehydration to improve performance
Filter events by invocation_id during rehydration in _reconstruct_node_states to avoid linear scans of unrelated invocations in long sessions. This improves performance from O(N) to O(K).

Change-Id: Ia5e03d6ce824905b61ec37753e52ff7e3917e48d
2026-04-16 14:46:18 -07:00
Bo Yang 990e56fb26 refactor(workflow): Remove triggered_by from workflow and context
Change-Id: I7bc1e1591de3dafec2eae9de0c83a1ca1b2ee7cd
2026-04-16 14:21:35 -07:00
Bo Yang cc3ba8a4cb refactor(workflow): Make JoinNode stateless by moving join logic to orchestrator
Moved the stateful aggregation logic from JoinNode to the Workflow orchestrator to simplify node implementation and prevent state leaks in loops. Removed in_nodes from Context as it is no longer needed.

Change-Id: If0677f65c735ee3bce985478f7990b4a3335b7df
2026-04-16 14:10:25 -07:00
Google Team Member 546ca735ba feat: Add "google-adk" user agent to Parameter Manager and Secret Manager clients
Update unit tests to verify the presence and value of the client_info argument during client initialization.

PiperOrigin-RevId: 900448054
Change-Id: I9204681625f4a83b3fb8e9114fd4ba90fa9b2c68
2026-04-16 14:07:16 -07:00
Liang Wu 98a443ac6a feat(live): Add live_session_id to LlmResponse
This change introduces a `live_session_id` field to the `LlmResponse` dataclass and populates it in all responses generated by the `GeminiLlmConnection` when using a Live session. This allows tracking which Live session each response belongs to.

Co-authored-by: Liang Wu <wuliang@google.com>
PiperOrigin-RevId: 900354397
Change-Id: I05be852aeba76ec62e70321ff8029654e5b22bdd
2026-04-16 14:07:04 -07:00
Shangjie Chen 2127220ff5 refactor(workflow): Rename _node_runner_class to _node_runner for consistency
Renamed _node_runner_class to _node_runner to follow naming conventions and updated tests accordingly.

Change-Id: Ibc47255e6422a9bcb1368b79933d4499909534d7
2026-04-16 13:35:32 -07:00
Shangjie Chen 21f9420888 refactor(events): Move _NodePathBuilder to events for clean layering
Moved _NodePathBuilder from workflow to events because it is used by NodeInfo in event.py, which is in the core events package. This avoids events depending on workflow.

Change-Id: If37b994ad9e7012af2027c9d80a984cef65c5255
2026-04-15 23:14:50 -07:00
Shangjie Chen 2d05804093 feat(workflow): enforce isidentifier on BaseNode names and fix tests
Enforced that BaseNode names must be valid Python identifiers, matching BaseAgent behavior.
Fixed tests that were using lambdas without explicit names.

Change-Id: I614d11c237d2b16c1fcd575606bbc95e11c42b17
2026-04-15 19:23:41 -07:00
Shangjie Chen bbfdbef567 refactor(workflow): Rename _scan_node_events to _reconstruct_node_states
Restored numbered comments in _scan_node_events and renamed it to _reconstruct_node_states to better reflect its purpose of reconstructing node states from events. Updated usages in _workflow.py and _dynamic_node_scheduler.py. Fixed imports and calls in test_rehydration_utils.py.

Change-Id: Ic693cbbce42fca80f82fe442503bbd9b3b15142f
2026-04-15 16:50:22 -07:00
Shangjie Chen 9c07e2c376 test(streaming): Enable streaming tests by removing conftest skip hook
The workflow-based LlmAgent was removed and replaced with the original LlmAgent, which supports the required streaming interfaces. The tests now pass successfully.

Change-Id: Iab47c3771eca70e2a7bcab4bebb128c480b14a61
2026-04-15 16:26:08 -07:00
Shangjie Chen 45560d9705 refactor(workflow): Move get_next_pending_nodes to WorkflowGraph and fix style
Move the helper function _get_next_pending_nodes from _trigger_processor.py into WorkflowGraph class as an instance method. This improves cohesion and allows keeping _trigger.py as a pure data model file containing just the Trigger class, while deleting _trigger_processor.py.

Also formatted _workflow_graph.py to use 2-space indentation to follow ADK style guide, and added unit tests for the new method in test_workflow_graph.py. All tests passed.

Change-Id: Ie4a3059388a99bffc82db3299ad83af3976a442d
2026-04-15 15:31:05 -07:00
George Weale 15329416cb fix(workflow): persist ctx.route across interrupt/resume for rerun_on_resume=False nodes
routes set via ctx.route were lost when a rerun_on_resume=False node
interrupted and resumed

Change-Id: Ic1927d67675adca5745bc41b76eb0a32a4476521
2026-04-15 15:20:21 -07:00
Xiang (Sean) Zhou 7969548259 feat: Add Vertex AI Agent Engine Sandbox integration for computer use
Implement AgentEngineSandboxComputer, a BaseComputer implementation that
    uses Vertex AI Agent Engine Computer Use Sandbox as a remote browser
    environment. This enables computer-use agents to operate in secure,
    isolated cloud-based browser environments.

    Key features:
    - SandboxClient: Low-level CDP client using vertexai SDK send_command()
    - AgentEngineSandboxComputer: BaseComputer implementation with session
      state management for sandbox sharing across invocations
    - Support for both auto-provisioning and bring-your-own-sandbox (BYOS) modes
    - Automatic token refresh and retry logic for transient navigation errors
    - Fix wait_5_seconds adapter to properly pass tool_context

Co-authored-by: Xiang (Sean) Zhou <seanzhougoogle@google.com>
PiperOrigin-RevId: 900292803
Change-Id: Ia1bdf22aa228b22367ece8ca9ba60ee96321ef99
2026-04-15 13:54:57 -07:00
Haiyuan Cao dd69ec4055 fix: Resolve BigQuery plugin issues with A2A transfers, spans, and metadata
Fixes #5073, #5310, and #5311 with three targeted updates to the `BigQueryAgentAnalyticsPlugin` (no changes to ADK core):
- Classifies `TransferToAgentTool` transfers to `RemoteA2aAgent` as `TRANSFER_A2A` instead of the generic `TRANSFER_AGENT` by resolving the target agent at the call level.
- Ensures a self-consistent BigQuery span tree by preferring the plugin's internal span stack over ambient OTel spans, resolving dangling `parent_span_id` references.
- Surfaces remote A2A interaction metadata (`a2a:request`, `a2a:response`, etc.) in BigQuery by detecting them in custom metadata and logging new `A2A_INTERACTION` events.

Co-authored-by: Haiyuan Cao <haiyuan@google.com>
PiperOrigin-RevId: 900224778
Change-Id: I1b28a2d5b77e4831d035b56448061db4988dcece
2026-04-15 13:54:41 -07:00
Xuan Yang bb6a4b6488 refactor(agents): Revert "Support ADK 2.0 workflow execution in SequentialAgent"
Change-Id: I1ed948f8b79a707ea505c297457dc56e2d155e44
2026-04-15 13:53:38 -07:00
George Weale 654bcd0904 fix: execute on_event_callback before append_event to persist plugin modifications
Previously, on_event_callback ran after append_event, so plugin
modifications (e.g. custom_metadata) were only visible in the
yielded stream but never persisted to the session store. This
moves the callback before persistence, matching the documented
contract in base_plugin.py.

Fixes #3990

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 899845848
Change-Id: I7243da8767d7c5a5a28d4a433db3c5b82c494281
2026-04-15 13:52:48 -07:00
Liang Wu 8995fde1ac refactor(live): Use send_client_content to send conversation history
The `send` method was deprecated.

Co-authored-by: Liang Wu <wuliang@google.com>
PiperOrigin-RevId: 899826160
Change-Id: Ib400cc1fc7c8260ac1c946bb27f27af8d9d9fb3c
2026-04-15 13:51:37 -07:00
Liang Wu 17caa61b88 refactor(live): Use send_tool_response for function responses
The Gemini session now has a dedicated `send_tool_response` method, which is used instead of wrapping function responses in a `LiveClientToolResponse` and calling `send`.

`send` is deprecated.

Co-authored-by: Liang Wu <wuliang@google.com>
PiperOrigin-RevId: 899796634
Change-Id: I527909d64265628440098978583386a82e573fcb
2026-04-15 13:50:53 -07:00
Shangjie Chen c9a5b44764 refactor(workflow): Clarify dynamic node fallback by renaming variables
Rename _schedule_dynamic_node_internal to _workflow_scheduler to clearly distinguish it from standalone execution paths. This prevents maintainers from misinterpreting valid fallback logic as dead legacy code. Also fix associated unit test mocks.

Change-Id: Ic76fd7348235321a460a0af31687f2e18b5524cc
2026-04-15 13:29:07 -07:00
Shangjie Chen c92763b4db refactor(workflow): Consolidate rehydration logic for better maintainability
Move _wrap_response, _unwrap_response, validate_resume_response, and _scan_node_events from monolithic utility files to dedicated _rehydration_utils.py, and split tests accordingly.

Change-Id: Icb99765668c576d459c564187ab4a073c66a2837
2026-04-15 13:05:27 -07:00
George Weale a962b55c1f fix(workflow): use DFS reachability check from START in graph validation
The connectivity check only verified that each node appeared as a to_node in some edge, which allowed disconnected routed subgraphs (e.g., B -> C (route='x'), C -> B (route='y')) to pass validation even though they were unreachable from START. Replace with a DFS traversal from START that computes true reachability.

Change-Id: I9f0457acbbbf9959d23792106cfa72a7c6e5b009
2026-04-15 11:34:41 -07:00