Skip to content

feat: internalize DataFlow cleaning pipelines - #41

Merged
2233admin merged 4 commits into
mainfrom
codex/dataflow-native-cleaning
Jul 25, 2026
Merged

feat: internalize DataFlow cleaning pipelines#41
2233admin merged 4 commits into
mainfrom
codex/dataflow-native-cleaning

Conversation

@2233admin

Copy link
Copy Markdown
Owner

What changed

  • added versioned native Data Operator packs and Canvas node selection
  • added SHA-pinned OpenDCAI/DataFlow compatibility for 34 deterministic cleaning classes
  • added DataFlow graph import, compile, runtime execution, provenance, and fail-closed behavior
  • added golden fixtures, upstream oracle checks, and end-to-end workflow tests

Why

OpenCLI Admin needs to reproduce DataFlow-style data preparation and cleaning pipelines without requiring DataFlow or pandas at application runtime, while avoiding fake implementations for model-, retrieval-, OCR-, or document-dependent operators.

Validation

  • 2026 passed, 13 skipped (90.42% Python coverage)
  • Data operator Canvas projection passed
  • TypeScript tsc --noEmit passed
  • targeted Ruff checks passed
  • independent review: APPROVE, P0/P1/P2 = 0

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@2233admin, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 56 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: CHILL

Plan: Pro Plus

Run ID: e0533d50-db01-43db-9708-c57820f4b13e

📥 Commits

Reviewing files that changed from the base of the PR and between 100d870 and a2614bd.

📒 Files selected for processing (11)
  • .github/workflows/ci.yml
  • backend/workflow/capability_projection.py
  • backend/workflow/compiler.py
  • backend/workflow/dataflow_compat.py
  • docs/dataflow-compatibility-matrix.md
  • docs/dataflow-operator-packs.md
  • frontend/lib/workflow/node-internals.ts
  • frontend/package.json
  • tests/compat/dataflow/test_pinned_compatibility.py
  • tests/integration/test_dataflow_operator_pipeline_api.py
  • tests/unit/test_dataflow_compat.py
📝 Walkthrough

Walkthrough

Adds deterministic data operators and SHA-pinned DataFlow compatibility, integrates them into workflow compilation, execution, import, demand drafting, capabilities, and Canvas configuration, and adds comprehensive unit, compatibility, integration, fixture, and documentation coverage.

Changes

Data operator runtime

