Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 52 additions & 17 deletions .downstream/native-domains.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,14 @@ nothing. While no Codex runtime row is stored,

## Codex fingerprint simulation

Extensions owns `/admin/codex-fingerprint`. General settings and account
create/edit/bulk-edit forms no longer submit fingerprint configuration.
The Extensions sidebar owns one Codex entry. `/admin/codex` combines borrowing
configuration and status; `/admin/codex/identity` shows client identity; and
`/admin/codex/advanced` owns manual UA/version, identifier modes and compression.
The previous runtime, fingerprint, borrowing and status URLs redirect to these
pages. Configured borrowing opens with its editor folded; a new setup opens the
three-step editor. Full saved/effective identity fields remain available in
keyboard-accessible disclosures. General settings and account create/edit/bulk
forms do not submit fingerprint configuration.
`GET/PUT /admin/settings/codex-fingerprint` uses `enabled`, `user_agent`,
`client_version` and `version_auto_sync_enabled`; all four fields are required
on PUT. GET also returns the synced and effective simulation versions and the
Expand Down Expand Up @@ -71,18 +77,13 @@ orchestration catalogs retain their existing capabilities.

## Codex gateway borrowing

The Extensions sidebar has separate Codex Borrowing and Pelican Tests entries.
Borrowing's in-page navigation switches between settings at
`/admin/codex-gateway-borrow`, route status at
`/admin/codex-gateway-borrow/status`. Pelican generation/history lives at
`/admin/pelican-tests`; `/admin/codex-pelican-comparison` redirects there.
Opening or navigating these pages only reads
local data. Settings drafts contain configuration IDs/options only, persist in
the browser session, and never affect the saved configuration used by status or
generation. Only explicit actions save, prepare, verify or generate. The status
page polls read-only status every five seconds while preparation/verification is
active; local countdowns expire displayed routes without renewing them. Leaving
the Pelican page cancels its queued and active generation.
The unified Codex page separates configuration, route qualification, actual
request usage and upstream completion. Pelican stays at `/admin/pelican-tests`;
its legacy comparison URL redirects there. Page reads and navigation only read
local data. Unsaved configuration stays in browser-session storage. Saving an
enabled configuration starts exactly one finite preparation, shown as queued
before the worker starts. Status polls every five seconds only while work is
active; countdowns expire routes without renewing them.

`GET/PUT /admin/codex-gateway-borrow/config` owns one JSON setting,
`codex_gateway_borrow_config`: `enabled`, `source_account_ids`,
Expand All @@ -103,9 +104,43 @@ Source and target probes use separate HTTP/TLS pools. Business WS connections
stay in the ordinary account pool, with fixed one-hour continuation anchors.

Status/config/history reads do not call models. Saving an enabled config starts
one finite preparation; business cache misses prepare synchronously. There is
no renewal timer. `/prepare` and `/verify` explicitly prepare or revalidate.
All probes only record observations, including failures before business dispatch.
one finite preparation; business misses prepare on demand. There is no renewal
timer. `/prepare` and `/verify` retain their interfaces. Same-revision, same-wire
qualifications share work; cancelling one caller does not cancel other waiters,
and the final departing waiter cancels abandoned work. Distinct targets no longer
fail because another target is validating. Outbound observations share the existing
ten execution slots and serialize by account. Preparation has a ten-minute bound,
continues after a target failure, and reports partial readiness after rechecking
expiry. Policy, credential/model-map and exit changes invalidate displayed evidence;
actual dispatch still verifies the finalized request fingerprint.

Status adds `setup`, `observed_since` and `recent_usage`. The last actual dispatch
and in-process counters are retained per configured target/model/protocol/purpose;
configuration publication resets this index. HTTP/SSE, pooled WS and native WS
relay observations record application independently from upstream completion.
Diagnostic and business requests are distinguished. This index contains no request
payloads or credential/cookie values; the existing administrator error records remain.

`POST /admin/codex-gateway-borrow/diagnose` streams an explicit fixed-account
comparison with `account_id`, `model`, `transport` (`http` or `ws`), and optional
`request_limit` (1–8, default 8). Its ordinary and borrowed requests use the normal
sender with observation isolation and a fixed short prompt. WS uses separate
short-lived diagnostic anchors and performs one continuation on each socket;
account/global WS settings must already permit WS. Source/target probes and all
compatibility sends count toward the same hard request limit. Events include
`phase`, `request`, `result`, `done`, and `error`; results contain original output,
completion, actual borrow application and reported model. Cancellation releases
owned work and sockets. The UI retains the current run and can download JSON;
there is no new table or migration. These observations do not prove intelligence
or physical model identity. Normal token refresh remains permitted.

Ranxi reference: v2.10.2 (`d3e43f2de33af9e987cffa511d76dfabbcd749da`). Its five
fingerprint, gateway-cookie, target-probe and automatic-setup core files are
unchanged from the retained v2.10.0 reference. OAuth search-history compatibility
is supplied by the official v0.2.15 integration. Automatic group membership,
Mihomo exit rotation and removal of harvesting attempt/concurrency bounds are
not adopted; existing account mappings, network routes and protocol switches stay
under their existing owners.

