From 390cf6694a0df1989f4730f9523c51f3fb276c5e Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 10 Oct 2026 05:12:29 +0800 Subject: [PATCH 1/3] Separate source acquisition from target STATE probe requests --- .downstream/native-domains.md | 6 ++- .../service/codex_gateway_borrow_probe.go | 37 ++++++++++++++++++- ...dex_gateway_borrow_source_contract_test.go | 37 +++++++++++++++++++ 3 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 backend/internal/service/codex_gateway_borrow_source_contract_test.go diff --git a/.downstream/native-domains.md b/.downstream/native-domains.md index 51f07c60b868..0f3fa818eae8 100644 --- a/.downstream/native-domains.md +++ b/.downstream/native-domains.md @@ -96,7 +96,11 @@ groups, quota or scheduling state. Normal credential refresh remains available. Source acquisition shares one process-local `__oailb` candidate. Its maximum lease is 230 seconds, capped by an earlier upstream expiry; receiving the same value during a live lease does not extend it. Source preparation is sequential -and has a 90-second budget. Each target/model is validated with two completed +and uses the ordinary OAuth account-test payload (default instructions, fixed +short user prompt, medium effort), with its own configured normal connection +policy. It does not inherit target-probe session/window headers, routing hints, +encrypted-output options or connection-close behavior. +It has a 90-second budget. Each target/model is validated with two completed HTTP 200 streams, at most 45 seconds each: the first must return STATE, and the second may omit STATE or return the same value. Qualification includes the target identity, actual model, exit, client headers, STATE and TLS profile. diff --git a/backend/internal/service/codex_gateway_borrow_probe.go b/backend/internal/service/codex_gateway_borrow_probe.go index 5c3c1ff870a0..6c9a5a1d2c25 100644 --- a/backend/internal/service/codex_gateway_borrow_probe.go +++ b/backend/internal/service/codex_gateway_borrow_probe.go @@ -105,8 +105,11 @@ func (s *CodexGatewayBorrowService) acquireSource(ctx context.Context, id int64) if err != nil { return nil, err.Error(), err } - shot, err := s.fireObservation(ctx, template.Header, a, codexGatewayBorrowSourceModel, "Reply with OK only.", "medium", proxy, nil, - "", nil, HTTPUpstreamProfileCodexBorrowSource, codexGatewayBorrowProbeMaxBody) + req, err := borrowSourceObservationRequest(ctx, template.Header) + if err != nil { + return nil, err.Error(), err + } + shot, err := s.sendObservation(req, a, proxy, nil, codexGatewayBorrowSourceModel, codexGatewayBorrowProbeMaxBody) if err != nil { return nil, shot.errorText, err } @@ -125,6 +128,36 @@ func (s *CodexGatewayBorrowService) acquireSource(ctx context.Context, id int64) return candidate, "", nil } +// The pinned ranxi source acquisition uses the ordinary OAuth account-test +// payload, not the target's two-shot STATE-probe protocol. Preserve the shared +// account identity and observation isolation, but do not manufacture probe +// sessions, a window or a routing hint, or force this normal source connection +// closed. The source keeps its own configured OpenAI transport and proxy. +func borrowSourceObservationRequest(ctx context.Context, headers http.Header) (*http.Request, error) { + ctx = WithCodexGatewayBorrowObservation(WithHTTPUpstreamRedirectsDisabled(WithHTTPUpstreamProfile(ctx, HTTPUpstreamProfileCodexBorrowSource))) + payload := createOpenAITestPayload(codexGatewayBorrowSourceModel, true, "Reply with OK only.") + payload["reasoning"] = map[string]string{"effort": "medium"} + body, err := json.Marshal(payload) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, http.MethodPost, chatgptCodexURL, bytes.NewReader(body)) + if err != nil { + return nil, err + } + req.Host, req.Header = "chatgpt.com", headers.Clone() + if req.Header == nil { + req.Header = make(http.Header) + } + for _, name := range []string{"Cookie", "Content-Length", "Content-Encoding", "Content-MD5", "X-Codex-Turn-State", "X-Codex-Turn-Metadata", "X-OpenAI-Internal-Codex-Responses-Lite", "conversation_id", "session_id", "session-id", "thread-id", "x-client-request-id", "x-codex-window-id", openAICodexRoutingHintHeader} { + deleteOpenAIHeaderEqualFold(req.Header, name) + } + req.Header.Set("Accept", "text/event-stream") + req.Header.Set("Content-Type", "application/json") + req.Header.Set("OpenAI-Beta", "responses=experimental") + return req, nil +} + func borrowCookieScoped(cookie *http.Cookie, path string) bool { if cookie == nil || (cookie.Domain != "" && !strings.EqualFold(strings.TrimPrefix(cookie.Domain, "."), "chatgpt.com")) { return false diff --git a/backend/internal/service/codex_gateway_borrow_source_contract_test.go b/backend/internal/service/codex_gateway_borrow_source_contract_test.go new file mode 100644 index 000000000000..2dffdb990340 --- /dev/null +++ b/backend/internal/service/codex_gateway_borrow_source_contract_test.go @@ -0,0 +1,37 @@ +package service + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + "github.com/tidwall/gjson" +) + +func TestCodexBorrowSourceUsesNormalAccountTestContract(t *testing.T) { + headers := http.Header{"Authorization": {"Bearer synthetic-source"}, "User-Agent": {"synthetic-persisted-identity"}, "Originator": {"codex-tui"}, "Version": {"0.162.1"}} + for _, name := range []string{"session-id", "thread-id", "session_id", "x-codex-window-id", "x-codex-turn-state", "x-codex-turn-metadata", "x-codex-routing-hint", "Cookie", "Content-Encoding"} { + headers.Set(name, "target-only-value") + } + original := headers.Clone() + req, err := borrowSourceObservationRequest(context.Background(), headers) + require.NoError(t, err) + require.False(t, req.Close, "source acquisition keeps normal connection policy") + require.Equal(t, original, headers) + require.Equal(t, "Bearer synthetic-source", req.Header.Get("Authorization")) + require.Equal(t, "synthetic-persisted-identity", req.Header.Get("User-Agent")) + for _, name := range []string{"session-id", "thread-id", "session_id", "x-codex-window-id", "x-codex-turn-state", "x-codex-turn-metadata", "x-codex-routing-hint", "Cookie", "Content-Encoding"} { + require.Empty(t, req.Header.Get(name), name) + } + body := borrowCoreBody(t, req) + want := createOpenAITestPayload("gpt-6-astra", true, "Reply with OK only.") + require.Equal(t, want["instructions"], gjson.GetBytes(body, "instructions").String()) + require.Equal(t, "Reply with OK only.", gjson.GetBytes(body, "input.0.content.0.text").String()) + require.Equal(t, "medium", gjson.GetBytes(body, "reasoning.effort").String()) + require.False(t, gjson.GetBytes(body, "parallel_tool_calls").Exists()) + require.False(t, gjson.GetBytes(body, "include").Exists()) + require.Equal(t, HTTPUpstreamProfileCodexBorrowSource, HTTPUpstreamProfileFromContext(req.Context())) + require.True(t, IsCodexGatewayBorrowObservation(req.Context())) + require.True(t, HTTPUpstreamRedirectsDisabled(req.Context())) +} From e1a475a149e3520f303f70a595f6a899e5ec9d3d Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 10 Oct 2026 05:32:51 +0800 Subject: [PATCH 2/3] Preload identical official test images from publisher registries --- .github/scripts/prepare_test_images.py | 27 +++++++++++++++++++++++++ .github/workflows/backend-ci.yml | 4 +++- .github/workflows/downstream-verify.yml | 1 + 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 .github/scripts/prepare_test_images.py diff --git a/.github/scripts/prepare_test_images.py b/.github/scripts/prepare_test_images.py new file mode 100644 index 000000000000..a97cd742967d --- /dev/null +++ b/.github/scripts/prepare_test_images.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +"""Preload unchanged CI image names from their publishers' alternate registries. + +The OCI index digests below were checked against the matching Docker Hub tags. +Postgres/Redis are Docker Official Images on ECR Public; Ryuk is published by +Testcontainers on GHCR. No Docker Hub credentials or pull-limit workarounds are +used. Docker verifies immutable digests; existing tests use these local aliases. +""" + +import subprocess + + +IMAGES = ( + ("public.ecr.aws/docker/library/postgres@sha256:aa6eb304ddb6dd26df23d05db4e5cb05af8951cda3e0dc57731b771e0ef4ab29", "postgres:18.1-alpine3.23"), + ("public.ecr.aws/docker/library/redis@sha256:0514fa59e3d84e2f3be66731c5401fc2a85b8ea71b49c0116c850f6a558076f4", "redis:8.4-alpine"), + ("ghcr.io/testcontainers/ryuk@sha256:31b31269d06603366cbfd0284708dcd2e281e8a4188e53fce3d3304439d0df3d", "testcontainers/ryuk:0.13.0"), +) + + +def prepare(run=subprocess.run): + for source, local_name in IMAGES: + run(["docker", "pull", "--platform", "linux/amd64", source], check=True) + run(["docker", "tag", source, local_name], check=True) + + +if __name__ == "__main__": + prepare() diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index bf8a2ba4579f..4f9dbdcd374f 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -45,7 +45,9 @@ jobs: run: make test-unit - name: Integration tests working-directory: backend - run: make test-integration + run: | + python3 ../.github/scripts/prepare_test_images.py + make test-integration frontend: runs-on: ubuntu-latest diff --git a/.github/workflows/downstream-verify.yml b/.github/workflows/downstream-verify.yml index a6246bc3525d..633f6218c5bf 100644 --- a/.github/workflows/downstream-verify.yml +++ b/.github/workflows/downstream-verify.yml @@ -45,6 +45,7 @@ jobs: - name: Native gateway borrowing contracts working-directory: backend run: | + python3 ../.github/scripts/prepare_test_images.py go test -p=1 -tags unit ./internal/service ./internal/repository ./internal/handler ./internal/handler/admin -run '^Test(CodexGatewayBorrow|Pelican|AccountObservation|ObservedAccountLease)' -count=1 go test -p=1 -tags integration ./internal/repository -run '^TestCodexGatewayBorrowTestRepositoryIntegrationLifecycle$' -count=1 - name: Codex preparation coordination race tests From 8e1e3ba7e2c9c477b0fe0a88ff72f61abe7d312f Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 10 Oct 2026 05:54:23 +0800 Subject: [PATCH 3/3] Use verified registry cache for candidate and release builders --- .github/buildkitd.toml | 4 ++++ .github/workflows/downstream-release.yml | 3 +++ .github/workflows/downstream-verify.yml | 3 +++ 3 files changed, 10 insertions(+) create mode 100644 .github/buildkitd.toml diff --git a/.github/buildkitd.toml b/.github/buildkitd.toml new file mode 100644 index 000000000000..cafe35af5927 --- /dev/null +++ b/.github/buildkitd.toml @@ -0,0 +1,4 @@ +# Google-operated cache of public Docker Hub images. BuildKit keeps the original +# image references and validates their content digests; no credentials change. +[registry."docker.io"] + mirrors = ["mirror.gcr.io"] diff --git a/.github/workflows/downstream-release.yml b/.github/workflows/downstream-release.yml index 76ad8be90980..6c2e763b2eca 100644 --- a/.github/workflows/downstream-release.yml +++ b/.github/workflows/downstream-release.yml @@ -94,6 +94,9 @@ jobs: python3 .github/scripts/release_preflight.py --stage before-build \ --tag "$RELEASE_TAG" --source-sha "$SOURCE_SHA" - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 + with: + driver-opts: image=mirror.gcr.io/moby/buildkit@sha256:cec9f139f45e93c5c69c60f8b07cfad9f43f4ef6b6a6cd917527fea5ff2e3dea + buildkitd-config: .github/buildkitd.toml - name: Build release image without publication id: build uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 diff --git a/.github/workflows/downstream-verify.yml b/.github/workflows/downstream-verify.yml index 633f6218c5bf..2a0d1d1d4ac1 100644 --- a/.github/workflows/downstream-verify.yml +++ b/.github/workflows/downstream-verify.yml @@ -195,6 +195,9 @@ jobs: steps: - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 + with: + driver-opts: image=mirror.gcr.io/moby/buildkit@sha256:cec9f139f45e93c5c69c60f8b07cfad9f43f4ef6b6a6cd917527fea5ff2e3dea + buildkitd-config: .github/buildkitd.toml - name: Build candidate uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: