# STRING PPI production wiring — Task 2 identity validation Date: 2026-07-21 Authoritative target: `/home/xzq/5090/PlantAI-dev4090-7/PlantAI-dev` Scope: job-local target identity input only; no resolver/preflight decision wiring. ## Boundaries - Added a read-only job-local identity reader for `config.xlsx`. - Added independent request fields `targetScientificName` and `ppiTargetTreeTaxonId`. - Passed those fields to the backend and returned a separate `target_identity` object. - Did not modify KaKs, Ortholog, PPI execution, legacy recommendation replacement, or STRING resolver preflight decisions. ## Discovery evidence The required first CodeGraph query was attempted: ```bash timeout 15s codegraph explore "PpiSpeciesResolutionRequest species-resolution API job config.xlsx Species_Code Species_Name Taxon_ID TaxID NCBI_TaxID targetScientificName ppiTargetTreeTaxonId" ``` Exit code: `124`; no output. Targeted `rg` and bounded source reads were used after recording the timeout. ## RED evidence Initial identity suite: ```bash ssh planai5090 'cd /home/xzq/5090/PlantAI-dev4090-7/PlantAI-dev && /home/xzq/miniconda3/envs/test4py311/bin/python -m pytest -q tests/unit/test_ppi_target_identity.py' ``` Exit code: `1`. Result: `6 failed in 1.78s`. - Five tests failed because `resolve_ppi_target_identity` did not exist. - The request-contract test failed because `PpiSpeciesResolutionRequest` had no `targetScientificName` field. Additional backend propagation RED: ```bash ssh planai5090 'cd /home/xzq/5090/PlantAI-dev4090-7/PlantAI-dev && /home/xzq/miniconda3/envs/test4py311/bin/python -m pytest -q tests/unit/test_ppi_target_identity.py::test_explicit_identity_reaches_backend_without_using_ppi_species' ``` Exit code: `1`. Result: `1 failed in 0.32s`; failure was `TypeError: resolve_ppi_species_resolution() got an unexpected keyword argument 'target_scientific_name'`. ## GREEN evidence Focused Task 2 suite: ```bash ssh planai5090 'cd /home/xzq/5090/PlantAI-dev4090-7/PlantAI-dev && /home/xzq/miniconda3/envs/test4py311/bin/python -m pytest -q tests/unit/test_ppi_target_identity.py' ``` Exit code: `0`. Result: `7 passed in 1.80s`. ### Review-blocker correction: scientific-name conflict The first Gate 2 review found that an explicit scientific name could be mixed with a different job scientific name and the job TaxID. Two negative tests were added before changing production code: ```bash ssh planai5090 'cd /home/xzq/5090/PlantAI-dev4090-7/PlantAI-dev && /home/xzq/miniconda3/envs/test4py311/bin/python -m pytest -q tests/unit/test_ppi_target_identity.py::test_explicit_scientific_name_conflict_never_mixes_with_job_taxon tests/unit/test_ppi_target_identity.py::test_species_resolution_api_surfaces_scientific_name_conflict' ``` RED exit code: `1`. Result: `2 failed in 1.31s`; both helper and API returned `resolved` instead of `conflict`. After adding only the normalized-name consistency check, the same command had exit code `0`: `2 passed in 1.25s`. The full Task 2 identity suite then had exit code `0`: `9 passed in 1.37s`. The conflict response is `target_scientific_name_conflict` and deliberately leaves both `scientific_name` and `tree_taxon_id` empty, preventing a mixed name/TaxID identity from reaching later stages. Syntax and related identity/API/snapshot regression: ```bash ssh planai5090 'cd /home/xzq/5090/PlantAI-dev4090-7/PlantAI-dev && /home/xzq/miniconda3/envs/test4py311/bin/python -m py_compile apps/web/pipeline_backend.py apps/web/vue3/main.py tests/unit/test_ppi_target_identity.py && /home/xzq/miniconda3/envs/test4py311/bin/python -m pytest -q tests/unit/test_ppi_target_identity.py tests/unit/test_ppi_frontend_display_contract.py tests/unit/test_string_species_snapshot.py tests/unit/test_string_species_registry.py' ``` Exit code: `0`. Result: `46 passed in 13.67s`. Existing backend preflight regression probe: ```bash ssh planai5090 'cd /home/xzq/5090/PlantAI-dev4090-7/PlantAI-dev && /home/xzq/miniconda3/envs/test4py311/bin/python -m pytest -q tests/unit/test_pipeline_backend_execution_env.py -k "ppi_species_resolution or os_direct_ppi"' ``` Exit code: `1`. Result: `8 passed, 1 failed, 30 deselected`. The sole failure is `test_os_direct_ppi_uses_complete_string_registry_without_ortholog`, which expects a top-level `selection_mode`. That field belongs to Task 3 resolver/preflight wiring and is intentionally absent after Task 2. It is not claimed as passing here and was not repaired out of scope. ## Identity contract verified - `Species_Code=Sv` plus repeated `Setaria_viridis` / `Setaria viridis` rows yields one canonical `Setaria viridis` identity. - `Taxon_ID`, `TaxID`, and `NCBI_TaxID` are accepted; within one row their priority is that order. - Explicit tree TaxID has priority but a mismatch with the unique job TaxID returns `target_taxon_id_conflict`; no silent overwrite occurs. - Bare `Sv` with no job or explicit identity returns `target_string_tree_unresolved`; no two-letter-code guess occurs. - Existing curated `Os -> 39947` and `Ft -> 62330` compatibility identities remain available. - `ppiSpecies` remains `auto` when explicit target identity fields are supplied; target tree identity is not placed in `ppiSpecies`. ## Changed files and hashes ```text apps/web/pipeline_backend.py 1c94e33a061fdeba6cc3816968b4e722a03fd3a2886665f6e28d848e24003ccf apps/web/vue3/main.py f12cddaaeb0d3e39165884cf1603ef05a79a14ceb02da5e1629fe83fd4e4180f tests/unit/test_ppi_target_identity.py 7282a78e0e8a384f1ea1ad2abc58464b7d204ddd27e65a8da6e46b11e5965bdc ``` ## Gate 2 - Job scientific-name identity: PASS. - Explicit scientific-name and target-tree-TaxID request channels: PASS. - Supported TaxID columns and deterministic row priority: PASS. - Explicit/job TaxID conflict is structured: PASS. - Explicit/job normalized scientific-name conflict is structured at helper and API layers: PASS. - Conflicting scientific name is never combined with the job TaxID: PASS. - Bare unknown short code is not guessed: PASS. - Os/Ft compatibility identity: PASS. - Target identity remains separate from `ppiSpecies`: PASS. - Task 2 focused and related non-resolver regressions: PASS. - Task 3 `selection_mode` regression: NOT PASSED / out of Task 2 scope. Task 2 Gate: **PASS for identity-input scope only**. This does not authorize or claim Task 3 resolver/preflight completion. ## Online Gate on port 9010 The target has no systemd unit or dedicated 9010 restart script. The observed authoritative process-management pattern was: - `start_celery_9000.sh` with externally supplied `BBAV5_QUEUE_PREFIX=plantai_4090_9010` and `BBAV5_REDIS_URL=redis://localhost:6379/11` for five workers; - direct `nohup ... python -m uvicorn apps.web.vue3.main:app --port 9010` for the Web process. Before restart, the exact 9010 master PIDs were: ```text Web 2391890 default 2391860 heavy 2391866 medium 2391873 light 2391880 rnaseq 2391891 launcher shell 2391849 ``` All six service processes had cwd `/home/xzq/5090/PlantAI-dev4090-7/PlantAI-dev`, queue prefix `plantai_4090_9010`, and Redis URL `redis://localhost:6379/11`. Only those exact PIDs were stopped: ```bash kill -TERM 2391890 2391860 2391866 2391873 2391880 2391891 2391849 ``` Workers were restarted with: ```bash env BBAV5_PROJECT_ROOT=/home/xzq/5090/PlantAI-dev4090-7/PlantAI-dev \ PLANTAI_PROJECT_ROOT=/home/xzq/5090/PlantAI-dev4090-7/PlantAI-dev \ BBAV5_CONDA_BASE=/home/xzq/miniconda3 \ BBAV5_DEFAULT_ENV=test4py311 \ BBAV5_QUEUE_PREFIX=plantai_4090_9010 \ BBAV5_REDIS_URL=redis://localhost:6379/11 \ BBAV5_CELERY_LOG_DIR=logs/runtime_9010/celery \ BBAV5_RNASEQ_CONCURRENCY=2 \ bash ./start_celery_9000.sh ``` Web was restarted with: ```bash nohup env BBAV5_PROJECT_ROOT=/home/xzq/5090/PlantAI-dev4090-7/PlantAI-dev \ PLANTAI_PROJECT_ROOT=/home/xzq/5090/PlantAI-dev4090-7/PlantAI-dev \ BBAV5_QUEUE_PREFIX=plantai_4090_9010 \ BBAV5_REDIS_URL=redis://localhost:6379/11 \ /home/xzq/miniconda3/envs/test4py311/bin/python -m uvicorn \ apps.web.vue3.main:app --host 0.0.0.0 --port 9010 \ > logs/runtime_9010/web.log 2>&1 < /dev/null & ``` The combined remote shell initially attempted to write `web.pid` before its backgrounded command group had created `logs/runtime_9010`, producing one `No such file or directory` message. Process inspection showed the Web process was healthy at PID `2455555`; the temporary wrapper shell PID `2455480` was terminated and `logs/runtime_9010/web.pid` was corrected to `2455555`. No second Web process was started. After restart, the master PIDs were: ```text Web 2455555 default 2455521 heavy 2455528 medium 2455537 light 2455544 rnaseq 2455556 ``` Every PID changed. All six new processes have the authoritative target cwd, queue prefix `plantai_4090_9010`, and Redis DB 11. The exact worker health check was: ```bash env BBAV5_PROJECT_ROOT=/home/xzq/5090/PlantAI-dev4090-7/PlantAI-dev \ PLANTAI_PROJECT_ROOT=/home/xzq/5090/PlantAI-dev4090-7/PlantAI-dev \ BBAV5_QUEUE_PREFIX=plantai_4090_9010 \ BBAV5_REDIS_URL=redis://localhost:6379/11 \ /home/xzq/miniconda3/envs/test4py311/bin/celery -A celery_app inspect ping \ -d plantai_4090_9010_default@xzq-Rack-Server,plantai_4090_9010_heavy@xzq-Rack-Server,plantai_4090_9010_medium@xzq-Rack-Server,plantai_4090_9010_light@xzq-Rack-Server,plantai_4090_9010_rnaseq@xzq-Rack-Server \ --timeout=10 ``` Exit code: `0`; result: `5 nodes online`, all five returned `pong`. HTTP `GET http://127.0.0.1:9010/docs` returned `200`. ### Live API evidence Both requests used the real Sv job `job_1784552755238_b8762f` and endpoint `POST http://127.0.0.1:9010/api/ppi/species-resolution`. Exact request commands: ```bash curl -sS -X POST http://127.0.0.1:9010/api/ppi/species-resolution \ -H "Content-Type: application/json" \ --data-binary '{"job_id":"job_1784552755238_b8762f","prefix":"Sv.HKL","ppiSpecies":"auto","targetSpecies":["Sv"],"referenceSpecies":[],"geneFamily":"HKL","requestedStages":["ppi"],"targetScientificName":"Setaria viridis","ppiTargetTreeTaxonId":"4556"}' curl -sS -X POST http://127.0.0.1:9010/api/ppi/species-resolution \ -H "Content-Type: application/json" \ --data-binary '{"job_id":"job_1784552755238_b8762f","prefix":"Sv.HKL","ppiSpecies":"auto","targetSpecies":["Sv"],"referenceSpecies":[],"geneFamily":"HKL","requestedStages":["ppi"],"targetScientificName":"Oryza sativa","ppiTargetTreeTaxonId":"39947"}' ``` Consistent explicit identity: ```json { "targetScientificName": "Setaria viridis", "ppiTargetTreeTaxonId": "4556" } ``` Response summary: ```json { "success": true, "requested_ppi_species": "auto", "selection_mode": null, "target_identity": { "status": "resolved", "scientific_name": "Setaria viridis", "tree_taxon_id": "4556", "identity_source": "explicit_taxon_id", "config_scientific_name": "Setaria viridis", "issue_code": "" } } ``` Conflicting explicit identity: ```json { "targetScientificName": "Oryza sativa", "ppiTargetTreeTaxonId": "39947" } ``` Response summary: ```json { "success": true, "requested_ppi_species": "auto", "selection_mode": null, "target_identity": { "status": "conflict", "issue_code": "target_scientific_name_conflict", "scientific_name": "", "tree_taxon_id": "", "explicit_scientific_name": "Oryza sativa", "config_scientific_name": "Setaria viridis" } } ``` `selection_mode=null` is expected because Task 3 resolver/preflight wiring is not part of this Gate. Port 9000 and port 19000 processes were not targeted or restarted. Online Task 2 Gate: **PASS**.