From 62b5cf6f009d409d46450fafa2ec8a5b87e9b0bc Mon Sep 17 00:00:00 2001 From: Alexander Boldakov Date: Fri, 4 Sep 2026 19:13:51 +0000 Subject: [PATCH 1/3] test(genomic-intelligence): drop data.input.sequence_length from the recorded shapes The fixtures at the top of this file are labelled shapes recorded from the live service, and they carried a data.input.sequence_length the service no longer returns. It was removed from the echo on all seven predict operations on 2026-09-03 (contract revision 13): it meant the scored window on expression and the submitted length everywhere else, duplicating meta.sequence_length on six operations and contradicting it on the seventh. Keeping it in a fixture that claims to be a recording invites the next reader to reach for it again, which is the bug 9625541e and c85c15a7 removed from the tools. No parser reads the echo, so no assertion moves: 57 passed, 9 skipped, same as before. The one place the key stays is the payload in test_parse_expression_takes_the_submitted_length_from_meta_not_the_echo, which exists to make meta and the echo disagree. That is still worth pinning -- data.input is additionalProperties: true and no contract pin covers it, so nothing stops the key returning, and an older recorded body still has it. Its docstring said the service carries the key today; it now says when it went and why the test keeps it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019Tc2vgq9BQP1YAPYABu8b9 --- .../test_genomic_intelligence.py | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/tests/sequence_scoring_tests/test_genomic_intelligence.py b/tests/sequence_scoring_tests/test_genomic_intelligence.py index add8fb02..f41a8758 100644 --- a/tests/sequence_scoring_tests/test_genomic_intelligence.py +++ b/tests/sequence_scoring_tests/test_genomic_intelligence.py @@ -80,7 +80,7 @@ "data": { "task": "promoter", "model": "g0-promoter-2000bp", - "input": {"sequence_name": "demo", "sequence_length": 400}, + "input": {"sequence_name": "demo"}, "summary": {"total_windows": 1, "promoter_windows": 0, "threshold_used": 0.5}, "regions": [], "window_details": [ @@ -103,7 +103,7 @@ "data": { "task": "splice", "model": "g0-splice-bigbird", - "input": {"sequence_name": "demo", "sequence_length": 1200}, + "input": {"sequence_name": "demo"}, "summary": {"total_sites": 1, "donor_sites": 1, "acceptor_sites": 0, "total_windows": 1}, "sites": [ { @@ -126,7 +126,7 @@ "data": { "task": "enhancer", "model": "g0-deepstarr", - "input": {"sequence_name": "demo", "sequence_length": 1200}, + "input": {"sequence_name": "demo"}, "summary": {"total_windows": 1, "dev_score_max": -0.91, "hk_score_max": -0.09}, "windows": [ { @@ -148,7 +148,7 @@ "data": { "task": "chromatin", "model": "g0-deepsea", - "input": {"sequence_name": "demo", "sequence_length": 1200}, + "input": {"sequence_name": "demo"}, "summary": { "total_windows": 1, "total_annotations": 383, @@ -164,7 +164,7 @@ "data": { "task": "annotation", "model": "g0-annotation", - "input": {"sequence_name": "demo", "sequence_length": 25000}, + "input": {"sequence_name": "demo"}, "summary": {"total_transcripts": 1, "forward_strand": 1, "reverse_strand": 0}, "transcripts": [ { @@ -186,7 +186,7 @@ "data": { "task": "expression", "model": "g0-expression", - "input": {"sequence_name": "demo", "sequence_length": 9198}, + "input": {"sequence_name": "demo"}, "summary": {}, "prediction": { "expression": 0.95, @@ -203,7 +203,7 @@ "task": "find_genes_and_predict_expression", "annotation_model": "g0-annotation", "expression_model": "g0-expression", - "input": {"sequence_name": "HBB", "sequence_length": 25000}, + "input": {"sequence_name": "HBB"}, "summary": {"genes_found": 2, "genes_predicted": 1, "genes_skipped": 1}, "annotation": {}, "expression_predictions": [ @@ -307,11 +307,14 @@ def test_parse_expression_reads_applied_window_from_meta() -> None: def test_parse_expression_takes_the_submitted_length_from_meta_not_the_echo() -> None: """``sequence_length`` is the submission, read from ``meta``. - ``data.input`` carries a ``sequence_length`` of its own holding the 9,198 bp - scored window, so the two disagree for any locus longer than one window. - Only ``meta.sequence_length`` is in the published schema; the echo is an - untyped object no contract pin covers. This payload makes them disagree so - that reading the echo fails here rather than in a user's report. + ``data.input`` used to carry a ``sequence_length`` of its own holding the + 9,198 bp scored window, so the two disagreed for any locus longer than one + window. The service dropped that key from the echo at contract revision 13 + (2026-09-03), but ``data.input`` is an untyped, open object no contract pin + covers, so nothing stops it coming back or an older recorded body still + carrying it. Only ``meta.sequence_length`` is in the published schema. This + payload keeps the removed key and makes the two disagree, so that reading + the echo fails here rather than in a user's report. """ payload = { "data": { From 9f238f5cae509811e30f0f6628a84264f4f88cf8 Mon Sep 17 00:00:00 2001 From: Alexander Boldakov Date: Fri, 4 Sep 2026 23:11:37 +0000 Subject: [PATCH 2/3] test(genomic-intelligence): two more places that still describe the old echo Follow-on to the fixture commit, same key and same reason. _GOOD_DATA is the minimal envelope the transport and envelope-shape tests hand back, and its data.input carried a sequence_length of 400. Nothing reads it: no tool in this package touches data.input at all, and every assertion on that payload is an identity check against the dict itself. It is now {"sequence_name": "demo"}, which is what the service echoes for that submission, so the payload no longer offers a reader a field the API does not send. The comment in test_gi_expression_benchmark said, in the present tense, that data.input carries a sequence_length of its own holding the window. That has been false since contract revision 13 on 2026-09-03. The two assertions it explains are still exactly right, and are still the only place the submitted length and the scored window can be told apart, so only the tense and the consequence move: a reader of the echo now gets None rather than the window. 60 passed, 9 skipped, unchanged. ruff check and ruff format clean. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019Tc2vgq9BQP1YAPYABu8b9 --- .../test_genomic_intelligence.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/sequence_scoring_tests/test_genomic_intelligence.py b/tests/sequence_scoring_tests/test_genomic_intelligence.py index f41a8758..91366df3 100644 --- a/tests/sequence_scoring_tests/test_genomic_intelligence.py +++ b/tests/sequence_scoring_tests/test_genomic_intelligence.py @@ -603,7 +603,7 @@ def _install_session( return session -_GOOD_DATA = {"model": "g0-promoter-2000bp", "input": {"sequence_length": 400}, "summary": {"total_windows": 1}} +_GOOD_DATA = {"model": "g0-promoter-2000bp", "input": {"sequence_name": "demo"}, "summary": {"total_windows": 1}} _BAD_SUMMARY_PAYLOAD: dict[str, Any] = {"data": {**_GOOD_DATA, "summary": "all good"}, "meta": _META} @@ -1090,9 +1090,13 @@ def test_gi_expression_benchmark() -> None: result = output.results[0] # This locus is longer than one window, so it is the only place the two # quantities can be told apart: sequence_length must be what was submitted - # (meta), and the window must stay 9,198 wide. data.input carries a - # sequence_length of its own holding the window -- reading that one is the - # mistake these two assertions exist to catch. + # (meta), and the window must stay 9,198 wide. data.input used to carry a + # sequence_length of its own holding the window, and reading that one + # instead of meta is the mistake these two assertions exist to catch. The + # service dropped that key at contract revision 13 on 2026-09-03, so today + # a reader of the echo gets None rather than the window -- still wrong, + # still caught here, and data.input is an open object that nothing stops + # the key returning to. assert result.sequence_length == len(locus) assert result.scored_window is not None assert result.scored_window[1] - result.scored_window[0] == EXPRESSION_WINDOW_BP From 7e7de0ed0af24ad9da0f86492ff27e734e5011e4 Mon Sep 17 00:00:00 2001 From: Alexander Boldakov Date: Sun, 6 Sep 2026 18:32:24 +0000 Subject: [PATCH 3/3] test(genomic-intelligence): carry the recorded echoes to contract revision 16 Revision 13 dropped data.input.sequence_length, which is what this branch started as. Revision 16 (2026-09-06) went further: it dropped submitted_sequence_length and scored_window from the expression echo, and made every data.input a typed, closed object -- additionalProperties: false, all fields required, and covered by the consumer contract pins for the first time. Recorded against live PROD today: promoter / splice / enhancer / chromatin / annotation {sequence_name} expression {sequence_name, description, tss_index} find_genes_and_predict_expression {sequence_name, description} The client needed no change -- parse_expression_data already reads meta.task_specific_counts.scored_window with no echo fallback, which is the whole point of the earlier commits on this branch. What was stale was the recorded bodies and the prose around them. Two new tests, because a recorded fixture has nothing upstream keeping it honest: a stale one describes a service that no longer exists and the client it exercises goes on passing. test_recorded_input_echoes_carry_no_derived_key sweeps all seven payloads for the three removed keys; test_the_recorded_expression_echo_matches_the_published_shape pins the two non-trivial shapes by set equality, so a re-added key fails too. Both verified red by reintroducing scored_window into the expression echo. Deliberately NOT touched: the adversarial payload in test_parse_expression_takes_the_submitted_length_from_meta_not_the_echo, which keeps all three removed keys and makes them disagree with meta on purpose. Its value is that it does not match the current contract, and its docstring now says so, because "fix the fixture to match the schema" is the obvious wrong move for the next reader. --- .../test_genomic_intelligence.py | 72 ++++++++++++++++--- 1 file changed, 61 insertions(+), 11 deletions(-) diff --git a/tests/sequence_scoring_tests/test_genomic_intelligence.py b/tests/sequence_scoring_tests/test_genomic_intelligence.py index 91366df3..700e5441 100644 --- a/tests/sequence_scoring_tests/test_genomic_intelligence.py +++ b/tests/sequence_scoring_tests/test_genomic_intelligence.py @@ -186,7 +186,7 @@ "data": { "task": "expression", "model": "g0-expression", - "input": {"sequence_name": "demo"}, + "input": {"sequence_name": "demo", "description": "K562 RNA-seq", "tss_index": 4599}, "summary": {}, "prediction": { "expression": 0.95, @@ -203,7 +203,7 @@ "task": "find_genes_and_predict_expression", "annotation_model": "g0-annotation", "expression_model": "g0-expression", - "input": {"sequence_name": "HBB"}, + "input": {"sequence_name": "HBB", "description": "HBB locus"}, "summary": {"genes_found": 2, "genes_predicted": 1, "genes_skipped": 1}, "annotation": {}, "expression_predictions": [ @@ -304,17 +304,65 @@ def test_parse_expression_reads_applied_window_from_meta() -> None: assert result.scored_window == [0, 9198] +def test_recorded_input_echoes_carry_no_derived_key() -> None: + """No recorded ``data.input`` carries a key the service stopped echoing. + + Contract revision 16 (2026-09-06) made every ``data.input`` a closed object: + ``additionalProperties: false``, every field required. The published shapes + are ``{sequence_name}`` for promoter / splice / enhancer / chromatin / + annotation, ``{sequence_name, description, tss_index}`` for expression, and + ``{sequence_name, description}`` for the composite workflow. + + These fixtures are recorded bodies, so nothing upstream keeps them honest -- + a stale one simply describes a service that no longer exists, and the client + it exercises goes on passing. This test is that check. It deliberately does + not cover the adversarial payload below, which keeps the removed keys on + purpose. + """ + derived = {"sequence_length", "submitted_sequence_length", "scored_window"} + recorded = { + "promoter": _PROMOTER_PAYLOAD, + "splice": _SPLICE_PAYLOAD, + "enhancer": _ENHANCER_PAYLOAD, + "chromatin": _CHROMATIN_PAYLOAD, + "annotation": _ANNOTATION_PAYLOAD, + "expression": _EXPRESSION_PAYLOAD, + "workflow": _WORKFLOW_PAYLOAD, + } + for task, payload in recorded.items(): + echo = payload["data"]["input"] + assert derived.isdisjoint(echo), f"{task} echo still carries {derived & set(echo)}" + + +def test_the_recorded_expression_echo_matches_the_published_shape() -> None: + """Expression echoes exactly its three inputs, and the composite its two.""" + assert set(_EXPRESSION_PAYLOAD["data"]["input"]) == { + "sequence_name", + "description", + "tss_index", + } + assert set(_WORKFLOW_PAYLOAD["data"]["input"]) == {"sequence_name", "description"} + + def test_parse_expression_takes_the_submitted_length_from_meta_not_the_echo() -> None: """``sequence_length`` is the submission, read from ``meta``. ``data.input`` used to carry a ``sequence_length`` of its own holding the 9,198 bp scored window, so the two disagreed for any locus longer than one window. The service dropped that key from the echo at contract revision 13 - (2026-09-03), but ``data.input`` is an untyped, open object no contract pin - covers, so nothing stops it coming back or an older recorded body still - carrying it. Only ``meta.sequence_length`` is in the published schema. This - payload keeps the removed key and makes the two disagree, so that reading - the echo fails here rather than in a user's report. + (2026-09-03), then dropped ``submitted_sequence_length`` and + ``scored_window`` at revision 16 (2026-09-06), which also made every + ``data.input`` a typed, closed object -- ``additionalProperties: false``, + every field required, and covered by the consumer pins for the first time. + Only ``meta`` carries the derived numbers: ``meta.sequence_length`` for the + submission and ``meta.task_specific_counts.scored_window`` for the window. + + A recorded body is not the live document, so being closed upstream does not + stop an older payload -- or a replayed cassette -- still carrying the + removed keys. That is what this test is for: the payload below keeps all + three and makes them disagree with ``meta``, so reading the echo fails here + rather than in a user's report. Do not "fix" this fixture by making it match + the current contract; its whole value is that it does not. """ payload = { "data": { @@ -1093,10 +1141,12 @@ def test_gi_expression_benchmark() -> None: # (meta), and the window must stay 9,198 wide. data.input used to carry a # sequence_length of its own holding the window, and reading that one # instead of meta is the mistake these two assertions exist to catch. The - # service dropped that key at contract revision 13 on 2026-09-03, so today - # a reader of the echo gets None rather than the window -- still wrong, - # still caught here, and data.input is an open object that nothing stops - # the key returning to. + # service dropped that key at contract revision 13 on 2026-09-03, and at + # revision 16 on 2026-09-06 dropped scored_window and + # submitted_sequence_length too, closing data.input against extra keys. So + # today a reader of the echo gets None rather than the window -- still + # wrong, and still caught here. This is a live call, so it also fails if + # the service ever reopens the echo and a reader drifts back to it. assert result.sequence_length == len(locus) assert result.scored_window is not None assert result.scored_window[1] - result.scored_window[0] == EXPRESSION_WINDOW_BP