Skip to content

fix(OMN-20611): HandlerHttpRest reports a 402 as payment_required without body or header - #4594

Merged
jonahgabriel merged 5 commits into
devfrom
jonah/omn-20611-http-402-payment-required
Oct 7, 2026
Merged

jonahgabriel merged 5 commits into
devfrom
jonah/omn-20611-http-402-payment-required

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

OMN-20611, plan task T0.4 (knowledge-base-internal beta/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_bytes now returns status: "payment_required" for an HTTP 402. The payload keeps status_code and the headers except PAYMENT-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.

Consumer What it does with a non-success status
contracts/handlers/http/handler_contract.yaml Routes http.get and http.post to the handler by operation_match. Declares no output status enum. No change needed.
Runtime registration and dispatch (handler_bootstrap_source.py, util_wiring.py, runtime_host_process.py, envelope_validator.py) Register and dispatch by handler type http. None read the result status.
omnimemory adapter_embedding_http.py (the only caller that reads status) Any status other than success raises EmbeddingClientError("Request failed with status=payment_required ..."). Before, a 402 reached the status_code >= 400 branch and put the response body in the error text. It is still an error, now without third-party body text.
omnibase_spi protocol_primitive_effect_executor.py, omnibase_core replay models Name the operation strings only. No status handling.
omnibase_core validation tests, omnimarket golden chain test, onex_change_control receipts Mention the handler name only.
omniclaude, omniintelligence, omnidash, omniweb, omnibase_compat No reference.

Tests

  • New parametrised TestHandlerHttpRestPaymentRequired (200, 404, 402). The 402 case failed before the change.
  • tests/unit/handlers/test_handler_http.py and test_handler_http_env.py: 144 passed. mypy clean on handler_http.py. Pre-commit passed on commit.

Not done

  • No dev-202 run. Unit-level proof only; T0.4 names no lab step. The h201 stable lane was not touched.
  • PR text written by hand: landing_text.py was refused with local_model_binding_missing (backend local-coder has no model_name).

Evidence-Ticket: OMN-20611
Evidence-Source: OCC#12955

…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.
@jonahgabriel
jonahgabriel changed the base branch from main to dev October 5, 2026 18:19
jonahgabriel pushed a commit to OmniNode-ai/onex_change_control that referenced this pull request Oct 5, 2026
…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>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 Gate blocks 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 Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

✅ Hostile Reviewer — REVIEWED

Critical findings: 0
Major findings: 5
Total findings: 10
Models succeeded: qwen3-review,gpt-oss-review
Models unavailable: none

Action required: findings were posted as review threads. Address or reject each one, then resolve its thread — the Hostile Review Thread Gate fails while hostile-reviewer threads are unresolved (OMN-17492).


Semantics (OMN-17492 — the model finds, thread resolution gates)

Surface Meaning Blocks merge?
Review threads Per-finding, posted by the reviewer No (informational)
Hostile Review Thread Gate Deterministic: unresolved hostile-reviewer threads exist Fails until resolved (not yet a required context)
degraded verdict Fewer than 2 models succeeded (infra) Fails this job with a named reason

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.
@jonahgabriel
jonahgabriel enabled auto-merge October 5, 2026 18:40
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.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@jonahgabriel

Copy link
Copy Markdown
Collaborator Author

Lab: PR omnibase_infra#4594 head 864c1aa (OMN-20611) was tested on host omninode-pc (lab h201) using the command uv run pytest tests/integration/handlers/test_http_handler_integration.py tests/unit/handlers/test_handler_http.py -n 0. The result was 122 passed and 1 skipped (a pytest-httpserver timeout-delay test, unrelated). The test TestHttpPaymentRequired::test_http_get_402_is_payment_required_without_payment_text PASSED over a real HTTP server through HandlerHttpRest. A RED control was performed by reverting src/omnibase_infra/handlers/handler_http.py to origin/dev in the same worktree, causing that test to fail with AssertionError: assert 'success' == 'payment_required'; the file was restored and the worktree is clean. The dev lane runtime was not hotpatched.

@jonahgabriel
jonahgabriel added this pull request to the merge queue Oct 5, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 5, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

…20611)

CodeQL Analyze (actions) and (javascript-typescript) failed at the results
upload step on bdce07a; both contexts are green on dev 3ff83b0.
PR changes only Python files; dev does not touch the PR's 3 files.
Fresh head re-runs CodeQL and CI on the current base.
@jonahgabriel
jonahgabriel enabled auto-merge October 7, 2026 18:51
@jonahgabriel

Copy link
Copy Markdown
Collaborator Author

Lab: head=c2d1c295ace72a526beda5ec73de6903a3e6ca87 host=h202 lane=fan-infra-4594 omnibase_infra#4594 (OMN-20611): uv run --frozen pytest tests/integration/handlers/test_http_handler_integration.py tests/unit/handlers/test_handler_http.py -q -n 0 -> 122 passed, 1 skipped (test_http_handler_integration.py:416, pytest-httpserver has no response delays).
The head is a clean merge of dev 3ff83b0 into bdce07a. CodeQL Analyze (actions) and Analyze (javascript-typescript) failed at the results upload step on bdce07a with no error annotation; both are green on dev 3ff83b0, and the PR changes only Python files.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 filter if name.lower() != _PAYMENT_REQUIRED_HEADER compares 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant