Files
PlantAI-dev/.superpowers/sdd/e2e-final-fix-report.md
2026-08-03 18:42:47 +08:00

9.9 KiB
Executable File

PPI ID-map E2E final review fix report

Date: 2026-07-10

Status

PASS. The Important current-job isolation finding and the Minor direct-positive coverage gap are closed with one backend behavior change and two focused regressions.

Production scope remained limited to apps/web/pipeline_backend.py. No resolver, STRING client/policy, Reset, frontend, contract, manifest schema, or CLI behavior was changed.

Root cause

run_stage("ppi", ...) preserved an unknown explicit ppi_species by calling species_to_string_code(requested_ppi_species, requested_ppi_species). An absolute fallback therefore reached:

Path(job.input_dir) / ppi_species / f"{ppi_species}.pep_cds_idmap.tsv"

Because an absolute pathlib operand resets the accumulated root, a matching-looking ID map outside the current job could be selected and forwarded to the subprocess.

RED

Added test_ppi_explicit_absolute_species_rejected_before_outside_id_map_lookup, which creates an ID map outside job.input_dir and supplies its absolute stem as explicit ppi_species.

Before the production fix:

1 failed, 1 passed, 34 deselected in 0.84s
FAILED test_ppi_explicit_absolute_species_rejected_before_outside_id_map_lookup
assert launched == []
Left contains one more item
[CMD] ... --species /tmp/.../outside/soe ... --id-map /tmp/.../outside/soe.pep_cds_idmap.tsv

This is the expected failure: the outside path was selected and run_cmd_logged was called.

The accompanying direct-positive test passed during RED, characterizing existing intended behavior rather than introducing new production behavior.

GREEN

The backend now canonicalizes every explicit non-auto value with:

species_to_string_code(requested_ppi_species, "")

An empty result returns ❌ PPI explicit unsupported STRING species: ... immediately, before scope/mapping work, ID-map path construction, or subprocess launch. Supported explicit species retain the canonical code and are not overwritten by scope reference fallback.

The security and direct-positive tests then passed:

2 passed, 34 deselected in 0.70s

Verification

Focused PPI/ID-map regression:

/home/xia-lab/miniconda3/envs/test4py311/bin/python -m pytest \
  tests/unit/test_ppi_id_resolution.py \
  tests/unit/test_pipeline_backend_execution_env.py \
  -k 'id_map or ppi' -q

52 passed, 6 deselected in 3.81s

Full four-file convergence regression:

/home/xia-lab/miniconda3/envs/test4py311/bin/python -m pytest \
  tests/unit/test_id_resolution.py \
  tests/unit/test_ppi_id_resolution.py \
  tests/unit/test_ppi_string_policy.py \
  tests/unit/test_pipeline_backend_execution_env.py -q

77 passed in 8.01s

Compilation:

env PYTHONPYCACHEPREFIX=/tmp/e2e-final-pycache \
  /home/xia-lab/miniconda3/envs/test4py311/bin/python -m py_compile \
  skills/ppi/run.py apps/web/pipeline_backend.py \
  tests/unit/test_pipeline_backend_execution_env.py

exit 0

All pytest runs emitted the pre-existing non-failing RequestsDependencyWarning about the installed urllib3/chardet/charset_normalizer combination.

Validation was synthetic backend command capture, not a new biological full-data or live STRING execution. No service or Celery restart was performed. No diagnosis record was changed because the new backend error is already explicit and the frozen scope excluded diagnosis changes.

Exact diff

--- apps/web/pipeline_backend.py.before
+++ apps/web/pipeline_backend.py
@@ -8641,7 +8641,9 @@
         ppi_selected_targets = []
         requested_ppi_species = str(ppi_species or "").strip().lower()
         explicit_ppi_species = bool(requested_ppi_species and requested_ppi_species != "auto")
-        ppi_species = requested_ppi_species if explicit_ppi_species else ""
+        ppi_species = species_to_string_code(requested_ppi_species, "") if explicit_ppi_species else ""
+        if explicit_ppi_species and not ppi_species:
+            return f"❌ PPI explicit unsupported STRING species: {requested_ppi_species}", manifest
         mapping_file = None
         if scope_file:
             ppi_scope_path = resolve_scope_path(scope_file, in_dir, ws)
@@ -8661,12 +8663,11 @@
             )
             ppi_reference_species = references[0] if references else ""
             target_string_code = species_to_string_code(ppi_selected_targets[0], "") if len(ppi_selected_targets) == 1 else ""
