Conversation
Both VOSI endpoints emitted their XML document with no HTTP status line and no headers at all -- the first bytes on the wire were `<?xml version="1.0"...`. These are nph- (non-parsed-headers) CGI endpoints, so the script owns the entire response; nginx and Cloudflare reject the result as a bad upstream response. Every other response in tap.py already prints a CRLF-terminated status line and headers; these two were simply missing them. Emit `HTTP/1.1 200 OK`, `Content-type: text/xml` and the blank header-terminating line, all CRLF-terminated, matching the existing convention in this file (print supplies the LF after the literal \r). Also dispatch these two endpoints before workspace resolution. They are static documents that need no workspace, but they were falling through to the "retrieve workspace from jobid" branch with an empty jobid. That resolved to <workdir>/TAP and returned a 500 whenever that directory did not already exist -- so on a fresh deployment these endpoints failed outright, and on an established one they reached the emitter and produced the header-less response above. Add tests/test_vosi_http_response.py, which asserts on raw socket bytes rather than going through a forgiving client library: a CRLF-terminated status line, no bare LF or CR within any header line, CRLFCRLF ending the header block, an XML Content-type, and an intact body. Verified red/green both ways -- the tests fail against the unpatched code and also catch the `HTTP/1.1 200 OK\n` bare-LF variant. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`ruff check .` has been failing on develop since PR #23 merged (2026-06-02), on a W291 trailing-whitespace hit at TAP/vositables.py:214 introduced by 9024259. The lint step runs before pytest, so the whole test matrix aborts before a single test executes. requirements-test.txt asked for `ruff>=0.1`, so CI silently tracked whatever ruff had shipped most recently; a newer ruff began flagging this line on code nobody had touched. Pin ruff==0.15.2 so rule changes arrive as a deliberate bump rather than a surprise red build. The vositables.py change is whitespace-only -- `git diff -w` is empty. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The existing VOSI tests go through the session-scoped `tap_server` fixture, which shares one TAP_WORKDIR across the whole suite. By the time they run, earlier sync requests have already created <workdir>/TAP, so a full-suite run only ever exercised the established-server path -- the fresh-deployment case, where the workspace-resolution bug actually returned a 500, was covered only by accident when the file happened to be run in isolation. Add test_vosi_works_without_existing_workspace, which drives the CGI directly against a pristine per-test workdir and asserts both that the response opens with a CRLF-terminated 200 and that the endpoints create no workspace at all. Verified it pins the second defect specifically: reverting only the dispatch move (keeping the header fix) fails these two tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review preference from @jpl-jengelke on #26, mirrored here to keep the two branches in sync. Make the CRLF requirement explicit at each print rather than relying on a trailing \r plus print's implicit \n. Byte output is unchanged -- both forms emit `...\r\n`, and the wire-level tests in tests/test_vosi_http_response.py still pass unmodified -- but the intent is legible without having to remember what print appends, which matters on a file where a bare LF is what broke the endpoint. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The NEA deployment already answers /capabilities and /availability with application/xml, and that is the type we want for these documents. Match it here so the two do not diverge, and update the wire-level assertion to match. Scope is deliberately the two VOSI emitters this PR already touches; the other Content-type sites (including __printVosiTables__ and the error paths) are left alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
/capabilitiesand/availabilityemit their XML document with no HTTP status line and no headers at all — the first bytes on the wire are<?xml version="1.0"....These are nph- (non-parsed-headers) CGI endpoints, so the script owns the entire response. nginx and Cloudflare reject the result as a bad upstream response, which is what's breaking these two endpoints in the RedHat/NGINX deployment. Both are IVOA requirements.
This is not a CRLF-variant problem. Every other hand-rolled response in the codebase is already correct —
tap.py(×11) andvositables.py(×1) all useprint("...\r"), which yields a proper\r\n. These two endpoints were simply missing their headers entirely, which is why standard TAP requests were unaffected.Second defect
Both endpoints fell through to the "retrieve workspace from jobid" branch with an empty jobid. That resolved to
<workdir>/TAPand returned a 500 whenever that directory did not already exist.So on an established server the directory exists, the request reaches the emitter, and you get the header-less response above. On a fresh deployment these endpoints fail outright. They're static documents that need no workspace at all.
Changes
2565a6d— the fixHTTP/1.1 200 OK,Content-type: text/xml, and the blank header-terminating line — all CRLF-terminated, matching the existing convention in this file (printsupplies the LF after the literal\r).tests/test_vosi_http_response.py.ca7a755— deterministic coverage for the workspace defectThe tests above go through the session-scoped
tap_serverfixture, which shares oneTAP_WORKDIRacross the suite. By the time they run, earlier sync requests have already created<workdir>/TAP— so a full-suite run only exercised the established-server path. The fresh-deployment case, where the workspace bug actually returned a 500, was covered only by accident when the file happened to run in isolation.test_vosi_works_without_existing_workspacedrives the CGI against a pristine per-test workdir and asserts both a CRLF-terminated 200 and that the endpoints create no workspace at all. Confirmed it pins the second defect specifically: reverting only the dispatch move, keeping the header fix, fails exactly these two tests.4a12ff9— unblocking CI (pre-existing, unrelated to the VOSI bug)ruff check .has been failing ondevelopsince PR #23 merged on 2026-06-02 — a W291 trailing-whitespace hit atTAP/vositables.py:214introduced by9024259. The lint step runs before pytest, so the entire test matrix aborted before a single test executed, and the new tests below got no CI signal at all.requirements-test.txtasked forruff>=0.1, so CI silently tracked whatever ruff shipped most recently and a newer release began flagging code nobody had touched. Pinned toruff==0.15.2so rule changes arrive as a deliberate bump. Thevositables.pychange is whitespace-only —git diff -wis empty.Folded in here rather than split out, by request. Happy to break it into its own PR if you'd rather keep this diff to the VOSI files.
Tests
The new tests assert on raw socket bytes rather than going through a forgiving client library —
requestswould happily paper over exactly the malformation nginx rejects. They check:HTTP/1.1 200 OKCRLFCRLFterminates the header blockContent-typeis presentVerified red/green in both directions: they fail against the unpatched code, and they also catch the
HTTP/1.1 200 OK\nbare-LF variant described in the original report, so they'd guard against that regression too.Full suite: 63 passed, 2 skipped (both skips pre-existing and unrelated).
ruff check .clean.Branching note
developis a strict superset ofmain(9 ahead, 0 behind), so this lands ondevelop. Companion PR #26 carries the same code fix tohotfix/table-validation, which NEA runs in production — that branch is 81 commits behind and predates the HTTP test fixture, so it gets the code fix only.🤖 Generated with Claude Code