# Dev Mock Translation Provider Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use /ship (recommended) or /executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

Audience: AI coding agents first.

**Goal:** Add credential-selected, deterministic mock translation to production-hosted API and worker while preserving live Gemini behavior and production accounting guarantees.

**Architecture:** Persist execution mode on authenticated license and every async job. Route live calls through Gemini adapter and mock-license calls through deterministic provider; use Prisma for durable ownership/lifecycle and Redis only for cross-process gates/counters. Mock control API creates scenario jobs under same authenticated mock license, eliminating client/provider selectors and scenario-assignment races.

**Tech Stack:** TypeScript, Express, Prisma/PostgreSQL, Bull/Redis, Jest, Supertest, existing Gemini SDK.

---

## Wave Plan

| Wave | Tasks | Files touched | Safe to parallelize? |
|---|---|---|---|
| 1 | Task 1 | Prisma schema + migration | single task |
| 2 | Task 2, Task 3 | license auth/issuance; provider boundary | yes — no file overlap |
| 3 | Task 4 | mock store/provider | single task |
| 4 | Task 5, Task 6 | translation service/routes; worker | yes — no file overlap |
| 5 | Task 7 | mock control API | single task |
| 6 | Task 8 | real API+worker integration tests | single task |
| 7 | Task 9 | full verification + rollback proof | single task |

## File Map

- `api/prisma/schema.prisma`, `api/prisma/migrations/20260801120000_add_translation_execution_mode/migration.sql` — durable credential/job mode and scenario.
- `api/src/middleware/auth.ts`, `api/src/types/index.ts`, `api/src/routes/admin/licenses.ts`, `api/src/services/multilingualLicenseService.ts` — trusted mode propagation and restricted mock issuance.
- `api/src/services/translationProvider.ts`, `api/src/services/geminiTranslationProvider.ts` — stable provider seam and Gemini adapter.
- `api/src/services/mockTranslationProvider.ts`, `api/src/services/mockTranslationStore.ts` — deterministic behavior and Redis coordination.
- `api/src/services/translationService.ts`, `api/src/routes/translate.ts`, `api/src/routes/jobs.ts` — sync/bulk/async mode propagation and job snapshot.
- `api/src/worker.ts` — persisted provider selection, retry/terminal correctness, credits/webhooks.
- `api/src/routes/dev/mockTranslator.ts`, `api/src/server.ts` — credential-gated test control.
- `api/src/__tests__/unit/**`, `api/src/__tests__/integration/**`, `api/src/__tests__/e2e/**` — security, contracts, and real-process lifecycle coverage.

### Task 1: Persist credential mode and job scenario

**Wave:** 1  
**Blocks:** Tasks 2–8  
**Blocked by:** —

**Files:**

- Modify: `api/prisma/schema.prisma`
- Create: `api/prisma/migrations/20260801120000_add_translation_execution_mode/migration.sql`

**Contract:**

- Enum `TranslationExecutionMode`: `live`, `mock`.
- Enum `MockTranslationScenario`: `success`, `pending_hold`, `processing_hold`, `rate_limited`, `transient_failure`, `permanent_failure`, `timeout`, `malformed_response`.
- `License.execution_mode TranslationExecutionMode @default(live)` plus index `[execution_mode, status]`.
- `TranslationJob.execution_mode TranslationExecutionMode @default(live)` and nullable `mock_scenario MockTranslationScenario?` plus index `[execution_mode, status, created_at]`.
- Migration is additive and backfills existing rows through database defaults. No destructive/down migration.

**Behavior:** Existing credentials/jobs remain `live`; scenario must be null for normal live creation paths.

**Acceptance:**

- Run: `cd api && npx prisma validate && npx prisma generate && npm run build`
- Expected: PASS — generated client exposes both enums/fields; existing TypeScript builds.

- [ ] Add failing schema contract assertion or migration inspection test.
- [ ] Add enum/columns/indexes and SQL migration.
- [ ] Run acceptance.
- [ ] Commit only listed paths.

### Task 2: Secure mock-license issuance and auth context

**Wave:** 2  
**Blocks:** Tasks 5, 7, 8  
**Blocked by:** Task 1

**Files:**

- Modify: `api/src/types/index.ts` — add `LicenseData.executionMode`.
- Modify: `api/src/middleware/auth.ts` — load trusted license mode.
- Modify: `api/src/routes/admin/licenses.ts` — authorize explicit mock issuance.
- Modify: `api/src/services/multilingualLicenseService.ts` — persist/list/detail mode; keep live default.
- Test: `api/src/__tests__/unit/middleware/licenseAuth.test.ts`
- Test: `api/src/__tests__/unit/routes/adminLicenses.test.ts`
- Test: `api/src/__tests__/unit/services/multilingualLicenseService.test.ts`

