- 2df63b7ce038: was checking _column_exists but not _table_exists.
If llm_providers/validation_policies/llm_validation_results don't
exist (fresh DB), _column_exists returns False and add_column crashes.
Now checks _table_exists first.
- a1b2c3d4e5f6 (20260603_add_token_limits_to_llm_providers): no guard
at all. llm_providers is created by create_all() at runtime. Guard
matches pattern used by ed28d34edde7, 9f8e7d6c5b4a and others.
dataset_review_sessions (and potentially other tables in FK_DEFS) are
created at runtime by init_db() → Base.metadata.create_all(), not by
Alembic migrations. On fresh databases, these tables don't exist when
migrations run, causing ALTER TABLE to crash.
Fix: check table existence before operating on each FK. If a table
doesn't exist, create_all() will create it with the correct FK
definition (the model already has ondelete='CASCADE').
This is the same pattern used by other migrations (9f8e7d6c5b4a,
ed28d34edde7, c9d8e7f6a5b4, 86c7b1d6a710) for create_all()-only tables
like llm_providers, roles, validation_policies, llm_validation_results.
- Add e1f2a3b4c5d6 migration: create dataset_review_sessions with FK
ondelete='CASCADE' (matching the model). Previously created at runtime
by init_db() → create_all().
- Update f0e9d8c7b6a5: change down_revision from a5b6c7d8e9f0 to
e1f2a3b4c5d6 so the table exists when this migration runs.
- All other create_all()-only tables (llm_providers, roles,
validation_policies, llm_validation_results) already have guards
(_table_exists()) in their respective migrations.
- Add d1e2f3a4b5c6 migration: create task_records table matching the
TaskRecord model (previously created at runtime by init_db() via
Base.metadata.create_all).
- Update a5b6c7d8e9f0: down_revision now points to d1e2f3a4b5c6, so
task_records exists when this migration runs. Remove the
inspector.has_table() guard (no longer needed).
- create_all() in init_db() becomes a no-op for task_records — the
table is now fully managed by Alembic.
- Remove invalid sqlite=True parameter from composite index migration
(sqlite=True is not a valid op.create_index parameter)
- Fix test_assistant_api assertions for updated response format
- Fix test_git_status_route edge case assertions
- Fix test_audit_service expected value after metric changes
- Fix test_session_repository assertion after store refactor
Add support for regex-based dictionary entries across the full stack:
Backend:
- DictionaryEntry model: add is_regex column (Boolean, default False)
- DictionaryEntryCRUD: validate regex on add_entry(), compile on creation
- _enforce_dictionary: match by regex pattern when is_regex=True
- dictionary_filter: support is_regex in filter/query
- metrics: include is_regex entries in metrics calculations
- Alembic migration: 20260604_add_is_regex_to_dictionary_entries
- Merge migration: 351afb8f961a (merge is_regex + composite index heads)
Frontend:
- DictionaryDetailModel: add is_regex field to DictionaryEntry interface,
EditForm, and addForm; sync edit/add form state with backend schema
- Add composite index ix_translation_records_run_source_hash
for NOT EXISTS dedup subquery performance + Alembic migration
- Remove duplicate #endregion in orchestrator.py (INV_3)
- Replace hardcoded RU fallback 'Статус' with 'Status'
- Add early return guard to loadMoreRecords()
- Show records summary always when recordsTotal > 0
- Removes source_dialect and target_dialect from TerminologyDictionary model,
schemas, routes, helper serialization, and all tests
- Adds allowed_languages config field (BCP-47 language codes) to GlobalSettings
with validation, consolidated settings response, and API endpoint
- Adds alembic migration f1a2b3c4d5e6 to drop the two columns
Backend:
- TranslationRun model: add cache_hits Integer column (default 0)
- TranslationRunResponse schema: add cache_hits field
- _helpers.py/_run_list_routes.py/orchestrator_aggregator.py: include
cache_hits in all run API responses
- _batch_proc.py: count pre_rows served from translation cache per
batch, return cache_hits in batch result
- executor.py: accumulate cache_hits across batches, persist to run
- Alembic migration: dabc9709 — add cache_hits column to translation_runs
Frontend:
- translationRun.svelte.ts: add cacheHits to store state, WS handler,
polling handler
- TranslationRunProgress.svelte: 5-col stats grid with purple Cache card
- TranslationRunGlobalIndicator.svelte: 5-col stats with Cache
- TranslationRunResult.svelte: 5-col detail stats with Cache card
- History page: cache_hits shown in run list row + detail panel
Visual: cache hits shown in purple alongside green/yellow/red metrics
(total/success/failed/skipped). Visible during run + in history.
Tests: backend 69/69 translate tests ✅, frontend 698/698 tests ✅,
frontend build ✅
Add binary-search probe endpoint POST /providers/{id}/probe-max-images
that discovers the per-request image limit by sending incrementally
more 1x1 JPEGs via the provider's own API. Result is cached in the
new max_images column on the provider config.
- LLMProviderConfig: add max_images: int | None
- LLMProvider (SQLAlchemy): add max_images column
- Migration ed28d34edde7: clean ADD COLUMN
- LLMProviderService: create/update/set_max_images
- POST /providers/{id}/probe-max-images: binary search + error parsing
- ProviderConfig.svelte: 'Detect' button in edit modal + HelpTooltip
- i18n (en/ru): 11 new keys for probe UI
- Added 4 missing columns to _ensure_translation_jobs_columns() inline
migration: target_language_column, target_source_column,
target_source_language_column, disable_reasoning — these were in the
SQLAlchemy model but had no Alembic migration or _ensure_*() coverage
- Added missing api_key, assistant, clean_release model imports to
alembic/env.py so autogenerate can see all tables
Migration now sets is_admin=true for existing Admin roles.
has_permission falls back to role.name == 'Admin' check if
is_admin flag is missing (backward compat for pre-migration roles).
Alembic migration 86c7b1d6a710 adds is_admin BOOLEAN DEFAULT FALSE
to the roles table. Fixes login crash after M-3 change:
psycopg2.errors.UndefinedColumn: column roles.is_admin does not exist
Also adds _ensure_roles_is_admin_column() as an additive migration
safety net for environments that don't run alembic automatically.
To apply: cd backend && source .venv/bin/activate && alembic upgrade head
- Add ondelete=CASCADE/SET NULL to all environment and translate FK constraints
- Add is_multimodal to GET /api/settings/consolidated response
- Fix toggleActive to not overwrite is_multimodal with false
- New SearchableMultiSelect component for dashboard search with multi-select
- Fix validation task page: task data unwrapping, date formatting, dashboard multi-select
- Fix infinite effect loop on dashboards page via queueMicrotask guard
- 3 new Alembic migrations (merge f0e9d8c7b6a5, translate b0c1d2e3f4a5)
Backend:
- /api/validation/ CRUD for validation tasks (policies)
- Run history with 6 filters + pagination
- Alembic migrations (provider_id, policy_id)
- 28 pytest tests
Frontend:
- /validation — task list page with status filters + CRUD
- /validation/[id] — task config (Config + History tabs)
- /validation/history — run history with metrics
- Sidebar nav entry under Operations
- i18n en/ru
Playwright (ScreenshotService):
- Removed 25s hardcoded sleeps → conditional waits
- CDP fallback to full_page screenshot
- Total timeout enforcement (120s)
- Debug screenshots → temp dir with cleanup
QA fixes:
- Debug screenshot accumulation in production (tempdir + rmtree)
- Frontend API payload field name mismatch
- History issues field reference fix
- delete_runs scoped by policy_id
Belief protocol:
- @RATIONALE/@REJECTED on 27 C4+ contracts
Closes: VALIDATION-REWORK-2026
- Add source_hash column (SHA256 of source_text+source_data+dict_hash+config_hash) to TranslationRecord for cache dedup
- Add Alembic migration b1c2d3e4f5a6 for the new column and composite index (source_hash, status)
- Implement _compute_source_hash / _check_translation_cache in executor.py — before LLM call, check if same source+dict+config combo was already translated successfully
- Implement cache-aware routing in _process_batch: full cache hit → skip LLM, partial hit → route to LLM
- Store source_hash on all new TranslationRecord rows for future cache hits
- Strengthen dictionary prompt instructions from 'use when applicable' to 'MUST use — mandatory'
- Add _enforce_dictionary() post-processing: force-replace dictionary terms in LLM output if LLM ignored them
- Fix CRITICAL: use run.dict_snapshot_hash/config_hash (exist on TranslationRun, not TranslationJob)
- Fix MAJOR: verify cached languages cover ALL target_languages before accepting cache hit
- Fix MAJOR: prevent regex back-reference injection in dictionary enforcement (use lambda)
- Fix: add joinedload to cache lookup to avoid N+1 queries
- Fix: remove redundant single-column index (composite index is sufficient)
- Fix(frontend): parse paginated /translate/dictionaries response correctly (result.items)
Migration 2a7b8c9d0e1f adds target_languages (JSON) to translation_jobs.
Uses ADD COLUMN IF NOT EXISTS for idempotent PostgreSQL deployment.
Fixes 500 error on GET /api/translate/jobs:
column translation_jobs.target_languages does not exist
Migration chain is now clean: 5 linear revisions, no orphans.
- Delete migrate_sqlite_to_postgres.py (one-time script, no longer needed)
- Fix @INVARIANT in mappings.py: SQLite → PostgreSQL
- Remove deprecated source_language column from TranslationJob model
+ Alembic migration 8dd0a93af539 to drop column if exists
- Remove source_language from TranslateJobResponse schema
Fixes recurring 'source_language does not exist' scheduler error
CR1: Add needs_review, language_overridden to translation_languages migration
CR2: Add per_language_metrics to translation_metric_snapshots migration
H1: BulkFindReplaceService.preview() now uses replacement_text (not find pattern)
H2: get_run_records now includes per-language data via selectinload
H3: Preview per-language approve/reject uses correct (jobId, rowId, lang) args
H4: _compute_config_hash now includes target_languages
- Auto-detection of source language per row via LLM (US6)
- Multi-target translation — one LLM call for N languages (US1-US3)
- Language-aware storage: TranslationLanguage, per-language stats
- Multilingual dictionaries with language-pair-aware filtering (US7)
- Inline correction on any run result + submit-to-dictionary (US8)
- Context-aware dictionary: auto-capture row context, usage notes,
Jaccard similarity, priority flagging in LLM prompts (US8b)
- Configurable preview sample size 1-100, cost warning at >30
- Per-language history & metrics with MetricSnapshot preservation
- 36 files, +5022/-373, all specs GRACE-Poly v2.6 compliant