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
6 changes: 6 additions & 0 deletions .downstream/native-domains.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,12 @@ Optional `comparison: encoding` keeps the same current body, tier, identity,
cookie and exit, and compares plaintext probes with the configured business
codec. It changes no global setting or business qualification; actual per-shot
encoding distinguishes an enabled zstd comparison from two plaintext samples.
The same probe-only scenario can select a configured source for one plaintext,
no-tier Astra pair against its own current live candidate. It cannot generate,
test another source model, acquire a replacement or substitute a different
candidate owner. `probe_context.subject_role` distinguishes source self-checks
from target checks. Self-check results do not qualify business traffic or mutate
the source's health/status.

Ranxi reference: v2.10.2 (`d3e43f2de33af9e987cffa511d76dfabbcd749da`). Its five
fingerprint, gateway-cookie, target-probe and automatic-setup core files are
Expand Down
8 changes: 6 additions & 2 deletions backend/internal/service/codex_gateway_borrow_diagnose.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,11 @@ func (s *CodexGatewayBorrowService) Diagnose(ctx context.Context, request CodexB
return errors.New("request_limit must be between 1 and 8")
}
cfg := s.ConfigSnapshot()
if !cfg.Enabled || !slices.Contains(cfg.TargetAccountIDs, request.AccountID) || !slices.Contains(cfg.Models, request.Model) {
sourceProbe := request.Scenario == "probe_contract" && request.Model == codexGatewayBorrowSourceModel && slices.Contains(cfg.SourceAccountIDs, request.AccountID)
if sourceProbe && (request.ServiceTier != "" || request.Comparison == "encoding") {
return errors.New("source self-check uses the current plaintext Astra template without a service tier")
}
if !cfg.Enabled || (!sourceProbe && (!slices.Contains(cfg.TargetAccountIDs, request.AccountID) || !slices.Contains(cfg.Models, request.Model))) {
return errors.New("select a saved, enabled target and model")
}
account, err := s.accounts.GetByID(ctx, request.AccountID)
Expand All @@ -166,7 +170,7 @@ func (s *CodexGatewayBorrowService) Diagnose(ctx context.Context, request CodexB
ctx, cancel := borrowRevisionContext(WithAccountObservation(ctx), revisionCtx, 10*time.Minute)
defer cancel()
if request.Scenario == "probe_contract" {
return s.diagnoseProbeContract(ctx, revision, revisionCtx, request, limit, emit)
return s.diagnoseProbeContract(ctx, revision, revisionCtx, request, sourceProbe, limit, emit)
}
var requests atomic.Int32
modes := []bool{false, true}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
// a selected Sol model here is explicitly the downstream model extension.
type CodexBorrowProbeContext struct {
SourceAccountID int64 `json:"source_account_id"`
SubjectRole string `json:"subject_role"`
SourceRequestEncoding string `json:"source_request_encoding,omitempty"`
TargetProxyID *int64 `json:"target_proxy_id"`
CookieFingerprint string `json:"cookie_fingerprint"`
Expand All @@ -52,7 +53,7 @@ func borrowRanxiLiteralPayload(model, tier string) []byte {
return []byte(body + `}`)
}

func (s *CodexGatewayBorrowService) diagnoseProbeContract(ctx context.Context, revision uint64, revisionCtx context.Context, request CodexBorrowDiagnosticRequest, limit int, emit func(CodexBorrowDiagnosticEvent)) error {
func (s *CodexGatewayBorrowService) diagnoseProbeContract(ctx context.Context, revision uint64, revisionCtx context.Context, request CodexBorrowDiagnosticRequest, sourceProbe bool, limit int, emit func(CodexBorrowDiagnosticEvent)) error {
var requests atomic.Int32
d := &codexBorrowDiagnostic{limit: int32(limit), requests: &requests, borrow: true, session: uuid.NewString(), serviceTier: request.ServiceTier,
onRequest: func(count int32) {
Expand All @@ -69,10 +70,16 @@ func (s *CodexGatewayBorrowService) diagnoseProbeContract(ctx context.Context, r
return err // A blocked local tier must not consume a source or probe request.
}
finalTier := gjson.GetBytes(policyBody, "service_tier").String()
if sourceProbe && finalTier != "" {
return errors.New("local tier policy changes the source self-check template; no model request was sent")
}
s.mu.Lock()
candidate := s.candidate
current := s.candidateCurrentLocked(revision, candidate)
s.mu.Unlock()
if sourceProbe && (!current || candidate.sourceID != account.ID) {
return errors.New("source self-check requires its current live candidate; no different source is prepared or substituted")
}
if !current {
if err := s.prepareSource(ctx, revision, revisionCtx); err != nil {
return err
Expand All @@ -85,6 +92,10 @@ func (s *CodexGatewayBorrowService) diagnoseProbeContract(ctx context.Context, r
if !current {
return ErrCodexGatewayBorrowChanged
}
role := "target"
if sourceProbe {
role = "source"
}
// The snapshot cannot be silently replaced halfway through the comparison.
ctx, cancel := context.WithDeadline(ctx, candidate.expires)
defer cancel()
Expand All @@ -106,6 +117,9 @@ func (s *CodexGatewayBorrowService) diagnoseProbeContract(ctx context.Context, r
if request.Comparison == "encoding" {
shapes = []string{codexBorrowProbePlain, codexBorrowProbeConfigured}
}
if sourceProbe {
shapes = []string{codexBorrowProbeCurrent}
}
for _, shape := range shapes {
if err := ctx.Err(); err != nil {
return err
Expand All @@ -131,6 +145,7 @@ func (s *CodexGatewayBorrowService) diagnoseProbeContract(ctx context.Context, r
result := CodexBorrowDiagnosticResult{Scenario: "probe_contract", Mode: "borrowed", ProbeOnly: true,
Verification: &verification, DurationMS: time.Since(started).Milliseconds(), ReportedModel: verification.ReportedModel,
ProbeContext: &CodexBorrowProbeContext{SourceAccountID: candidate.sourceID, TargetProxyID: account.ProxyID,
SubjectRole: role,
SourceRequestEncoding: candidate.sourceRequestEncoding,
CookieFingerprint: borrowHash(candidate.cookie.Value), CandidateExpiresAt: candidate.expires,
TemplateFingerprint: identity, RequestedTier: request.ServiceTier, FinalTier: finalTier, RoutingHint: template.Header.Get(openAICodexRoutingHintHeader),
Expand Down
51 changes: 51 additions & 0 deletions backend/internal/service/codex_gateway_borrow_source_self_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package service

import (
"context"
"net/http"
"testing"
"time"

"github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"
)

func TestCodexBorrowSourceSelfCheckUsesOnlyCurrentOwner(t *testing.T) {
var calls int
source := borrowCoreAccount(1)
s := newBorrowCoreTest(t, func(req *http.Request, _ string, id int64, _ int, _ *tlsfingerprint.Profile) (*http.Response, error) {
calls++
assert.EqualValues(t, 1, id)
assert.Equal(t, codexGatewayBorrowSourceModel, gjson.GetBytes(borrowCoreBody(t, req), "model").String())
assert.Empty(t, req.Header.Get("Content-Encoding"))
return borrowCoreResponse(codexGatewayBorrowSourceModel, "OK", "source-state"), nil
}, source)
borrowCoreCandidate(s, time.Now().Add(time.Minute))
var results []*CodexBorrowDiagnosticResult
err := s.Diagnose(context.Background(), CodexBorrowDiagnosticRequest{Scenario: "probe_contract", AccountID: 1, Model: codexGatewayBorrowSourceModel, Transport: "http"}, func(e CodexBorrowDiagnosticEvent) {
if e.Result != nil {
results = append(results, e.Result)
}
})
require.NoError(t, err)
require.Equal(t, 2, calls)
for _, r := range results {
require.Equal(t, "source", r.ProbeContext.SubjectRole)
require.True(t, r.ProbeOnly)
require.False(t, r.Applied || r.Dispatched || r.Completed)
}
require.Empty(t, s.qualifications)
require.Empty(t, s.Status().RecentUsage)
for _, request := range []CodexBorrowDiagnosticRequest{
{AccountID: 1, Model: codexGatewayBorrowSourceModel, Transport: "http", Mode: "borrowed"},
{Scenario: "probe_contract", AccountID: 1, Model: "gpt-6.1-sol", Transport: "http"},
} {
require.Error(t, s.Diagnose(context.Background(), request, func(CodexBorrowDiagnosticEvent) {}))
}
require.Equal(t, 2, calls, "source selection cannot enter generation or another model")
s.candidate = nil
require.Error(t, s.Diagnose(context.Background(), CodexBorrowDiagnosticRequest{Scenario: "probe_contract", AccountID: 1, Model: codexGatewayBorrowSourceModel, Transport: "http"}, func(CodexBorrowDiagnosticEvent) {}))
require.Equal(t, 2, calls, "a self-check does not silently prepare a replacement candidate")
}
Loading