TS_STATEFUL_CONVERSION_PLAN_FOLLOWUPS

TS Stateful Conversion — Follow-ups

Date: 2026-04-04 Repo: jmchilton/galaxy-tool-util-ts (galaxy-tool-util) Parent plan: TS_STATEFUL_CONVERSION_PLAN

Loose ends and polish items discovered after Steps 1–7 of the parent plan shipped. Organized small-to-large so each item can land independently.


Done (2026-04-04)

1. Wire precheck into stateful wrappers

Step 5 of the parent plan shipped precheckNativeWorkflow + 9 tests but nothing called it — legacy-encoded and ${...}-interpolated workflows went straight into the walker and threw from _assertNotStringContainer as an opaque per-step error. API was also out of step with the rest of the stateful path: took Map<toolId, inputs> instead of the version-aware ToolInputsResolver.

Done:

2. Validation wrapper (deferred from Step 2)

Step 2 deferred convertStateToFormat2Validated + ConversionValidationFailure to Step 3 where createFieldModel validation was to integrate with toFormat2/toNative. Step 3 didn’t pick it up. The stateful wrappers only caught whatever the walker happened to throw — no pre/post Effect-schema validation existed.

Done:

Bonus: classified fallback failures

Unblocked by (1)+(2), cleanly folded in during runner refactor.

Gotcha worth remembering: createFieldModel returns undefined if a parameter type’s generator isn’t registered. Registration happens via side-effect imports in schema/parameters/index.ts. Importing model-factory directly (bypassing the schema barrel) gets silent-undefined back with no test failures. Fix: top-of-file import "../schema/parameters/index.js"; in any module that calls createFieldModel without going through the public barrel.

Totals: 4461 schema (+11), 104 CLI (+1), 97 core, 13 proxy. make check + make test clean.


Done (2026-04-04, session 2: polish PR)

3. Shared stale-keys module ✅

No behavior change in existing tests. Schema suite 4463 passed | 88 skipped.

4. Version-aware roundtrip resolver test ✅

5. Diff-output UX on gxwf roundtrip

Totals: 4463 schema (+2), 107 CLI (+3), 97 core, 13 proxy. make check + make test clean.


Done (2026-04-05)

6. IWC sweep test (shape deviation: sweep, not committed goldens)

The plan called for a committed 10–20 workflow golden corpus. Chose the lighter gated-sweep shape instead to match the existing iwc-sweep.test.ts pattern — no committed fixtures, no tool bundles in the repo, runs off the user’s ~/.galaxy/tool_info_cache/ and a local IWC checkout. Primary purpose (regression harness for real-world workflows) is met; CI-reproducibility tradeoff accepted.

Done:

7. Subworkflow recursion in roundtrip

Landed as part of the 2026-04-04 polish diff but wasn’t mentioned in this doc until now.

Done:

Sweep findings + triage (2026-04-05)

First run against 120 IWC workflows / 2515 tool steps was committed red (28 failing workflows, 314 error diffs). Triaged four patterns in the same session:

  1. __identifier__ / __workflow_invocation_uuid__ runtime leaks — classified via new isRuntimeLeakKey in stale-keys.ts, new runtime_leak_stripped benign kind in the differ. Mirrors Galaxy’s RUNTIME_LEAK category in stale_keys.py. Kept as defense-in-depth for uncached-tool cases. (Commit f292736, -43 error diffs)
  2. Tool-definition-aware pre-cleanclean.ts gained stripStaleKeysToolAware (thin wrapper around walkNativeState with identity leaf callback + SKIP_VALUE for missing leaves). roundtripValidate now pre-cleans each tool step’s tool_state before diffing — mirrors Galaxy’s roundtrip_validate(clean_stale=True) default + _strip_recursive semantics. Cascade-fixed “simple scalar drop” pattern (saveLog, i, mode, plasmids, al, e/f/g/m/q, SN, cut, block_size, dark_bg, add_cell_metadata, …) — those were all undeclared stale keys. (Commit 5267257, 24 → 7 failing workflows, 271 → 193 errors)
  3. nullundefined"null" scalar equivalence — JSON has no undefined; a JS undefined reaching the differ means a key was absent or unset, semantically equivalent to explicit null. Matches Python’s orig_val in (None, "null") treatment. Fixed the dominant conditional-branch pattern (hyphy, scanpy, influenza, VGP8, …). (Commit 9310666, 7 → 1 failing workflow, 193 → 9 errors)

Final baseline after triage:

120 workflows, 2515 tool steps
verdicts: 1 clean, 76 benign-only, 1 with real errors, 0 crashed
forward fallbacks: post_validation=76
reverse fallbacks: pre_validation=76, post_validation=2244
benign diffs (4101): connection_only_section_omitted=4043,
                     all_null_section_omitted=37,
                     multi_select_normalized=21
error diffs: 9 (1 workflow)

Remaining issues:

Sweep test still red on clinicalmp. Each remaining issue is standalone.


Larger refactor (do last)

8. Walker unification with state-merge.ts

Parent plan Future Work item. state-merge.ts does parallel tree traversal (mutating, different signature) to walker.ts. Maintenance cost grows as strict-mode / connection-validation / stale-key classification land.

Plan:

Blast radius: large. Touches a load-bearing file with many downstream callers (validate-workflow.ts, expansion, stateful conversion). Wait until (3)–(7) have stabilized and (6) provides a strong regression harness.


Not prioritized (parked)

These are genuine gaps but either too speculative or too big for the current polish pass. Revisit after the items above land.


Suggested ordering for next session

Polish PR (one session): (3) + (4) + (5). Done 2026-04-04.

Fixture PR (one session): (6). Done 2026-04-05 as a gated sweep (shape deviation from plan).

Follow-on (one session): (7). Done 2026-04-04 bundled into the polish diff.

Later (separate session): (8). Wait for sweep bug triage (see findings above) to stabilize first.