**Contract:**

- `LicenseData.executionMode: TranslationExecutionMode` comes only from Prisma-authenticated license.
- `createLicense({ ..., user_id?, execution_mode? })`; omitted mode resolves `live`.
- Admin create body accepts `execution_mode: 'live' | 'mock'` and `user_id`.
- Mock issuance requires `req.admin.role === AdminRole.ADMIN`, explicit owner, `plugin === 'international'`, active owner, and active International subscription. Otherwise `403 MOCK_LICENSE_ISSUANCE_FORBIDDEN` or `422 MOCK_LICENSE_OWNER_REQUIRED`.
- Public onboarding/checkout cannot pass mode. Update/status/limits/key regeneration cannot change mode.

**Behavior:** Existing live admin behavior stays compatible. Support role can perform existing allowed live operation but cannot mint mock. Auth still validates license status, expiry, plugin, site activation, owner, user, and subscription before attaching mode.

**Acceptance:**

- Run: `cd api && npm test -- --runInBand src/__tests__/unit/middleware/licenseAuth.test.ts src/__tests__/unit/routes/adminLicenses.test.ts src/__tests__/unit/services/multilingualLicenseService.test.ts`
- Expected: PASS — DB mode reaches request context; mock issuance matrix fails closed; live defaults/regeneration preserved.

- [ ] Write failing authorization/default/immutability tests.
- [ ] Implement contract.
- [ ] Run acceptance.
- [ ] Commit only listed paths.

### Task 3: Add provider contract and Gemini adapter

**Wave:** 2  
**Blocks:** Tasks 4–6  
**Blocked by:** Task 1

**Files:**

- Create: `api/src/services/translationProvider.ts`
- Create: `api/src/services/geminiTranslationProvider.ts`
- Modify: `api/src/services/geminiClient.ts` — export existing request/result types needed by adapter; no model/prompt behavior change.
- Test: `api/src/__tests__/unit/services/translationProvider.test.ts`

**Contract:**

- `TranslationProvider` contains current text, bulk, structured signatures from design.
- `TranslationProviderError` exposes `kind`, `retryable`, safe `code`, and non-secret message.
- `validateProviderResult(operation, value)` rejects empty/invalid text, structured, token, model, item-ID, and bulk result shapes as `malformed_response`.
- Gemini adapter normalizes rate limit/transient/timeout/permanent errors without changing primary `gemini-3.1-flash-lite` or fallback `gemini-3.5-flash-lite` behavior.

**Acceptance:**

- Run: `cd api && npm test -- --runInBand src/__tests__/unit/services/translationProvider.test.ts src/__tests__/unit/services/geminiThinkingTokens.test.ts`
- Expected: PASS — interface/result validation, typed Gemini errors, and Gemini regression pass.

- [ ] Write failing contract tests.
- [ ] Implement boundary/adapter and export minimum existing types.
- [ ] Run acceptance.
- [ ] Commit only listed paths.

### Task 4: Build deterministic mock provider and Redis store

**Wave:** 3  
**Blocks:** Tasks 5–8  
**Blocked by:** Task 3

**Files:**

- Create: `api/src/services/mockTranslationStore.ts`
- Create: `api/src/services/mockTranslationProvider.ts`
- Modify: `api/src/services/translationProvider.ts` — add complete live/mock selector after both implementations exist.
- Test: `api/src/__tests__/unit/services/mockTranslationStore.test.ts`
- Test: `api/src/__tests__/unit/services/mockTranslationProvider.test.ts`

**Contract:**

- Store methods: `recordCall(jobId, attempt)`, `getObservation(jobId)`, `waitForRelease(jobId, signal)`, `release(jobId)`, `reset(jobId)`.
- Keys use `mock-translator:v1:job:<validated UUID>:{gate,calls,attempts}`, 24-hour TTL, atomic counters, persistent released marker plus pub/sub wakeup.
- Provider context requires persisted `jobId`, scenario, and attempt for async scenarios; ordinary sync/bulk mock calls default `success`.
- Stable output preserves HTML placeholders, exception placeholders, printf tokens, Gutenberg markup, item IDs/order; result uses fixed token counts and `mock-translator-v1`.
- Failure scenarios implement table in design. Timeout throws immediately; no timer sleep. `processing_hold` is abortable on shutdown.
- `getTranslationProvider(mode, context)` selects only `live→GeminiTranslationProvider`, `mock→MockTranslationProvider`; kill switch rejects mock with `MOCK_TRANSLATION_DISABLED`; no fallback across providers.

**Acceptance:**

- Run: `cd api && npm test -- --runInBand src/__tests__/unit/services/mockTranslationStore.test.ts src/__tests__/unit/services/mockTranslationProvider.test.ts`
- Expected: PASS — selection matrix, kill switch, all scenarios, missed-publish release, TTL, UUID rejection, isolation, abort, and content-preservation assertions pass under fake Redis.

- [ ] Write failing store/provider tests.
- [ ] Implement store/provider.
- [ ] Run acceptance.
- [ ] Commit only listed paths.

### Task 5: Propagate trusted mode through API service paths

**Wave:** 4  
**Blocks:** Tasks 7, 8  
**Blocked by:** Tasks 2–4

**Files:**

- Modify: `api/src/services/translationService.ts`
- Modify: `api/src/routes/translate.ts`
- Modify: `api/src/routes/jobs.ts`
- Test: `api/src/__tests__/unit/services/translationService.test.ts`
- Test: `api/src/__tests__/integration/routes/translate.test.ts`
- Test: `api/src/__tests__/integration/routes/jobs.test.ts`

**Contract:**

- Sync/bulk service entrypoints accept trusted `executionMode`; routes pass `req.license?.executionMode ?? 'live'`. International cannot use generic API key, so its mock mode always originates from license context.
- Async creation writes `execution_mode`; mock defaults `mock_scenario: success`; live requires null scenario.
- Dedup filters include `execution_mode`; control-created scenario jobs bypass content dedup.
- Queue payload cannot contain execution mode/scenario. Worker receives job ID and existing business payload only.
- Mock success uses normal validation, exception restoration, job persistence, character-credit deduction, cost calculation, metrics, and response schema.

**Acceptance:**

- Run: `cd api && npm test -- --runInBand src/__tests__/unit/services/translationService.test.ts src/__tests__/integration/routes/translate.test.ts src/__tests__/integration/routes/jobs.test.ts`
- Expected: PASS — live/mock propagation, no selector acceptance, dedup isolation, accounting parity, and public response compatibility pass.

- [ ] Write failing propagation/security/dedup tests.
- [ ] Replace direct Gemini calls with provider boundary and snapshot mode.
- [ ] Run acceptance.
- [ ] Commit only listed paths.

### Task 6: Make worker retries and terminal writes correct

**Wave:** 4  
**Blocks:** Tasks 7–9  
**Blocked by:** Tasks 3, 4

**Files:**

- Modify: `api/src/worker.ts`
- Modify: `api/src/services/creditService.ts` — only if existing deduction idempotency seam is not used by worker.
- Test: `api/src/__tests__/unit/workerProcessors.test.ts`
- Test: `api/src/__tests__/unit/services/creditService.test.ts`

**Contract:**

- Extract/export processor functions so unit tests invoke them without booting worker.
- Processor reloads Prisma job and selects provider from persisted mode/scenario. Reject DB/payload owner mismatch.
- Conditional claim: only `pending→processing`; retries may resume `processing`; terminal job exits without provider/accounting.
- Retryable errors throw until final configured attempt. Non-retryable errors call `job.discard()` then finalize. Mock jobs use server-selected zero backoff; live Bull defaults remain unchanged.
- Only final failure writes `failed`, `completed_at`, and one failure webhook. Intermediate attempts update safe observation only.
- Success transaction guarantees one conditional `processing→completed` plus one credit deduction; cancelled/failed/completed CAS miss produces no deduction or success webhook.
- `pending_hold` cancelled job is never enqueued. Processing cancellation stays rejected by existing API contract.

**Acceptance:**

- Run: `cd api && npm test -- --runInBand src/__tests__/unit/workerProcessors.test.ts src/__tests__/unit/services/creditService.test.ts`
- Expected: PASS — attempt counts, discard, final-only webhook, exactly-once deduction, stale/cancelled job, owner mismatch, and live backoff regression pass.

- [ ] Write failing processor/accounting tests.
- [ ] Implement persisted selection and terminal outcome rules.
- [ ] Run acceptance.
- [ ] Commit only listed paths.

### Task 7: Add credential-gated mock control API

**Wave:** 5  
**Blocks:** Task 8  
**Blocked by:** Tasks 4–6

**Files:**

- Create: `api/src/routes/dev/mockTranslator.ts`
- Modify: `api/src/server.ts`
- Test: `api/src/__tests__/integration/routes/mockTranslator.test.ts`

**Contract:**

- Mount `/v1/dev/mock-translator` unconditionally; every route runs `authenticateApiKey`, then requires `req.license.executionMode === 'mock'` and kill switch off.
- `POST /jobs` body = existing async request fields plus required scenario enum. It creates job with authenticated owner/plugin/mode and either atomically queues or leaves `pending_hold` unqueued.
- `GET /jobs/:jobId`, `POST /jobs/:jobId/release`, `POST /jobs/:jobId/reset` enforce authenticated license ID/user ownership and response/error table from design.
- Status response contains metadata/counters only; no source, translation, callback secret, credentials, stack, or Redis key.
- Release/reset are idempotent and job-scoped. Pending release enqueue uses a durable compare-and-set marker to prevent duplicate queue jobs.

**Acceptance:**

- Run: `cd api && npm test -- --runInBand src/__tests__/integration/routes/mockTranslator.test.ts`
- Expected: PASS — full auth matrix, live concealment, foreign-job concealment, validation, redaction, idempotency, kill switch, and state transitions pass.

- [ ] Write failing Supertest authorization/transition tests.
- [ ] Implement router and mount.
- [ ] Run acceptance.
- [ ] Commit only listed paths.

### Task 8: Prove real API + worker lifecycle without sleeps

**Wave:** 6  
**Blocks:** Task 9  
**Blocked by:** Tasks 1–7

**Files:**

- Create: `api/src/__tests__/e2e/mockTranslationLifecycle.test.ts`
- Create: `api/src/__tests__/e2e/helpers/mockTranslationFixture.ts`
- Modify: `api/jest.config.js` only if e2e suite needs a dedicated timeout/project split.

**Contract:**

- Start real Express app and exported worker processors against isolated PostgreSQL schema/database and Redis DB/prefix.
- Fixture creates dedicated user, active International subscription, credits, mock license, site activation, and callback receiver. Teardown scopes deletes to fixture IDs.
- Poll control status by state/counter with bounded deadline; no fixed sleeps. Release explicit gates.
- Cases: live credential selects Gemini spy only; mock never calls Gemini; success; pending hold→cancelled; pending hold→release→completed; processing hold→release→completed; rate limit retry exhaustion; transient retry exhaustion; permanent one attempt; timeout retry exhaustion; malformed non-retryable failure; reset; parallel isolation.
- Assert exact statuses, attempt counts, one/zero credit transaction, balance delta, model/cost fields, one terminal webhook, no pre-terminal webhook, safe errors, logs/response redaction.

**Acceptance:**

- Run: `cd api && npm run test:e2e -- --runInBand src/__tests__/e2e/mockTranslationLifecycle.test.ts`
- Expected: PASS — all lifecycle cases complete without Gemini/network traffic or sleep-based timing.

- [ ] Write isolated fixture and failing lifecycle cases.
- [ ] Complete integration seams needed by cases without broadening public API.
- [ ] Run acceptance twice to detect leaked state/flakiness.
- [ ] Commit only listed paths.

### Task 9: Verify production safety and rollback

**Wave:** 7  
**Blocks:** —  
**Blocked by:** Tasks 6, 8

**Files:** No source changes expected. Fix only failures caused by Tasks 1–8 in owning task files.

**Acceptance:**

- Run: `cd api && npx prisma validate && npm run lint && npm run build && npm test -- --runInBand`
- Expected: PASS with no warnings — schema, lint, typecheck/build, full test suite.
- Run: `cd api && MOCK_TRANSLATION_KILL_SWITCH=1 npm test -- --runInBand src/__tests__/integration/routes/mockTranslator.test.ts src/__tests__/unit/services/translationProvider.test.ts`
- Expected: PASS — mock requests fail closed; live provider selection remains Gemini.
- Run: `cd api && npm run test:e2e -- --runInBand src/__tests__/e2e/mockTranslationLifecycle.test.ts`
- Expected: PASS twice consecutively — zero leaked Redis/Bull/test-principal state.
- Probe: issue one live-license translation and one mock-license translation against same built app with Gemini mocked only at adapter boundary.
- Expected: live invocation count `1`; mock invocation count `0`; providers reported `gemini` and `mock` respectively.

- [ ] Run every acceptance command; address every warning/failure.
- [ ] Verify migration deploys against disposable copy and previous app build tolerates additive columns.
- [ ] Verify kill-switch/revoke runbook: active mock wait aborts, no Gemini fallback, live job unaffected.
- [ ] Commit only failure fixes in previously listed owning files; no documentation/config expansion.

## Plan self-review

- Requirement coverage: credential selection, production safety, API/worker separation, deterministic five-state lifecycle, eight scenarios, retries, timeout, malformed output, cancellation, isolation, credits, authorization, observability, rollout, rollback.
- Decision pass: no open human decision. Credential model follows current International license auth and admin issuance path.
- Seam audit: provider boundary has Gemini/mock implementations; Redis store hides cross-process gate mechanics; no decorative provider factory/config subsystem.
- Wave audit: same-wave tasks have no file overlap; every consumer follows contract/schema provider tasks.
- Scope guard: implementation may touch only files enumerated per task. No plugin/frontend/deployment changes required.
