( env: Readonly<Record<string, string | undefined>> = process.env, )
| 2257 | * POST is the expected source of that signal. |
| 2258 | */ |
| 2259 | export function buildCronProbeResolver( |
| 2260 | env: Readonly<Record<string, string | undefined>> = process.env, |
| 2261 | ): CronProbeResolver { |
| 2262 | const railwayToken = env.RAILWAY_TOKEN; |
| 2263 | const railwayProjectId = env.RAILWAY_PROJECT_ID; |
| 2264 | const railwayEnvironmentId = env.RAILWAY_ENVIRONMENT_ID; |
| 2265 | const aimockUrl = env.AIMOCK_URL; |
| 2266 | |
| 2267 | // Construct once at boot; each cron tick reuses these closures. Env |
| 2268 | // reads at boot only — rotating RAILWAY_TOKEN requires a restart, same |
| 2269 | // as every other env-driven adapter in this service. |
| 2270 | let aimockInvoker: |
| 2271 | | (() => Promise<import("./types/index.js").ProbeResult<unknown>>) |
| 2272 | | null = null; |
| 2273 | if (railwayToken && railwayProjectId && railwayEnvironmentId && aimockUrl) { |
| 2274 | const adapter = createRailwayAdapter({ |
| 2275 | token: railwayToken, |
| 2276 | projectId: railwayProjectId, |
| 2277 | environmentId: railwayEnvironmentId, |
| 2278 | }); |
| 2279 | // Boot-time auth probe. Without this, a bad RAILWAY_TOKEN only surfaces |
| 2280 | // as a generic "railway gql 401: ..." error on the first cron tick — |
| 2281 | // hours or days after deploy. We fire `listServices` once at |
| 2282 | // construction; a 401 is logged with a specific |
| 2283 | // `RAILWAY_AUTH_FAILED` hint that operators can grep for. We do NOT |
| 2284 | // throw / exit non-zero — other probes (deploy/e2e) run without |
| 2285 | // Railway and must stay up — but we log at error level so the |
| 2286 | // healthcheck or log-alerts pipeline catches it. |
| 2287 | adapter.listServices().catch((err) => { |
| 2288 | logErrorWithStack(logger, "orchestrator.RAILWAY_AUTH_FAILED", err, { |
| 2289 | hint: "aimock-wiring probe will fail on every cron tick — check RAILWAY_TOKEN / RAILWAY_PROJECT_ID", |
| 2290 | }); |
| 2291 | }); |
| 2292 | aimockInvoker = async () => |
| 2293 | aimockWiringProbe.run( |
| 2294 | { |
| 2295 | aimockUrl, |
| 2296 | listServices: adapter.listServices, |
| 2297 | getServiceEnv: adapter.getServiceEnv, |
| 2298 | }, |
| 2299 | { |
| 2300 | now: () => new Date(), |
| 2301 | logger, |
| 2302 | env: process.env as Readonly<Record<string, string | undefined>>, |
| 2303 | }, |
| 2304 | ); |
| 2305 | } else { |
| 2306 | logger.info("orchestrator.aimock-wiring-probe-disabled", { |
| 2307 | hasToken: !!railwayToken, |
| 2308 | hasProjectId: !!railwayProjectId, |
| 2309 | hasEnvironmentId: !!railwayEnvironmentId, |
| 2310 | hasAimockUrl: !!aimockUrl, |
| 2311 | }); |
| 2312 | } |
| 2313 | |
| 2314 | return (dimension: string) => { |
| 2315 | if (dimension === "aimock_wiring") return aimockInvoker; |
| 2316 | // Other cron-only dimensions require an external webhook trigger to |
no test coverage detected
searching dependent graphs…