@@ -10,6 +10,11 @@ import {
1010 getInstallationRepository ,
1111 listInstallationRepositories ,
1212} from "./auth/github-app" ;
13+ import {
14+ resolveScmProviderFromEnv ,
15+ SourceControlProviderError ,
16+ type SourceControlProviderName ,
17+ } from "./source-control" ;
1318import { RepoSecretsStore , RepoSecretsValidationError } from "./db/repo-secrets" ;
1419import { SessionIndexStore } from "./db/session-index" ;
1520
@@ -89,6 +94,18 @@ function error(message: string, status = 400): Response {
8994 return json ( { error : message } , status ) ;
9095}
9196
97+ function withCorsAndTraceHeaders ( response : Response , ctx : RequestContext ) : Response {
98+ const headers = new Headers ( response . headers ) ;
99+ headers . set ( "Access-Control-Allow-Origin" , "*" ) ;
100+ headers . set ( "x-request-id" , ctx . request_id ) ;
101+ headers . set ( "x-trace-id" , ctx . trace_id ) ;
102+ return new Response ( response . body , {
103+ status : response . status ,
104+ statusText : response . statusText ,
105+ headers,
106+ } ) ;
107+ }
108+
92109/**
93110 * Get Durable Object stub for a session.
94111 * Returns the stub or null if session ID is missing.
@@ -115,6 +132,46 @@ const SANDBOX_AUTH_ROUTES: RegExp[] = [
115132 / ^ \/ s e s s i o n s \/ [ ^ / ] + \/ p r $ / , // PR creation from sandbox
116133] ;
117134
135+ type CachedScmProvider =
136+ | {
137+ envValue : string | undefined ;
138+ provider : SourceControlProviderName ;
139+ error ?: never ;
140+ }
141+ | {
142+ envValue : string | undefined ;
143+ provider ?: never ;
144+ error : SourceControlProviderError ;
145+ } ;
146+
147+ let cachedScmProvider : CachedScmProvider | null = null ;
148+
149+ function resolveDeploymentScmProvider ( env : Env ) : SourceControlProviderName {
150+ const envValue = env . SCM_PROVIDER ;
151+ if ( ! cachedScmProvider || cachedScmProvider . envValue !== envValue ) {
152+ try {
153+ cachedScmProvider = {
154+ envValue,
155+ provider : resolveScmProviderFromEnv ( envValue ) ,
156+ } ;
157+ } catch ( errorValue ) {
158+ cachedScmProvider = {
159+ envValue,
160+ error :
161+ errorValue instanceof SourceControlProviderError
162+ ? errorValue
163+ : new SourceControlProviderError ( "Invalid SCM provider configuration" , "permanent" ) ,
164+ } ;
165+ }
166+ }
167+
168+ if ( cachedScmProvider . error ) {
169+ throw cachedScmProvider . error ;
170+ }
171+
172+ return cachedScmProvider . provider ;
173+ }
174+
118175/**
119176 * Check if a path matches any public route pattern.
120177 */
@@ -129,6 +186,47 @@ function isSandboxAuthRoute(path: string): boolean {
129186 return SANDBOX_AUTH_ROUTES . some ( ( pattern ) => pattern . test ( path ) ) ;
130187}
131188
189+ function enforceImplementedScmProvider (
190+ path : string ,
191+ env : Env ,
192+ ctx : RequestContext
193+ ) : Response | null {
194+ try {
195+ const provider = resolveDeploymentScmProvider ( env ) ;
196+ if ( provider !== "github" && ! isPublicRoute ( path ) ) {
197+ logger . warn ( "SCM provider not implemented" , {
198+ event : "scm.provider_not_implemented" ,
199+ scm_provider : provider ,
200+ http_path : path ,
201+ request_id : ctx . request_id ,
202+ trace_id : ctx . trace_id ,
203+ } ) ;
204+ const response = error (
205+ `SCM provider '${ provider } ' is not implemented in this deployment.` ,
206+ 501
207+ ) ;
208+ return withCorsAndTraceHeaders ( response , ctx ) ;
209+ }
210+
211+ return null ;
212+ } catch ( errorValue ) {
213+ const errorMessage =
214+ errorValue instanceof SourceControlProviderError
215+ ? errorValue . message
216+ : "Invalid SCM provider configuration" ;
217+
218+ logger . error ( "Invalid SCM provider configuration" , {
219+ event : "scm.provider_invalid" ,
220+ error : errorValue instanceof Error ? errorValue : String ( errorValue ) ,
221+ request_id : ctx . request_id ,
222+ trace_id : ctx . trace_id ,
223+ } ) ;
224+
225+ const response = error ( errorMessage , 500 ) ;
226+ return withCorsAndTraceHeaders ( response , ctx ) ;
227+ }
228+ }
229+
132230/**
133231 * Validate sandbox authentication by checking with the Durable Object.
134232 * The DO stores the expected sandbox auth token.
@@ -406,32 +504,21 @@ export async function handleRequest(
406504 // Sandbox auth passed, continue to route handler
407505 } else {
408506 // Both HMAC and sandbox auth failed
409- const corsHeaders = new Headers ( sandboxAuthError . headers ) ;
410- corsHeaders . set ( "Access-Control-Allow-Origin" , "*" ) ;
411- corsHeaders . set ( "x-request-id" , ctx . request_id ) ;
412- corsHeaders . set ( "x-trace-id" , ctx . trace_id ) ;
413- return new Response ( sandboxAuthError . body , {
414- status : sandboxAuthError . status ,
415- statusText : sandboxAuthError . statusText ,
416- headers : corsHeaders ,
417- } ) ;
507+ return withCorsAndTraceHeaders ( sandboxAuthError , ctx ) ;
418508 }
419509 }
420510 } else {
421511 // Not a sandbox auth route, return HMAC auth error
422- const corsHeaders = new Headers ( hmacAuthError . headers ) ;
423- corsHeaders . set ( "Access-Control-Allow-Origin" , "*" ) ;
424- corsHeaders . set ( "x-request-id" , ctx . request_id ) ;
425- corsHeaders . set ( "x-trace-id" , ctx . trace_id ) ;
426- return new Response ( hmacAuthError . body , {
427- status : hmacAuthError . status ,
428- statusText : hmacAuthError . statusText ,
429- headers : corsHeaders ,
430- } ) ;
512+ return withCorsAndTraceHeaders ( hmacAuthError , ctx ) ;
431513 }
432514 }
433515 }
434516
517+ const providerCheck = enforceImplementedScmProvider ( path , env , ctx ) ;
518+ if ( providerCheck ) {
519+ return providerCheck ;
520+ }
521+
435522 // Find matching route
436523 for ( const route of routes ) {
437524 if ( route . method !== method ) continue ;
@@ -460,12 +547,6 @@ export async function handleRequest(
460547 return error ( "Internal server error" , 500 ) ;
461548 }
462549
463- // Create new response with CORS + correlation headers
464- const corsHeaders = new Headers ( response . headers ) ;
465- corsHeaders . set ( "Access-Control-Allow-Origin" , "*" ) ;
466- corsHeaders . set ( "x-request-id" , ctx . request_id ) ;
467- corsHeaders . set ( "x-trace-id" , ctx . trace_id ) ;
468-
469550 const durationMs = Date . now ( ) - startTime ;
470551 logger . info ( "http.request" , {
471552 event : "http.request" ,
@@ -479,11 +560,7 @@ export async function handleRequest(
479560 ...ctx . metrics . summarize ( ) ,
480561 } ) ;
481562
482- return new Response ( response . body , {
483- status : response . status ,
484- statusText : response . statusText ,
485- headers : corsHeaders ,
486- } ) ;
563+ return withCorsAndTraceHeaders ( response , ctx ) ;
487564 }
488565 }
489566
0 commit comments