-            if explicit_ppi_species:
-                ppi_species = species_to_string_code(requested_ppi_species, requested_ppi_species)
-            elif target_string_code:
-                ppi_species = target_string_code
-            elif ppi_reference_species:
-                ppi_species = species_to_string_code(ppi_reference_species, ppi_species)
+            if not explicit_ppi_species:
+                if target_string_code:
+                    ppi_species = target_string_code
+                elif ppi_reference_species:
+                    ppi_species = species_to_string_code(ppi_reference_species, ppi_species)
 
             scope_id = str(ppi_scope_data.get("scope_id") or "")
             use_target_string_directly = bool(
--- tests/unit/test_pipeline_backend_execution_env.py.before
+++ tests/unit/test_pipeline_backend_execution_env.py
@@ -278,6 +278,53 @@
     assert not stale_image.exists()
 
 
+def test_ppi_direct_supported_target_forwards_exact_current_job_id_map(tmp_path, monkeypatch):
+    job = backend.JobInfo(
+        job_id="job_test",
+        input_dir=str(tmp_path / "input"),
+        workspace_dir=str(tmp_path / "workspace"),
+        logs_dir=str(tmp_path / "workspace" / "logs"),
+        created_at="",
+    )
+    input_dir = tmp_path / "input"
+    workspace = tmp_path / "workspace"
+    final_dir = workspace / "final"
+    (input_dir / "osa").mkdir(parents=True)
+    final_dir.mkdir(parents=True)
+    (workspace / "logs").mkdir(parents=True)
+    id_map_file = input_dir / "osa" / "osa.pep_cds_idmap.tsv"
+    id_map_file.write_text("species_code\tprotein_id\nosa\tOs01g0810800\n", encoding="utf-8")
+    (final_dir / "Os.CRR.final_members.id").write_text("Os01t0810800-01\n", encoding="utf-8")
+    scope_file = final_dir / "Os.CRR.CRR.target-Os.ref-At.target_plus_reference.scope.json"
+    _write_ppi_scope(scope_file)
+    captured = {}
+
+    def fake_run_cmd_logged(cmd, log_path, cwd=None):
+        captured["cmd"] = cmd
+        _write_fake_ppi_outputs(workspace / "ppi")
+        return 0
+
+    monkeypatch.setattr(backend, "can_start_new_task", lambda *args, **kwargs: (True, "ok"))
+    monkeypatch.setattr(backend, "run_cmd_logged", fake_run_cmd_logged)
+    monkeypatch.setattr(backend, "download_string_network_image", lambda *args, **kwargs: None)
+
+    message, manifest = backend.run_stage(
+        "ppi",
+        job,
+        "Os.CRR",
+        ppi_species="osa",
+        ppi_score=400,
+        scope_file=str(scope_file),
+        analysis_route="target_plus_reference",
+    )
+
+    cmd = captured["cmd"]
+    assert "✅ PPI analysis completed" in message
+    assert manifest and manifest.exists()
+    assert cmd[cmd.index("--id-map") + 1] == str(id_map_file)
+    assert "--mapping" not in cmd
+
+
 def test_infer_string_species_from_ortholog_mapping_does_not_default_to_ath(tmp_path):
     mapping = tmp_path / "Ft.unknown.orthologs.txt"
     mapping.write_text("FtGene1\tRefGene1\t90\tFt\tUnknownSpecies\n", encoding="utf-8")
@@ -1105,6 +1152,50 @@
     assert message == f"❌ PPI projected analysis requires species-matched ID map: {expected}"
 
 
+def test_ppi_explicit_absolute_species_rejected_before_outside_id_map_lookup(tmp_path, monkeypatch):
+    job = backend.JobInfo(
+        job_id="job_test",
+        input_dir=str(tmp_path / "job" / "input"),
+        workspace_dir=str(tmp_path / "job" / "workspace"),
+        logs_dir=str(tmp_path / "job" / "workspace" / "logs"),
+        created_at="",
+    )
+    input_dir = tmp_path / "job" / "input"
+    workspace = tmp_path / "job" / "workspace"
+    final_dir = workspace / "final"
+    outside_species = tmp_path / "outside" / "soe"
+    outside_id_map = outside_species.with_name("soe.pep_cds_idmap.tsv")
+    input_dir.mkdir(parents=True)
+    final_dir.mkdir(parents=True)
+    (workspace / "logs").mkdir(parents=True)
+    outside_id_map.parent.mkdir(parents=True)
+    outside_id_map.write_text("species_code\tprotein_id\nsoe\tSoeGene1\n", encoding="utf-8")
+    (final_dir / "Os.CRR.final_members.id").write_text("Os01t0810800-01\n", encoding="utf-8")
+    (final_dir / "Os.CRR.orthologs.txt").write_text(
+        "Os01t0810800-01\tSoeGene1\t90\tOs\tsoe\n", encoding="utf-8"
+    )
+    scope_file = final_dir / "Os.CRR.CRR.target-Os.ref-soe.target_plus_reference.scope.json"
+    _write_ppi_scope(scope_file, reference_species="soe")
+    launched = []
+    monkeypatch.setattr(backend, "can_start_new_task", lambda *args, **kwargs: (True, "ok"))
+    monkeypatch.setattr(backend, "run_cmd_logged", lambda *args, **kwargs: launched.append(args))
+
+    requested_species = str(outside_species)
+    message, _manifest = backend.run_stage(
+        "ppi",
+        job,
+        "Os.CRR",
+        ppi_species=requested_species,
+        scope_file=str(scope_file),
+        analysis_route="target_plus_reference",
+    )
+
+    assert launched == []
+    assert "unsupported STRING species" in message
+    assert requested_species in message
+    assert str(outside_id_map) not in message
+
+
 def test_ppi_missing_authoritative_skill_never_selects_legacy_cli(tmp_path, monkeypatch):
     job = backend.JobInfo(
         job_id="job_test",

The report file itself is the only additional created file and is not included recursively in the diff above.

Concerns

  • The dependency warning remains pre-existing and out of scope.
  • This fix validates supported species before constructing the deterministic ID-map path. It does not add a separate symlink-containment policy for a correctly named in-job map; that was not requested by the binding fix instruction and would broaden the frozen design.