Repository navigation
fix(OMN-20611): HandlerHttpRest reports a 402 as payment_required without body or header - #4594
Conversation
…hout body or header A 402 now returns status payment_required. The payload keeps status_code and the other headers and drops the body and the PAYMENT-REQUIRED header. 200 and 404 still return success. Parametrised test covers 200, 404 and 402.
…12955) * evidence(OMN-20611): author OCC companion for OmniNode-ai/omnibase_infra#4594 OCC companion by node_pr_lifecycle_fix_effect (OMN-13317 F1 / OMN-13990 / OMN-14285). Product PR head 4c279a2bd3d68de9438fc7400cf07f161c129051. * evidence(OMN-20611): self-bind OCC#12955 + rebind contract_sha256 --------- Co-authored-by: omnimarket-bot <bot@omninode.ai>
There was a problem hiding this comment.
Hostile Reviewer — adversarial findings (OMN-17492)
Models succeeded: qwen3-review, gpt-oss-review
Models failed: none
New finding threads: 2
Deduped (already posted on this PR): 0
Below quorum (one model only, reported not threaded): 7
Nit-level findings suppressed: 1
The model is the FINDER, never the gate: merge is gated only by the
deterministic Hostile Review Thread Gate, which blocks while
hostile-reviewer threads are unresolved. Resolve each thread after
addressing (or rejecting, with a reply) its finding.
Below quorum: 7 finding(s) raised by one model only (OMN-18479)
These are reported and NOT dropped, but they get no thread and do not block: a single model's finding no other model reproduced is not evidence enough to stop a merge. Read them; act on them if they are right.
- [MAJOR]
src/omnibase_infra/handlers/handler_http.py:833-836(qwen3-review) — Docstring contradicts implementation regarding PAYMENT-REQUIRED header | The docstring for _build_response_from_bytes states that a 402 response is returned 'with no body and no PAYMENT-REQUIRED heade - [MAJOR]
src/omnibase_infra/handlers/handler_http.py:875-878(qwen3-review) — Incomplete header filtering may leak sensitive data on 402 | The implementation only filters out the 'payment-required' header. However, a 402 response from a third-party payment gateway may contain o - [MAJOR]
tests/unit/handlers/test_handler_http.py:3120-3125(qwen3-review) — Test does not verify that other headers are preserved on 402 | The test test_status_mapping_and_payload asserts that 'x-request-id' is preserved on a 402 response, which is good. However, it does not - [MAJOR]
src/omnibase_infra/handlers/handler_http.py(gpt-oss-review) — Introduced new handler status value breaking existing contract | The added handling for HTTP 402 responses returns a result with "status": "payment_required" instead of the previously always "success" - [MINOR]
src/omnibase_infra/handlers/handler_http.py:877(qwen3-review) — Header filtering is case-sensitive in a case-insensitive context | The implementation uses name.lower() != _PAYMENT_REQUIRED_HEADER to filter headers. This is correct for case-insensitive matching. Ho - [MINOR]
src/omnibase_infra/handlers/handler_http.py(gpt-oss-review) — Potential leakage of sensitive response headers | When a 402 response is processed, all response headers except the "payment-required" header are copied into the payload. If the upstream service inclu - [MINOR]
src/omnibase_infra/handlers/handler_http.py(gpt-oss-review) — Redundant correlation_id field in result payload | The 402 handling block adds "correlation_id": str(correlation_id) inside the result dict while also passing correlation_id as a separate argument to
Findings demoted from threads (anchor rejected)
-
[MINOR] hostile-reviewer (qwen3-review)
No handling for 402 in non-streaming code path | The diff only modifies _build_response_from_bytes, which is used for streaming responses. If there is a non-streaming code path that also handles 402 responses, it may not have the same filtering logic. The diff does not show any changes to non-streaming paths, which could lead to inconsistent behavior. | Evidence: The diff only shows changes to _build_response_from_bytes and the test file. There is no indication that other code paths handle 402 responses. |
Resolve this thread when addressed — the
Hostile Review Thread Gateblocks while hostile-reviewer threads are unresolved (OMN-17492). -
[MINOR] hostile-reviewer (gpt-oss-review)
Docstring does not fully describe new 402 behavior | The updated docstring mentions that status is "payment_required" with no body and no PAYMENT-REQUIRED header, but it does not clarify that other headers are still returned or that the payload includes status_code, potentially misleading developers. | Evidence: Docstring change at lines 831‑834. | Fix: Expand the docstring to explicitly state which fields are present in the payload for a 402 response and note any security considerations.
Resolve this thread when addressed — the
Hostile Review Thread Gateblocks while hostile-reviewer threads are unresolved (OMN-17492).
✅ Hostile Reviewer — REVIEWEDCritical findings: 0
Semantics (OMN-17492 — the model finds, thread resolution gates)
Powered by omniintelligence.review_pairing.cli_review — multi-model adversarial review: qwen3-review (Qwen3.8-27B), gpt-oss-review (gpt-oss-120b) (OMN-8468/OMN-8524/OMN-17492) |
The PR changes src/omnibase_infra/handlers/handler_http.py while 0.38.66 equals the latest published version, so release-identity failed. Bumps pyproject.toml, uv.lock and runtime_protocol.lock.json to 0.38.67 and regenerates docker/runners/runner-image.lock.json; check_release_identity.py now prints OK.
Adds TestHttpPaymentRequired to tests/integration/handlers/test_http_handler_integration.py: a 402 from pytest-httpserver reports status payment_required, drops the body and the PAYMENT-REQUIRED header, keeps other headers. Passes on the branch; fails with handler_http.py restored from origin/dev. Satisfies the Integration Test Coverage gate.
There was a problem hiding this comment.
Hostile Reviewer — adversarial findings (OMN-17492)
Models succeeded: qwen3-review, gpt-oss-review
Models failed: none
New finding threads: 0
Deduped (already posted on this PR): 0
Below quorum (one model only, reported not threaded): 8
Nit-level findings suppressed: 1
The model is the FINDER, never the gate: merge is gated only by the
deterministic Hostile Review Thread Gate, which blocks while
hostile-reviewer threads are unresolved. Resolve each thread after
addressing (or rejecting, with a reply) its finding.
Below quorum: 8 finding(s) raised by one model only (OMN-18479)
These are reported and NOT dropped, but they get no thread and do not block: a single model's finding no other model reproduced is not evidence enough to stop a merge. Read them; act on them if they are right.
- [MAJOR]
src/omnibase_infra/handlers/handler_http.py, _build_response_from_bytes, 402 branch(qwen3-review) — 402 response omits correlation_id from payload | The 402 branch in _build_response_from_bytes constructs the result dict with only 'status', 'payload', and 'correlation_id' keys. The docstring states - [MAJOR]
src/omnibase_infra/handlers/handler_http.py, _build_response_from_bytes, 402 branch(qwen3-review) — Header filtering is case-sensitive and incomplete | The 402 branch filters headers using name.lower() != _PAYMENT_REQUIRED_HEADER. This correctly handles the canonical 'PAYMENT-REQUIRED' form, but the - [MAJOR]
tests/unit/handlers/test_handler_http.py, TestHandlerHttpRestPaymentRequired(qwen3-review) — No test for 402 with POST operation | The handler supports both http.get and http.post. The 402 handling is in _build_response_from_bytes, which is shared by both operations. However, all tests for 40 - [MAJOR]
src/omnibase_infra/handlers/handler_http.py: line ~831(gpt-oss-review) — Introduced undocumented status value breaking API contract | The handler now returns a result with "status": "payment_required" for HTTP 402 responses. Existing consumers and downstream components exp - [MINOR]
tests/unit/handlers/test_handler_http.py, TestHandlerHttpRestPaymentRequired(qwen3-review) — No test for 402 with empty or missing headers | The 402 branch iterates response.headers.items(). If the upstream returns a 402 with no headers at all (or only the PAYMENT-REQUIRED header), the result - [MINOR]
src/omnibase_infra/handlers/handler_http.py, _build_response_from_bytes docstring(qwen3-review) — Docstring contradicts implementation on correlation_id placement | The updated docstring says the return wraps 'status, payload, and correlation_id'. For the 402 branch, correlation_id is a top-level - [MINOR]
tests/integration/handlers/test_http_handler_integration.py, test_http_get_402_is_payment_required_without_payment_text(qwen3-review) — Integration test asserts on repr() of result dict | The integration test uses repr(result) to verify that payment material is absent. repr() of a dict includes all nested values as string representati - [MINOR]
src/omnibase_infra/handlers/handler_http.py, _build_response_from_bytes(qwen3-review) — 402 handling is inlined in _build_response_from_bytes rather than in a dedicated method | The 402 branch is a 20-line block inlined in _build_response_from_bytes. This method already handles content-t
|
Lab: PR omnibase_infra#4594 head 864c1aa (OMN-20611) was tested on host omninode-pc (lab h201) using the command |
There was a problem hiding this comment.
Hostile Reviewer — adversarial findings (OMN-17492)
Models succeeded: qwen3-review, gpt-oss-review
Models failed: none
New finding threads: 0
Deduped (already posted on this PR): 0
Below quorum (one model only, reported not threaded): 10
Nit-level findings suppressed: 1
The model is the FINDER, never the gate: merge is gated only by the
deterministic Hostile Review Thread Gate, which blocks while
hostile-reviewer threads are unresolved. Resolve each thread after
addressing (or rejecting, with a reply) its finding.
Below quorum: 10 finding(s) raised by one model only (OMN-18479)
These are reported and NOT dropped, but they get no thread and do not block: a single model's finding no other model reproduced is not evidence enough to stop a merge. Read them; act on them if they are right.
- [MAJOR]
src/omnibase_infra/handlers/handler_http.py, _build_response_from_bytes, 402 branch(qwen3-review) — 402 branch omits 'body' key entirely, breaking payload schema consistency | For non-402 responses the payload dict always contains a 'body' key (set to body_text or a chunked marker). The new 402 bran - [MAJOR]
src/omnibase_infra/handlers/handler_http.py, 402 branch header comprehension(qwen3-review) — Header filtering is case-sensitive and incomplete against RFC 6581 | The 402 branch filters out the header whose lowercased name equals 'payment-required'. However, the filter iterates response.header - [MAJOR]
src/omnibase_infra/handlers/handler_http.py:877-889(gpt-oss-review) — Potential leakage of sensitive response headers on 402 handling | When a 402 response is processed, the code copies all response headers except the "payment-required" header into the payload. Other he - [MAJOR]
src/omnibase_infra/handlers/handler_http.py:865-889(gpt-oss-review) — Streaming response not closed on early return for 402 | The early return for a 402 status occurs after the response body has been read but before the streaming response object is explicitly closed. Th - [MAJOR]
src/omnibase_infra/handlers/handler_http.py:831-834(gpt-oss-review) — API contract extension introduces new status value without backward compatibility guarantees | The handler now returns a result with "status": "payment_required" for 402 responses, whereas previously - [MINOR]
src/omnibase_infra/handlers/handler_http.py, 402 branch(qwen3-review) — 402 branch does not strip Set-Cookie or other sensitive headers | The 402 branch passes through all headers except 'payment-required'. This includes Set-Cookie, Authorization, WWW-Authenticate, and an - [MINOR]
tests/unit/handlers/test_handler_http.py, TestHandlerHttpRestPaymentRequired(qwen3-review) — Unit test does not verify that non-402 responses still include the PAYMENT-REQUIRED header | The parametrized unit test asserts that for status 200 and 404, 'payment-required' is in the lowercased hea - [MINOR]
tests/unit/handlers/test_handler_http.py, TestHandlerHttpRestPaymentRequired(qwen3-review) — No test for 402 with empty or missing headers dict | The 402 branch iterates response.headers.items(). If the upstream response has no headers at all (an edge case with some HTTP clients or proxies), - [MINOR]
src/omnibase_infra/handlers/handler_http.py, _build_response_from_bytes(qwen3-review) — 402 handling is hardcoded to a single status code with no extension point | The 402 branch is a special-case if-block inside _build_response_from_bytes. If additional status codes need similar 'typed - [MINOR]
src/omnibase_infra/handlers/handler_http.py:845-862(gpt-oss-review) — Unnecessary body read for 402 responses | The implementation reads the full response body (await response.text()) before checking for a 402 status, even though the body is discarded for payment resp
|
Lab: head=c2d1c295ace72a526beda5ec73de6903a3e6ca87 host=h202 lane=fan-infra-4594 omnibase_infra#4594 (OMN-20611): |
There was a problem hiding this comment.
Hostile Reviewer — adversarial findings (OMN-17492)
Models succeeded: qwen3-review, gpt-oss-review
Models failed: none
New finding threads: 0
Deduped (already posted on this PR): 0
Below quorum (one model only, reported not threaded): 9
Nit-level findings suppressed: 1
The model is the FINDER, never the gate: merge is gated only by the
deterministic Hostile Review Thread Gate, which blocks while
hostile-reviewer threads are unresolved. Resolve each thread after
addressing (or rejecting, with a reply) its finding.
Below quorum: 9 finding(s) raised by one model only (OMN-18479)
These are reported and NOT dropped, but they get no thread and do not block: a single model's finding no other model reproduced is not evidence enough to stop a merge. Read them; act on them if they are right.
- [MAJOR]
src/omnibase_infra/handlers/handler_http.py, _build_response_from_bytes docstring(qwen3-review) — Docstring contradicts implementation: claims no PAYMENT-REQUIRED header is passed | The docstring for _build_response_from_bytes states that a 402 response is returned 'with no body and no PAYMENT-REQ - [MAJOR]
src/omnibase_infra/handlers/handler_http.py, _build_response_from_bytes(qwen3-review) — Header filtering is case-sensitive on the key, not the value | The filterif name.lower() != _PAYMENT_REQUIRED_HEADERcompares the lowercased header name against the constant string 'payment-require - [MAJOR]
tests/unit/handlers/test_handler_http.py, TestHandlerHttpRestPaymentRequired(qwen3-review) — No handling for 402 responses that are not JSON or have no body | The 402 branch in _build_response_from_bytes unconditionally returns a payload with status_code and filtered headers, but does not ins - [MAJOR]
src/omnibase_infra/handlers/handler_http.py: lines 888‑894(gpt-oss-review) — Potential leakage of payment‑related headers other than PAYMENT-REQUIRED | The 402 branch filters only the header named "payment-required". If a payment provider includes additional sensitive headers - [MAJOR]
src/omnibase_infra/handlers/handler_http.py: added constants and conditional block around line 880(gpt-oss-review) — Embedding payment‑specific logic in generic HTTP handler breaks separation of concerns | The handler now treats HTTP status 402 as a domain‑specific "payment_required" answer, coupling the generic HTT - [MINOR]
src/omnibase_infra/handlers/handler_http.py, _build_response_from_bytes(qwen3-review) — 402 status is mapped to 'payment_required' but 404 is mapped to 'success' | The parametrize test shows that a 404 response is mapped to status 'success' with the body preserved. This is a pre-existing - [MINOR]
tests/integration/handlers/test_http_handler_integration.py, TestHttpPaymentRequired(qwen3-review) — Integration test does not verify that other headers are preserved in 402 | The integration test asserts that 'x-request-id' is present in the 402 payload headers, which is good. However, it does not t - [MINOR]
tests/unit/handlers/test_handler_http.py, TestHandlerHttpRestPaymentRequired(qwen3-review) — No test for 402 with the PAYMENT-REQUIRED header absent | All tests include the PAYMENT-REQUIRED header in the 402 response. There is no test for a 402 response that does not include the PAYMENT-REQUI - [MINOR]
src/omnibase_infra/handlers/handler_http.py: line ~880(gpt-oss-review) — Redundant correlation_id field in 402 response payload | The 402 handling block adds a "correlation_id" entry inside the result payload while ModelHandlerOutput.for_compute already receives correlatio
Summary
OMN-20611, plan task T0.4 (
knowledge-base-internalbeta/plans/2026-10-05-agent-payments-402-plan.md). Operator ruling 2026-10-05T17:35:15Z: Phase 0 is fixed now.HandlerHttpRest._build_response_from_bytesnow returnsstatus: "payment_required"for an HTTP 402. The payload keepsstatus_codeand the headers exceptPAYMENT-REQUIRED, and drops the body. 200 and 404 keep"success". Every other non-2xx status is unchanged on purpose (plan section 13).This is a functional expansion (R7), so the consumer inventory is below.
Consumer inventory
Found by grep over omnibase_infra, omnibase_core, omnibase_spi, omniclaude, omnimarket, omniintelligence, omnimemory, omnidash, omniweb, onex_change_control and omnibase_compat.
successstatuscontracts/handlers/http/handler_contract.yamlhttp.getandhttp.postto the handler byoperation_match. Declares no output status enum. No change needed.handler_bootstrap_source.py,util_wiring.py,runtime_host_process.py,envelope_validator.py)http. None read the result status.omnimemoryadapter_embedding_http.py(the only caller that reads status)successraisesEmbeddingClientError("Request failed with status=payment_required ..."). Before, a 402 reached thestatus_code >= 400branch and put the response body in the error text. It is still an error, now without third-party body text.omnibase_spiprotocol_primitive_effect_executor.py,omnibase_corereplay modelsomnibase_corevalidation tests,omnimarketgolden chain test,onex_change_controlreceiptsTests
TestHandlerHttpRestPaymentRequired(200, 404, 402). The 402 case failed before the change.tests/unit/handlers/test_handler_http.pyandtest_handler_http_env.py: 144 passed. mypy clean onhandler_http.py. Pre-commit passed on commit.Not done
landing_text.pywas refused withlocal_model_binding_missing(backendlocal-coderhas nomodel_name).Evidence-Ticket: OMN-20611
Evidence-Source: OCC#12955