From 47e92c2e6c43854e975510e5cc8409dfe8478962 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 10 Oct 2026 15:57:46 +0800 Subject: [PATCH] Reuse live Codex route proofs across business STATE updates --- .downstream/native-domains.md | 7 ++++++- backend/internal/service/codex_gateway_borrow.go | 8 ++++++-- .../internal/service/codex_gateway_borrow_core_test.go | 10 +++++++++- .../service/codex_gateway_borrow_diagnose_test.go | 1 + 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/.downstream/native-domains.md b/.downstream/native-domains.md index 02fc2276bf87..7653f7ea8dd3 100644 --- a/.downstream/native-domains.md +++ b/.downstream/native-domains.md @@ -112,7 +112,12 @@ cookie ordering are unchanged. 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. +the target identity, actual model, exit, client headers and TLS profile. +The probe clears the business STATE and validates its own two-shot STATE, so +echoing the next business STATE does not invalidate an otherwise identical live +proof. The sender keeps that exact business STATE and its existing continuation +isolation. Native session/window identity, model, tier, exit and the original +candidate expiry still constrain reuse; no credential gains a longer lease. Target probes preserve the template's native session/thread/window headers and generate only a fresh legacy `session_id` for each shot, matching ranxi's full probe contract. The effective service tier in the gateway-owned routing hint is diff --git a/backend/internal/service/codex_gateway_borrow.go b/backend/internal/service/codex_gateway_borrow.go index 552096077581..5d59ec86068c 100644 --- a/backend/internal/service/codex_gateway_borrow.go +++ b/backend/internal/service/codex_gateway_borrow.go @@ -805,8 +805,12 @@ func (s *CodexGatewayBorrowService) prepareSource(ctx context.Context, rev uint6 func borrowRequestFingerprint(req *http.Request, model, proxy string, profile *tlsfingerprint.Profile, cookie string) string { tlsJSON, _ := json.Marshal(profile) - values := []string{cookie, model, proxy, req.URL.EscapedPath(), req.Header.Get("Authorization"), req.Header.Get("ChatGPT-Account-ID"), req.Header.Get("User-Agent"), req.Header.Get("Originator"), req.Header.Get("Version"), req.Header.Get("X-Codex-Turn-State"), string(tlsJSON)} + values := []string{cookie, model, proxy, req.URL.EscapedPath(), req.Header.Get("Authorization"), req.Header.Get("ChatGPT-Account-ID"), req.Header.Get("User-Agent"), req.Header.Get("Originator"), req.Header.Get("Version"), string(tlsJSON)} values = append(values, codexBorrowRequestServiceTier(req)) + // Probes clear business STATE and mint their own independent two-shot STATE. + // A client's next turn must not discard that live route proof merely because + // it echoes a new business STATE. Apply still preserves the client's exact + // STATE; the candidate lease and native session identity remain part of reuse. // Include the native identity and routing policy preserved by the probe. // Per-request tracing IDs and the probe's random legacy session_id are not // qualification identities and must not defeat reuse or failure cooldowns. @@ -951,7 +955,7 @@ func (s *CodexGatewayBorrowService) apply(req *http.Request, account *Account, m s.qualifications = make(map[string]codexGatewayBorrowTargetCheck) } qualificationKey := fmt.Sprintf("%d:%s:%s", account.ID, model, key) - // Bound request-specific STATE/header variants; expired proofs are discarded. + // Bound request-identity variants; expired proofs are discarded. if len(s.qualifications) >= 1024 { oldestKey := "" var oldest time.Time diff --git a/backend/internal/service/codex_gateway_borrow_core_test.go b/backend/internal/service/codex_gateway_borrow_core_test.go index 30b9b0351237..65eb2cf81476 100644 --- a/backend/internal/service/codex_gateway_borrow_core_test.go +++ b/backend/internal/service/codex_gateway_borrow_core_test.go @@ -259,7 +259,15 @@ func TestCodexGatewayBorrowTargetCacheModelAndFingerprintIsolation(t *testing.T) _, app, err := s.Apply(template, a, "gpt-6.1-sol", "target-exit", nil, false) require.NoError(t, err) require.True(t, app.Applied, "hot cache is read before global target lock") - for _, header := range []string{"Authorization", "ChatGPT-Account-ID", "User-Agent", "Originator", "Version", "X-Codex-Turn-State"} { + continuation := template.Clone(template.Context()) + continuation.Header.Set("X-Codex-Turn-State", "next-business-state") + continuation.Header.Set("X-Client-Request-ID", "next-trace") + wire, reused, err := s.Apply(continuation, a, "gpt-6.1-sol", "target-exit", nil, true) + require.NoError(t, err, "business STATE is cleared from the independent probe, so it cannot invalidate its proof") + require.Equal(t, app.ExpiresAt, reused.ExpiresAt, "reuse never extends the candidate lease") + require.Equal(t, "next-business-state", wire.Header.Get("X-Codex-Turn-State")) + require.Equal(t, "business-state", template.Header.Get("X-Codex-Turn-State"), "business histories remain isolated") + for _, header := range []string{"Authorization", "ChatGPT-Account-ID", "User-Agent", "Originator", "Version", "session-id", "thread-id", "x-codex-window-id", "x-codex-installation-id"} { req := template.Clone(template.Context()) req.Header.Set(header, req.Header.Get(header)+"-changed") _, _, err := s.Apply(req, a, "gpt-6.1-sol", "target-exit", nil, true) diff --git a/backend/internal/service/codex_gateway_borrow_diagnose_test.go b/backend/internal/service/codex_gateway_borrow_diagnose_test.go index d2e7a904612a..fec3537237d8 100644 --- a/backend/internal/service/codex_gateway_borrow_diagnose_test.go +++ b/backend/internal/service/codex_gateway_borrow_diagnose_test.go @@ -240,6 +240,7 @@ func testCodexBorrowDiagnoseHTTPToolContinuation(t *testing.T, tier string) { } require.True(t, results[1].ToolRoundTrip) require.Equal(t, 2, business) + require.Equal(t, 2, probes, "the returned business STATE must not revalidate the same live route") require.EqualValues(t, business+probes, count) require.LessOrEqual(t, count, int32(8)) usage := s.Status().RecentUsage[0]