Skip to content

Embed testId in Veo/Grok video poll ids for multi-tenant isolation (#278)#286

Merged
jpr5 merged 2 commits into
mainfrom
fix/video-testid-multitenant
Jun 28, 2026
Merged

Embed testId in Veo/Grok video poll ids for multi-tenant isolation (#278)#286
jpr5 merged 2 commits into
mainfrom
fix/video-testid-multitenant

Conversation

@jpr5

@jpr5 jpr5 commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

What

Completes the #278 async-video providers (Veo, Grok) for multi-tenant use: each provider now embeds the testId into 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 OpenRouter polling_url testId-embedding pattern.

No version bump — stays 1.34.0; the dedicated chore: release v1.35.0 PR 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; getTestId re-derives the testId from the query when no header is present.
  • Tests: a decisive cross-tenant routing test in both suites — two tenants submit jobs concurrently, and each tenant's header-less poll of its returned suffix-bearing id resolves its own job (asserts the specific payload, not just "not 404"). Replaces an earlier test that was tautological (passed even pre-fix). Suffix is asserted with an anchored ?testId=<encoded> regex; back-compat (default testId → bare id) retained.

Test plan

  • Full suite: 144 files / 4250 tests pass; tsc, eslint, prettier --check, build, test:exports (node10/node16-CJS/ESM/bundler) all green.
  • Red-green proven on the real submit+poll surface: reverting only veo-video.ts/grok-video.ts makes 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.

jpr5 added 2 commits June 27, 2026 23:31
…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.
@pkg-pr-new

pkg-pr-new Bot commented Jun 28, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@copilotkit/aimock@286

commit: 3f584c6

@jpr5 jpr5 merged commit 52d1b9b into main Jun 28, 2026
25 checks passed
@jpr5 jpr5 deleted the fix/video-testid-multitenant branch June 28, 2026 06:39
@jpr5 jpr5 mentioned this pull request Jun 28, 2026
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant