galaxyproject / galaxyproject/foundry
summarize-nextflow: parsed predicate sibling on conditionals[].guard (_derived_pattern)
- Dominant language
- TypeScript
- Stars
- 2
- Forks
- 3
- Avg merge
- 1d 22m
- Merged PRs (30d)
- 63
Description
## Summary
`workflow.conditionals[].guard` is a verbatim Groovy expression captured from the source pipeline:
```jsonc
{
"guard": "step in ['mapping', 'markduplicates', 'prepare_recalibration', 'recalibrate']",
"branch": "default",
"affects": ["FASTQ_PREPROCESS_GATK", "FASTQ_PREPROCESS_PARABRICKS"]
}
```
This is honest and load-bearing — every consumer can fall back to it — but it forces every downstream consumer (interface Mold, data-flow Mold, scope-narrowing logic, future eval cases) to parse Groovy expression syntax in order to ask the most basic reachability question: "given user choice `step=mapping`, is this conditional satisfied?".
Add a sibling structured field that captures a small recognized grammar of guard predicates, leaving the raw verbatim authoritative.
## Naming
The new field is **derived** — it's a parsed projection of the raw `guard` string, not source-of-truth ground that survives schema validation independently. The leading underscore signals "derived / synthesized / not an authoritative source field" by convention, and the name should make it obvious to any consumer reading the JSON that this is *not* a competing representation of the source.
**Pick: `_derived_pattern`.**
Considered:
| Candidate | Pros | Cons |
|---|---|---|
| `_derived_guard_pattern` | maximally explicit | verbose; redundant `_guard_` since parent is `conditionals[i]`. |
| **`_derived_pattern`** | **short; underscore = derived; "pattern" = the parsed shape; "guard" implied by parent.** | **slight overload with Foundry "pattern page" vocabulary.** |
| `_parsed_guard` | short; "parsed" ≈ derived | doesn't say "pattern". |
| `_guard_pattern` | short; says "guard" and "pattern" | doesn't say "derived" — relies on underscore convention. |
| `parsed_guard` (no underscore) | matches `aliases` / `tool` / `conda` style | doesn't visually mark "this is derived from a sibling". |
Open to renaming during review; `_derived_pattern` is the recommendation.
## Shape
```jsonc
{
"guard": "step in ['mapping', 'markduplicates']", // verbatim Groovy, authoritative
"_derived_pattern": { // null when not recognized
"param": "step",
"op": "in",
"values": ["mapping", "markduplicates"]
},
"branch": "default",
"affects": ["FASTQ_PREPROCESS_GATK"]
}
```
Schema sketch:
```jsonc
"_derived_pattern": {
"anyOf": [
{ "type": "null" },
{
"type": "object",
"additionalProperties": false,
"required": ["param", "op", "values"],
"properties": {
"param": { "type": "string", "description": "Source param name (e.g. `step`, `aligner`, `tools`, `wes`). Bare params like `params.skip_alignment` are normalized to `skip_alignment`." },
"op": { "type": "string", "enum": ["eq", "ne", "in", "not_in", "truthy", "falsy"] },
"values": { "type": "array", "items": { "type": ["string", "number", "boolean"] }, "description": "Empty array for `truthy` / `falsy`; one element for `eq` / `ne`; N elements for `in` / `not_in`." }
}
}
]
}
```
## Recognized grammar
Recognize a small fixed vocabulary; leave `_derived_pattern: null` for everything else.
| Source shape | Mapped predicate |
|---|---|
| `params.X == 'Y'` / `X == 'Y'` | `{param: X, op: eq, values: [Y]}` |
| `params.X != 'Y'` | `{param: X, op: ne, values: [Y]}` |
| `params.X in ['A', 'B']` | `{param: X, op: in, values: [A, B]}` |
| `!(params.X in [...])` | `{param: X, op: not_in, values: [...]}` |
| `params.X` (truthiness in if-condition) | `{param: X, op: truthy, values: []}` |
| `!params.X` | `{param: X, op: falsy, values: []}` |
| Anything else (compound `&&` / `||`, method calls, regex, channel-derived guards) | `null` |
Conjunctions (`&&` / `||`) are deliberately out of grammar — they're already implicit when one source `if (a && b) { ... }` block produces a single `conditionals[]` row whose `affects[]` is the intersection. If a future need surfaces, model it as separate rows rather than a nested predicate AST.
## Sarek payoff
Sarek's 5 conditionals would parse cleanly:
| guard (verbatim) | _derived_pattern |
|---|---|
| `step == 'mapping'` | `{param: step, op: eq, values: [mapping]}` |
| `step in ['mapping', 'markduplicates', 'prepare_recalibration', 'recalibrate']` | `{param: step, op: in, values: [...]}` |
| `aligner == 'parabricks'` (default branch) | `{param: aligner, op: eq, values: [parabricks]}` |
| `aligner == 'parabricks'` (alternate branch) | `{param: aligner, op: ne, values: [parabricks]}` *(branch=alternate inverts)* |
| `tools` | `{param: tools, op: truthy, values: []}` |
All 5 are in-grammar. The interface Mold's "scope-narrowing decision" becomes deterministic-shortlist: given user-chosen `step=mapping`, walk `conditionals[]`, check `_derived_pattern` against the user choice, mark every process in `affects[]` of a guard whose predicate disagrees as out-of-scope, pre-filter the DAG before the LLM ever sees it.
## Tradeoff
A second representation that can drift from the raw. Keep `guard` authoritative; `_derived_pattern` is best-effort. The Groovy expression grammar is unbounded, so the parser stays small on purpose — better to leave 10% unrecognized than over-engineer a partial AST. The leading underscore exists in part to telegraph "if these disagree, trust `guard`".
## Implementation notes
- Live in `parseWorkflowConditionals` in `packages/summarize-nextflow/src/resolver.ts`.
- One regex per shape; if none match, set null. No Groovy AST library.
- Add eval cases in `content/molds/summarize-nextflow/eval.md`:
- **fidelity**: every nf-core tier-tagged fixture's recognized predicates round-trip from `guard` → `_derived_pattern` → re-rendered string.
- **fidelity**: a synthetic compound-guard (`a && b`) emits `_derived_pattern: null`, not a wrong predicate.
## Context
- Refinement journal: `content/molds/summarize-nextflow/refinements/2026-05-08-sarek-emulation.md`.
- Surfaced during: 5-step NEXTFLOW → GALAXY emulation against nf-core/sarek (2026-05-08, `content/log.md`).
- Downstream consumers that benefit: `nextflow-summary-to-galaxy-interface` (scope-narrowing), `nextflow-summary-to-galaxy-data-flow` (conditional disposition), eventual `nextflow-summary-to-cwl-*` Molds.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in packages/summarize-nextflow/src/resolver.ts at parseWorkflowConditionals, then read content/molds/summarize-nextflow/eval.md and the referenced Sarek refinement notes. Implement the documented fixed predicate grammar while preserving the raw guard, and verify that recognized predicates round-trip and compound guards produce null in the eval cases.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- tooling
- Issue type
- Feature
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100