runsight-core 0.2.2__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {runsight_core-0.2.2 → runsight_core-0.2.3}/PKG-INFO +1 -1
- {runsight_core-0.2.2 → runsight_core-0.2.3}/pyproject.toml +1 -1
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/blocks/_registry.py +10 -1
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/blocks/code.py +1 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/blocks/dispatch.py +1 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/blocks/gate.py +1 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/blocks/linear.py +1 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/blocks/loop.py +1 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/blocks/synthesize.py +1 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/blocks/workflow_block.py +93 -10
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/yaml/parser.py +15 -97
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core.egg-info/PKG-INFO +1 -1
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core.egg-info/SOURCES.txt +1 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run219_auto_registration.py +38 -0
- runsight_core-0.2.3/tests/test_run774_workflow_block_builder_path.py +189 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/README.md +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/setup.cfg +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/__init__.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/artifacts.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/assertions/__init__.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/assertions/base.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/assertions/contract.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/assertions/custom.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/assertions/deterministic/__init__.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/assertions/deterministic/linguistic.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/assertions/deterministic/performance.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/assertions/deterministic/string.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/assertions/deterministic/structural.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/assertions/registry.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/assertions/scoring.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/blocks/__init__.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/blocks/_helpers.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/blocks/base.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/blocks/registry.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/budget_enforcement.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/conditions/__init__.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/conditions/engine.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/eval/__init__.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/eval/runner.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/isolation/__init__.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/isolation/credentials.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/isolation/envelope.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/isolation/errors.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/isolation/handlers.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/isolation/harness.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/isolation/interceptors.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/isolation/ipc.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/isolation/ipc_models.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/isolation/pool.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/isolation/worker.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/isolation/worker_proxies.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/isolation/worker_support.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/isolation/wrapper.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/llm/__init__.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/llm/client.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/llm/model_catalog.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/memory/__init__.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/memory/budget.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/memory/token_counting.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/memory/windowing.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/observer.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/primitives.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/py.typed +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/runner.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/security.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/state.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/tools/__init__.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/tools/_catalog.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/tools/contract.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/tools/delegate.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/tools/file_io.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/tools/http.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/workflow.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/yaml/__init__.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/yaml/discovery/__init__.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/yaml/discovery/_assertion.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/yaml/discovery/_base.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/yaml/discovery/_soul.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/yaml/discovery/_tool.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/yaml/discovery/_workflow.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/yaml/registry.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/yaml/schema.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core.egg-info/dependency_links.txt +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core.egg-info/requires.txt +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core.egg-info/top_level.txt +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_achat_budget_enforcement.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_artifact_store_wiring.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_artifacts.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_base_block.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_baseblock_artifact_helpers.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_block_timeout_enforcement.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_blocks.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_budget_enforcement_types.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_budget_limits_schema.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_budget_migration_remaining.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_budget_models.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_budget_session.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_budget_wiring.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_carry_context_blockresult.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_code_block.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_codeblock_sandbox_hardening.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_composite_observer_isolation.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_condition_engine.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_conftest_isolation_mock.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_context_truncation.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_conversation_histories.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_cross_feature_integration.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_custom_asset_tool_contract.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_discovery.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_dispatch_block_stateful.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_dispatch_budget_isolation.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_dispatch_exit_def.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_dispatch_synthesize_integration.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_dispatch_v2.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_e2e_block_timeout.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_e2e_cost_cap.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_e2e_dispatch_budget.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_e2e_warn_and_flow_timeout.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_fit_to_budget_phase1.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_fit_to_budget_phase2.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_gate_error_subclass.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_gate_file_writer_blocks.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_integration_blocks_workflow.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_integration_merge_validation.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_integration_runner_primitives.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_integration_state_blocks.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_integration_workflow.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_integration_workflow_block.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_integration_workflow_block_backward_compat.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_integration_workflow_block_e2e.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_integration_workflow_block_parser.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_integration_workflow_block_with_other_blocks.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_iso_001_envelope_models.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_iso_002_ipc_protocol.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_iso_003_harness.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_iso_004_worker.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_iso_005_block_migration.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_iso_006_dispatch_delegate.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_iso_007_monitoring.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_iso_008_credentials.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_iso_e2e.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_linearblock_stateful.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_loop_block.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_loop_break_conditions.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_loop_carry_context.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_loop_exports_schema.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_loop_workflow_validation.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_loopblock_kwargs_forwarding.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_loopblock_stateful_integration.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_model_catalog.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_observer.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_observer_soul_extension.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_parser_inputs_outputs.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_parser_workflow_block.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_primitives.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_primitives_extended.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_prompt_hash.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_registry.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_remove_placeholder_block.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_retry_config.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_retry_execution.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_retry_stateful.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_retryblock_migration.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run126_code_block_parser_and_achat.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run127_runner_get_client_api_key.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run137_async_subprocess.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run141_multi_provider_keys.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run170_complex_read_sites.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run177_block_result.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run178_write_sites_block_result.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run179_strict_block_result.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run181_read_site_migration.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run222_migrate_blocks.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run377_yaml_enabled.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run415_no_builtin_souls.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run468_parser_soul_field_forwarding.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run469_discover_soul_fields.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run569_project_root_resolution.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run570_kill_inline_souls.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run571_wire_soul_ref_to_library.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run572_library_soul_tool_governance.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run603_workflow_interface_schema.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run604_interface_execution.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run605_on_error_modes.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run606_runtime_depth_parity.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run614_integration_subworkflow.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run628_noise_cleanup_verification.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run629_dispatch_e2e.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run644_dispatch_runtime_rename.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run645_dispatch_schema_canonicalization.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run663_child_observer_wrapper.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run663_parser_round_trip.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run668_depends_error_routes.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run669_gate_shortcuts.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run670_error_route_runtime.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run671_routes_shorthand.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run675_block_execution_context.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run676_execute_block_extraction.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run677_workflow_run_execute_block_wiring.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run678_loop_execute_block_wiring.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run680_codeblock_exit_handle.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run681_linearblock_exit_conditions.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run682_workflowblock_loopblock_e2e.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run683_nested_loopblock_observer.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run684_exit_handle_all_block_types.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run685_eval_debt_integration.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run688_soul_assertions_cleanup.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run690_delete_duplicate_resolve_soul.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run692_inline_soul_fixture_migration.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run693_step_wrapper_assertions.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run694_eval_yaml_schema.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run695_eval_runner.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run699_eval_integration.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run700_eval_e2e.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run701_state_isolation_verification.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run702_mixed_pipeline_e2e.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run703_dispatch_in_loop_e2e.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run704_error_route_output_mapping_e2e.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run787_discovery_foundation.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run788_workflow_repo_soul_scanner.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run789_tool_scanner_callers.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run789_tools_router_scanner.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run790_workflow_repo_scanner.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run790_workflow_scanner.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run792_unified_scanner_integration.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run794_assertion_scanner.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run797_custom_assertion_registration.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run800_custom_assertion_eval_e2e.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run803_tool_pydantic_validation.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run817_ipc_models_extract.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run818_interceptors_extract.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run819_worker_proxies_extract.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run820_worker_support_extract.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_run_821_docker_hardening.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_runner.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_runner_messages.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_sandbox_hardening.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_schema.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_schema_validation.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_state.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_tool_integration.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_tool_registry.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_windowing.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_workflow.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_workflow_block_execute.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_workflow_block_recursion.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_workflow_defensive_observer.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_workflow_output_conditions.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_yaml_assertions_config.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_yaml_dx_e2e.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_yaml_dx_sugar.py +0 -0
- {runsight_core-0.2.2 → runsight_core-0.2.3}/tests/test_yaml_parser.py +0 -0
|
@@ -33,7 +33,16 @@ def register_block_def(block_type: str, def_cls: Type["Any"]) -> None:
|
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def register_block_builder(block_type: str, builder: Callable) -> None:
|
|
36
|
-
"""Register a builder callable for *block_type*.
|
|
36
|
+
"""Register a builder callable for *block_type*.
|
|
37
|
+
|
|
38
|
+
Raises ``ValueError`` if *block_type* is already registered with a
|
|
39
|
+
**different** callable (re-registering the same callable is idempotent).
|
|
40
|
+
"""
|
|
41
|
+
existing = BLOCK_BUILDER_REGISTRY.get(block_type)
|
|
42
|
+
if existing is not None and existing is not builder:
|
|
43
|
+
raise ValueError(
|
|
44
|
+
f"Duplicate block-builder registration for '{block_type}': {existing!r} vs {builder!r}"
|
|
45
|
+
)
|
|
37
46
|
BLOCK_BUILDER_REGISTRY[block_type] = builder
|
|
38
47
|
|
|
39
48
|
|
|
@@ -17,7 +17,7 @@ from runsight_core.state import BlockResult, WorkflowState
|
|
|
17
17
|
if TYPE_CHECKING:
|
|
18
18
|
from runsight_core.workflow import Workflow
|
|
19
19
|
from runsight_core.yaml.registry import WorkflowRegistry
|
|
20
|
-
from runsight_core.yaml.schema import WorkflowInterfaceDef
|
|
20
|
+
from runsight_core.yaml.schema import RunsightWorkflowFile, WorkflowInterfaceDef
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class WorkflowBlock(BaseBlock):
|
|
@@ -392,6 +392,56 @@ from runsight_core.blocks._registry import register_block_def as _register_block
|
|
|
392
392
|
_register_block_def("workflow", WorkflowBlockDef)
|
|
393
393
|
|
|
394
394
|
|
|
395
|
+
def _validate_workflow_block_contract(
|
|
396
|
+
block_id: str,
|
|
397
|
+
block_def: Any,
|
|
398
|
+
child_file: "RunsightWorkflowFile",
|
|
399
|
+
) -> None:
|
|
400
|
+
child_interface = child_file.interface
|
|
401
|
+
if child_interface is None:
|
|
402
|
+
raise ValueError(
|
|
403
|
+
f"WorkflowBlock '{block_id}': child workflow '{block_def.workflow_ref}' "
|
|
404
|
+
"must declare an interface"
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
declared_inputs = {item.name: item for item in child_interface.inputs}
|
|
408
|
+
declared_outputs = {item.name for item in child_interface.outputs}
|
|
409
|
+
|
|
410
|
+
for binding_name in (block_def.inputs or {}).keys():
|
|
411
|
+
if binding_name not in declared_inputs:
|
|
412
|
+
raise ValueError(
|
|
413
|
+
f"WorkflowBlock '{block_id}': unknown interface input '{binding_name}'. "
|
|
414
|
+
f"Declared child inputs: {sorted(declared_inputs)}"
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
missing_required = [
|
|
418
|
+
item.name
|
|
419
|
+
for item in child_interface.inputs
|
|
420
|
+
if item.required and item.default is None and item.name not in (block_def.inputs or {})
|
|
421
|
+
]
|
|
422
|
+
if missing_required:
|
|
423
|
+
raise ValueError(
|
|
424
|
+
f"WorkflowBlock '{block_id}': missing required interface inputs {missing_required}"
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
for binding_name in (block_def.outputs or {}).values():
|
|
428
|
+
if binding_name not in declared_outputs:
|
|
429
|
+
raise ValueError(
|
|
430
|
+
f"WorkflowBlock '{block_id}': unknown interface output '{binding_name}'. "
|
|
431
|
+
f"Declared child outputs: {sorted(declared_outputs)}"
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def _resolve_workflow_block_max_depth(
|
|
436
|
+
file_def: Any,
|
|
437
|
+
block_def: Any,
|
|
438
|
+
) -> int:
|
|
439
|
+
"""Resolve the max_depth value a workflow block will enforce at runtime."""
|
|
440
|
+
if block_def.max_depth is not None:
|
|
441
|
+
return block_def.max_depth
|
|
442
|
+
return file_def.config.get("max_workflow_depth", 10)
|
|
443
|
+
|
|
444
|
+
|
|
395
445
|
# -- Builder function --------------------------------------------------------
|
|
396
446
|
|
|
397
447
|
|
|
@@ -401,17 +451,50 @@ def build(
|
|
|
401
451
|
souls_map: Dict[str, Any],
|
|
402
452
|
runner: Any,
|
|
403
453
|
all_blocks: Dict[str, Any],
|
|
454
|
+
*,
|
|
455
|
+
workflow_registry: "WorkflowRegistry" | None = None,
|
|
456
|
+
api_keys: Dict[str, str] | None = None,
|
|
457
|
+
workflow_base_dir: str = ".",
|
|
458
|
+
parent_file_def: Any | None = None,
|
|
459
|
+
**_: Any,
|
|
404
460
|
) -> WorkflowBlock:
|
|
405
|
-
"""Build a WorkflowBlock from a block definition.
|
|
461
|
+
"""Build a WorkflowBlock from a block definition."""
|
|
462
|
+
if getattr(block_def, "workflow_ref", None) is None:
|
|
463
|
+
raise ValueError(f"WorkflowBlock '{block_id}': workflow_ref is required")
|
|
464
|
+
if workflow_registry is None:
|
|
465
|
+
raise ValueError(
|
|
466
|
+
f"WorkflowBlock '{block_id}': workflow_registry must be provided "
|
|
467
|
+
"when building workflow blocks"
|
|
468
|
+
)
|
|
406
469
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
470
|
+
# Import the parser lazily to keep block registration free of parser cycles.
|
|
471
|
+
from runsight_core.yaml.parser import parse_workflow_yaml
|
|
472
|
+
|
|
473
|
+
child_file = workflow_registry.get(block_def.workflow_ref)
|
|
474
|
+
_validate_workflow_block_contract(block_id, block_def, child_file)
|
|
475
|
+
|
|
476
|
+
child_raw = child_file.model_dump() if hasattr(child_file, "model_dump") else child_file
|
|
477
|
+
child_wf = parse_workflow_yaml(
|
|
478
|
+
child_raw,
|
|
479
|
+
workflow_registry=workflow_registry,
|
|
480
|
+
api_keys=api_keys,
|
|
481
|
+
_base_dir=workflow_base_dir,
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
max_depth = (
|
|
485
|
+
_resolve_workflow_block_max_depth(parent_file_def, block_def)
|
|
486
|
+
if parent_file_def is not None
|
|
487
|
+
else block_def.max_depth or 10
|
|
488
|
+
)
|
|
489
|
+
return WorkflowBlock(
|
|
490
|
+
block_id=block_id,
|
|
491
|
+
child_workflow=child_wf,
|
|
492
|
+
inputs=block_def.inputs or {},
|
|
493
|
+
outputs=block_def.outputs or {},
|
|
494
|
+
workflow_ref=block_def.workflow_ref,
|
|
495
|
+
max_depth=max_depth,
|
|
496
|
+
interface=child_file.interface,
|
|
497
|
+
on_error=block_def.on_error,
|
|
415
498
|
)
|
|
416
499
|
|
|
417
500
|
|
|
@@ -386,45 +386,10 @@ from runsight_core.yaml.schema import rebuild_block_def_union as _rebuild # noq
|
|
|
386
386
|
_rebuild()
|
|
387
387
|
del _rebuild
|
|
388
388
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
child_file: RunsightWorkflowFile,
|
|
394
|
-
) -> None:
|
|
395
|
-
child_interface = child_file.interface
|
|
396
|
-
if child_interface is None:
|
|
397
|
-
raise ValueError(
|
|
398
|
-
f"WorkflowBlock '{block_id}': child workflow '{block_def.workflow_ref}' "
|
|
399
|
-
"must declare an interface"
|
|
400
|
-
)
|
|
401
|
-
|
|
402
|
-
declared_inputs = {item.name: item for item in child_interface.inputs}
|
|
403
|
-
declared_outputs = {item.name for item in child_interface.outputs}
|
|
404
|
-
|
|
405
|
-
for binding_name in (block_def.inputs or {}).keys():
|
|
406
|
-
if binding_name not in declared_inputs:
|
|
407
|
-
raise ValueError(
|
|
408
|
-
f"WorkflowBlock '{block_id}': unknown interface input '{binding_name}'. "
|
|
409
|
-
f"Declared child inputs: {sorted(declared_inputs)}"
|
|
410
|
-
)
|
|
411
|
-
|
|
412
|
-
missing_required = [
|
|
413
|
-
item.name
|
|
414
|
-
for item in child_interface.inputs
|
|
415
|
-
if item.required and item.default is None and item.name not in (block_def.inputs or {})
|
|
416
|
-
]
|
|
417
|
-
if missing_required:
|
|
418
|
-
raise ValueError(
|
|
419
|
-
f"WorkflowBlock '{block_id}': missing required interface inputs {missing_required}"
|
|
420
|
-
)
|
|
421
|
-
|
|
422
|
-
for binding_name in (block_def.outputs or {}).values():
|
|
423
|
-
if binding_name not in declared_outputs:
|
|
424
|
-
raise ValueError(
|
|
425
|
-
f"WorkflowBlock '{block_id}': unknown interface output '{binding_name}'. "
|
|
426
|
-
f"Declared child outputs: {sorted(declared_outputs)}"
|
|
427
|
-
)
|
|
389
|
+
from runsight_core.blocks.workflow_block import ( # noqa: E402
|
|
390
|
+
_resolve_workflow_block_max_depth,
|
|
391
|
+
_validate_workflow_block_contract,
|
|
392
|
+
)
|
|
428
393
|
|
|
429
394
|
|
|
430
395
|
def _validate_workflow_block_runtime_placement(
|
|
@@ -436,16 +401,6 @@ def _validate_workflow_block_runtime_placement(
|
|
|
436
401
|
return None
|
|
437
402
|
|
|
438
403
|
|
|
439
|
-
def _resolve_workflow_block_max_depth(
|
|
440
|
-
file_def: RunsightWorkflowFile,
|
|
441
|
-
block_def: Any,
|
|
442
|
-
) -> int:
|
|
443
|
-
"""Resolve the max_depth value a workflow block will enforce at runtime."""
|
|
444
|
-
if block_def.max_depth is not None:
|
|
445
|
-
return block_def.max_depth
|
|
446
|
-
return file_def.config.get("max_workflow_depth", 10)
|
|
447
|
-
|
|
448
|
-
|
|
449
404
|
def validate_workflow_call_contracts(
|
|
450
405
|
file_def: RunsightWorkflowFile,
|
|
451
406
|
*,
|
|
@@ -643,53 +598,6 @@ def parse_workflow_yaml(
|
|
|
643
598
|
# Step 5: Build all blocks (single pass)
|
|
644
599
|
built_blocks: Dict[str, BaseBlock] = {}
|
|
645
600
|
for block_id, block_def in file_def.blocks.items():
|
|
646
|
-
# Special case: WorkflowBlock (type: workflow)
|
|
647
|
-
# Handle before registry lookup to avoid "unknown type" error
|
|
648
|
-
if block_def.type == "workflow":
|
|
649
|
-
# Validate workflow_ref field (redundant check — already enforced by BlockDef validator)
|
|
650
|
-
if block_def.workflow_ref is None:
|
|
651
|
-
raise ValueError(f"WorkflowBlock '{block_id}': workflow_ref is required")
|
|
652
|
-
|
|
653
|
-
# Require workflow_registry parameter
|
|
654
|
-
if workflow_registry is None:
|
|
655
|
-
raise ValueError(
|
|
656
|
-
f"WorkflowBlock '{block_id}': a WorkflowRegistry must be provided "
|
|
657
|
-
f"to parse_workflow_yaml() when workflow blocks are used. "
|
|
658
|
-
f"Pass workflow_registry=... parameter."
|
|
659
|
-
)
|
|
660
|
-
|
|
661
|
-
# Resolve child workflow file (registry returns RunsightWorkflowFile)
|
|
662
|
-
child_file = workflow_registry.get(block_def.workflow_ref)
|
|
663
|
-
_validate_workflow_block_contract(block_id, block_def, child_file)
|
|
664
|
-
|
|
665
|
-
# Normalize to dict so parse_workflow_yaml receives Union[str, Dict] not model instance
|
|
666
|
-
child_raw = child_file.model_dump() if hasattr(child_file, "model_dump") else child_file
|
|
667
|
-
|
|
668
|
-
# Recursively parse child workflow (passes registry + base_dir for nested workflows)
|
|
669
|
-
child_wf = parse_workflow_yaml(
|
|
670
|
-
child_raw,
|
|
671
|
-
workflow_registry=workflow_registry,
|
|
672
|
-
api_keys=api_keys,
|
|
673
|
-
_base_dir=workflow_base_dir,
|
|
674
|
-
)
|
|
675
|
-
|
|
676
|
-
# Instantiate WorkflowBlock
|
|
677
|
-
from runsight_core.blocks.workflow_block import WorkflowBlock
|
|
678
|
-
|
|
679
|
-
block = WorkflowBlock(
|
|
680
|
-
block_id=block_id,
|
|
681
|
-
child_workflow=child_wf,
|
|
682
|
-
inputs=block_def.inputs or {},
|
|
683
|
-
outputs=block_def.outputs or {},
|
|
684
|
-
workflow_ref=block_def.workflow_ref,
|
|
685
|
-
max_depth=_resolve_workflow_block_max_depth(file_def, block_def),
|
|
686
|
-
interface=child_file.interface,
|
|
687
|
-
on_error=block_def.on_error,
|
|
688
|
-
)
|
|
689
|
-
|
|
690
|
-
built_blocks[block_id] = block
|
|
691
|
-
continue # Skip registry lookup
|
|
692
|
-
|
|
693
601
|
from runsight_core.blocks._registry import get_builder
|
|
694
602
|
|
|
695
603
|
builder = get_builder(block_def.type)
|
|
@@ -700,7 +608,17 @@ def parse_workflow_yaml(
|
|
|
700
608
|
f"Unknown block type '{block_def.type}' for block '{block_id}'. "
|
|
701
609
|
f"Available types: {sorted(BLOCK_BUILDER_REGISTRY.keys())}"
|
|
702
610
|
)
|
|
703
|
-
built_blocks[block_id] = builder(
|
|
611
|
+
built_blocks[block_id] = builder(
|
|
612
|
+
block_id,
|
|
613
|
+
block_def,
|
|
614
|
+
souls_map,
|
|
615
|
+
runner,
|
|
616
|
+
built_blocks,
|
|
617
|
+
workflow_registry=workflow_registry,
|
|
618
|
+
api_keys=api_keys,
|
|
619
|
+
workflow_base_dir=workflow_base_dir,
|
|
620
|
+
parent_file_def=file_def,
|
|
621
|
+
)
|
|
704
622
|
|
|
705
623
|
# Step 6.2: Bridge _declared_exits from schema to runtime blocks
|
|
706
624
|
for block_id, block_def in file_def.blocks.items():
|
|
@@ -213,6 +213,7 @@ tests/test_run701_state_isolation_verification.py
|
|
|
213
213
|
tests/test_run702_mixed_pipeline_e2e.py
|
|
214
214
|
tests/test_run703_dispatch_in_loop_e2e.py
|
|
215
215
|
tests/test_run704_error_route_output_mapping_e2e.py
|
|
216
|
+
tests/test_run774_workflow_block_builder_path.py
|
|
216
217
|
tests/test_run787_discovery_foundation.py
|
|
217
218
|
tests/test_run788_workflow_repo_soul_scanner.py
|
|
218
219
|
tests/test_run789_tool_scanner_callers.py
|
|
@@ -101,6 +101,44 @@ class TestBlockDefRegistry:
|
|
|
101
101
|
# Cleanup
|
|
102
102
|
BLOCK_BUILDER_REGISTRY.pop("test_builder_type", None)
|
|
103
103
|
|
|
104
|
+
def test_register_block_builder_is_idempotent_for_same_callable(self):
|
|
105
|
+
"""register_block_builder permits re-registering the same callable."""
|
|
106
|
+
from runsight_core.blocks._registry import (
|
|
107
|
+
BLOCK_BUILDER_REGISTRY,
|
|
108
|
+
register_block_builder,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def fake_builder():
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
register_block_builder("same_builder_type", fake_builder)
|
|
116
|
+
register_block_builder("same_builder_type", fake_builder)
|
|
117
|
+
assert BLOCK_BUILDER_REGISTRY.get("same_builder_type") is fake_builder
|
|
118
|
+
finally:
|
|
119
|
+
BLOCK_BUILDER_REGISTRY.pop("same_builder_type", None)
|
|
120
|
+
|
|
121
|
+
def test_register_block_builder_rejects_different_duplicate(self):
|
|
122
|
+
"""register_block_builder rejects silent builder replacement."""
|
|
123
|
+
from runsight_core.blocks._registry import (
|
|
124
|
+
BLOCK_BUILDER_REGISTRY,
|
|
125
|
+
register_block_builder,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def first_builder():
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
def second_builder():
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
register_block_builder("conflicting_builder_type", first_builder)
|
|
136
|
+
with pytest.raises(ValueError, match="Duplicate block-builder registration"):
|
|
137
|
+
register_block_builder("conflicting_builder_type", second_builder)
|
|
138
|
+
assert BLOCK_BUILDER_REGISTRY.get("conflicting_builder_type") is first_builder
|
|
139
|
+
finally:
|
|
140
|
+
BLOCK_BUILDER_REGISTRY.pop("conflicting_builder_type", None)
|
|
141
|
+
|
|
104
142
|
def test_get_all_block_types_returns_dict(self):
|
|
105
143
|
"""get_all_block_types() returns a dict snapshot of registered types."""
|
|
106
144
|
from runsight_core.blocks._registry import get_all_block_types
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Failing tests for RUN-774: route workflow blocks through the registered builder.
|
|
3
|
+
|
|
4
|
+
These tests pin the remaining actionable debt in the workflow-block parser path:
|
|
5
|
+
- parse_workflow_yaml should use the registered "workflow" builder, not a parser-only special case
|
|
6
|
+
- the registered builder should be able to construct a real WorkflowBlock when given parser context
|
|
7
|
+
- missing WorkflowRegistry should fail with an actionable ValueError from the builder path
|
|
8
|
+
- workflow blocks referenced from LoopBlock.inner_block_refs should still come through the builder path
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
from unittest.mock import MagicMock
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
from runsight_core import LoopBlock, WorkflowBlock
|
|
19
|
+
from runsight_core.blocks._registry import BLOCK_BUILDER_REGISTRY
|
|
20
|
+
from runsight_core.workflow import Workflow
|
|
21
|
+
from runsight_core.yaml.parser import parse_workflow_yaml
|
|
22
|
+
from runsight_core.yaml.registry import WorkflowRegistry
|
|
23
|
+
from runsight_core.yaml.schema import RunsightWorkflowFile
|
|
24
|
+
|
|
25
|
+
_EMPTY_INTERFACE = {"inputs": [], "outputs": []}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _child_workflow_file() -> RunsightWorkflowFile:
|
|
29
|
+
child_dict = {
|
|
30
|
+
"version": "1.0",
|
|
31
|
+
"interface": _EMPTY_INTERFACE,
|
|
32
|
+
"blocks": {
|
|
33
|
+
"child_step": {
|
|
34
|
+
"type": "code",
|
|
35
|
+
"code": "def main(data):\n return {'child_step': 'done'}",
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"workflow": {
|
|
39
|
+
"name": "child_workflow",
|
|
40
|
+
"entry": "child_step",
|
|
41
|
+
"transitions": [{"from": "child_step", "to": None}],
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
return RunsightWorkflowFile.model_validate(child_dict)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _parent_workflow_dict() -> dict[str, Any]:
|
|
48
|
+
return {
|
|
49
|
+
"version": "1.0",
|
|
50
|
+
"config": {
|
|
51
|
+
"max_workflow_depth": 7,
|
|
52
|
+
},
|
|
53
|
+
"blocks": {
|
|
54
|
+
"invoke_child": {
|
|
55
|
+
"type": "workflow",
|
|
56
|
+
"workflow_ref": "child_workflow",
|
|
57
|
+
},
|
|
58
|
+
"loop_block": {
|
|
59
|
+
"type": "loop",
|
|
60
|
+
"inner_block_refs": ["invoke_child"],
|
|
61
|
+
"max_rounds": 1,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
"workflow": {
|
|
65
|
+
"name": "parent_workflow",
|
|
66
|
+
"entry": "invoke_child",
|
|
67
|
+
"transitions": [
|
|
68
|
+
{"from": "invoke_child", "to": "loop_block"},
|
|
69
|
+
{"from": "loop_block", "to": None},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_parse_workflow_yaml_routes_workflow_blocks_through_registered_builder_even_inside_loops(
|
|
76
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Parsing should invoke the registered workflow builder instead of a parser special case."""
|
|
79
|
+
child_file = _child_workflow_file()
|
|
80
|
+
registry = WorkflowRegistry(allow_filesystem_fallback=False)
|
|
81
|
+
registry.register("child_workflow", child_file)
|
|
82
|
+
|
|
83
|
+
calls: list[dict[str, Any]] = []
|
|
84
|
+
|
|
85
|
+
def spy_builder(
|
|
86
|
+
block_id: str,
|
|
87
|
+
block_def: Any,
|
|
88
|
+
souls_map: dict[str, Any],
|
|
89
|
+
runner: Any,
|
|
90
|
+
all_blocks: dict[str, Any],
|
|
91
|
+
**parser_context: Any,
|
|
92
|
+
) -> WorkflowBlock:
|
|
93
|
+
calls.append(
|
|
94
|
+
{
|
|
95
|
+
"block_id": block_id,
|
|
96
|
+
"block_def": block_def,
|
|
97
|
+
"souls_map": souls_map,
|
|
98
|
+
"runner": runner,
|
|
99
|
+
"all_blocks": dict(all_blocks),
|
|
100
|
+
"parser_context": parser_context,
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
child_workflow = parse_workflow_yaml(
|
|
104
|
+
child_file.model_dump(),
|
|
105
|
+
workflow_registry=parser_context["workflow_registry"],
|
|
106
|
+
api_keys=parser_context.get("api_keys"),
|
|
107
|
+
_base_dir=parser_context["workflow_base_dir"],
|
|
108
|
+
)
|
|
109
|
+
parent_file_def = parser_context["parent_file_def"]
|
|
110
|
+
max_depth = (
|
|
111
|
+
block_def.max_depth
|
|
112
|
+
if block_def.max_depth is not None
|
|
113
|
+
else parent_file_def.config.get("max_workflow_depth", 10)
|
|
114
|
+
)
|
|
115
|
+
return WorkflowBlock(
|
|
116
|
+
block_id=block_id,
|
|
117
|
+
child_workflow=child_workflow,
|
|
118
|
+
inputs=block_def.inputs or {},
|
|
119
|
+
outputs=block_def.outputs or {},
|
|
120
|
+
workflow_ref=block_def.workflow_ref,
|
|
121
|
+
max_depth=max_depth,
|
|
122
|
+
interface=child_file.interface,
|
|
123
|
+
on_error=block_def.on_error,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
monkeypatch.setitem(BLOCK_BUILDER_REGISTRY, "workflow", spy_builder)
|
|
127
|
+
|
|
128
|
+
parent_workflow = parse_workflow_yaml(
|
|
129
|
+
_parent_workflow_dict(),
|
|
130
|
+
workflow_registry=registry,
|
|
131
|
+
_base_dir=str(tmp_path),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
assert calls, "parse_workflow_yaml did not call the registered workflow builder"
|
|
135
|
+
assert isinstance(parent_workflow, Workflow)
|
|
136
|
+
assert isinstance(parent_workflow.blocks["invoke_child"], WorkflowBlock)
|
|
137
|
+
assert parent_workflow.blocks["invoke_child"].child_workflow.name == "child_workflow"
|
|
138
|
+
assert isinstance(parent_workflow.blocks["loop_block"], LoopBlock)
|
|
139
|
+
assert parent_workflow.blocks["loop_block"].inner_block_refs == ["invoke_child"]
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_registered_workflow_builder_accepts_parser_context_and_builds_real_workflow_block(
|
|
143
|
+
tmp_path: Path,
|
|
144
|
+
) -> None:
|
|
145
|
+
"""The registered workflow builder should build a real WorkflowBlock with parser context."""
|
|
146
|
+
child_file = _child_workflow_file()
|
|
147
|
+
registry = WorkflowRegistry(allow_filesystem_fallback=False)
|
|
148
|
+
registry.register("child_workflow", child_file)
|
|
149
|
+
parent_file = RunsightWorkflowFile.model_validate(_parent_workflow_dict())
|
|
150
|
+
block_def = parent_file.blocks["invoke_child"]
|
|
151
|
+
builder = BLOCK_BUILDER_REGISTRY["workflow"]
|
|
152
|
+
|
|
153
|
+
block = builder(
|
|
154
|
+
"invoke_child",
|
|
155
|
+
block_def,
|
|
156
|
+
{},
|
|
157
|
+
MagicMock(),
|
|
158
|
+
{},
|
|
159
|
+
workflow_registry=registry,
|
|
160
|
+
api_keys={},
|
|
161
|
+
workflow_base_dir=str(tmp_path),
|
|
162
|
+
parent_file_def=parent_file,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
assert isinstance(block, WorkflowBlock)
|
|
166
|
+
assert block.child_workflow.name == "child_workflow"
|
|
167
|
+
assert block.workflow_ref == "child_workflow"
|
|
168
|
+
assert block.max_depth == 7
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def test_registered_workflow_builder_requires_workflow_registry_with_actionable_value_error(
|
|
172
|
+
tmp_path: Path,
|
|
173
|
+
) -> None:
|
|
174
|
+
"""The builder should fail explicitly when workflow_registry is missing."""
|
|
175
|
+
parent_file = RunsightWorkflowFile.model_validate(_parent_workflow_dict())
|
|
176
|
+
block_def = parent_file.blocks["invoke_child"]
|
|
177
|
+
builder = BLOCK_BUILDER_REGISTRY["workflow"]
|
|
178
|
+
|
|
179
|
+
with pytest.raises(ValueError, match="WorkflowRegistry|workflow_registry|registry"):
|
|
180
|
+
builder(
|
|
181
|
+
"invoke_child",
|
|
182
|
+
block_def,
|
|
183
|
+
{},
|
|
184
|
+
MagicMock(),
|
|
185
|
+
{},
|
|
186
|
+
api_keys={},
|
|
187
|
+
workflow_base_dir=str(tmp_path),
|
|
188
|
+
parent_file_def=parent_file,
|
|
189
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/assertions/deterministic/__init__.py
RENAMED
|
File without changes
|
{runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/assertions/deterministic/linguistic.py
RENAMED
|
File without changes
|
|
File without changes
|
{runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/assertions/deterministic/string.py
RENAMED
|
File without changes
|
{runsight_core-0.2.2 → runsight_core-0.2.3}/src/runsight_core/assertions/deterministic/structural.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|