Layer / File(s) Summary
Operator registry and compatibility execution
backend/workflow/data_operators.py, backend/workflow/dataflow_compat.py
Adds versioned deterministic operators, pinned DataFlow alias translation, compatibility executors, metrics, lineage, rejection tracking, and fail-closed validation.
Contracts, capability projection, and compilation
backend/workflow/compiler.py, backend/workflow/runtime_registry.py, backend/workflow/runtime_contracts.py, backend/workflow/capability_projection.py, backend/workflow/node_registry.py, backend/schemas/workflow.py
Registers data-operator node kinds, projects operator manifests, validates operator and pack versions, merges configuration, and accepts JSON fields and the dataflow runtime.
Workflow assembly and native execution
backend/workflow/demand_assembler.py, backend/workflow/opencli_hda_tracer.py
Builds demand-driven operator chains and executes native data operators with lineage, metrics, bounded rejection IDs, and failure events.
DataFlow import and HTTP errors
backend/workflow/external_importer.py, backend/api/v1/workflows.py
Imports SHA-pinned DataFlow graphs as native nodes and converts import validation errors into HTTP 400 responses.
Canvas catalog and operator configuration
frontend/lib/workflow/*, frontend/lib/flow/*, frontend/components/flow/inspector.tsx, frontend/scripts/test-data-operator-nodes.mjs
Adds data-operator catalog entries, contracts, internals, versioned selection, JSON editing, custom tokens, optional numbers, and operator-config reset behavior.
Validation and compatibility coverage
tests/unit/*, tests/compat/*, tests/integration/*, tests/fixtures/dataflow/*
Adds deterministic operator tests, pinned compatibility goldens, optional upstream-oracle checks, and HTTP workflow acceptance tests.
Compatibility documentation
docs/dataflow-compatibility-matrix.md, docs/dataflow-operator-packs.md
Documents pinned compatibility identities, runnable operator packs, resource-dependent packs, import behavior, and acceptance contracts.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Canvas
  participant WorkflowAPI
  participant RuntimeRegistry
  participant DataOperator
  Canvas->>WorkflowAPI: submit workflow with operatorId, packVersion, and config
  WorkflowAPI->>RuntimeRegistry: validate catalog kind and resolve runtime metadata
  RuntimeRegistry->>DataOperator: resolve operator and pack
  WorkflowAPI->>DataOperator: execute candidate items
  DataOperator-->>WorkflowAPI: output items, metrics, lineage, and rejected IDs
  WorkflowAPI-->>Canvas: workflow events and results
Loading

Suggested reviewers: lunnynight

Poem

A rabbit hops through data streams,
With pinned packs and tidy dreams.
Nodes align and configs glow,
Rejected carrots gently show.
Clean paths trace from start to end—
“Deterministic!” sings my friend.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.37% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: internalizing DataFlow cleaning pipelines into native support.
Description check ✅ Passed The description is clearly related to the changeset and matches the added DataFlow packs, import, runtime, and tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@repowise-bot

repowise-bot Bot commented Jul 25, 2026

Copy link
Copy Markdown

✅ Health: 7.8

📋 At a glance
18 new findings introduced.

🚨 Change risk: 9.9/10 (high)
This change's risk is driven by:

  • more lines added than baseline
  • more scattered than baseline

📊 Full report · ⭐ Star Repowise · 📥 Install bot · Last updated 2026-07-25 07:19 UTC
Silence on a single PR with [skip repowise] in the title · Per-repo toggle on repowise.dev/settings?tab=bot

@2233admin

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

- text.rule-filter: reject configs with != 1 field instead of silently
  ignoring extra fields (fail-closed, matches compat single-input_key scope)
- text.deduplicate: treat missing/non-string fields as empty text instead
  of raising KeyError; reject empty field lists
- node-internals: resolve data-operator internals via explicit
  operatorId->kind map instead of operatorId.split(".")[1]
- ci: run tests/compat + tests/integration in backend job; add frontend
  workflow-checks job (data-operator projection + workflow contracts)
- tests: cover multi-field rejection, empty-field rejection, missing-field
  dedup

@coderabbitai coderabbitai 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.

Actionable comments posted: 8

🧹 Nitpick comments (21)
tests/unit/test_data_operators.py (2)

323-330: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Empty-batch loop never exercises the 1.1.0 specs. execute_data_operator(spec.operator_id, []) omits pack_version, so the three v1.1.0 specs resolve back to the legacy binding and are asserted twice instead of once each. Passing pack_version=spec.pack_version closes that gap.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_data_operators.py` around lines 323 - 330, Update
test_empty_batch_has_consistent_metrics_for_every_operator to pass each spec’s
pack_version into execute_data_operator alongside spec.operator_id and the empty
input, ensuring the v1.1.0 specifications resolve to their intended bindings.

50-73: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Both tests couple to COMPAT_OPERATOR_DEFINITIONS[0] ordering while the config at Line 68 hardcodes text.clean keys. If the definition order changes, Line 68's fields/operations config becomes an unsupported-config ValueError rather than a meaningful failure. Also, the operatorId/operator_id dual-key lookup is dead defence — tests/unit/test_dataflow_compat.py (Line 43) asserts the camelCase keys.

♻️ Select the definition explicitly
-    definition = COMPAT_OPERATOR_DEFINITIONS[0]
-    operator_id = definition.get("operatorId", definition.get("operator_id"))
-    pack_version = definition.get("packVersion", definition.get("pack_version"))
+    definition = next(
+        item
+        for item in COMPAT_OPERATOR_DEFINITIONS
+        if item["operatorId"] == "text.clean"
+    )
+    operator_id = definition["operatorId"]
+    pack_version = definition["packVersion"]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_data_operators.py` around lines 50 - 73, Update
test_versioned_registry_keeps_legacy_default_and_resolves_compat_exactly and
test_execute_reports_the_resolved_pack_version to select the compatibility
definition explicitly by its expected operator ID or another stable identifier,
rather than using COMPAT_OPERATOR_DEFINITIONS[0]. Use the definition’s asserted
camelCase keys directly, and ensure the execute_data_operator input matches the
selected operator’s supported configuration instead of hardcoding text.clean
fields and operations.
tests/unit/test_dataflow_compat.py (1)

214-224: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Naming collision hurts readability. The rejected parameter (input text) is shadowed conceptually by rejected_ids; renaming to rejecting_text/passing_text makes the assertion pair obvious.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_dataflow_compat.py` around lines 214 - 224, Rename the
test_phase2_rules_preserve_strict_and_inclusive_boundaries parameters passing
and rejected to passing_text and rejecting_text, and update their uses in the
_candidate calls. Keep the output and rejected_ids assertions unchanged.
tests/integration/test_dataflow_operator_pipeline_api.py (3)

547-567: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use itertools.pairwise for the consecutive-node edges. Also silences Ruff B905/RUF007.

♻️ Proposed change
+from itertools import pairwise
@@
-        for source, target in zip(ordered_ids, ordered_ids[1:])
+        for source, target in pairwise(ordered_ids)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/test_dataflow_operator_pipeline_api.py` around lines 547 -
567, Update the consecutive-node edge construction in the ordered_ids flow to
use itertools.pairwise instead of zip(ordered_ids, ordered_ids[1:]), adding the
required import if needed; preserve the existing source/target port mapping and
edge output.

Source: Linters/SAST tools


1006-1024: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated operator-id list. The same seven node ids are already spelled out at Lines 645-653; extract a module-level constant so the determinism test and the pipeline test cannot drift apart when the chain changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/test_dataflow_operator_pipeline_api.py` around lines 1006 -
1024, Extract the shared seven operator IDs from the existing list near the
determinism test into a module-level constant, then update partials to filter
against that constant instead of an inline set. Reuse the same constant in both
tests so changes to the operator chain remain synchronized.

124-341: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Manifest snapshot duplicates the registry lock in tests/compat/dataflow/test_pinned_compatibility.py (Lines 172-322). Both encode every operator's configKeys verbatim, so any registry change requires two synchronized edits. Consider asserting the HTTP projection against list_data_operator_specs() here and keeping the literal snapshot in one place.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/test_dataflow_operator_pipeline_api.py` around lines 124 -
341, Replace the duplicated _EXPECTED_OPERATOR_MANIFEST literal in the dataflow
pipeline API test with an assertion derived from list_data_operator_specs(),
reusing the registry-backed manifest data already maintained by the pinned
compatibility test. Preserve validation of the HTTP response projection,
including operator metadata and configKeys, while removing the second manually
synchronized snapshot.
tests/compat/dataflow/test_upstream_oracle.py (3)

69-84: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

New test files trip Ruff rules with no test-scoped exemption. Both findings stem from one root cause: the repo's Ruff configuration applies production-oriented rules (S6xx, RUF001) to tests/, so deliberate test data and dev-only subprocess calls fail lint.

  • tests/compat/dataflow/test_upstream_oracle.py#L69-L84: silence S603/S607 for the fixed-argv git calls, or exempt tests/ from flake8-bandit rules.
  • tests/unit/test_dataflow_compat.py#L80-L85: silence RUF001 for the intentional fullwidth , , fixtures.

Prefer a single per-file-ignores entry for tests/** over scattered # noqa comments.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/compat/dataflow/test_upstream_oracle.py` around lines 69 - 84, Update
the Ruff configuration with a single tests/** per-file-ignores entry covering
S603, S607, and RUF001. This should exempt the fixed-argument git subprocess
calls in tests/compat/dataflow/test_upstream_oracle.py:69-84 and the intentional
fullwidth-character fixtures in tests/unit/test_dataflow_compat.py:80-85; do not
add scattered noqa comments.

Source: Linters/SAST tools


176-177: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

getattr failures escape the skip guard. _import_class raises AttributeError (not ImportError) when the module exists but the pinned class was renamed, turning a stale-checkout situation into an error instead of a skip/clear failure. Consider catching (ImportError, AttributeError).

Also applies to: 231-232

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/compat/dataflow/test_upstream_oracle.py` around lines 176 - 177, Update
the exception handler around _import_class in the affected test sections to
catch both ImportError and AttributeError, so missing modules and renamed pinned
classes are handled by the existing pytest.skip path. Preserve the current skip
message and behavior.

69-84: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Ruff S603/S607 will flag these git calls. Argv is fixed and the root comes from a developer-supplied env var, so the ast-grep "command from incoming request" hint is a false positive — but the Ruff findings still need either a per-file ignore or a tests/ exclusion in the Ruff config to keep lint green.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/compat/dataflow/test_upstream_oracle.py` around lines 69 - 84, Suppress
Ruff S603/S607 for the fixed-argument git subprocess calls in
test_upstream_oracle.py, using a narrowly scoped per-file ignore or an
equivalent tests/ exclusion in the Ruff configuration. Keep the existing
subprocess behavior unchanged and ensure lint passes without broadening
unrelated suppressions.

Source: Linters/SAST tools

tests/compat/dataflow/test_pinned_compatibility.py (1)

373-386: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicate lock assertions. Lines 374-375 restate what test_fixture_is_auditable_and_pinned_to_the_supported_upstream_revision already asserts (lines 59, 64); the phase-2 value here is the blocklist digests and the 34-alias set union. Trimming keeps failures pointing at one place.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/compat/dataflow/test_pinned_compatibility.py` around lines 373 - 386,
The test_phase2_manifest_locks_alias_count_and_new_source_asset_digests test
redundantly asserts the compatibility alias count and upstream file count
already covered by
test_fixture_is_auditable_and_pinned_to_the_supported_upstream_revision. Remove
those duplicate assertions while retaining the blocklist digest checks and the
34-alias source ID set-union validation.
tests/integration/test_workflow_patch_api.py (1)

828-828: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Mutating the parametrized dict in place. node is the module-level dict built at collection time; assigning node["id"] mutates shared state across invocations. It's idempotent here, but adding the id in the parametrize data (or node = {**node, "id": "unsupported"}) keeps the case data immutable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/test_workflow_patch_api.py` at line 828, Update the
parametrized test case around node so it does not mutate the shared module-level
dictionary in place; add the unsupported id during parameter construction or
create a new dictionary copy with the id before use, preserving the existing
test behavior.
backend/workflow/data_operators.py (2)

810-822: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

symbolRatio predicate has a redundant clause.

not c.isalnum() and not c.isalpha()isalnum() is already true for every alphabetic char, so the isalpha() term never changes the result. Dropping it makes the intent (non-alphanumeric = symbol) clearer.

♻️ Simplify the symbol predicate
-    symbols = [c for c in non_space if not c.isalnum() and not c.isalpha()]
+    symbols = [c for c in non_space if not c.isalnum()]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/workflow/data_operators.py` around lines 810 - 822, Update the
symbols comprehension in _statistics to classify characters using only the
non-alphanumeric predicate, removing the redundant alphabetic check while
preserving the existing symbolRatio calculation.

465-480: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

PII redaction regexes are duplicated between _text_refine and _clean_text.

The email and phone patterns are byte-identical in both places. Extracting them into module-level compiled constants keeps the two operators from drifting apart as the patterns evolve.

Also applies to: 523-527

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/workflow/data_operators.py` around lines 465 - 480, Extract the
duplicated email and phone redaction patterns used by _text_refine and
_clean_text into shared module-level compiled regex constants. Update both
functions to reuse those constants while preserving the existing substitution
behavior and replacement tokens.
backend/workflow/demand_assembler.py (2)

264-267: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use itertools.pairwise for the successive-pair walk (Ruff B905/RUF007).

The neighbouring zip at Line 211 already passes strict=True; this one has no explicit strict=, which Ruff flags, and pairwise expresses the intent directly.

♻️ Proposed fix
-        for source_id, target_id in zip(
-            operator_node_ids,
-            operator_node_ids[1:],
-        ):
+        for source_id, target_id in pairwise(operator_node_ids):

Add the import:

from itertools import pairwise
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/workflow/demand_assembler.py` around lines 264 - 267, Replace the
successive-pair loop over operator_node_ids with itertools.pairwise, adding the
pairwise import and preserving the existing source_id/target_id iteration
behavior.

Source: Linters/SAST tools


307-415: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keyword heuristic is fine, but the operator descriptors are now a second source of truth for pack versions.

"1.1.0" if dataflow_compat else "1.0.0" hardcodes pack versions that also live in dataflow_compat.COMPAT_PACK_VERSION and data_operators._LEGACY_VERSION. Importing those constants keeps drafting in sync when a pack version bumps.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/workflow/demand_assembler.py` around lines 307 - 415, Update
_data_operators_for_need to reuse the canonical pack-version constants from
dataflow_compat.COMPAT_PACK_VERSION and data_operators._LEGACY_VERSION instead
of hardcoding "1.1.0" and "1.0.0" in the clean, deduplicate, and rule-filter
descriptors. Preserve the existing dataflow_compat selection behavior while
importing and referencing those shared constants.
backend/workflow/runtime_registry.py (1)

345-374: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Data-operator resolution rules and the legacy pack-version default are duplicated across three layers. The same "read operatorId → default packVersion to 1.0.0 → resolve spec → classify unknown/unsupported/kind-mismatch" logic exists in the compiler validator, the runtime resolver, and the tracer executor, so a rule change must be edited in three places to stay consistent.

  • backend/workflow/runtime_registry.py#L345-L374: extract the shared resolution/classification into one helper (ideally exported from data_operators) and call it here.
  • backend/workflow/compiler.py#L388-L413: consume that helper instead of re-deriving expected_kind, the packVersion default, and the error codes.
  • backend/workflow/opencli_hda_tracer.py#L97-L98: import the legacy pack-version default rather than redefining _LEGACY_DATA_OPERATOR_PACK_VERSION a third time.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/workflow/runtime_registry.py` around lines 345 - 374, Centralize
data-operator resolution and error classification in a shared helper, preferably
exported from data_operators, covering operatorId parsing, the 1.0.0 legacy
pack-version default, spec resolution, and
unknown/unsupported-version/kind-mismatch outcomes. Update
_resolve_data_operator_node in backend/workflow/runtime_registry.py (345-374)
and the compiler validation flow in backend/workflow/compiler.py (388-413) to
use this helper instead of duplicating the rules; update
backend/workflow/opencli_hda_tracer.py (97-98) to import the shared legacy
pack-version constant and remove its local definition.
backend/workflow/external_importer.py (1)

197-215: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reconstructing the source-id prefix duplicates dataflow_compat.

f"dataflow@{DATAFLOW_COMPAT_SHA}::" is built here twice while dataflow_compat already owns _SOURCE_PREFIX (and the DATAFLOW_ALIAS_SOURCE_IDS map). Exporting that prefix and importing it keeps the format in one place.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/workflow/external_importer.py` around lines 197 - 215, Update
_dataflow_source_id to reuse the exported source-prefix constant from
dataflow_compat instead of rebuilding f"dataflow@{DATAFLOW_COMPAT_SHA}::"
locally. Import that constant and use it both when reconstructing source_id and
when validating its prefix, while preserving the existing error behavior.
backend/workflow/capability_projection.py (1)

545-555: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Annotate the spec index with its real type.

dict[str, list[object]] makes spec.operator_id / spec.kind untyped accesses on object; a type checker cannot verify this helper. Import DataOperatorSpec alongside list_data_operator_specs and use dict[str, list[DataOperatorSpec]].

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/workflow/capability_projection.py` around lines 545 - 555, Update the
spec index in the workflow capability projection to use the concrete
DataOperatorSpec type: import DataOperatorSpec alongside
list_data_operator_specs and change specs_by_kind from dict[str, list[object]]
to dict[str, list[DataOperatorSpec]], preserving the existing grouping and
sorting logic.
backend/workflow/opencli_hda_tracer.py (1)

524-547: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Swallowed exception leaves no server-side diagnostics.

The failed event intentionally carries only errorType (no message), which is good for not leaking candidate data, but the exception itself is then discarded entirely — nothing is logged. A structured logger.exception(...) here keeps the operator failure debuggable without widening what the trace exposes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/workflow/opencli_hda_tracer.py` around lines 524 - 547, In the
data-operator exception handler around _binding_id and WorkflowRunBlockReason,
add a structured logger.exception call before continuing so the original
exception and traceback are retained server-side. Keep reason.details and the
emitted failed event limited to the existing errorType and non-sensitive fields,
without exposing exc details in the trace.
frontend/components/flow/inspector.tsx (1)

295-335: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Every valid JSON keystroke takes an undo snapshot.

Each time the textarea content becomes valid JSON, updateParameterFieldupdateParameterInterfaceField runs, and that action unconditionally calls get().takeSnapshot() before set() (store.ts lines 999-1001). Typing out a JSON object will therefore push many intermediate snapshots onto the undo stack (one per moment the text is valid), making undo effectively useless for this field compared to a single edit action.

Consider only committing/snapshotting on blur (or debouncing the commit), while still updating jsonDrafts/jsonErrors live for immediate validation feedback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/components/flow/inspector.tsx` around lines 295 - 335, The JSON
Textarea’s onChange currently calls updateParameterField for every valid
intermediate value, creating an undo snapshot per keystroke. Keep updating
jsonDrafts and jsonErrors during onChange for live validation, but defer
updateParameterField and its snapshot to onBlur, committing the latest valid
parsed value once while preserving invalid-input feedback.
frontend/scripts/test-data-operator-nodes.mjs (1)

1-311: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Solid test coverage; consider adding a case for nodes without ui.catalogId.

This harness thoroughly exercises the catalog-created data-operator flow (operator selection, JSON config parsing, version pinning, draft clearing). All node fixtures here go through createWorkflowNodeFromCatalog, so ui.catalogId is always one of the four "intelligence.data.*" ids. Adding a case for a node with params.operatorId/config set directly (no matching ui.catalogId — e.g. simulating a DataFlow-imported node) would have caught the gating/kind-inference inconsistencies flagged in frontend/lib/flow/store.ts and frontend/lib/workflow/node-internals.ts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/scripts/test-data-operator-nodes.mjs` around lines 1 - 311, Extend
the harness with an imported node whose params.operatorId and params.config are
set directly and whose ui has no matching catalogId, simulating a
DataFlow-imported node. Use importWorkflowProject and applyWorkflowCapabilities
to verify the node is recognized as a data operator without catalog metadata,
and assert parameter-interface updates and inferred node kind behave
consistently with catalog-created nodes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/workflow/capability_projection.py`:
- Around line 602-604: Update the operatorIds construction in the capability
projection to remove duplicate operator IDs while preserving their first-seen
order, matching the deduplicated packs behavior. Keep the operators collection
unchanged and use the existing operators sequence as the source.

In `@backend/workflow/compiler.py`:
- Around line 441-466: The fallback classification around resolve_data_operator
must detect operator IDs registered in any pack version, not only the legacy
version. Replace the legacy-only probe with the existing all-version registry
lookup, such as list_data_operator_specs(), and classify the result as
unsupported_data_operator_version when the ID exists in another version;
preserve unknown_data_operator only when no registered version contains
operator_id.

In `@backend/workflow/dataflow_compat.py`:
- Around line 926-932: Update the lineStartWithBulletpoint branch around
_paragraphs and ratio to handle an empty paragraphs result before dividing by
len(paragraphs), returning the same safe outcome used by the related empty-input
checks. Preserve the existing bullet detection and threshold behavior for
non-empty paragraphs.
- Around line 631-636: Update the field extraction in the deduplication loop
around `_normalized` so missing or non-string field values are handled
defensively instead of indexing `normalized` directly. Reuse the module’s
established `_unsupported(...)` behavior or coerce invalid values to an empty
string, ensuring malformed candidates do not raise `KeyError` or
`AttributeError` while preserving normal string handling.

In `@backend/workflow/runtime_registry.py`:
- Around line 391-403: Validate the merged operator configuration in the
compilation flow around config construction, using the available
spec.config_keys to reject any keys outside the supported set before runtime
execution. Apply the same validation in _validate_data_operator_node, preserve
operatorId/packVersion/config handling, and report unsupported parameters as
compile-time errors rather than allowing execute_data_operator to fail later.

In `@docs/dataflow-compatibility-matrix.md`:
- Around line 28-31: The “Tokenizer or statistical text” row conflates runnable
StopWordFilter behavior with unavailable tokenizer-backed refiners. Update its
examples to distinguish basic StopWordFilter support when use_tokenizer=false
from unsupported tokenizer-backed stop-word, stemming, and lemmatization
implementations.

In `@docs/dataflow-operator-packs.md`:
- Around line 22-33: Clarify the documentation around text.deduplicate to
distinguish the v1 native SimHash capability from the pinned
builtin.text-cleaning@1.1.0 compatibility profile. State that SimHash is
supported natively in v1 but excluded from the 1.1.0 pinned compatibility import
contract, consistent with SimHashDeduplicateFilter being unavailable while
HashDeduplicateFilter and NgramHashDeduplicateFilter are mapped.

In `@tests/compat/dataflow/test_pinned_compatibility.py`:
- Around line 573-589: Split the combined assertion in the compatibility test
into separate seam-specific checks. If translate_dataflow_alias is the intended
fail-closed gate, keep only its ValueError assertion and remove the unreachable
execute_data_operator call; otherwise, assert translation succeeds separately
and wrap execute_data_operator in its own pytest.raises block.

---

Nitpick comments:
In `@backend/workflow/capability_projection.py`:
- Around line 545-555: Update the spec index in the workflow capability
projection to use the concrete DataOperatorSpec type: import DataOperatorSpec
alongside list_data_operator_specs and change specs_by_kind from dict[str,
list[object]] to dict[str, list[DataOperatorSpec]], preserving the existing
grouping and sorting logic.

In `@backend/workflow/data_operators.py`:
- Around line 810-822: Update the symbols comprehension in _statistics to
classify characters using only the non-alphanumeric predicate, removing the
redundant alphabetic check while preserving the existing symbolRatio
calculation.
- Around line 465-480: Extract the duplicated email and phone redaction patterns
used by _text_refine and _clean_text into shared module-level compiled regex
constants. Update both functions to reuse those constants while preserving the
existing substitution behavior and replacement tokens.

In `@backend/workflow/demand_assembler.py`:
- Around line 264-267: Replace the successive-pair loop over operator_node_ids
with itertools.pairwise, adding the pairwise import and preserving the existing
source_id/target_id iteration behavior.
- Around line 307-415: Update _data_operators_for_need to reuse the canonical
pack-version constants from dataflow_compat.COMPAT_PACK_VERSION and
data_operators._LEGACY_VERSION instead of hardcoding "1.1.0" and "1.0.0" in the
clean, deduplicate, and rule-filter descriptors. Preserve the existing
dataflow_compat selection behavior while importing and referencing those shared
constants.

In `@backend/workflow/external_importer.py`:
- Around line 197-215: Update _dataflow_source_id to reuse the exported
source-prefix constant from dataflow_compat instead of rebuilding
f"dataflow@{DATAFLOW_COMPAT_SHA}::" locally. Import that constant and use it
both when reconstructing source_id and when validating its prefix, while
preserving the existing error behavior.

In `@backend/workflow/opencli_hda_tracer.py`:
- Around line 524-547: In the data-operator exception handler around _binding_id
and WorkflowRunBlockReason, add a structured logger.exception call before
continuing so the original exception and traceback are retained server-side.
Keep reason.details and the emitted failed event limited to the existing
errorType and non-sensitive fields, without exposing exc details in the trace.

In `@backend/workflow/runtime_registry.py`:
- Around line 345-374: Centralize data-operator resolution and error
classification in a shared helper, preferably exported from data_operators,
covering operatorId parsing, the 1.0.0 legacy pack-version default, spec
resolution, and unknown/unsupported-version/kind-mismatch outcomes. Update
_resolve_data_operator_node in backend/workflow/runtime_registry.py (345-374)
and the compiler validation flow in backend/workflow/compiler.py (388-413) to
use this helper instead of duplicating the rules; update
backend/workflow/opencli_hda_tracer.py (97-98) to import the shared legacy
pack-version constant and remove its local definition.

In `@frontend/components/flow/inspector.tsx`:
- Around line 295-335: The JSON Textarea’s onChange currently calls
updateParameterField for every valid intermediate value, creating an undo
snapshot per keystroke. Keep updating jsonDrafts and jsonErrors during onChange
for live validation, but defer updateParameterField and its snapshot to onBlur,
committing the latest valid parsed value once while preserving invalid-input
feedback.

In `@frontend/scripts/test-data-operator-nodes.mjs`:
- Around line 1-311: Extend the harness with an imported node whose
params.operatorId and params.config are set directly and whose ui has no
matching catalogId, simulating a DataFlow-imported node. Use
importWorkflowProject and applyWorkflowCapabilities to verify the node is
recognized as a data operator without catalog metadata, and assert
parameter-interface updates and inferred node kind behave consistently with
catalog-created nodes.

In `@tests/compat/dataflow/test_pinned_compatibility.py`:
- Around line 373-386: The
test_phase2_manifest_locks_alias_count_and_new_source_asset_digests test
redundantly asserts the compatibility alias count and upstream file count
already covered by
test_fixture_is_auditable_and_pinned_to_the_supported_upstream_revision. Remove
those duplicate assertions while retaining the blocklist digest checks and the
34-alias source ID set-union validation.

In `@tests/compat/dataflow/test_upstream_oracle.py`:
- Around line 69-84: Update the Ruff configuration with a single tests/**
per-file-ignores entry covering S603, S607, and RUF001. This should exempt the
fixed-argument git subprocess calls in
tests/compat/dataflow/test_upstream_oracle.py:69-84 and the intentional
fullwidth-character fixtures in tests/unit/test_dataflow_compat.py:80-85; do not
add scattered noqa comments.
- Around line 176-177: Update the exception handler around _import_class in the
affected test sections to catch both ImportError and AttributeError, so missing
modules and renamed pinned classes are handled by the existing pytest.skip path.
Preserve the current skip message and behavior.
- Around line 69-84: Suppress Ruff S603/S607 for the fixed-argument git
subprocess calls in test_upstream_oracle.py, using a narrowly scoped per-file
ignore or an equivalent tests/ exclusion in the Ruff configuration. Keep the
existing subprocess behavior unchanged and ensure lint passes without broadening
unrelated suppressions.

In `@tests/integration/test_dataflow_operator_pipeline_api.py`:
- Around line 547-567: Update the consecutive-node edge construction in the
ordered_ids flow to use itertools.pairwise instead of zip(ordered_ids,
ordered_ids[1:]), adding the required import if needed; preserve the existing
source/target port mapping and edge output.
- Around line 1006-1024: Extract the shared seven operator IDs from the existing
list near the determinism test into a module-level constant, then update
partials to filter against that constant instead of an inline set. Reuse the
same constant in both tests so changes to the operator chain remain
synchronized.
- Around line 124-341: Replace the duplicated _EXPECTED_OPERATOR_MANIFEST
literal in the dataflow pipeline API test with an assertion derived from
list_data_operator_specs(), reusing the registry-backed manifest data already
maintained by the pinned compatibility test. Preserve validation of the HTTP
response projection, including operator metadata and configKeys, while removing
the second manually synchronized snapshot.

In `@tests/integration/test_workflow_patch_api.py`:
- Line 828: Update the parametrized test case around node so it does not mutate
the shared module-level dictionary in place; add the unsupported id during
parameter construction or create a new dictionary copy with the id before use,
preserving the existing test behavior.

In `@tests/unit/test_data_operators.py`:
- Around line 323-330: Update
test_empty_batch_has_consistent_metrics_for_every_operator to pass each spec’s
pack_version into execute_data_operator alongside spec.operator_id and the empty
input, ensuring the v1.1.0 specifications resolve to their intended bindings.
- Around line 50-73: Update
test_versioned_registry_keeps_legacy_default_and_resolves_compat_exactly and
test_execute_reports_the_resolved_pack_version to select the compatibility
definition explicitly by its expected operator ID or another stable identifier,
rather than using COMPAT_OPERATOR_DEFINITIONS[0]. Use the definition’s asserted
camelCase keys directly, and ensure the execute_data_operator input matches the
selected operator’s supported configuration instead of hardcoding text.clean
fields and operations.

In `@tests/unit/test_dataflow_compat.py`:
- Around line 214-224: Rename the
test_phase2_rules_preserve_strict_and_inclusive_boundaries parameters passing
and rejected to passing_text and rejecting_text, and update their uses in the
_candidate calls. Keep the output and rejected_ids assertions unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 70948026-fbd2-407a-91ac-bdca59d16124

📥 Commits

Reviewing files that changed from the base of the PR and between 414c87c and 100d870.

📒 Files selected for processing (32)
  • backend/api/v1/workflows.py
  • backend/schemas/workflow.py
  • backend/workflow/capability_projection.py
  • backend/workflow/compiler.py
  • backend/workflow/data_operators.py
  • backend/workflow/dataflow_compat.py
  • backend/workflow/demand_assembler.py
  • backend/workflow/external_importer.py
  • backend/workflow/node_registry.py
  • backend/workflow/opencli_hda_tracer.py
  • backend/workflow/runtime_contracts.py
  • backend/workflow/runtime_registry.py
  • docs/dataflow-compatibility-matrix.md
  • docs/dataflow-operator-packs.md
  • frontend/components/flow/inspector.tsx
  • frontend/lib/flow/store.ts
  • frontend/lib/flow/types.ts
  • frontend/lib/workflow/capabilities.ts
  • frontend/lib/workflow/node-catalog.ts
  • frontend/lib/workflow/node-contracts.ts
  • frontend/lib/workflow/node-internals.ts
  • frontend/lib/workflow/parameter-interface.ts
  • frontend/lib/workflow/schema.ts
  • frontend/scripts/test-data-operator-nodes.mjs
  • tests/compat/dataflow/test_pinned_compatibility.py
  • tests/compat/dataflow/test_upstream_oracle.py
  • tests/fixtures/dataflow/pinned_f62aa134_golden.json
  • tests/fixtures/dataflow/pinned_f62aa134_phase2_golden.json
  • tests/integration/test_dataflow_operator_pipeline_api.py
  • tests/integration/test_workflow_patch_api.py
  • tests/unit/test_data_operators.py
  • tests/unit/test_dataflow_compat.py

Comment thread backend/workflow/capability_projection.py Outdated
Comment thread backend/workflow/compiler.py
Comment thread backend/workflow/dataflow_compat.py Outdated
Comment thread backend/workflow/dataflow_compat.py
Comment on lines +391 to +403
flat_config = {
key: value
for key, value in node.params.items()
if key not in {"operatorId", "packVersion", "config"}
}
nested_config = node.params.get("config")
config: Any = (
{**flat_config, **nested_config}
if isinstance(nested_config, dict)
else flat_config
if nested_config is None
else nested_config
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Flat params leak into the operator config and only fail at run time.

Every param other than operatorId/packVersion/config is merged into config, and execute_data_operator rejects unknown keys with ValueError. So an authoring-time stray param (or a UI field written into params) compiles cleanly and then fails the run with Unsupported config for …. Since spec.config_keys is already available here (and in _validate_data_operator_node), validating the merged keys at compile time would surface this as a compile error instead of a failed run.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/workflow/runtime_registry.py` around lines 391 - 403, Validate the
merged operator configuration in the compilation flow around config
construction, using the available spec.config_keys to reject any keys outside
the supported set before runtime execution. Apply the same validation in
_validate_data_operator_node, preserve operatorId/packVersion/config handling,
and report unsupported parameters as compile-time errors rather than allowing
execute_data_operator to fail later.

Comment thread docs/dataflow-compatibility-matrix.md Outdated
Comment thread docs/dataflow-operator-packs.md
Comment on lines +573 to +589
with pytest.raises(ValueError, match="dataflow_operator_unsupported"):
invocation = translate_dataflow_alias(
DATAFLOW_ALIAS_SOURCE_IDS[alias],
init_config,
{"input_key": "content"},
)
execute_data_operator(
invocation.operator_id,
[
{
"candidateId": f"{alias}-tokenizer",
"normalizedData": {"content": "plain input"},
}
],
invocation.config,
pack_version=invocation.pack_version,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Two calls inside one pytest.raises block makes the second unreachable.

If translate_dataflow_alias raises (which it will for use_tokenizer: True if the fail-closed check lives in translation), execute_data_operator is never invoked, so the test silently asserts nothing about the executor path. Pin down which seam is expected to fail closed.

♻️ Split the two seams
-    with pytest.raises(ValueError, match="dataflow_operator_unsupported"):
-        invocation = translate_dataflow_alias(
-            DATAFLOW_ALIAS_SOURCE_IDS[alias],
-            init_config,
-            {"input_key": "content"},
-        )
-        execute_data_operator(
-            invocation.operator_id,
-            [
-                {
-                    "candidateId": f"{alias}-tokenizer",
-                    "normalizedData": {"content": "plain input"},
-                }
-            ],
-            invocation.config,
-            pack_version=invocation.pack_version,
-        )
+    invocation = translate_dataflow_alias(
+        DATAFLOW_ALIAS_SOURCE_IDS[alias],
+        init_config,
+        {"input_key": "content"},
+    )
+    with pytest.raises(ValueError, match="dataflow_operator_unsupported"):
+        execute_data_operator(
+            invocation.operator_id,
+            [
+                {
+                    "candidateId": f"{alias}-tokenizer",
+                    "normalizedData": {"content": "plain input"},
+                }
+            ],
+            invocation.config,
+            pack_version=invocation.pack_version,
+        )

If translation is the intended gate, drop the executor call instead.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
with pytest.raises(ValueError, match="dataflow_operator_unsupported"):
invocation = translate_dataflow_alias(
DATAFLOW_ALIAS_SOURCE_IDS[alias],
init_config,
{"input_key": "content"},
)
execute_data_operator(
invocation.operator_id,
[
{
"candidateId": f"{alias}-tokenizer",
"normalizedData": {"content": "plain input"},
}
],
invocation.config,
pack_version=invocation.pack_version,
)
invocation = translate_dataflow_alias(
DATAFLOW_ALIAS_SOURCE_IDS[alias],
init_config,
{"input_key": "content"},
)
with pytest.raises(ValueError, match="dataflow_operator_unsupported"):
execute_data_operator(
invocation.operator_id,
[
{
"candidateId": f"{alias}-tokenizer",
"normalizedData": {"content": "plain input"},
}
],
invocation.config,
pack_version=invocation.pack_version,
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/compat/dataflow/test_pinned_compatibility.py` around lines 573 - 589,
Split the combined assertion in the compatibility test into separate
seam-specific checks. If translate_dataflow_alias is the intended fail-closed
gate, keep only its ValueError assertion and remove the unreachable
execute_data_operator call; otherwise, assert translation succeeds separately
and wrap execute_data_operator in its own pytest.raises block.

- dataflow_compat: guard lineStartWithBulletpoint against whitespace-only
  text (ZeroDivisionError), matching the lineEndWithEllipsis guard
- capability_projection: dedupe operatorIds across pack versions while
  preserving order
- compiler: probe all registered pack versions when classifying
  unknown_data_operator vs unsupported_data_operator_version
- tests/compat: split translate/execute seams so the tokenizer fail-closed
  assertion pins the executor path instead of hiding behind translation
- docs: clarify runnable StopWordFilter (use_tokenizer=false) vs
  tokenizer-backed processing, and SimHash v1-native vs pinned 1.1.0 scope
- test_dataflow_operator_pipeline_api: operatorIds assertion now expects
  the deduped projection (order-preserving unique ids)
- ci: frontend job needs pnpm 10 — frontend/pnpm-workspace.yaml is a
  pnpm 10 config store (allowBuilds, no packages field), pnpm 9 aborts
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@2233admin
2233admin merged commit aa1674b into main Jul 25, 2026
12 checks passed
@2233admin
2233admin deleted the codex/dataflow-native-cleaning branch July 25, 2026 10:09
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