Skip to content

feat(validate): enum-check COMFY_DYNAMICCOMBO_V3 selections + presence-check dotted sub-inputs (BE-3358)#573

Open
mattmillerai wants to merge 2 commits into
mainfrom
matt/be-3358-dynamic-combo-validation
Open

feat(validate): enum-check COMFY_DYNAMICCOMBO_V3 selections + presence-check dotted sub-inputs (BE-3358)#573
mattmillerai wants to merge 2 commits into
mainfrom
matt/be-3358-dynamic-combo-validation

Conversation

@mattmillerai

Copy link
Copy Markdown
Collaborator

ELI-5

Some cloud nodes (ClaudeNode, ReveImageCreateNode, …) have a "pick one" input like model where the pick brings its own extra settings (model.max_tokens, model.temperature). Until now comfy validate ignored all of that completely: you could pick a model that doesn't exist and pass garbage settings, and validate would say everything is fine — then the server would reject it. Now validate checks the pick is real, checks the picked option's required settings are present and in range, and warns about settings the server would ignore.

What

COMFY_DYNAMICCOMBO_V3 inputs validated end-to-end in comfy_cli/cql/engine.py, mirroring the server (_io.py DynamicCombo.Option.as_dict / _expand_schema_for_dynamic → the required_input_missing presence check in execution.py:884-900):

  • Parse: each option's {"key", "inputs": {"required", "optional"}} sub-schema is parsed into new Port.selection_keys / Port.dynamic_options via the existing _parse_inputs machinery, so nested dynamic combos (model.mode.budget) recurse naturally. Malformed options (non-dict, missing/non-string key, non-dict inputs) are skipped.
  • Validate (_check_dynamic_combo / _expand_dynamic_port):
    • selection not a known key → hard unknown_enum_value carrying the full valid_options list (same shape as the existing enum error);
    • required dotted sub-inputs of the selected option absent → required_input_missing per key (same shape as the phase-1 path);
    • present dotted sub-values run validate_shape / validate_catalog exactly like top-level ports (shape_mismatch, unknown_enum_value, below_min/above_max route to errors per BE-3357);
    • required dynamic port entirely absent → required_input_missing (phase 1 deliberately skips dynamic types, so this pass owns it);
    • dotted keys matching no sub-port of the selection → unknown_input warning (server ignores extra keys);
    • link-valued (2-list) selections and sub-values are skipped defensively — the generic loop already edge-checks them.
  • Discovery: nodes show / describe output now includes selection_keys for dynamic-combo ports; choices stays [] (selection keys are not flat enum choices — _is_scalar_choice semantics untouched).

Both BE-3349 repros now fail correctly: {"model": "Opus 4.6"}required_input_missing on model.max_tokens/model.mode; {"model": "NotARealModel", "model.bogus_key": 5}unknown_enum_value on model.

Judgment calls

  • Unknown-dotted-key warnings are suppressed when the base selection is absent/invalid/link-valued — the sub-keys of an unresolved selection can't be judged, and the primary error already tells the agent what to fix; warning on model.bogus_key while also erroring model itself would be pile-on noise. Once the selection is fixed, a re-validate flags the bogus key.
  • Unparseable options degrade to the old lenient behavior (no selection check at all) rather than false-erroring on a schema we can't read — the validator only becomes strict where the option schema genuinely parsed. This bounds the false-positive risk of the new hard errors to cases where the server-side option expansion would also fail.
  • Sub-port presence checks mirror _check_required_present's exclusions (autogrow, COMFY_DYNAMICSLOT) to avoid double-error shapes.

Not touched (pre-existing on main)

tests/comfy_cli/command/test_validate_command.py::test_api_format_unchanged and ::test_empty_dict_payload_unchanged fail on origin/main itself (verified on a pristine checkout) — a semantic conflict between #551 (BE-3357 prompt_no_outputs hard error) and #553 (BE-3359 tests that assume outputless workflows exit 0). #565 (BE-3406) is already working in exactly that area, so this PR leaves it alone.

Tests

New fixture tests/comfy_cli/fixtures/dynamic_combo_object_info.json (BE-3349-shaped synthetic node: two options with different required sub-inputs incl. INT min/max and an enum, one option nesting a second dynamic combo) + 15 tests in TestDynamicComboInputs covering every case above, both BE-3349 repros, nested selections, malformed options, link-valued selections, and describe output. Existing autogrow tests unchanged.

pytest: 2653 passed on this branch minus the 2 pre-existing main failures above; ruff check + ruff format --check clean.

