Give the main E2E lane the two seams the index fleet's WRITER path crosses, so a spec can drive system.code-graph-refresh end to end — boot, poll, settle — against the fake orchestrator, with no real container, no Fly token and no network.
MOTIR_FLEET_ORCHESTRATOR=fake is set (playwright.config.ts), and indexFleetConfig() returns a fake digest under it, so the CONTAINER half is already stubbed. What is not stubbed is what bootIndexContainer does BEFORE it provisions, and both calls leave the process:
mintCodeGraphRunCredential -> POST {MOTIR_AI_URL}/v1/code-graph/run-credential. MOTIR_AI_URL / MOTIR_AI_SERVICE_TOKEN are set only in playwright.acceptance.config.ts, and NO mock in lib/test-*mock*.ts intercepts that path.requireRepoTarballUrlResolver(getGitProvider('github')) -> an installation token minted from the App JWT, then GET /repos/{owner}/{name}/tarball/{ref} read for its 302 Location. GITHUB_APP_ID / GITHUB_APP_PRIVATE_KEY are set in NEITHER lane, and lib/test-github-repos-mock.ts intercepts the provisioning paths, not the tarball redirect.tests/jobs/code-graph-index.test.ts stubs both in-process (stubIndexFleet in tests/helpers/indexFleet.ts). A Playwright server is a separately-spawned process, so an in-process stub cannot reach it — which is exactly the reason every other boundary in this lane has a lib/test-*-mock.ts installed by instrumentation.ts behind a flag.
Follow lib/test-github-repos-mock.ts and lib/test-billing-mock.ts verbatim — same MockAgent, same instrumentation.ts flag registration, same journal. Two intercepts and the env to reach them:
lib/test-code-graph-mock.ts, behind E2E_TEST_CODE_GRAPH=1, intercepting the MOTIR_AI_URL origin's POST /v1/code-graph/run-credential with a well-formed CodeGraphRunCredential (an opaque credential string and an expiresAt), and api.github.com's GET /repos/{owner}/{name}/tarball/{ref} with a 302 carrying a Location — the shape resolveTarballUrl reads, not a body.MOTIR_AI_URL, MOTIR_AI_SERVICE_TOKEN, GITHUB_APP_ID and a generated GITHUB_APP_PRIVATE_KEY on the app webServer, and — this is the half a reader will miss — on the WORKER process too. tests/e2e/_helpers/job-worker-process.ts spawns a THIRD process that inherits the RUNNER's env, not webServer.env; MOTIR-3498 was filed because EMAIL_PROVIDER was set on the webServer only and every engine-routed send went to the console provider. A supervisor runs IN THE WORKER, so the worker is the process that actually makes both calls.ENDS at: a Playwright spec being able to drive runIndexFleetSteps to a succeeded job_run carrying output.repoRef, on the engine, in the main lane.
Adds NO new webServer entry and NO new service — one more instrumentation.ts mock behind one more flag, which is the lane's established shape.
Does NOT write the spec. MOTIR-3487 does, and is blocked on this.
Does NOT touch the acceptance lane, which already sets MOTIR_AI_URL for other reasons.
Does NOT stub the orchestrator — the fake adapter is already selected by the shipped config seam and is not a mock.
system.code-graph-refresh run claimed by the lane's worker reaches settleIndexContainer and writes ONE succeeded job_run per repo carrying one output.repoRef — asserted by a spec-level smoke in this card, so the seam is proven by the thing that will consume it.mintCodeGraphRunCredential's VALIDATOR accepts; a response missing credential must fail the mint loudly, and a test asserts that arm rather than only the happy one.Location is what reaches MOTIR_INDEX_TARBALL_URL in the container spec, and NO response body is ever read — tests/helpers/indexFleet.ts's byte trap is the precedent and the same property must hold here.E2E_TEST_CODE_GRAPH is absent by default, so no existing spec's server behaviour changes; tests/e2e/jobs-flow.spec.ts and tests/e2e/migrate-index-fleet.spec.ts still pass untouched.lib/test-github-repos-mock.ts · lib/test-billing-mock.ts — the two mocks to mirrorinstrumentation.ts — the flag registration tableplaywright.config.ts — the app webServer env blocktests/e2e/_helpers/job-worker-process.ts — the third process, and why it needs the same variablestests/helpers/indexFleet.ts — stubIndexFleet, the in-process equivalent this ports across the process boundarylib/ai/motirAiClient.ts — mintCodeGraphRunCredential and its response validatorlib/services/codeGraphIndexDispatchService.ts — bootIndexContainer, the two calls