The historical `/admin/codex-gateway-borrow/tests` endpoints retain their
cache-qualified, 90-second request contract. They share execution admission with
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/downstream-verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ jobs:
run: |
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
working-directory: backend
run: go test -p=1 -race ./internal/service -run '^TestCodexBorrow(Flight|DifferentTargets)' -count=1
- name: Upstream automation classifier tests
run: |
python3 .github/scripts/test_upstream_risk.py
Expand Down Expand Up @@ -145,6 +148,7 @@ jobs:
src/components/admin/codex/__tests__/BorrowPelicanPreview.spec.ts \
src/views/admin/__tests__/CodexGatewayBorrowView.spec.ts \
src/views/admin/__tests__/CodexGatewayBorrowStatusView.spec.ts \
src/components/admin/codex/__tests__/CodexBorrowActivity.spec.ts \
src/views/admin/__tests__/CodexPelicanComparisonView.spec.ts \
src/api/admin/__tests__/pelicanTests.spec.ts \
src/components/admin/__tests__/CodexBorrowNav.spec.ts \
Expand Down
39 changes: 38 additions & 1 deletion backend/internal/handler/admin/codex_gateway_borrow_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func (h *CodexGatewayBorrowHandler) SaveConfig(c *gin.Context) {

func (h *CodexGatewayBorrowHandler) Status(c *gin.Context) {
codexGatewayBorrowPrivateHeaders(c)
response.Success(c, h.core.Status())
response.Success(c, h.core.CurrentStatus(c.Request.Context()))
}

func (h *CodexGatewayBorrowHandler) Prepare(c *gin.Context) {
Expand Down Expand Up @@ -268,3 +268,40 @@ func (h *CodexGatewayBorrowHandler) ServePreview(c *gin.Context) {
}
c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(html))
}

// Diagnose streams explicit administrator observations; opening a page never invokes it.
func (h *CodexGatewayBorrowHandler) Diagnose(c *gin.Context) {
codexGatewayBorrowPrivateHeaders(c)
var request service.CodexBorrowDiagnosticRequest
if err := c.ShouldBindJSON(&request); err != nil {
response.BadRequest(c, err.Error())
return
}
middleware.SetAuditExtra(c, map[string]any{"result": "codex_gateway_borrow_diagnostic", "account_id": request.AccountID, "model": request.Model, "transport": request.Transport})
c.Header("Content-Type", "text/event-stream")
c.Header("X-Accel-Buffering", "no")
c.Status(http.StatusOK)
var emission sync.Mutex
var observedRequests int32
emit := func(event service.CodexBorrowDiagnosticEvent) {
emission.Lock()
defer emission.Unlock()
if event.Requests > observedRequests {
observedRequests = event.Requests
}
if event.Type == "error" {
event.Requests = observedRequests
}
if c.Request.Context().Err() != nil {
return
}
raw, err := json.Marshal(event)
if err == nil {
_, _ = fmt.Fprintf(c.Writer, "data: %s\n\n", raw)
c.Writer.Flush()
}
}
if err := h.core.Diagnose(c.Request.Context(), request, emit); err != nil {
emit(service.CodexBorrowDiagnosticEvent{Type: "error", Error: err.Error(), Limit: service.CodexBorrowDiagnosticMaxRequests})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,36 @@ import (
"github.com/stretchr/testify/require"
)

func TestCodexGatewayBorrowDiagnosePreflightNeverDispatches(t *testing.T) {
gin.SetMode(gin.TestMode)
for _, test := range []struct {
name, body string
status int
}{
{"disabled", `{"account_id":1,"model":"gpt-6-astra","transport":"http"}`, http.StatusOK},
{"unsupported transport", `{"account_id":1,"model":"gpt-6-astra","transport":"invalid"}`, http.StatusOK},
{"malformed", `{`, http.StatusBadRequest},
} {
t.Run(test.name, func(t *testing.T) {
router := gin.New()
handler := NewCodexGatewayBorrowHandler(nil, nil, nil)
router.POST("/diagnose", handler.Diagnose)
response := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodPost, "/diagnose", strings.NewReader(test.body))
request.Header.Set("Content-Type", "application/json")
router.ServeHTTP(response, request)
require.Equal(t, test.status, response.Code)
require.Equal(t, "private, no-store", response.Header().Get("Cache-Control"))
if test.status == http.StatusOK {
require.Contains(t, response.Header().Get("Content-Type"), "text/event-stream")
require.Contains(t, response.Body.String(), `"type":"error"`)
require.Contains(t, response.Body.String(), `"requests":0`)
require.NotContains(t, response.Body.String(), `"type":"request"`)
}
})
}
}

type codexGatewayBorrowHandlerTestRepo struct {
task *service.CodexGatewayBorrowTestTask
result *service.CodexGatewayBorrowTestResult
Expand Down
1 change: 1 addition & 0 deletions backend/internal/server/routes/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ func registerCodexGatewayBorrowRoutes(admin *gin.RouterGroup, h *handler.Handler
borrow.GET("/status", h.Admin.CodexGatewayBorrow.Status)
borrow.POST("/prepare", h.Admin.CodexGatewayBorrow.Prepare)
borrow.POST("/verify", h.Admin.CodexGatewayBorrow.Verify)
borrow.POST("/diagnose", h.Admin.CodexGatewayBorrow.Diagnose)
borrow.POST("/tests", h.Admin.CodexGatewayBorrow.StartTests)
borrow.GET("/tests", h.Admin.CodexGatewayBorrow.ListTests)
borrow.GET("/tests/:id", h.Admin.CodexGatewayBorrow.GetTest)
Expand Down
Loading
Loading