@mattmillerai mattmillerai added the agent-coded PR authored by the agent-work loop label Jul 22, 2026
@mattmillerai
mattmillerai marked this pull request as ready for review July 22, 2026 19:07
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 34 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ed2fc9a2-7747-4b93-831b-560c4bd2c4cf

📥 Commits

Reviewing files that changed from the base of the PR and between d4ff53e and 153a5ab.

📒 Files selected for processing (4)
  • comfy_cli/cql/engine.py
  • tests/comfy_cli/command/test_validate_command.py
  • tests/comfy_cli/cql/test_engine.py
  • tests/comfy_cli/fixtures/dynamic_combo_object_info.json
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch matt/be-3358-dynamic-combo-validation
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch matt/be-3358-dynamic-combo-validation

Comment @coderabbitai help to get the list of available commands.

@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. enhancement New feature or request labels Jul 22, 2026
@mattmillerai mattmillerai added the cursor-review Request Cursor bot review label Jul 22, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Cursor Review — Consolidated panel

Triggered by @mattmillerai.

Found 5 finding(s).

Severity Count
🟠 High 1
🟡 Medium 2
🟢 Low 1
⚪ Nit 1

Panel: 6/8 reviewers contributed findings.

Reviewers that did not contribute: kimi-k2.5:adversarial (empty), kimi-k2.5:edge-case (empty)

Comment thread comfy_cli/cql/engine.py Outdated
Comment thread comfy_cli/cql/engine.py Outdated
Comment thread comfy_cli/cql/engine.py Outdated
Comment thread comfy_cli/cql/engine.py Outdated
Comment thread comfy_cli/cql/engine.py Outdated
…l (BE-3358)

Address all five cursor-review panel findings on PR #573:

- Autogrow sub-inputs under a dynamic-combo option (High): slot keys
  (model.images.image0, ...) now register as valid instead of warning
  unknown_input; a required autogrow subtree with zero wired slots errors
  (autogrow_no_slots) and a bare single connection errors
  (autogrow_bare_input) — mirroring the top-level autogrow path.
- Recursion DoS (Medium): the _parse_inputs <-> _parse_dynamic_options
  mutual recursion is depth-bounded by _MAX_SUBGRAPH_DEPTH; a hostile
  object_info with pathologically nested combos degrades leniently
  instead of crashing with RecursionError.
- Stale sub-key false positives (Medium): _check_dynamic_combo now runs
  before the generic edge checks and exports valid/unresolved key sets,
  so a stale link-valued sub-key from a previous selection (which the
  server ignores) no longer hard-errors dangling_edge/
  output_index_out_of_range — it keeps its unknown_input warning.
- required_input_missing hint (Low): selection keys truncate to the
  first 8 plus a count, like the unknown_enum_value branch.
- Stray-key attribution (Nit): unknown dotted keys are attributed to the
  deepest RESOLVED combo prefix (model.mode='fast'), not the top-level
  base, and the hint lists that level's sub-keys.

Also repair two tests that are red on main itself (semantic conflict
between BE-3357's prompt_no_outputs check and BE-3359's validate tests,
merged independently): test_api_format_unchanged gains an output node;
test_empty_dict_payload_unchanged now expects the correct
prompt_no_outputs rejection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mattmillerai

Copy link
Copy Markdown
Collaborator Author

Pushed 153a5ab addressing all five cursor-review panel findings (autogrow sub-ports under dynamic options, recursion-depth cap, stale sub-key edge-check false positives, hint truncation, deepest-prefix stray-key attribution) — each thread has details and dedicated tests.

Note on the build CI failure: the two failing tests (test_api_format_unchanged, test_empty_dict_payload_unchanged) are red on main itself — a semantic conflict between #551 (BE-3357, prompt_no_outputs hard check) and #553 (BE-3359, validate tests that use output-less workflows), which merged independently. This push repairs them here since this PR touches the same validator: the API-format test gains a SaveImage output node, and the empty-dict test now expects the (correct) prompt_no_outputs rejection. Full suite: 2663 passed locally; ruff 0.15.15 (CI pin) clean.

@mattmillerai

Copy link
Copy Markdown
Collaborator Author

Heads-up: #576 just shipped the two test_validate_command.py fixture fixes standalone (they're what's breaking build CI repo-wide on main, tracked separately). Once it merges, this branch will need a small rebase in that file — keep #576's version of test_api_format_unchanged and the renamed test_empty_dict_payload_not_converted_and_rejected; the intent is identical.

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

Labels

agent-coded PR authored by the agent-work loop cursor-review Request Cursor bot review enhancement New feature or request size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants