Embed testId in Veo/Grok video poll ids for multi-tenant isolation (#278)#286
Merged
Conversation
…ion (#278) Veo and Grok async video submit store the job keyed `${testId}:${bareId}` (testId from getTestId(req)) but return an OPAQUE bare id — `operations/{uuid}` for Veo, `{uuid}` for Grok — with no tenant scope. A multi-tenant client that polls the returned id WITHOUT an x-test-id header resolves to DEFAULT_TEST_ID and 404s for any non-default testId. The established OpenRouter provider avoids this by embedding the testId in the returned polling_url via testIdSuffix. Mirror that here: append testIdSuffix(testId, "?") to the id RETURNED to the client at all four submit sites (Veo replay + record, Grok replay + record), while the job stays STORED under the bare id. On poll, route matching uses parsedUrl.pathname (query excluded), so the regex captures the bare id, and getTestId picks `?testId=` off the query — reconstructing the same key. Record mode is untouched: the upstream poll uses the bare job id / upstreamPollingUrl. testIdSuffix returns "" for DEFAULT_TEST_ID, so default-tenant returned ids stay byte-identical, preserving existing fixtures and tests. Adds multi-tenant isolation tests to veo-video.test.ts and grok-video.test.ts mirroring openrouter-video.test.ts: a non-default tenant polls its returned id (testId embedded) without the header and resolves; a bare id polled under another tenant 404s; and a default-tenant submit returns the bare id (no suffix).
…uffix (#278) Replace the tautological header-stripping isolation tests (which 404'd on both pre- and post-fix code) with a decisive concurrent-tenant test in both veo-video and grok-video suites. Two tenants submit distinct jobs concurrently so both coexist in the job map, then each polls ONLY its own RETURNED suffix-bearing id sending NO x-test-id header. The header-less poll must resolve via the embedded `?testId=` suffix to that tenant's own job (asserting its own video uri/url, not the peer's, not 404). On pre-fix code the returned id is a bare uuid carrying no testId, so a header-less poll falls to the default testId, the key misses, and the poll 404s — the test fails (RED). Also tighten the existing substring suffix check to an exact `?testId=<encoded>` query-param assertion.
commit: |
Merged
jpr5
added a commit
that referenced
this pull request
Jun 28, 2026
## Release v1.35.0 Version bump cut off latest `main` (includes #285 blocks-completion and #286 video-testid). ### Version surfaces bumped (1.34.0 → 1.35.0) These five literal build surfaces track the aimock npm release line and were bumped together: 1. `package.json` — `"version"` → `1.35.0` 2. `.claude-plugin/plugin.json` — `"version"` → `1.35.0` 3. `.claude-plugin/marketplace.json` — `plugins[0].source.version` → `^1.35.0` (caret preserved) 4. `charts/aimock/Chart.yaml` — `appVersion` → `"1.35.0"` 5. `packages/aimock-pytest/src/aimock_pytest/_version.py` — `AIMOCK_VERSION` → `"1.35.0"` ### Deliberately NOT changed (independently versioned) Verified against release history — these do **not** track the aimock release line and follow their own cadence, so they are left as-is: - `charts/aimock/Chart.yaml` `version:` stays **`0.1.0`** (Helm chart version — independent of the app it packages) - `packages/aimock-pytest/pyproject.toml` `version` stays **`0.4.0`** (the Python package's own release cadence, distinct from the npm release it pins via `AIMOCK_VERSION`) ### Hygiene fix - `packages/aimock-pytest/README.md` — corrected the stale `--aimock-version` default from `1.33.0` → `1.35.0` to match `AIMOCK_VERSION`. ### CHANGELOG - Renamed the accumulated `## [Unreleased]` section to `## [1.35.0] - 2026-06-27` and inserted a fresh empty `## [Unreleased]` above it. Bullet contents unchanged. ### What's in 1.35.0 - **`blocks` feature completed across all providers** — ordered text/tool-call block streaming now honored on replay across Anthropic, OpenAI (Responses + chat-completions), Gemini, Ollama, Cohere, Bedrock (invoke + Converse), and Gemini Interactions, with record-side block capture where the wire protocol allows (#274). - **Blocks-only fixtures are first-class** — a non-empty `blocks` array is a complete response shape on its own (no `content`/`toolCalls` required); `validateBlocks` rejects malformed arrays at load time (#274). - **Veo / Grok multi-tenant testId isolation** — native Google Veo and xAI Grok Imagine async video lifecycle mocks with record-mode live proxying and per-tenant testId isolation (#278, #282). ### Publishing Publishing is **not** performed here. The release is cut and verified; the actual publish is the manual `npm run release` step, pending maintainer action. ### Verification - `npm run build` — clean (exit 0) - `npx vitest run` — **4250/4250 passed**, 144/144 files (exit 0) - `npm run test:exports` — all 🟢 (node10 / node16 CJS+ESM / bundler, across all entry points) - Type-emitting build clean (`.d.ts` emit succeeded; no separate `tsc` script in this package)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Completes the #278 async-video providers (Veo, Grok) for multi-tenant use: each provider now embeds the
testIdinto the poll/operation id it returns, so a poll that carries the testId only in the URL (not in a header) still resolves to the correct tenant's in-flight job. Mirrors the existing OpenRouterpolling_urltestId-embedding pattern.No version bump — stays
1.34.0; the dedicatedchore: release v1.35.0PR owns the bump.Why
When the original #278 video work landed, the submit→poll round-trip relied on the testId arriving via header on the poll. Real provider SDKs poll the returned operation/job URL directly and don't necessarily re-send a custom header, so under concurrent multi-tenant use a header-less poll fell to the default testId → key miss → 404 (the job was stored under
${testId}:${id}). Embedding the testId in the returned id makes it travel with the poll URL.Changes
src/veo-video.ts/src/grok-video.ts: append?testId=<encoded>to the returned poll/operation id (only when a non-default testId is present — single-tenant flows still return a bare id). The router strips the query before route-matching;getTestIdre-derives the testId from the query when no header is present.?testId=<encoded>regex; back-compat (default testId → bare id) retained.Test plan
tsc,eslint,prettier --check,build,test:exports(node10/node16-CJS/ESM/bundler) all green.veo-video.ts/grok-video.tsmakes both concurrent-tenant tests fail (header-less poll 404s); restoring passes 31/31.Notes
No cross-tenant data-leak existed pre-fix (a missing/mismatched testId yields a key miss → loud 404, never a tenant-agnostic scan); this change is about header-less polls resolving correctly per tenant, confirmed by an independent silent-failure review.