Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 61 additions & 23 deletions .claude/skills/configure-k8s-deployment.md

Large diffs are not rendered by default.

28 changes: 22 additions & 6 deletions .claude/skills/create-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,22 +44,31 @@ class MyWorkflow(WorkflowManager):
with t[1]:
self.ui.input_TOPP("ToolName2")

def execution(self) -> None:
def execution(self) -> bool:
# Must return True on success, False on any failure - see "Execution" below.
if not self.params["mzML-files"]:
self.logger.log("ERROR: No input files selected.")
return
return False

in_files = self.file_manager.get_files(self.params["mzML-files"])
self.logger.log(f"Processing {len(in_files)} files...")

# Step 1
out_step1 = self.file_manager.get_files(in_files, "featureXML", "step1")
self.executor.run_topp("ToolName1", input_output={"in": in_files, "out": out_step1})
if not self.executor.run_topp(
"ToolName1", input_output={"in": in_files, "out": out_step1}
):
return False

# Step 2
in_step2 = self.file_manager.get_files(out_step1, collect=True)
out_step2 = self.file_manager.get_files("result.consensusXML", set_results_dir="step2")
self.executor.run_topp("ToolName2", input_output={"in": in_step2, "out": out_step2})
if not self.executor.run_topp(
"ToolName2", input_output={"in": in_step2, "out": out_step2}
):
return False

return True

@st.fragment
def results(self) -> None:
Expand Down Expand Up @@ -111,8 +120,14 @@ The 4 pages call these methods respectively:
- `self.file_manager.get_files("name.ext", set_results_dir="dir")` — single result file

### Execution
- `self.executor.run_topp("ToolName", input_output={"in": [...], "out": [...]})` — run a TOPP tool
- `self.executor.run_python("script_name", {"in": [...]})` — run a Python tool from `src/python-tools/`
- `self.executor.run_topp("ToolName", input_output={"in": [...], "out": [...]})` — run a TOPP tool; returns `False` if any command failed
- `self.executor.run_python("script_name", {"in": [...]})` — run a Python tool from `src/python-tools/`; returns `False` if the script is missing or exited non-zero
- **`execution()` must be annotated `-> bool` and return `True` only when every step
succeeded**, `False` on bad input or a failed tool. `workflow_process()` logs the
`WORKFLOW FINISHED` marker only for a truthy return, and a missing marker is classified
as an error — so an `execution()` that returns nothing renders "Errors occurred, check
log file." after a perfectly good run. Gate every `run_topp` / `run_python` call on its
return value too, or a tool that fails halfway is still reported as a success.

### UI widgets
- `self.ui.upload_widget(key, name, file_types, fallback)` — file upload with example data fallback
Expand Down Expand Up @@ -164,6 +179,7 @@ def configure(self) -> None:
- [ ] Workflow class in `src/` subclassing `WorkflowManager`
- [ ] `__init__` calls `super().__init__("Name", st.session_state["workspace"])`
- [ ] `upload()`, `configure()`, `execution()`, `results()` implemented
- [ ] `execution()` annotated `-> bool`, returning `True` on success and `False` on any failure
- [ ] `@st.fragment` on `configure()` and `results()`
- [ ] `reactive=True` on any widget whose value controls other widgets' visibility
- [ ] 4 content pages created in `content/`
Expand Down
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Shell scripts are executed by /bin/sh inside Linux containers and by POSIX
# shells in tests/test_seed_demos.py. A CRLF checkout on Windows breaks both:
# sh reports "not found" on every carriage return, and a shebang with a
# trailing CR fails to exec. Pin them to LF regardless of core.autocrlf.
*.sh text eol=lf
32 changes: 20 additions & 12 deletions .github/kind-config.yaml
Original file line number Diff line number Diff line change
@@ -1,27 +1,35 @@
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
# Two-node cluster that mirrors the production memory-tier topology, so
# the Build-and-Test job passes regardless of which tier a fork selects
# in its overlay (memory-tier-low or memory-tier-high). Without both
# labels present, flipping the overlay would leave pods Pending on the
# single kind node.
# Two schedulable nodes, which is the smallest cluster that can observe the
# property the deployment now rests on: workers spread across hosts, and one
# workspace volume reachable from all of them. On a single node every spread
# constraint is satisfied vacuously - maxSkew is measured over eligible
# domains and one domain is always perfectly balanced - so a one-node cluster
# would report success for exactly the arrangement this suite exists to rule
# out.
#
# No node labels. Nothing under k8s/ selects a node any more: the scheduler
# places pods, and the manifests only declare how big a worker is. The nodes
# carried openms.de/memory-tier=low and =high while the memory-tier components
# patched a matching nodeSelector onto every Deployment; those patches are
# gone, and leaving the labels behind would imply the pinning had survived
# somewhere. assert_no_node_pinning_anywhere text-scans this file for exactly
# that.
nodes:
- role: control-plane
# Multi-node kind clusters taint the control-plane with
# node-role.kubernetes.io/control-plane:NoSchedule. Clear it so
# app pods with nodeSelector memory-tier=low can actually land
# here (single-node kind had no such taint, hence the original
# workflow passed without this patch).
# node-role.kubernetes.io/control-plane:NoSchedule. Clear it so app pods
# can land here as well: with the taint in place there is one schedulable
# node, and every cross-node assertion - two pods on two nodes, the
# cross-node write, the cross-node flock, the worker spread - collapses
# into a tautology it cannot fail.
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
taints: []
labels:
openms.de/memory-tier: low
# ingress-ready is required by the kind variant of the
# ingress-nginx deploy manifest applied in CI.
ingress-ready: "true"
- role: worker
labels:
openms.de/memory-tier: high
Loading
Loading