Files
PlantAI-dev/.superpowers/sdd/task-3-verification.md
2026-08-03 18:42:47 +08:00

8.1 KiB
Executable File

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:

payload = [{
    "queryIndex": 0,
    "queryItem": "A",
    "stringId": "3562.A",
    "ncbiTaxonId": 3562,
}]
StringAPI.validate_mapping_rows(payload, ["A"], "3562")

Exit code: 0

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:

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

/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

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

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

/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

collected 31 items / 5 deselected / 26 selected
26 passed, 5 deselected in 5.65s

Syntax compilation

/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

/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

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

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.