Skip to content

Send a Host-supplied plan proof to the Gateway, and contact a loopback Gateway directly - #14

Draft
sublimator wants to merge 2 commits into
cppalliance:masterfrom
sublimator:sign-in-with-chatgpt
Draft

sublimator wants to merge 2 commits into
cppalliance:masterfrom
sublimator:sign-in-with-chatgpt

Conversation

@sublimator

@sublimator sublimator commented Nov 14, 2026 •

Copy link
Copy Markdown

Draft, for reference. One of three drafts that together add Sign in with
ChatGPT. The others are cppalliance/runvil-gateway#35 and cppalliance/runvil-workshop#22. This is the
smallest, and Workshop's draft pins it. Use it or not.

What it does

Two commits on 9dd953b5, both in harness-gateway-client.

  • Plan proof. A Host can hand the client a proof string, and the client
    sends it as X-Runvil-Plan-Proof on model rounds and on the model list.
    The Gateway serves a person's ChatGPT-plan models only to a caller that
    presents the proof it published for its current boot. Without a proof the
    client sends what it sent before. Debug redacts the proof like the key.
  • Loopback Gateway. When the Gateway is on loopback, the client follows
    no redirect and ignores the process proxy. A bearer or a proof then cannot
    be carried to another server. Any other Gateway keeps today's behaviour.
  • One behaviour change. fetch_model_catalog now validates its API root
    the way a chat endpoint is validated. A root with a query, a fragment or
    credentials is refused with InvalidValue. It used to be sent as written.

build_request_body is untouched, because the proof is a header. The plan
note is plan/2026-11-14-1-plan-proof-and-direct-loopback.plan.md.

One choice a reviewer may question: a loopback client that cannot be built
panics. reqwest::Client::new already panics on the same failure, and
returning the error would change the constructors' signatures.

How it was checked

On macOS arm64 only.

  • Every command in AGENTS.md, plus cargo deny and cargo audit. The
    facade check reports 0 violations and unchanged listings.
  • Tests: 3,647 of 3,648. The one failure is harness-test-plugin
    lifecycle::the_fixture_is_loaded_invoked_unloaded_once_and_closed. It
    fails the same way on 9dd953b5 itself on this Mac.
  • Each commit passes format, clippy and the client crate's tests alone.
  • The proxy tests fail when no_proxy() is removed, and the redirect tests
    fail when the redirect policy is removed.

Not done

  • Windows was not run.

The ChatGPT plan extension proposed for `runvil-gateway` serves a plan model only to a caller that presents the plan proof of the Gateway's current boot, in the header `X-Runvil-Plan-Proof`. `harness-gateway-client` could not send one. A Host now passes the proof to `GatewayChat::with_plan_proof` or `GatewayBroker::with_plan_proof`, and the client sends it on each model round and on the model list.

- `PLAN_PROOF_HEADER` names the header. A client that was given no proof omits it, so its requests are the ones it sent before.
- The proof is a `SecretString` and is not a bearer. `Authorization` is unchanged, and `Debug` prints `<redacted>` for the proof on `GatewayChat` and on `GatewayBroker`.
- `fetch_model_catalog_with_plan_proof` sends the proof on its one request, and `fetch_model_catalog` sends none. Both call the private `fetch_catalog`.
- The crate reads no file and no environment variable for the proof. `GatewayChat::from_env` gains no variable, and `GatewaySearch` sends no proof.
- `build_request_body` is unchanged, because the proof is a header.
- New tests pin the header beside the bearer, the header on a keyless client, its absence without the call and through `from_env`, the model list with and without a proof, and the redaction.

Plan: /plan/2026-11-14-1-plan-proof-and-direct-loopback.plan.md
Every client in `harness-gateway-client` was `reqwest::Client::new()`, which uses the process's proxy settings and follows redirects. A request to a Gateway on the local machine could go out through `HTTP_PROXY` or `ALL_PROXY` with the bearer key and the plan proof in it, and a redirect could send the plan proof and the request body to another server. A loopback Gateway is now contacted with no proxy, and a redirect comes back as its own status. Any other address keeps reqwest's defaults.

- `transport::http_client` builds the client from `GatewayEndpoint::is_loopback`. For a loopback endpoint it sets `no_proxy()` and `redirect::Policy::none()`. A failed build panics, because a fall back to the default client would bring both back.
- `GatewayChat::new`, `GatewayChat::keyless`, and `GatewaySearch` use it. The catalog keeps two process-wide clients, one for a loopback Gateway and one for any other.
- `fetch_model_catalog` and `fetch_model_catalog_with_plan_proof` validate `base_url` with `GatewayEndpoint::new` before any request. A root that is not http or https, names no host, embeds credentials, or has a query or a fragment returns `InvalidValue`. It was sent as written.
- `tests/loopback_direct.rs` gives a chat round, a catalog fetch, and a search a redirect from a loopback Gateway, and the target must receive nothing. It runs the same three in a child process with `HTTP_PROXY` and `ALL_PROXY` set to a counting listener, after a plain reqwest client proves that the listener is consulted.
- `an_invalid_loopback_catalog_root_is_refused_before_any_request` pins the refusal for a query, a fragment, and credentials, and that the error repeats none of the input.

Plan: /plan/2026-11-14-1-plan-proof-and-direct-loopback.plan.md
@coderabbitai

coderabbitai Bot commented Nov 14, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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