2026-06-15 — Service Bus multi-token outfmt (/v1/jobs path)¶
Motivation¶
External services wanted to enqueue a BLAST request in the sibling's /v1/jobs
shape — blast_options with a multi-token tabular outfmt
("7 std staxids sstrand qseq sseq") and an extra CLI string — and still have
the dashboard track and render the job. The Service Bus consumer only spoke the
/api/v1/elastic-blast/submit contract, whose ExternalBlastOptions pins
outfmt to 5 (XML, for the Hsp_hseq→FASTA enrichment) and has no
additional_options / extra field. So a multi-token message silently lost its
output layout and ran with the default options.
The sibling already exposes both endpoints against the same job store:
/api/v1/elastic-blast/submit (XML-locked) and /v1/jobs (free-form
blast_options incl. multi-token outfmt + extra). The dashboard result
parser already auto-branches XML (outfmt 5) vs tabular (outfmt 6/7) and
maps the extended columns (staxids / sscinames / sstrand), so the UI
already renders tabular jobs — the only missing link was the queue routing.
User-facing change¶
- A Service Bus message carrying
blast_optionsis routed to/v1/jobs. The consumer detects theblast_optionsobject (the/v1/jobsshape) and forwards it to the sibling/v1/jobsendpoint, preserving the multi-tokenoutfmtandextraverbatim. A message using the legacyoptionsobject (ExternalBlastOptions) keeps the existing XML path — the two are mutually exclusive by key name, so a producer opts in explicitly. - Submit-time validation rejects an un-mergeable tabular layout. A sharded
DB (e.g. core_nt) runs the shard-merge finalizer, which re-ranks shard hits by
evalue/bitscoreresolved by name. A tabularoutfmtmissing either is rejected at submit (HTTP-style 422 in the model; dead-lettered on the queue) instead of failing the merge minutes later.std(which includes both) and a bare numeric code pass. - Sharded-DB profile promotion + server-derived source apply on both paths.
A
core_ntmessage with a missing/standardresource_profileis still promoted tocore_nt_safe, andsubmission_source=servicebusis stamped server-side (a producer cannot spoof it). - Dashboard UI renders these jobs unchanged — job tracking (jobstate row + message-flow trace), the jobs list, and the result tabs all work via the existing tabular-aware parser. No UI change was needed.
Sample message (multi-token)¶
{
"program": "blastn",
"db": "core_nt",
"query_fasta": ">NC_003310.1 ...\nATG...",
"blast_options": {
"evalue": 0.05,
"max_target_seqs": 100,
"outfmt": "7 std staxids sstrand qseq sseq",
"extra": "-word_size 28 -dust yes -soft_masking false -searchsp 32156241807668"
},
"resource_profile": "core_nt_safe"
}
API / IaC diff summary¶
- New
external_blast.submit_job_v1(payload, …)— posts to the siblingPOST /v1/jobs, sharing the transport-retry + stale-token-401 self-heal contract ofsubmit_job(delegates withsubmit_path="/v1/jobs"). - New
ExternalBlastV1Request/BlastV1Optionsvalidation models in api/routes/elastic_blast.py — the dashboard mirror of the siblingJobSubmitRequest(Mode B) with the shard-mergeoutfmtguard. - Service Bus drain (api/tasks/servicebus/tasks.py):
_is_v1_jobs_message+_build_v1_jobs_payload;_drain_handlerroutes tosubmit_job_v1whenblast_optionsis present. - No sibling change / image rebuild — both sibling endpoints already exist; this only changes which one the queue consumer calls. Worker redeploy carries it.
Validation evidence¶
- Backend:
uv run pytest -q api/tests— 3719 passed, 3 skipped. New suitetest_servicebus_v1_multitoken.py(model accept/reject, routing detection, payload build + profile promotion,submit_job_v1posts to/v1/jobs). - Lint:
uv run ruff check api— clean. - The XML path is unchanged:
test_servicebus_tasks.py(legacyoptions→/api/v1/elastic-blast/submit, outfmt 5) stays green.