# Task 3 Independent Verification — Typed STRING API and deterministic PPI ID resolution ## Verdict **FAIL** Most Task 3 behavior is implemented and passes fresh focused, backend, offline, multiprocessing, syntax, and direct production-path checks. One blocking protocol-validation defect remains: STRING mapping rows that omit (or provide a non-string) `preferredName` are accepted as valid. The approved Task 3 requires the mapping boundary to retain `queryItem/queryIndex/stringId/preferredName/ ncbiTaxonId`, reject malformed rows, and perform explicit JSON-shape validation. ## Blocking finding ### Mapping validation does not require `preferredName` `skills/ppi/run.py:184-234` validates the payload container, `queryIndex`, echoed `queryItem`, `stringId`, and `ncbiTaxonId`, but the malformed-row condition at lines 226-232 never checks `preferredName`. Fresh direct reproducer: ```python payload = [{ "queryIndex": 0, "queryItem": "A", "stringId": "3562.A", "ncbiTaxonId": 3562, }] StringAPI.validate_mapping_rows(payload, ["A"], "3562") ``` Exit code: `0` ```text ACCEPTED_MALFORMED_MAPPING [{'queryIndex': 0, 'queryItem': 'A', 'stringId': '3562.A', 'ncbiTaxonId': 3562}] ``` This row should instead raise `StringApiError(kind="protocol")`. Without that check, the typed STRING boundary silently converts absent/invalid preferred-name evidence to an empty or stringified value later in resolution rather than classifying the response as a protocol/shape failure. The focused malformed-row parameterization at `tests/unit/test_ppi_id_resolution.py:507-534` covers a missing `stringId` but has no missing/non-string `preferredName` case. Required correction: include a non-empty string `preferredName` in the mapping row shape validation and add focused missing/non-string regression cases. ## Baseline/current and scope evidence Commands were run from `/data/project_roots/PlantAI-dev`. This workspace has no functional Git repository, so the review-package baselines were used exactly as directed: ```bash diff -u /tmp/ppi-id-resolution-task3-baseline/run.py skills/ppi/run.py diff -u /tmp/ppi-id-resolution-task3-baseline/pipeline_backend.py apps/web/pipeline_backend.py diff -u /tmp/ppi-id-resolution-task3-baseline/test_ppi_string_policy.py tests/unit/test_ppi_string_policy.py ``` The expected exit status for changed pairs was `1`. The policy test pair was identical (exit `0`). Complete-file inspection was performed for the new `gfa_core/string_rate_gate.py` and `tests/unit/test_ppi_id_resolution.py` files. The implementation changes are confined to the Task 3 allowlist: - `skills/ppi/run.py`: typed API, relationship origins, deterministic candidate resolution, selected-ID network submission, per-relationship report, and empty-network handling. - `gfa_core/string_rate_gate.py`: shared flock/timestamp pacing. - `apps/web/pipeline_backend.py`: one import and the active optional STRING image helper's injected client/gate plus `PlantAI` identity. - `tests/unit/test_ppi_id_resolution.py`: new offline protocol, provenance, execution, and multiprocessing tests. - `.superpowers/sdd/task-3-report.md`: implementation record. No Reset API, frontend labels, contract/diagnosis, broad Ortholog redesign, or unrelated backend production change appears in the Task 3 baseline diff. ## Fresh required verification ### Focused Task 3 suite ```bash /home/xia-lab/miniconda3/envs/test4py311/bin/python -m pytest \ tests/unit/test_ppi_id_resolution.py tests/unit/test_ppi_string_policy.py -q ``` Exit code: `0` ```text collected 43 items tests/unit/test_ppi_id_resolution.py ............................... tests/unit/test_ppi_string_policy.py ............ 43 passed in 0.62s ``` ### Explicit no-live-network rerun I reran the same 43 tests after replacing `socket.socket.connect` with a function that raises `AssertionError("live network blocked by verifier")`. Exit code: `0` ```text 43 passed in 0.57s ``` This establishes that the focused unit suite, including its forked-process rate-gate checks, does not contact STRING or any other live network service. ### Relevant backend PPI/image selection ```bash /home/xia-lab/miniconda3/envs/test4py311/bin/python -m pytest \ tests/unit/test_pipeline_backend_execution_env.py \ -k 'string_network_image or ppi' -q ``` Exit code: `0` ```text collected 31 items / 5 deselected / 26 selected 26 passed, 5 deselected in 5.65s ``` ### Syntax compilation ```bash /home/xia-lab/miniconda3/envs/test4py311/bin/python -m py_compile \ gfa_core/string_rate_gate.py skills/ppi/run.py apps/web/pipeline_backend.py ``` Exit code: `0`; no output. ### Combined regression ```bash /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 ``` Exit code: `0` ```text collected 86 items 86 passed in 6.59s ``` Pytest/import startup consistently emitted the pre-existing `RequestsDependencyWarning` for the environment's installed urllib3 and character-detection package versions. It did not affect any assertion. ## Direct production `PpiSkill.run()` probe An independent temporary-file probe invoked the unmodified production `PpiSkill.run()` with injected local fake STRING clients. It did not call the network and verified all of the following in one run: - 24 canonical-to-reference relationships remain 24 TSV data rows. - The 24 origins collapse to 12 unique reference queries. - Five reference queries resolve from raw versioned XP evidence and seven only from local numeric GeneID fallback. - `get_network()` receives exactly 12 deduplicated selected STRING IDs, not raw XP IDs, local GeneIDs, or 24 duplicated relationship values. - A valid `network -> []` returns stage success with `network_status=empty_network` and a header-only `protein1/protein2/score` TSV. - Zero resolved STRING IDs return `status=error` with `ppi_string_id_resolution_failed` and do not call `get_network()`. - A network `StringApiError(kind="http", status_code=503)` propagates as that typed external API failure and is not converted to empty-network success. Exit code: `0` ```text DIRECT_PROBE_PASS 24_relationships 12_queries 5_raw 7_geneid; empty_network_success; zero_resolution_short_circuit; api_failure_typed ``` ## Other requirement evidence - `PpiSkill.run()` constructs origins from exact versioned Ortholog references, calls the integrated resolver, writes one summary row per retained relationship, and submits `resolution.selected_string_ids` to the network call. - Candidate probes and final selected STRING IDs are deduplicated while origin rows are not. Conflicting local candidates and conflicting Ortholog references remain explicit; converging references remain resolved. - Mapping `404` and `200 []` are zero-resolution responses. Other HTTP, transport, JSON, and the currently validated protocol failures remain typed. - `network -> []` and network HTTP failure follow distinct paths. - Version discovery accepts both `stable_address` and `string_stable_address`; identifier endpoints use POST, mapping uses `echo_query=1`, scientific calls use `caller_identity=PlantAI`, and the request timeout is 30 seconds. - `StringRateGate` uses the common default/env-selected timestamp path, `fcntl.flock`, an atomically replaced timestamp file, a one-second production interval, and injectable clock/sleeper. Scientific API calls pass through it in `_request_json`; every optional image/SVG attempt calls the same gate type. Fresh real multiprocessing tests verified scientific/scientific and scientific/image serialization. ## Scope and residual validation - No production or test source file was edited during verification. This verification document is the only created file. - No live STRING call, backend service execution, Celery restart, or full real job was performed. Those are explicitly deferred to Task 6; Task 3 uses realistic local fakes and temporary data. - Task 3 cannot pass until malformed mapping rows lacking a valid `preferredName` are rejected with a typed protocol error and covered by a regression test.