specks updated

This commit is contained in:
2026-05-14 15:27:41 +03:00
parent c6189876b3
commit 5e741a4332
9 changed files with 28306 additions and 60361 deletions

View File

@@ -0,0 +1,105 @@
# 🐞 Bug Report: MCP Axiom — Intermittent Request Timeouts
**Reported by**: Kilo Code (agent)
**Date**: 2026-05-14
**Affected tools**: `axiom_semantic_index rebuild`, `axiom_semantic_validation` (all operations), `axiom_semantic_context workspace_health`
**Severity**: 🔴 High — blocks semantic audit pipeline
---
## 1. Symptom
Repeated `MCP error -32001: Request timed out` on operations that scan the full workspace (548 files, 2521 contracts, 1984 edges). Default 120s timeout consistently exceeded.
## 2. Reproduction Steps
### Step A — Successful baseline
```
axiom_semantic_index reindex
→ success, 1-2s
```
### Step B — AFTER reindex, run any scan
```
axiom_semantic_validation audit_contracts --file_path specs/028-llm-datasource-supeset/
→ success, 3-5s (narrow scope, 1 directory)
axiom_semantic_validation audit_contracts --file_path specs/028-llm-datasource-supeset/data-model.md
→ TIMEOUT after 120s (single file, should be fast!)
axiom_semantic_validation audit_contracts --file_path specs/028-llm-datasource-supeset/
→ TIMEOUT after 120s (was fast before, now slow — index state dependent?)
```
### Step C — Rebuild after edits
```
axiom_semantic_index rebuild --rebuild_mode full
→ TIMEOUT after 120s (1st attempt)
→ TIMEOUT after 120s (2nd attempt)
→ success after 3rd attempt (no code changes between attempts)
axiom_semantic_index reindex
→ TIMEOUT after 120s (was fast in Step A!)
```
### Step D — Workspace-wide operations (100% timeout)
```
axiom_semantic_context workspace_health
→ TIMEOUT after 120s (4 attempts, all timed out)
axiom_semantic_validation audit_belief_protocol --file_path specs/028-llm-datasource-supeset/
→ TIMEOUT after 120s (2 attempts)
```
## 3. Timing Analysis
| Operation | Scope | When Fast | When Slow |
|-----------|-------|-----------|-----------|
| `reindex` | 548 files in-memory | 1-2s | 120s+ (post audit/edit) |
| `rebuild full` | 548 files + DuckDB | 3-5s | 120s+ (70% of attempts) |
| `audit_contracts` (directory) | 8 files | 3-5s | 120s+ (30%) |
| `audit_contracts` (single file) | 1 file | — | 120s+ (100%!) |
| `workspace_health` | full w/s | — | 120s+ (100%) |
| `audit_belief_protocol` | full w/s | — | 120s+ (100%) |
**Key observations**:
- Single-file audit times out MORE than directory audit — counter-intuitive
- `reindex` goes from 2s → timeout after an audit or edit — lock contention sign
- Operations that succeed once tend to succeed on retries (caching)
## 4. Hypothesis
### H1 — Background rebuild lock (most likely)
`rebuild` or `audit` spawns a background index rebuild holding a file lock. Subsequent operations wait for lock → timeout. No progress reporting, so agent sees silent 120s with no clue.
**Evidence**: `reindex` is 2s normally, times out after `rebuild`/`audit` was recently called.
### H2 — 120s default too low
548 files × 0.25s = 137s. Full scan exceeds budget even normally.
**Evidence**: 588 source files (338 Python + 114 Svelte); workspace_health scans all.
### H3 — Index corruption after file edit
After editing `contracts/modules.md`, `reindex` went from 2s → timeout. Suggests in-memory index may corrupt, triggering expensive repair.
**Evidence**: Contract count drifted 2528 → 2521 → 2516 across rebuilds.
## 5. Workaround Used by Agent
1. `reindex` fast (1-2s) → immediately `audit_contracts` — usually works
2. `reindex` timeout → wait 30s, retry — sometimes works
3. `rebuild` timeout → retry 3× with 10s gaps — 70% success
4. `workspace_health` → give up, use directory-scoped `audit_contracts`
5. Directory-scoped audit OVER single-file (counter-intuitive but works)
## 6. Fix Recommendations
| Pri | Fix |
|-----|-----|
| P0 | Detect lock contention, report "Waiting for index lock (held by X since T)" |
| P0 | Bump default timeout to 300s or make it `workspace_size / 4` seconds |
| P1 | Progress: "Scanning 245/548 files... (45%)" |
| P1 | Single-file audit should NOT trigger full index scan |
| P2 | Auto-detect stale/corrupt index after file edits, prompt reindex |
| P2 | Add `--timeout_seconds N` parameter to all operations |
| P2 | Distinct error codes: `-32002 TIMEOUT_LOCK` vs `-32003 TIMEOUT_OP` vs generic `-32001` |

View File

@@ -64,18 +64,17 @@
### [DEF:SQLGenerator:Class]
`backend/src/plugins/translate/sql_generator.py`
@COMPLEXITY 3
@COMPLEXITY 4
@PURPOSE Generate safe dialect-appropriate INSERT/UPSERT SQL from TranslationRecord rows, keyed by configured target key columns. Detects dialect from Superset connection (PostgreSQL/Greenplum, ClickHouse supported for MVP). Validates and quotes identifiers per dialect rules; safely encodes values.
@PRE TranslationRecord rows exist with status translated or edited and final_value is non-null. Target table schema validated at configuration time. Target database dialect is PostgreSQL.
@POST Returns a syntactically valid, injection-safe PostgreSQL INSERT (or INSERT ... ON CONFLICT) statement string.
@PRE TranslationRecord rows exist with status translated or edited and final_value is non-null. Target table schema validated at configuration time.
@POST Returns a syntactically valid, injection-safe SQL INSERT/UPSERT statement for the detected dialect.
@SIDE_EFFECT None — pure function (no I/O, no mutation).
@RELATION CALLS -> [SupersetClient:Module]
@RATIONALE Separate contract because SQL generation reused by both manual and scheduled runs, and independently testable for SQL syntax correctness (SC-003) and injection safety.
@REJECTED UPDATE statements not generated because source is append-only (new-key-only strategy). UPSERT covers the overwrite case without separate UPDATE logic.
[/DEF:SQLGenerator:Class]
### [DEF:SupersetSqlLabExecutor:Class]
`backend/src/plugins/translate/superset_executor.py`
@COMPLEXITY 3
@COMPLEXITY 4
@PURPOSE Submit generated SQL to Superset SQL Lab API `/api/v1/sqllab/execute/`, poll execution status, record Superset query reference, status, and error details in the TranslationRun.
@PRE TranslationRun exists with translation_status completed/partial. SQL is generated and syntactically valid.
@POST TranslationRun.insert_status updated (submitted→running→succeeded|failed). Superset query reference, error details, rows_affected (if available) stored.
@@ -127,8 +126,6 @@
`backend/src/plugins/translate/metrics.py`
@COMPLEXITY 3
@PURPOSE Aggregate per-job metrics from live TranslationEvent log AND persistent MetricSnapshot table. For recent data (<90 days): compute from events. For cumulative totals: read latest MetricSnapshot + recent events.
@PRE TranslationEvent rows or MetricSnapshot rows exist for target job.
@POST Returns MetricsResponse DTO with accurate cumulative values spanning both live and pruned data.
@RELATION CALLS -> [TranslationEventLog:Class]
@RELATION CALLS -> [MetricSnapshot:Class]
[/DEF:TranslationMetrics:Class]
@@ -149,7 +146,6 @@
`backend/src/models/translate.py`
@COMPLEXITY 2
@PURPOSE SQLAlchemy ORM models: TranslationJob, TranslationRun, TranslationBatch, TranslationRecord, TranslationEvent, TranslationPreviewSession, TranslationPreviewRecord, TerminologyDictionary, DictionaryEntry, TranslationSchedule, TranslationJobDictionary, MetricSnapshot.
@RELATION INHERITS -> [Base:Class]
[/DEF:TranslateModels:Module]
### [DEF:TranslateSchemas:Module]
@@ -219,48 +215,42 @@
@UX_STATE closed, collecting, reviewing, submitting, submitted
[/DEF:BulkCorrectionSidebar:Component]
### [DEF:DictionaryEditor:Component]
## @{ DictionaryEditor [C:3] [TYPE Component]
`frontend/src/lib/components/translate/DictionaryEditor.svelte`
@COMPLEXITY 3
@PURPOSE Inline editor: add/edit/delete entries, CSV/TSV import with conflict preview (overwrite/keep existing), export.
@BRIEF Inline editor: add/edit/delete entries, CSV/TSV import with conflict preview (overwrite/keep existing), export.
@UX_STATE idle, loading, editing, importing, import_preview, import_conflict, saving
[/DEF:DictionaryEditor:Component]
## @} DictionaryEditor
### [DEF:DictionaryList:Component]
## @{ DictionaryList [C:3] [TYPE Component]
`frontend/src/routes/translate/dictionaries/+page.svelte`
@COMPLEXITY 3
@PURPOSE SvelteKit page listing dictionaries with language, term count, attachment info.
@BRIEF SvelteKit page listing dictionaries with language, term count, attachment info.
@UX_STATE idle, loading, empty, populated, delete_blocked
[/DEF:DictionaryList:Component]
## @} DictionaryList
### [DEF:ScheduleConfig:Component]
## @{ ScheduleConfig [C:3] [TYPE Component]
`frontend/src/lib/components/translate/ScheduleConfig.svelte`
@COMPLEXITY 3
@PURPOSE Schedule panel: type selector, cron/interval with timezone, next-N-executions preview, concurrency policy, enable/disable. Warns if no prior successful manual run.
@BRIEF Schedule panel: type selector, cron/interval with timezone, next-N-executions preview, concurrency policy, enable/disable. Warns if no prior successful manual run.
@UX_STATE idle, editing, validating, enabled, disabled, no_prior_run_warning
@UX_REACTIVITY Next execution times $derived with timezone display
[/DEF:ScheduleConfig:Component]
## @} ScheduleConfig
### [DEF:TranslationHistory:Component]
## @{ TranslationHistory [C:3] [TYPE Component]
`frontend/src/routes/translate/history/+page.svelte`
@COMPLEXITY 3
@PURPOSE Filterable run history: datasource, target table, row count, translation_status, insert_status, date. Detail view with config snapshot, Superset reference, SQL. Pruned runs show metadata only.
@BRIEF Filterable run history: datasource, target table, row count, translation_status, insert_status, date. Detail view with config snapshot, Superset reference, SQL. Pruned runs show metadata only.
@UX_STATE idle, loading, empty, populated, detail_open, pruned
[/DEF:TranslationHistory:Component]
## @} TranslationHistory
### [DEF:TranslateApiClient:Module]
## @{ TranslateApiClient [C:2] [TYPE Module]
`frontend/src/lib/api/translate.js`
@COMPLEXITY 2
@PURPOSE API client wrapping requestApi/fetchApi for all translate endpoints.
[/DEF:TranslateApiClient:Module]
@BRIEF API client wrapping requestApi/fetchApi for all translate endpoints.
## @} TranslateApiClient
### [DEF:translateStore:Store]
## @{ translateStore [C:3] [TYPE Store]
`frontend/src/lib/stores/translate.js`
@COMPLEXITY 3
@PURPOSE Svelte 5 rune store for translation feature state.
@BRIEF Svelte 5 rune store for translation feature state.
@RELATION BINDS_TO -> [TranslateApiClient:Module]
@RELATION BINDS_TO -> [TaskWebSocket:Module]
[/DEF:translateStore:Store]
## @} translateStore
---
@@ -275,3 +265,65 @@
| `[PermissionChecker:Dependency]` | FastAPI dependency for RBAC enforcement | Route handlers annotated per access-control matrix |
| `[TaskWebSocket:Module]` | WebSocket for real-time task progress | Translation run progress events streamed to frontend |
| `[TaskContext:Class]` | Background task lifecycle context | Orchestrator runs as async background task |
---
## Updated Contracts (2026-05-14 Multi-Language Optimization)
## @{ TranslationLanguage [C:1] [TYPE Class] [NEW]
`backend/src/models/translate.py`
## @} TranslationLanguage
## @{ TranslationRunLanguageStats [C:1] [TYPE Class] [NEW]
`backend/src/models/translate.py`
## @} TranslationRunLanguageStats
## @{ DictionaryEntry [C:2] [TYPE Class] [UPDATED]
`backend/src/models/translate.py`
@BRIEF A single source_term → target_term pair within a dictionary, with explicit language pair (BCP-47) and optional context_data + usage_notes.
## @} DictionaryEntry
## @{ InlineCorrectionService [C:4] [TYPE Class] [UPDATED]
`backend/src/plugins/translate/service.py` (extended)
@BRIEF Handle inline corrections from run results: auto-capture source row context, allow user to edit/remove context, validate language pair, create/update DictionaryEntry, record origin metadata.
@PRE Run record must exist. Source term and corrected term must be non-empty. Language pair must match row's metadata.
@POST DictionaryEntry created or updated with context_data (if source row has context columns), usage_notes (if provided), has_context flag, context_source tag. Origin metadata recorded.
@SIDE_EFFECT Creates/updates DictionaryEntry rows. Logs correction events.
@RELATION CALLS -> [DictionaryManager:Class]
@RELATION CALLS -> [TranslationEventLog:Class]
## @} InlineCorrectionService
## @{ ContextAwarePromptBuilder [C:3] [TYPE Class] [NEW]
`backend/src/plugins/translate/prompt_builder.py`
@BRIEF Construct LLM prompts with context-aware dictionary entries. For entries with has_context=True, render context annotations. Compute Jaccard similarity between entry context_data and incoming row context to flag priority_context entries.
@RELATION DEPENDS_ON -> [DictionaryManager:Class]
@RELATION DEPENDS_ON -> [LLMProviderService:Module]
## @} ContextAwarePromptBuilder
## @{ BulkFindReplaceService [C:4] [TYPE Class] [NEW]
`backend/src/plugins/translate/service.py` (extended)
@BRIEF Apply find-and-replace across translated values in a run. Supports regex or literal patterns, filtered by target language. Previews affected rows before applying.
@PRE Run must be completed. Pattern must be valid regex (if regex flag set).
@POST All matching cells are updated. If "submit to dictionary" flag is set, corrections are also created as DictionaryEntry rows.
@SIDE_EFFECT Updates TranslationLanguage.final_value for matched rows. Optionally creates DictionaryEntry rows.
@RELATION DEPENDS_ON -> [InlineCorrectionService:Class]
## @} BulkFindReplaceService
## @{ CorrectionCell [C:3] [TYPE Component] [NEW]
`frontend/src/lib/components/translate/CorrectionCell.svelte`
@BRIEF Inline-editable cell for translation results. Click to edit, submit to dictionary button after edit.
@UX_STATE view → clicking makes cell editable → editing → after edit shows "Submit to Dictionary" → submitting → submitted with "Dictionary ✓" badge | error.
@UX_FEEDBACK Toast on submit success/error. "Dictionary ✓" badge on corrected rows.
@UX_RECOVERY Retry button on submit failure. Edit persists locally.
@RELATION DEPENDS_ON -> [TranslateApiClient:Module]
@RELATION BINDS_TO -> [translateStore:Store]
## @} CorrectionCell
## @{ BulkReplaceModal [C:3] [TYPE Component] [NEW]
`frontend/src/lib/components/translate/BulkReplaceModal.svelte`
@BRIEF Modal for bulk find-and-replace across translated values. Pattern input, target language selection, preview table, apply action.
@UX_STATE closed → configuring → previewing → applying → applied | error.
@UX_FEEDBACK Pattern preview table. "N rows affected" counter. Warning at > 50 rows.
@UX_REACTIVITY Preview list $derived from pattern + run data.
@RELATION DEPENDS_ON -> [TranslateApiClient:Module]
## @} BulkReplaceModal

View File

@@ -5,12 +5,12 @@
## 1. SQLAlchemy ORM Entities (dialect-aware — PostgreSQL/Greenplum, ClickHouse supported)
### TranslationJob
### TranslationJob (UPDATED — multi-language)
```python
# [DEF:TranslationJob:Class]
# @COMPLEXITY 2
# @PURPOSE Persisted configuration for a translation job.
# @PURPOSE Persisted configuration for a translation job (multi-language).
class TranslationJob(Base):
__tablename__ = "translation_jobs"
@@ -31,8 +31,8 @@ class TranslationJob(Base):
upsert_strategy = Column(String, default="insert") # insert | skip_existing | overwrite
# LLM configuration
provider_id = Column(String, ForeignKey("llm_providers.id"), nullable=True)
target_language = Column(String, nullable=False) # BCP-47 tag, e.g., "ru", "en"
source_language = Column(String, nullable=True) # Optional BCP-47 tag
target_languages = Column(JSON, nullable=False) # [BCP-47, ...] — was target_language str, now list [UPDATED]
source_language = Column(String, nullable=True) # Optional fallback hint; auto-detected per row via LLM [DEPRECATED but preserved for migration]
prompt_template = Column(Text, nullable=True)
batch_size = Column(Integer, default=50)
# Timestamps
@@ -61,15 +61,14 @@ class TranslationJobDictionary(Base):
dictionary = relationship("TerminologyDictionary")
```
### TerminologyDictionary
### TerminologyDictionary (UPDATED — multilingual)
```python
class TerminologyDictionary(Base):
__tablename__ = "terminology_dictionaries"
id = Column(String, primary_key=True, default=generate_uuid)
name = Column(String, nullable=False)
target_language = Column(String, nullable=False) # BCP-47 tag
source_language = Column(String, nullable=True)
name = Column(String, nullable=False) # Organizational label only
# target_language removed — entries carry their own language pairs [UPDATED]
owner_id = Column(String, ForeignKey("users.id"), nullable=False)
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
updated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
@@ -77,8 +76,9 @@ class TerminologyDictionary(Base):
owner = relationship("User")
entries = relationship("DictionaryEntry", back_populates="dictionary", cascade="all, delete-orphan")
```
```
### DictionaryEntry
### DictionaryEntry (UPDATED — language-aware + context)
```python
class DictionaryEntry(Base):
@@ -87,18 +87,31 @@ class DictionaryEntry(Base):
dictionary_id = Column(String, ForeignKey("terminology_dictionaries.id"), nullable=False)
source_term = Column(String, nullable=False)
source_term_normalized = Column(String, nullable=False) # Lowercase, NFC normalized
source_language = Column(String, nullable=False) # BCP-47 tag [NEW]
target_language = Column(String, nullable=False) # BCP-47 tag [NEW — was on dictionary]
target_term = Column(String, nullable=False)
# Context [NEW section]
context_data = Column(JSON, nullable=True) # Snapshot of source row's context columns { col_name: value, ... }
usage_notes = Column(Text, nullable=True) # Free-form user notes on when this translation applies
has_context = Column(Boolean, default=False) # Fast filter flag for prompt construction
context_source = Column(String, nullable=True) # "auto" | "auto_with_edits" | "manual" | "bulk"
# Origin metadata
origin_run_id = Column(String, ForeignKey("translation_runs.id"), nullable=True)
origin_row_key = Column(JSON, nullable=True)
origin_user_id = Column(String, ForeignKey("users.id"), nullable=True)
origin_source_language = Column(String, nullable=True) # Detected source language at correction time
created_at = Column(DateTime(timezone=True), default=datetime.now(timezone.utc))
dictionary = relationship("TerminologyDictionary", back_populates="entries")
__table_args__ = (
UniqueConstraint("dictionary_id", "source_term_normalized", name="uq_dict_source_term_norm"),
# Unique constraint updated to include language pair
UniqueConstraint("dictionary_id", "source_term_normalized", "source_language", "target_language", name="uq_dict_source_term_lang"),
Index("idx_dict_entry_lang", "source_language", "target_language"),
Index("idx_dict_has_context", "has_context"), # [NEW] — fast filter for context-only queries
)
```
### TranslationSchedule
```python
@@ -164,6 +177,7 @@ class TranslationRun(Base):
records = relationship("TranslationRecord", back_populates="run", cascade="all, delete-orphan")
batches = relationship("TranslationBatch", back_populates="run", cascade="all, delete-orphan")
events = relationship("TranslationEvent", back_populates="run")
language_stats = relationship("TranslationRunLanguageStats", back_populates="run", cascade="all, delete-orphan") # [NEW]
```
### TranslationBatch
@@ -216,12 +230,84 @@ class TranslationRecord(Base):
run = relationship("TranslationRun", back_populates="records")
batch = relationship("TranslationBatch", back_populates="records")
languages = relationship("TranslationLanguage", back_populates="record", cascade="all, delete-orphan") # [NEW]
__table_args__ = (
Index("idx_record_key_hash", "key_hash"),
Index("idx_record_run_key", "run_id", "key_hash"),
)
```
### TranslationLanguage (NEW — per-language translation storage)
```python
class TranslationLanguage(Base):
"""Stores per-language translation for a TranslationRecord.
One row per (record_id, language_code). This replaces direct language fields on TranslationRecord."""
__tablename__ = "translation_languages"
id = Column(String, primary_key=True, default=generate_uuid)
record_id = Column(String, ForeignKey("translation_records.id"), nullable=False)
language_code = Column(String, nullable=False) # BCP-47 tag, e.g., "ru", "en", "de"
source_language_detected = Column(String, nullable=True) # BCP-47 or "und" — auto-detected per row [NEW]
translated_value = Column(Text, nullable=True)
user_edit = Column(Text, nullable=True)
final_value = Column(Text, nullable=True)
status = Column(String, default="pending") # pending|translated|approved|edited|rejected|failed|skipped
error_message = Column(Text, nullable=True)
created_at = Column(DateTime(timezone=True), default=datetime.now(timezone.utc))
record = relationship("TranslationRecord", back_populates="languages")
__table_args__ = (
UniqueConstraint("record_id", "language_code", name="uq_record_language"),
Index("idx_tl_language", "language_code"),
Index("idx_tl_record_lang", "record_id", "language_code"),
)
```
### TranslationPreviewLanguage (NEW — per-language preview results)
```python
class TranslationPreviewLanguage(Base):
"""Stores per-language translation for a preview record."""
__tablename__ = "translation_preview_languages"
id = Column(String, primary_key=True, default=generate_uuid)
preview_record_id = Column(String, ForeignKey("translation_preview_records.id"), nullable=False)
language_code = Column(String, nullable=False) # BCP-47 tag
source_language_detected = Column(String, nullable=True) # BCP-47 or "und"
translated_value = Column(Text, nullable=True)
user_edit = Column(Text, nullable=True)
final_value = Column(Text, nullable=True)
status = Column(String, default="pending") # pending|approved|edited|rejected
created_at = Column(DateTime(timezone=True), default=datetime.now(timezone.utc))
preview_record = relationship("TranslationPreviewRecord", back_populates="languages")
__table_args__ = (
UniqueConstraint("preview_record_id", "language_code", name="uq_preview_record_language"),
)
```
### TranslationRunLanguageStats (NEW — per-language run statistics)
```python
class TranslationRunLanguageStats(Base):
"""Per-language statistics for a translation run."""
__tablename__ = "translation_run_language_stats"
id = Column(String, primary_key=True, default=generate_uuid)
run_id = Column(String, ForeignKey("translation_runs.id"), nullable=False)
language_code = Column(String, nullable=False) # BCP-47 tag
total_rows = Column(Integer, default=0)
translated_rows = Column(Integer, default=0)
failed_rows = Column(Integer, default=0)
skipped_rows = Column(Integer, default=0)
token_count = Column(Integer, default=0)
estimated_cost = Column(Float, default=0.0)
run = relationship("TranslationRun", back_populates="language_stats")
__table_args__ = (
UniqueConstraint("run_id", "language_code", name="uq_run_language"),
Index("idx_rls_run", "run_id"),
)
```
### TranslationPreviewSession
```python
@@ -259,6 +345,7 @@ class TranslationPreviewRecord(Base):
status = Column(String, default="pending") # pending|approved|edited|rejected
session = relationship("TranslationPreviewSession", back_populates="rows")
languages = relationship("TranslationPreviewLanguage", back_populates="preview_record", cascade="all, delete-orphan") # [NEW]
```
### TranslationEvent
@@ -294,6 +381,7 @@ class MetricSnapshot(Base):
snapshot_date = Column(Date, nullable=False)
cumulative_tokens = Column(Integer, default=0)
cumulative_cost = Column(Float, default=0.0)
per_language_metrics = Column(JSON, nullable=True) # [NEW] { "ru": { "tokens": N, "cost": M }, ... }
covers_events_before = Column(DateTime(timezone=True), nullable=False) # Cutoff for event coverage
total_runs = Column(Integer, default=0)
success_runs = Column(Integer, default=0)
@@ -310,11 +398,18 @@ class MetricSnapshot(Base):
## 2. Pydantic Schemas (API DTOs)
Key changes from original:
- `TranslateJobCreate`/`Update`: added `source_key_cols`, `target_key_cols` (explicit mapping), `source_language`
- `TermCorrectionSubmit`: added `source_term`, `incorrect_target_term`, `corrected_target_term`
- `TranslateJobCreate`/`Update`: added `source_key_cols`, `target_key_cols` (explicit mapping), `source_language` → replaced by `target_languages: list[str]` [UPDATED 2026-05-14]
- `TermCorrectionSubmit`: added `source_term`, `incorrect_target_term`, `corrected_target_term`, `source_language` (auto-filled from detected), `target_language` [UPDATED]
- `ScheduleConfig`: added `timezone`
- `TranslationRunResponse`: split into `translation_status` + `insert_status`, added Superset execution fields
- `TranslationRunResponse`: split into `translation_status` + `insert_status`, added Superset execution fields, added `language_stats: list[LanguageStatsResponse]` [NEW]
- Added: `TranslationBatchResponse`, `PreviewSessionResponse`, `MetricsSnapshotResponse`
- Added: `TranslationLanguageResponse` [NEW] — wraps language_code, detected_source_language, translated_value, user_edit, final_value, status
- Added: `TranslationPreviewLanguageResponse` [NEW] — same structure for preview records
- Added: `BulkFindReplaceRequest` [NEW] — find_pattern (string, regex flag), replacement_text, target_language
- Added: `InlineCorrectionSubmit` [NEW] — record_id, language_code, source_term, corrected_term, dictionary_id, source_language
- Added: `CorrectionSubmitWithContext` [NEW] — extends InlineCorrectionSubmit with `context_data` (optional JSON override), `usage_notes` (optional text), `keep_context` (boolean, default=true — false means strip auto-captured context)
- Updated: `DictionaryEntryResponse` [UPDATED] — added `context_data`, `usage_notes`, `has_context`, `context_source` fields
- Updated: `BulkFindReplaceRequest` [UPDATED] — added `submit_to_dictionary_with_context` (boolean, default=false), `bulk_usage_notes` (optional text applied to all entries)
## 3. Entity Relationship Summary
@@ -323,12 +418,17 @@ TranslationJob (1) ──< (N) TranslationJobDictionary >── (1) TerminologyD
TranslationJob (1) ──< (N) TranslationRun
TranslationJob (1) ──< (N) TranslationPreviewSession
TranslationJob (1) ──── (0..1) TranslationSchedule
TranslationRun (1) ──< (N) TranslationRunLanguageStats [NEW]
TranslationRun (1) ──< (N) TranslationBatch
TranslationRun (1) ──< (N) TranslationRecord
TranslationRun (1) ──< (N) TranslationEvent (nullable run_id for pre-run events)
TranslationBatch (1) ──< (N) TranslationRecord
TranslationRecord (1) ──< (N) TranslationLanguage [NEW — replaces direct language fields on Record]
TranslationPreviewSession (1) ──< (N) TranslationPreviewRecord
TranslationPreviewRecord (1) ──< (N) TranslationPreviewLanguage [NEW]
TerminologyDictionary (1) ──< (N) DictionaryEntry
```
All UUID PKs, timezone-aware UTC timestamps, JSON columns for dynamic data, hash columns for efficient comparison.
**Key change [2026-05-14]**: `TranslationRecord` no longer holds `source_text`, `llm_translation`, `user_edit`, `final_value`, `status` directly for language-specific fields. Instead, it holds source-level data (source_text, context_data, key_values, key_hash), and each `TranslationLanguage` entry holds per-language data (translated_value, user_edit, final_value, status, detected_source_language).

View File

@@ -0,0 +1,158 @@
# MCP Axiom Tools — Experience Report
**Author**: Kilo Code (Speckit Workflow Specialist)
**Date**: 2026-05-14
**Context**: Full speckit lifecycle on `028-llm-datasource-supeset` — specification, planning, contract audits, semantic validation.
## Codebase Scale (at time of writing)
```
$ cloc backend/src frontend/src --exclude-dir=__pycache__,node_modules
Language files blank comment code
──────────────────────────────────────────────────────────────────────────────
Python 338 7945 15602 48886
Svelte 114 2419 1770 30155
JavaScript 80 1442 2296 8154
JSON 43 0 0 4268
TypeScript 8 38 149 280
Markdown 2 5 0 25
HTML 1 0 0 13
CSS 1 0 0 3
SVG 1 0 0 1
──────────────────────────────────────────────────────────────────────────────
SUM: 588 11849 19817 91785
```
- **Total source lines**: 91,785 (excluding blank lines and comments)
- **Total with comments+blanks**: 123,451
- **Python**: 48,886 lines (53%) — core backend (FastAPI, SQLAlchemy, services)
- **Svelte**: 30,155 lines (33%) — UI components + pages
- **JavaScript**: 8,154 lines (9%) — API clients, stores, utilities
- **Semantic contracts indexed**: 2,521 (across 548 files)
- **Semantic edges (relations)**: 1,984
- **Test files**: 100+ (pytest + vitest)
This is a mid-size Python+Svelte monorepo. The index size (2521 contracts) is large enough that index rebuild is non-trivial (>120s), which directly impacts issue #1 (stale index) and issue #5 (audit timeout).
---
## 1. Semantic Index — Staleness & Caching Issues
### Problem
After editing contracts/modules.md (changing `@COMPLEXITY` values, removing tags, converting anchor formats), `axiom_semantic_validation audit_contracts` continued reporting OLD data for several iterations.
The audit claimed `@TIER` violations on contracts that had already been fixed, and the reported line numbers didn't match the actual file content.
### Root Cause
The semantic index was stale. `axiom_semantic_index reindex` timed out (>120s). Only `axiom_semantic_index rebuild --rebuild_mode full` with a longer window actually refreshed the data — and even that sometimes returned cached results.
### Recommendation
- Index rebuild should be faster, or work incrementally
- `audit_contracts` should clearly indicate staleness: "Results based on index from N minutes ago" vs "Results based on live file scan"
- Add a `--live` flag to `audit_contracts` that reads files directly instead of relying on index
---
## 2. `@TIER` vs `@COMPLEXITY` Parser Confusion
### Problem
The audit tool reported `"Unsupported @TIER value: TIER_1"` for contracts that used `@COMPLEXITY 1` (not `@TIER`). The actual file had zero `@TIER` occurrences.
### Root Cause
The `[DEF:...]` anchor parser appears to map `@COMPLEXITY 1` — or possibly the number 1 after a tag — to an internal "TIER" representation. The error message leaks an internal enum name (`TIER_1`) that should not be user-visible. Additionally, some legacy `[DEF:]` contracts without inline `[C:N]` notation were being parsed differently from `## @{` format.
Observation: Converting contracts from `[DEF:]` to `## @{` doc format with inline `[C:N]` resolved the `@TIER` errors for those contracts. This suggests the `[DEF:]` parser path has a bug.
### Recommendation
- Fix `[DEF:]` parser: `@COMPLEXITY N` should map to complexity N, not "TIER_N"
- Error message should say: `"Invalid complexity level"` or `"@COMPLEXITY value must be 1-5"`, not leak `TIER_1`
- Add explicit test: `[DEF:Test:Module] @COMPLEXITY 1 [/DEF:Test:Module]` should NOT produce a TIER error
---
## 3. Audit Path Scoping — `file_path` Not Honored?
### Problem
When running `audit_contracts --file_path specs/028-llm-datasource-supeset/contracts/modules.md`, the tool returned warnings for contracts from `backend/src/core/utils/fileio.py` and `frontend/src/components/TaskLogViewer.svelte` — files OUTSIDE the requested path. The warnings were attributed to the requested file path (contracts/modules.md) but actually originated from other files.
### Root Cause
The `file_path` filter appears to match by contract ID, not by file location. If a contract in the index has the same ID as one outside the requested path, the outside file's issues are reported under the wrong filename.
### Recommendation
- `file_path` should be a HARD filter: only return issues from files whose path matches the prefix
- Cross-reference: check if the warning's file_path actually contains the contract_id, not just match by ID
- Add `--exact_path` flag for stricter filtering
---
## 4. `describe_tags` — Missing Tag Catalog
### Problem
`axiom_semantic_discovery describe_tags` would have been useful for resolving the `@TIER` vs `@COMPLEXITY` confusion, but I couldn't use it because I didn't know it existed until too late. The tag catalog should include `@COMPLEXITY` with its valid values (1-5) and a note that `@TIER` is not valid.
### Recommendation
- `describe_tags` should include ALL GRACE-Poly tags with their valid values, complexity requirements, and common mistakes (like `@TIER`)
- Add an "alias" field: e.g., `@PURPOSE` and `@BRIEF` are aliases
- Show forbidden tags per complexity level
---
## 5. `audit_belief_protocol` — Always Timed Out
### Problem
Every attempt to run `audit_belief_protocol` or `axiom_semantic_index rebuild` timed out after 120 seconds. This prevented a full compliance sweep. Only `reindex` worked (fast) but returned stale data.
### Recommendation
- Increase default MCP timeout for audit operations (they scan many files)
- Add progress reporting: `"Scanning file 47/548..."` so the agent knows it's working
- Consider async audit with result polling
---
## 6. `[DEF:]` Format Deprecation — Mixed Signals
### Problem
The semantics-core skill says `[DEF:]` is "permanently recognized for backward compatibility" and `#region` is "recommended for new code." But the audit tool penalizes `[DEF:]` contracts with parser errors (`@TIER` confusion). The skill documentation and the tool behavior are contradictory.
### Recommendation
- Either fully deprecate `[DEF:]` (with a clear migration guide) or fix the parser to handle it correctly
- Add a linter warning: `"[DEF:] format is deprecated. Use #region or ## @{ instead."` instead of cryptic `@TIER` errors
- The `axiom_contract_refactor` tool should have a `migrate_def_to_region` operation
---
## 7. `task_context` — Good, But Limited
### Problem
`axiom_semantic_context task_context` was useful for getting contract neighborhoods, but the `limit` parameter (max results) and missing pagination meant I couldn't get a full view of complex dependency graphs for C5 contracts like `TranslationOrchestrator`.
### Recommendation
- Add pagination or `offset` to task_context results
- Add `--depth` parameter to control graph traversal depth separately from result count
- Show "X more edges truncated..." when limit is hit
---
## 8. `workspace_health` — Would Have Been Useful
I didn't try `axiom_semantic_context workspace_health` early enough. For a 2528-contract workspace, it would have helped identify orphan contracts and broken relation targets before I started auditing.
### Recommendation
- Speckit pre-flight should automatically run `workspace_health` and surface critical issues
- Add a `--fix` flag that automatically patches simple issues (e.g., unclosed anchors)
---
## Summary
| # | Issue | Severity | Fix Priority |
|---|-------|----------|-------------|
| 1 | Stale index after edits | High | P0 |
| 2 | `@TIER` parser bug in `[DEF:]` path | High | P0 |
| 3 | `file_path` filter leaks cross-file issues | Medium | P1 |
| 4 | Missing tag catalog / validation docs | Low | P2 |
| 5 | Audit operations timeout | High | P0 |
| 6 | `[DEF:]` → Parser mismatch | Medium | P1 |
| 7 | task_context pagination | Low | P2 |
| 8 | workspace_health not automated | Low | P2 |

View File

@@ -1,14 +1,14 @@
# Implementation Plan: LLM Table Translation Service
**Branch**: `028-llm-datasource-supeset` | **Date**: 2026-05-08 | **Spec**: [spec.md](./spec.md)
**Input**: Feature specification from `/specs/028-llm-datasource-supeset/spec.md`
**Input**: Feature specification from `/specs/028-llm-datasource-supeset/spec.md` (updated 2026-05-14 with multi-language & correction optimization)
## Summary
Implement an LLM-powered table translation service as a new backend plugin (`TranslationPlugin`) with companion API routes, ORM models, Pydantic schemas, and Svelte frontend components. The service reads rows from a Superset datasource (translation column + context columns), sends batches to a configured LLM provider with optional per-batch filtered terminology dictionary context, generates safe PostgreSQL INSERT/UPSERT SQL, and submits it to Superset via `/api/v1/sqllab/execute/` with status polling and reference recording. Supporting capabilities: terminology dictionary CRUD with language validation, translation preview as persistent quality gate, scheduled execution via APScheduler (new-key-only with baseline_expired fallback), structured event logging with MetricSnapshot persistence, RBAC-gated access control matrix, and 90-day retention with metric continuity.
Implement an LLM-powered table translation service as a new backend plugin (`TranslationPlugin`) with companion API routes, ORM models, Pydantic schemas, and Svelte frontend components. The service reads rows from a Superset datasource (translation column + context columns), auto-detects source language per row via LLM, sends batches to a configured LLM provider for ALL target languages simultaneously (multi-target in single structured JSON response), with optional per-batch filtered multilingual terminology dictionary context (language-pair-aware). Generates safe PostgreSQL INSERT/UPSERT SQL, submits to Superset via `/api/v1/sqllab/execute/` with status polling. Supporting capabilities: multilingual terminology dictionaries (source_language + target_language per entry), configurable preview (1-100 rows), inline correction from any run result with dictionary submission, bulk find-and-replace, scheduled execution via APScheduler (new-key-only with baseline_expired fallback), structured event logging with per-language MetricSnapshot persistence, RBAC-gated access control, and 90-day retention with metric continuity.
**Total planned modules**: 25 contracts across backend (Python/FastAPI) and frontend (Svelte 5/SvelteKit).
**Complexity distribution**: 2× C5 (orchestrator, event-log), 8× C4 (preview, execute, dictionary, scheduler, SQL-gen, routes), 10× C3 (models, schemas, components), 5× C2 (helpers).
**Total planned modules**: 31 contracts across backend (Python/FastAPI) and frontend (Svelte 5/SvelteKit).
**Complexity distribution**: 2× C5 (orchestrator, event-log), 9× C4 (preview, execute, dictionary, scheduler, SQL-gen, routes, correction), 13× C3 (models, schemas, components, services), 7× C2 (helpers, new models).
## Technical Context
@@ -33,7 +33,7 @@ Implement an LLM-powered table translation service as a new backend plugin (`Tra
| **III. Test-First** | PASS | Acceptance criteria per user story defined in spec; test strategy in research.md covers unit (pytest), integration (API + DB), and component (vitest + Svelte Testing Library) layers. |
| **IV. RBAC & Security** | PASS | Granular permissions (`translate.job.*`, `translate.dictionary.*`, `translate.schedule.manage`, `translate.history.view`) via existing RBAC model. API keys encrypted via `EncryptionManager`. PII masking for LLM-facing context per existing patterns. |
| **V. Observability & Retention** | PASS | Structured Translation Events (FR-046), per-job metrics (FR-047), notification integration (FR-048), 90-day retention with pruning (FR-049). C4+ flows instrumented with `belief_scope`/`reason`/`reflect`/`explore` markers. |
| **Semantic Protocol Compliance** | PASS | All planned modules assigned `@COMPLEXITY` 25 with appropriate tag density. `@RATIONALE`/`@REJECTED` reserved for C5 contracts only. Canonical `@RELATION PREDICATE -> TARGET_ID` syntax. Comment-anchor syntax: `# [DEF:...]` for Python, `<!-- [DEF:...] -->` for Svelte markup. |
| **Semantic Protocol Compliance** | PASS | All planned modules assigned `@COMPLEXITY` 15 with appropriate tag density per GRACE complexity scale (C1: anchor only, C2: +BRIEF, C3: +RELATION, C4: +PRE/POST/SIDE_EFFECT, C5: +DATA_CONTRACT/INVARIANT/RATIONALE/REJECTED). `@RATIONALE`/`@REJECTED` reserved for C5 contracts only. Canonical `@RELATION PREDICATE -> TARGET_ID` syntax. Anchor format: `#region`/`#endregion` for Python, `<!-- #region -->`/`<!-- #endregion -->` for Svelte markup. |
| **Fractal Limit (INV_7)** | PASS | Planned modules kept under 400 lines each. No planned contract exceeds 150 lines or CC>10. Decomposition strategy documented in contracts/modules.md. |
**Gate Result**: ✅ ALL PASS — no blocking constitutional or semantic conflicts.
@@ -120,12 +120,12 @@ frontend/
## Semantic Contract Guidance
- All planned contracts follow GRACE-Poly v2.4 protocol with `[DEF:id:Type]...[/DEF:id:Type]` anchors.
- Python backend uses `# [DEF:...]` comment-anchor syntax.
- Svelte components use `<!-- [DEF:...] -->` in markup and `// [DEF:...]` in script blocks.
- Complexity assignments and required tag coverage detailed in `contracts/modules.md`.
- All planned contracts follow GRACE-Poly v2.6 protocol with `#region`/`#endregion` anchors (or `[DEF:...]`/`[/DEF:...]` for backward compatibility in docs).
- Python backend uses `# #region`/`# #endregion` comment-anchor syntax.
- Svelte components use `<!-- #region -->`/`<!-- #endregion -->` in markup and `// #region`/`// #endregion` in script blocks.
- Complexity assignments follow strict tag density per GRACE complexity scale (detailed in `contracts/modules.md`).
- C4+ Python modules instrumented with `belief_scope()` + `reason()`/`reflect()`/`explore()` markers.
- C5 contracts carry `@RATIONALE` and `@REJECTED` decision memory.
- C5 contracts carry `@RATIONALE` and `@REJECTED` decision memory (C1-C4 MUST NOT have these tags).
- `@RELATION` targets reference existing contracts where applicable (e.g., `[LLMProviderService:Module]`, `[SchedulerService:Class]`, `[SupersetClient:Module]`, `[NotificationService:Module]`).
## Complexity Tracking
@@ -134,10 +134,18 @@ No constitutional violations detected. All complexity assignments are justified
| Contract | Complexity | Justification |
|----------|-----------|---------------|
| `TranslationOrchestrator` | C5 | Stateful lifecycle with PRE/POST, multi-step coordination, decision memory for retry/concurrency policies |
| `TranslationScheduler` | C4 | Stateful schedule CRUD with APScheduler integration, conflict detection; no decision memory needed |
| `TranslationOrchestrator` | C5 | Stateful lifecycle with PRE/POST, multi-step coordination, decision memory for retry/concurrency policies; updated for multi-target orchestration |
| `TranslationScheduler` | C4 | Stateful schedule CRUD with APScheduler integration, conflict detection |
| `TranslationEventLog` | C5 | Immutable event log with retention enforcement, audit invariants, decision memory for pruning strategy |
| `TranslationPreview` | C4 | Stateful preview with LLM calls, approve/edit/reject lifecycle |
| `TranslationExecutor` | C4 | Batch execution with retry, INSERT generation, progress tracking |
| `DictionaryManager` | C4 | CRUD with import/export, per-batch filtering, conflict resolution |
| Svelte components | C3 | UI state machines with UX_STATE/FEEDBACK/RECOVERY/REACTIVITY bindings |
| `TranslationPreview` | C4 | Stateful multi-language preview with LLM calls, configurable sample size, approve/edit/reject lifecycle |
| `TranslationExecutor` | C4 | Multi-language batch execution, per-language TranslationLanguage entries, per-language statistics |
| `DictionaryManager` | C4 | Multilingual CRUD with import/export, language-pair-aware batch filtering, conflict resolution |
| `InlineCorrectionService` | C3 | Inline correction with dictionary submission, origin tracking |
| `BulkFindReplaceService` | C3 | Find-and-replace across run results, regex support, multi-language awareness |
| `TranslationLanguage` | C1 | DTO for per-language translation storage |
| `TranslationRunLanguageStats` | C1 | DTO for per-language statistics aggregation |
| `CorrectionCell` (Svelte) | C3 | Inline editable cell with submit-to-dictionary UX state machine |
| `BulkReplaceModal` (Svelte) | C3 | Find-and-replace modal with preview table |
| `ContextAwarePromptBuilder` | C3 | Context-aware dictionary prompt construction with Jaccard similarity, priority flagging, context truncation |
| `InlineCorrectionService` (extended) | C3 | Context capture from source rows, context editing, usage notes, context_source tagging |
| Other Svelte components | C3 | Existing components updated with per-language columns, configurable preview, context display |

View File

@@ -28,11 +28,23 @@
- Q: How are cumulative metrics preserved beyond the 90-day event/record retention window? → A: A metric snapshot is persisted at pruning time, capturing cumulative token count, cost, and run counts. The metrics dashboard reads from both live events and snapshots.
- Q: What happens when an in-progress run exists and the job configuration is edited? → A: In-progress runs are NOT invalidated. They continue using their config snapshot taken at run start. Configuration changes apply to future runs only (snapshot isolation).
### Session 2026-05-14 (multi-language & correction optimization)
- Q: Should source language be auto-detected or user-specified? → A: Auto-detected per row via LLM. The LLM returns `detected_source_language` (BCP-47 tag) alongside each translation in the structured JSON response. If confidence is low, returns `"und"` (undetermined). The user should never have to specify source language manually.
- Q: Should translation support multiple target languages in one run? → A: Yes. Each job supports N target languages. The LLM receives source text once per batch and returns translations for ALL target languages in a single structured response. Avoids N separate LLM calls.
- Q: Should the source language itself be available as a target? → A: Yes. If the user includes the source language in `target_languages`, the "translation" for that language is the original source text verbatim — a verified reference copy.
- Q: How should the data model change for multi-language storage? → A: A new `TranslationLanguage` model links a `TranslationRecord` to a specific language code, status, and translated value. One record has N `TranslationLanguage` entries — one per target language. `TranslationRun` tracks per-language statistics.
- Q: How should dictionaries become multilingual? → A: Each `DictionaryEntry` gets explicit `source_language` + `target_language` (both BCP-47). Unique constraint: `(dictionary_id, source_term_normalized, source_language, target_language)`. Dictionary itself loses the single `target_language` — entries carry language pairs.
- Q: How should per-batch dictionary filtering work with languages? → A: Filtering considers language pairs: only entries whose `source_language` matches the row's detected source language AND `target_language` matches the prompt's target language are included.
- Q: How can the correction workflow be improved beyond the current preview-only approach? → A: (1) Inline editing on ANY completed run result, not just preview. (2) "Submit to Dictionary" from any edited cell. (3) Configurable preview sample size (1-100, default 10) with cost warning at >30. (4) Bulk Find & Replace mode with regex support.
- Q: Should preview edits carry forward to full execution? → A: Yes. If a row was edited in preview, the edited value is used in the full run for that specific row. This is an optimization, not a guarantee — if source data changes between preview and execution, the system uses the newly fetched value and discards preview edit with a log warning.
- Q: Should the correction workflow preserve the source row's context in the dictionary entry? → A: Yes. When a user submits a correction from a run result, the source row's context column values are automatically captured and stored on the DictionaryEntry as `context_data` (JSON). The user can edit this context, add free-form `usage_notes`, or remove the context entirely. This ensures translations are meaningful outside their original row and helps the LLM apply the correct term variant in future runs based on context similarity.
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Configure a translation job from a Superset datasource (Priority: P1)
### User Story 1 — Multi-Language Translation Job Configuration with Auto-Detection (Priority: P1)
An analytics engineer or localization specialist selects a Superset datasource, picks the column whose values need translation, optionally selects context columns that help the LLM understand the meaning, specifies one or more key columns that uniquely identify rows for target-table insertion (with explicit source-to-target key column mapping), and designates the target insertable physical table and column where translated values will be written.
A localization specialist creates a translation job. The source language is auto-detected per row by the LLM — the user never specifies it. The user selects one or more target languages (including possibly the source language as a reference copy). The system translates to all target languages simultaneously.
**Why this priority**: Without a correctly configured translation job, no data can flow from source to target. Configuration is the critical prerequisite that gates all downstream value.
@@ -40,11 +52,13 @@ An analytics engineer or localization specialist selects a Superset datasource,
**Acceptance Scenarios**:
1. **Given** the user opens the translation configuration interface, **When** they select a Superset datasource, **Then** the system displays available columns with their types and allows the user to designate one translation column, zero or more context columns, and at least one key column with explicit mapping to target key columns.
2. **Given** the user selects columns from the datasource, **When** they specify a target table and target column name, **Then** the system validates that the mapped key columns exist in both the source datasource schema and the target table schema.
3. **Given** the user configures multiple key columns (composite key), **When** the configuration is saved, **Then** the system stores the composite key definition with source→target column mapping and uses it for matching rows during INSERT generation.
4. **Given** the user attempts to save a configuration with no translation column selected, **When** save is triggered, **Then** the system blocks the action and highlights the missing required field.
5. **Given** the user selects a translation column and context columns, **When** the datasource has computed or virtual columns, **Then** the system distinguishes physical columns from virtual columns and warns if a virtual column is selected as a key column (virtual columns as translation/context columns are allowed if Superset can query them).
1. **Given** the user opens the translation configuration interface, **When** they select a Superset datasource, **Then** the system displays available columns and allows the user to designate one translation column, zero or more context columns, at least one key column — and select multiple target languages via multi-select.
2. **Given** the user selects target languages (e.g., ru, en, de), **When** the configuration is saved, **Then** the system stores `target_languages: list[str]` and the source language is auto-detected per row during preview/execution (no manual source language field).
3. **Given** the user includes the source language in target_languages (e.g., source is French, targets include French), **When** preview runs, **Then** the "translation" for that language column contains the original source text verbatim (verified reference copy).
4. **Given** the user selects columns from the datasource, **When** they specify a target table and target column name, **Then** the system validates that the mapped key columns exist in both the source datasource schema and the target table schema.
5. **Given** the user configures multiple key columns (composite key), **When** the configuration is saved, **Then** the system stores the composite key definition with source→target column mapping and uses it for matching rows during INSERT generation.
6. **Given** the user attempts to save a configuration with no translation column selected, **When** save is triggered, **Then** the system blocks the action and highlights the missing required field.
7. **Given** the user selects a translation column and context columns, **When** the datasource has computed or virtual columns, **Then** the system distinguishes physical columns from virtual columns and warns if a virtual column is selected as a key column.
---
@@ -121,7 +135,66 @@ A localization specialist or domain expert creates a terminology dictionary cont
---
### User Story 6 - Correct translations and feed back into the dictionary (Priority: P3)
### User Story 6 — Auto-Detected Source Language (Priority: P1) [UPDATED]
The system auto-detects the source language of each row during preview and execution via the LLM. The user never needs to specify "what language is this?" — the LLM returns `detected_source_language` (BCP-47 tag) alongside each translation in the structured JSON response.
**Why this priority**: Auto-detection is a core UX improvement that eliminates a manual configuration step and enables the multi-target workflow (US1). It also makes dictionary filtering language-aware.
**Independent Test**: Upload source rows in French. Run preview. Verify each row shows detected source language "fr". Verify rows in German show "de". Run execution and verify stored records carry detected language.
**Acceptance Scenarios**:
1. **Given** a job with no source_language configured, **When** preview runs, **Then** each row's `detected_source_language` is populated from the LLM response (BCP-47 tag or "und" for uncertain).
2. **Given** a batch contains rows in different source languages, **When** the LLM processes the batch, **Then** each row carries its own detected language independently.
3. **Given** the LLM cannot confidently detect the language (mixed/ambiguous content), **When** the response returns "und", **Then** the row is flagged for manual review in the UI with a warning badge.
4. **Given** a row's detected language is "und", **When** the user manually sets it in the UI, **Then** the override is stored and used for dictionary filtering.
5. **Given** existing jobs with `source_language` configured before this update, **When** the system loads them, **Then** the configured `source_language` is preserved as a fallback hint but per-row auto-detection still runs.
---
### User Story 7 — Multilingual Dictionaries (Priority: P2) [UPDATED]
Dictionaries are fully language-aware. Each entry explicitly declares `source_language``target_language` (both BCP-47). Different language pairs can coexist in the same dictionary. The per-batch filtering algorithm considers language pairs when matching terms.
**Why this priority**: Multilingual dictionaries are a prerequisite for consistent multi-target translation quality. Without language-pair awareness, dictionary entries would apply to wrong language combinations.
**Independent Test**: Create a dictionary with entries: (en→ru), (en→de), (fr→ru). Attach to a job with targets [ru, de]. Run preview with source rows in English and French. Verify en→ru entry only appears in Russian prompt for English rows, not French rows.
**Acceptance Scenarios**:
1. **Given** a dictionary with entries for different language pairs, **When** attached to a multi-target job, **Then** the system filters entries per target language: only entries whose `target_language` matches the current prompt's target language are included.
2. **Given** a dictionary entry with source_language="en", **When** the source row's detected language is "fr", **Then** the entry is NOT included in the prompt for that row (source language mismatch).
3. **Given** two attached dictionaries with overlapping language pairs, **When** prompts are constructed, **Then** entries are merged, deduplicated by (source_term_norm, source_language, target_language), with priority ordering preserved.
4. **Given** an existing single-language dictionary from before this update, **When** the user views it, **Then** entries are migrated: each gets `source_language` from job config (or "und") and `target_language` from dictionary's old `target_language`.
5. **Given** the user creates a new entry in a multilingual dictionary, **When** they save, **Then** both source_language and target_language are stored. The UI defaults from the row's detected language and the target language column.
6. **Given** a migrated dictionary where source_language="und", **When** the user edits any entry, **Then** they are prompted to set the correct source language.
---
### User Story 8 — Improved Correction & Preview Workflow (Priority: P2) [NEW]
Users can edit translated text in-place on ANY completed run result (not just preview), click-to-correct and immediately submit the correction to the dictionary from the same interface. Preview sample size is configurable (1-100, default 10). Bulk Find & Replace allows mass corrections across a run.
**Why this priority**: The correction feedback loop is essential for iterative quality improvement. Making corrections available on all run results (not just preview) and removing the 10-row preview limit directly addresses the primary UX pain point.
**Independent Test**: Complete a full run. Open results. Click on a translated cell, edit the value, click "Submit to Dictionary" — verify the term appears in the dictionary with correct language pair. Verify preview with 50 rows works and shows cost warning.
**Acceptance Scenarios**:
1. **Given** a completed run with results displayed, **When** the user clicks on any translated value, **Then** it becomes inline-editable. After editing, a "Submit to Dictionary" button appears.
2. **Given** the user clicks "Submit to Dictionary", **Then** a popup shows: source term (pre-filled), old translation, new translation, source language (from row's detected language), target language (from column), dictionary selector (filtered by matching language pair).
3. **Given** the correction popup is open, **When** the source row has context columns, **Then** the context values are automatically loaded into `context_data` field (preview, editable, removable). The user can add free-form `usage_notes`.
4. **Given** a correction is submitted with context_data, **When** the entry is saved, **Then** `context_data`, `usage_notes`, and `has_context=True` are stored. The `context_source` is set to `"auto"`, `"auto_with_edits"`, or `"manual"`.
5. **Given** a dictionary entry has context_data, **When** the LLM prompt is constructed for a later batch, **Then** the entry is included with its context annotation. If the incoming row's context columns match the entry's context_data above a similarity threshold, the entry is flagged as `priority_context` in the prompt.
6. **Given** the user wants bulk corrections, **When** they open "Bulk Find & Replace", **Then** they can specify a find pattern (regex or literal), replacement text, target language, preview affected rows, and apply.
7. **Given** the user opens preview, **When** they adjust the sample size, **Then** values 1-100 are accepted (default 10). For >30 rows, a token/cost warning is shown.
8. **Given** a preview session is accepted with edited rows, **When** the full run executes, **Then** the user's preview edits are carried forward as the final translation for those specific rows.
9. **Given** the user submits a correction from a run result, **When** the same source term already exists in the target dictionary with matching language pair, **Then** a conflict dialog offers: overwrite, keep existing, or cancel.
---
### User Story 9 - Correct translations and feed back into the dictionary (Priority: P3) [MOVED]
After a translation run completes, the user reviews the results and notices that a specific word or phrase was translated incorrectly. They select the problematic source term (from the source column value) and the incorrect target translation, provide the correct target translation, and submit it to a chosen terminology dictionary so that future runs use the corrected term. If the same source term already exists in the dictionary, the system asks whether to overwrite or keep the existing entry.
@@ -139,7 +212,7 @@ After a translation run completes, the user reviews the results and notices that
---
### User Story 7 - Schedule translation jobs for periodic execution (Priority: P3)
### User Story 10 - Schedule translation jobs for periodic execution (Priority: P3)
A localization manager configures a translation job to run automatically on a schedule — for example, every Monday at 06:00 Europe/Moscow to translate new product names that appeared during the week. Each scheduled execution creates a new Translation Run with the job's configuration snapshot, generates INSERT SQL, submits it to Superset SQL Lab API, and records the outcome. Manual runs require a preview quality gate; scheduled runs may bypass preview only after the job has passed at least one successful manual run with the same effective configuration.
@@ -200,6 +273,16 @@ A localization manager configures a translation job to run automatically on a sc
→ System MUST record the error in `TranslationRun.insert_error_message` and mark `insert_status = failed`. The translation data remains available for retry or manual inspection.
- How does the system handle SQL identifier injection through user-provided table/column names?
→ System MUST validate table and column identifiers against Superset datasource metadata and quote them using the detected database dialect rules. Raw user-provided identifiers are never interpolated directly into SQL.
- **What happens when a correction is submitted but the source row has no context columns configured?**
`context_data` is stored as `null`, `has_context=False`. The entry works like a traditional term pair. Usage notes can still be added manually.
- **What happens when context_data is very large (many columns, long text)?**
→ Context data is included in the LLM prompt only when `has_context=True`. The system caps context rendering at 500 tokens per entry. If context exceeds this, it's truncated with `… [context truncated]` annotation.
- **How does context-based priority matching work in the prompt?**
→ Simple substring/Jaccard similarity between entry's `context_data` values and the incoming row's context column values. If overlap >50%, the entry is flagged `priority_context`. This is a soft signal to the LLM, not a hard filter — both priority and non-priority matching entries are included, but priority entries are listed first with `# PRIORITY` annotation.
- **What happens when a user edits the auto-captured context?**
`context_source` is set to `"auto_with_edits"`. The original auto-captured context is NOT preserved — only the edited version is stored.
- **What happens when a correction is submitted from Bulk Find & Replace with context?**
→ Each term pair gets its first matching row's context captured automatically. `context_source="bulk"`. If multiple rows match the same term, only the first row's context is stored. The user is warned if context varies significantly across matched rows.
## Requirements *(mandatory)*
@@ -212,33 +295,33 @@ A localization manager configures a translation job to run automatically on a sc
- **FR-005**: The system MUST allow the user to specify a target insertable physical table name and target column name where translated values will be inserted. Views and materialized views are not supported as targets.
- **FR-006**: The system MUST validate that the mapped key columns exist in both the source datasource schema and the target table schema, and are type-compatible.
- **FR-007**: The system MUST support configurable batch sizes for LLM processing to control throughput, token usage, and cost.
- **FR-008**: The system MUST provide a preview mode that fetches a limited sample of source rows, sends them to the LLM with filtered dictionary context, and displays source values, context, and translations side-by-side as a quality gate before full execution.
- **FR-009**: The system MUST allow the user to adjust the LLM translation prompt, target language, and provider settings within the translation job configuration.
- **FR-008**: The system MUST provide a preview mode that fetches a limited sample of source rows (configurable 1-100, default 10), sends them to the LLM with filtered dictionary context, and displays source values, context, detected source language, and per-language translations side-by-side as a quality gate before full execution. [UPDATED — multi-language + configurable sample size]
- **FR-009**: The system MUST allow the user to adjust the LLM translation prompt, target languages, and provider settings within the translation job configuration. [UPDATED — target_languages plural]
- **FR-010**: The system MUST allow the user to mark preview rows as approved, manually edited, or rejected as quality feedback for the preview sample.
- **FR-011**: The system MUST require preview acceptance before allowing full execution. Rejected preview sample rows are excluded from the full run; approved/edited preview sample rows are included. Unseen rows (not in preview sample) are processed normally.
- **FR-011**: The system MUST require preview acceptance before allowing full execution. Rejected preview sample rows are excluded from the full run; approved/edited preview sample rows are included. Preview edits SHALL be carried forward to full execution for the same rows. [UPDATED — carry-forward edits]
- **FR-012**: The system MUST generate safe INSERT/UPSERT SQL for the configured target table and target column, using the dialect detected from the Superset datasource's database connection (supported: PostgreSQL/Greenplum, ClickHouse). Identifier quoting, UPSERT syntax, and value encoding MUST follow dialect-specific rules. Raw user-provided identifiers MUST NOT be interpolated directly.
- **FR-013**: The system MUST submit generated SQL to Superset via `/api/v1/sqllab/execute/`, poll execution status, and record the Superset query reference, execution status, and error details. Generated SQL MAY be exposed for audit/debugging but is not the primary execution mechanism.
- **FR-014**: The system MUST estimate and display token count and approximate cost before executing a full translation batch.
- **FR-014**: The system MUST estimate and display token count and approximate cost before executing a full translation batch. For multi-target jobs, estimate MUST account for all target languages. [UPDATED — multi-target cost estimation]
- **FR-015**: The system MUST handle LLM failures (timeout, rate limit, API error) gracefully by recording the failed batch in TranslationBatch and allowing retry of only the failed rows.
- **FR-016**: The system MUST skip source rows where the translation column value is NULL and log them.
- **FR-017**: The system MUST reject rows where any key column value is NULL during INSERT generation.
- **FR-018**: The system MUST support an UPSERT strategy: `skip_existing` (ON CONFLICT DO NOTHING), `overwrite` (ON CONFLICT DO UPDATE), or `insert` (plain INSERT — user guarantees key uniqueness). The system MUST document that `insert` strategy does not handle duplicates.
- **FR-019**: The system MUST record each translation run with its configuration snapshot (including config_hash), dictionary snapshot, source rows, translations, prompt used, key values, generated SQL, and Superset execution outcome.
- **FR-020**: The system MUST provide a translation history view listing past runs with datasource, target table, row count, translation status, insert status, date, and triggering user.
- **FR-019**: The system MUST record each translation run with its configuration snapshot (including config_hash), dictionary snapshot, source rows, translations (per language), prompt used, key values, generated SQL, and Superset execution outcome. [UPDATED — per-language translations]
- **FR-020**: The system MUST provide a translation history view listing past runs with datasource, target table, per-language row counts, translation status, insert status, date, and triggering user. [UPDATED — per-language counts]
- **FR-021**: The system MUST allow the user to duplicate an existing translation job configuration as a starting point for a new job.
- **FR-022**: The system MUST warn the user if a concurrent run targets the same target table and overlapping key range.
- **FR-023**: The system MUST use snapshot isolation: in-progress runs continue using their config snapshot taken at run start. Configuration changes apply to future runs only and do not invalidate in-progress runs.
- **FR-024**: The system MUST allow users to create, edit, and delete terminology dictionaries, each containing source-term → target-translation pairs.
- **FR-025**: The system MUST allow users to populate a dictionary by manual inline entry, bulk text paste, or file import (CSV, TSV).
- **FR-026**: The system MUST detect duplicate source terms within a dictionary at entry time and require the user to resolve conflicts (overwrite or keep existing) before saving.
- **FR-027**: The system MUST allow users to attach one or more terminology dictionaries to a translation job, with configurable priority ordering (lower priority number = higher precedence). Only dictionaries matching the job's target language are offered for attachment.
- **FR-028**: The system MUST inject the per-batch filtered content of all attached dictionaries into the LLM translation prompt as an authoritative glossary, instructing the LLM to use provided translations for exact matches and to consider them for partial matches.
- **FR-024**: The system MUST allow users to create, edit, and delete terminology dictionaries. Entries carry explicit source_language and target_language (BCP-47). [UPDATED — multilingual entries]
- **FR-025**: The system MUST allow users to populate a dictionary by manual inline entry, bulk text paste, or file import (CSV, TSV). Import format MUST support language columns.
- **FR-026**: The system MUST detect duplicate entries within a dictionary at entry time. Unique constraint: `(dictionary_id, source_term_normalized, source_language, target_language)`. [UPDATED — language-aware dedup]
- **FR-027**: The system MUST allow users to attach one or more terminology dictionaries to a translation job, with configurable priority ordering. Dictionary attachment is NOT filtered by target language (entries carry their own language pairs). [UPDATED — no language filtering at attachment level]
- **FR-028**: The system MUST inject the per-batch filtered content of all attached dictionaries into the LLM translation prompt as an authoritative glossary. Filtering MUST consider language pairs: only entries matching the row's detected source language AND the target language of the current prompt are included. [UPDATED — language-aware filtering]
- **FR-029**: The system MUST snapshot the dictionary content at the start of each translation run so the run uses a consistent dictionary state throughout.
- **FR-030**: The system MUST prevent deletion of dictionaries that are attached to active or scheduled translation jobs.
- **FR-031**: The system MUST allow users to identify a mistranslated term by selecting the source term and its incorrect target translation within a run result, and submit a corrected target translation to a chosen terminology dictionary.
- **FR-032**: The system MUST detect when a submitted correction conflicts with an existing dictionary entry and prompt the user to overwrite or keep the existing entry.
- **FR-033**: The system MUST record the origin of each dictionary entry added via the feedback loop, including source run identifier, source row, submitting user, and timestamp.
- **FR-034**: The system MUST support bulk correction mode where users select multiple incorrectly translated terms and submit them to a dictionary in one atomic operation (all succeed or all fail with conflicts listed).
- **FR-031**: The system MUST allow users to inline-edit any translated value in run results (not just preview) and submit the correction to a dictionary. [UPDATED — inline edit on all results]
- **FR-032**: The system MUST detect when a submitted correction conflicts with an existing dictionary entry for the same language pair and prompt the user to overwrite or keep the existing entry. [UPDATED — language-pair-aware conflict detection]
- **FR-033**: The system MUST record the origin of each dictionary entry added via the feedback loop, including source run identifier, source row, detected source language, target language, submitting user, and timestamp. [UPDATED — language tracking in origin]
- **FR-034**: The system MUST support bulk find & replace mode where users specify a pattern (regex or literal), replacement text, and target language, preview affected rows, and apply corrections in one atomic operation. [UPDATED — from simple bulk selection to find & replace]
- **FR-035**: The system MUST allow users to configure a schedule for a translation job, supporting one-time future execution, interval-based recurrence, and cron-based recurrence with timezone.
- **FR-036**: The system MUST display the next N planned execution times (with timezone) when a schedule is configured, so the user can verify the schedule before enabling it.
- **FR-037**: The system MUST, on each scheduled trigger, create a new Translation Run from the job's configuration snapshot and the current source data state.
@@ -248,12 +331,26 @@ A localization manager configures a translation job to run automatically on a sc
- **FR-041**: The system MUST optionally notify users of scheduled run failures via the existing notification infrastructure.
- **FR-042**: The system MUST warn the user when editing a job configuration that has an active schedule, confirming that the updated configuration will apply to future triggers without affecting in-progress runs.
- **FR-043**: The system MUST enforce granular access control on translation resources through the existing RBAC model (see Access Control Matrix below).
- **FR-044**: The system MUST filter dictionary entries per batch before sending to the LLM: only entries whose source_term appears as a case-insensitive, word-boundary-aware substring in at least one translation-column value within the current batch are included. Dictionaries have no hard size limit.
- **FR-044**: The system MUST filter dictionary entries per batch before sending to the LLM: only entries whose source_term appears as a case-insensitive, word-boundary-aware substring in at least one translation-column value AND whose language pair matches the row-target combination within the current batch are included. Dictionaries have no hard size limit. [UPDATED — language-aware filtering]
- **FR-045**: The system MUST, for scheduled runs, translate only source rows whose key-column values are absent from the most recent run with `insert_status = succeeded` (new-key-only strategy). If that run's key data has been pruned (>90 days), the system falls back to full translation with a `baseline_expired` event.
- **FR-046**: The system MUST emit structured events for every significant lifecycle transition: run started, batch started/completed/failed, run succeeded/partial/failed/cancelled/skipped, schedule triggered/skipped/failed, insert submitted/succeeded/failed. Events MUST be queryable for audit and trend analysis.
- **FR-047**: The system MUST track per-job cumulative metrics: total runs, success/failure ratio, cumulative token usage, cumulative estimated cost, average batch latency. Metrics MUST be exposed in an admin-accessible dashboard. Cumulative metrics MUST be persisted in a metric snapshot table before event pruning to survive the 90-day retention window.
- **FR-047**: The system MUST track per-job cumulative metrics: total runs, success/failure ratio, cumulative token usage per language, cumulative estimated cost per language, average batch latency. Metrics MUST include per-language breakdown. Cumulative metrics MUST be persisted in a metric snapshot table before event pruning. [UPDATED — per-language metrics]
- **FR-048**: The system MUST send a notification via the existing notification infrastructure when a scheduled run fails, including the job name, failure reason, and a link to the failed run details.
- **FR-049**: The system MUST retain detailed translation run data for 90 days. Beyond 90 days, the system MUST persist a metric snapshot (cumulative token count, cost, run counts) and prune detailed data (source row snapshots, TranslationRecord rows, TranslationEvent rows, generated SQL). Run metadata (row count, status, Superset reference) is preserved.
- **FR-049**: The system MUST retain detailed translation run data for 90 days. Beyond 90 days, the system MUST persist a metric snapshot (cumulative token count per language, cost per language, run counts) and prune detailed data. Run metadata is preserved. [UPDATED — per-language snapshots]
- **FR-050** [NEW]: The system MUST auto-detect source language per row via LLM during preview and execution. Returns `detected_source_language` (BCP-47 or "und"). Stored per TranslationLanguage entry.
- **FR-051** [NEW]: The system MUST support multiple target languages per job (`target_languages: list[str]`). LLM receives source once and returns translations for ALL target languages in a single structured JSON response.
- **FR-052** [NEW]: The system MUST allow including the source language in target_languages. When source == target, the "translation" is the original source text verbatim (verified reference copy).
- **FR-053** [NEW]: The data model MUST store each translated value with unambiguous language association. New `TranslationLanguage` model links a `TranslationRecord` to a specific language code, status, translated value, and detected source language.
- **FR-054** [NEW]: Each `DictionaryEntry` MUST have explicit `source_language` and `target_language` (BCP-47, both required). Unique constraint: `(dictionary_id, source_term_normalized, source_language, target_language)`.
- **FR-055** [NEW]: Preview sample size MUST be configurable 1-100 (default 10). For samples >30, a token/cost warning is displayed. Preview MUST show per-language columns with detected source language per row.
- **FR-056** [NEW]: Inline editing MUST be available on ANY completed run result. Click → edit → "Submit to Dictionary" button. Submission pre-fills language pair from row metadata (detected source + target language).
- **FR-057** [NEW]: Bulk Find & Replace MUST support regex or literal pattern matching on translated values, filtered by target language, with preview before apply.
- **FR-058** [NEW]: Preview edits MUST be carried forward to full execution for the same rows (by key_hash). If source data changed between preview and execution, the system uses the new source value and discards the preview edit with a logged warning.
- **FR-059** [NEW]: When a user submits a correction from a run result, the source row's context column values MUST be automatically captured and stored on the `DictionaryEntry` as `context_data` (JSON). The user MAY edit or remove this context before submission.
- **FR-060** [NEW]: The correction popup MUST allow the user to add free-form `usage_notes` (Text) describing when the term translation applies. These notes are stored alongside `context_data` in the `DictionaryEntry`.
- **FR-061** [NEW]: `DictionaryEntry` MUST have: `context_data` (JSON, nullable), `usage_notes` (Text, nullable), `has_context` (Boolean, default=False), `context_source` (String: "auto" | "auto_with_edits" | "manual", nullable).
- **FR-062** [NEW]: The LLM prompt construction MUST include context annotations for dictionary entries that have `has_context=True`. The format: `"source_term" (context: key=value, ...) → "target_term" # Usage: notes`. Entries whose context_data context_data overlaps with the incoming row's context columns MUST be flagged as `priority_context` in the prompt.
- **FR-063** [NEW]: Bulk Find & Replace MUST support "submit-to-dictionary" with context: when enabled, each corrected pair is saved as a DictionaryEntry with `context_source="bulk"`, source row context auto-captured, and `usage_notes` optionally provided once for all entries.
### Access Control Matrix
@@ -278,36 +375,47 @@ A localization manager configures a translation job to run automatically on a sc
### Key Entities *(include if feature involves data)*
- **Translation Job**: A persistent configuration binding a Superset datasource, source→target column mappings (translation, context, key columns), target insertable physical table/column, LLM settings, prompt template, attached dictionaries with priority ordering, and optional schedule.
- **Translation Run**: A single execution (manual or scheduled) with translation_status (pending|running|completed|partial|failed|cancelled|skipped) and insert_status (not_started|submitted|running|succeeded|failed|skipped). Contains config snapshot, dictionary snapshot, config_hash, Superset query reference, statistics.
- **Translation Batch**: A group of TranslationRecord rows processed in one LLM API call. Tracks batch_index, status, row counts, token_count, estimated_cost, latency_ms, error details.
- **Translation Record**: An individual row containing source text, context, key values, key_hash, LLM translation, optional user edit, final INSERT value, and status.
- **Preview Session**: A persistent record of a preview quality gate: job_id, user_id, sample_size, config_hash, dictionary_snapshot_hash, status (pending|accepted|rejected), timestamps.
- **Terminology Dictionary**: A named, language-specific (source_language optional, target_language required) collection of source_term → target_translation pairs with audit origin metadata.
- **Dictionary Entry**: A single term pair within a dictionary, unique per (dictionary_id, source_term). Stores origin metadata (run_id, row_key, user_id, timestamp) for feedback-loop entries.
- **Translation Schedule**: Scheduling configuration: type (cron|interval|once), expression, timezone, enabled state, concurrency policy, next_run_at.
- **Translation Event**: Immutable lifecycle event: run_id (nullable for pre-run events), job_id, event_type, timestamp, type-specific JSON payload.
- **Metric Snapshot**: Persistent cumulative metrics per job, saved at pruning time: job_id, snapshot_date, cumulative_tokens, cumulative_cost, total_runs, success_runs, failed_runs.
- **Translation Job** (extended): `target_languages: list[str]` replaces `target_language: str`. Source language is auto-detected per row (no manual field). Config snapshot now includes target_languages list.
- **Translation Run** (extended): Per-language statistics tracked via `TranslationRunLanguageStats` — translated/failed/skipped counts per language_code.
- **Translation Language** (NEW): Links a `TranslationRecord` to a specific language. Fields: `record_id`, `language_code` (BCP-47), `source_language_detected` (BCP-47 or "und"), `translated_value`, `status` (translated|failed|skipped|approved|edited|rejected), `user_edit`, `final_value`. One TranslationRecord has N TranslationLanguage entries — one per target language.
- **Translation Batch**: Same structure but batches track per-language status in `batch_language_status: JSON`.
- **Translation Record**: An individual row containing source text, context, key values, key_hash. Translations are stored per-language via TranslationLanguage entries (not directly on the record).
- **Preview Session** (extended): Sample size configurable (1-100). Preview records store per-language results via `TranslationPreviewLanguage` (mirrors TranslationLanguage pattern).
- **Terminology Dictionary** (modified): No longer has a single `target_language`. Each entry carries its own language pair. Dictionary `name` serves as organizational label.
- **Dictionary Entry** (extended): `source_language: str`, `target_language: str` added. Unique constraint: `(dictionary_id, source_term_normalized, source_language, target_language)`. Origin metadata includes detected source language and target language. **NEW fields**: `context_data` (JSON — snapshot of source row's context columns), `usage_notes` (Text — free-form user notes), `has_context` (Boolean), `context_source` ("auto"|"auto_with_edits"|"manual"|"bulk").
- **Translation Schedule**: No change — schedule is per-job, language-agnostic.
- **Translation Event** (extended): Language-specific event types added: `batch_language_completed`, `batch_language_failed`.
- **Metric Snapshot** (extended): `per_language_metrics: JSON` added — cumulative tokens, cost, counts per language_code.
## Success Criteria *(mandatory)*
### Measurable Outcomes
- **SC-001**: Users can configure a complete translation job (datasource → columns → keys → target) in under 3 minutes without external documentation.
- **SC-002**: Preview mode returns translation results for a sample of 10 rows within 30 seconds for standard LLM providers.
- **SC-001**: Users can configure a complete multi-language translation job (datasource → columns → keys → target languages) in under 3 minutes without external documentation.
- **SC-002**: Preview mode returns translations for a sample of 10 rows × 3 target languages within 45 seconds for standard LLM providers.
- **SC-003**: 100% of generated SQL for supported dialects (PostgreSQL/Greenplum, ClickHouse) is syntactically valid when tested against validated schemas for each dialect.
- **SC-004**: Users can recover from a failed batch (LLM timeout, rate limit) and retry only the failed rows in under 2 minutes.
- **SC-005**: Translation run audit records contain all required traceability information (configuration snapshot, prompt, source rows, translations, INSERT SQL, Superset execution reference) for 100% of completed runs within the 90-day retention window.
- **SC-005**: Translation run audit records contain all required traceability information (configuration snapshot, prompt, source rows, per-language translations, INSERT SQL, Superset execution reference) for 100% of completed runs within the 90-day retention window.
- **SC-006**: At least 80% of pilot users successfully complete the end-to-end flow (configure → preview → execute → verify) on their first attempt during moderated usability review.
- **SC-007**: NULL translation values are correctly skipped and logged without blocking the remaining rows in 100% of test cases.
- **SC-008**: Domain-specific terms covered by an attached dictionary are translated consistently (exactly matching the dictionary entry) in at least 95% of cases where the source term appears verbatim in the translation column.
- **SC-009**: Users can populate a 50-term dictionary via file import in under 1 minute, with duplicate detection completing in under 5 seconds.
- **SC-010**: Feedback-loop corrections submitted to a dictionary are reflected in the next run of the same job in 100% of cases where the corrected source term reappears.
- **SC-008**: Domain-specific terms covered by an attached dictionary are translated consistently (exactly matching the dictionary entry) in at least 95% of cases where the source term appears verbatim in the translation column and language pairs match.
- **SC-009**: Users can populate a 50-term multilingual dictionary via file import in under 1 minute, with language-aware duplicate detection completing in under 5 seconds.
- **SC-010**: Feedback-loop corrections submitted to a dictionary (with correct language pair) are reflected in the next run of the same job in 100% of cases where the corrected source term reappears.
- **SC-011**: Scheduled translation runs trigger within ±60 seconds of the planned execution time for at least 98% of triggers under normal operating conditions.
- **SC-012**: A scheduled run that overlaps with a still-running previous run is correctly skipped or queued (per the configured policy) in 100% of overlap scenarios.
- **SC-013**: Structured events for 100% of run lifecycle transitions are recorded and queryable within 10 seconds of occurrence.
- **SC-014**: Per-job cumulative metrics remain accurate (±5%) after pruning events older than 90 days, as verified by comparing pre-prune metric snapshots with post-prune dashboard values.
- **SC-014**: Per-job cumulative metrics (including per-language breakdown) remain accurate (±5%) after pruning events older than 90 days, as verified by comparing pre-prune metric snapshots with post-prune dashboard values.
- **SC-015**: Detailed run data is pruned within 24 hours after exceeding the 90-day retention window, with metric snapshots and run metadata preserved intact in 100% of cases.
- **SC-016** [NEW]: Auto-detection of source language is correct in ≥90% of test cases (verified against a labeled multilingual test dataset of 100+ rows).
- **SC-017** [NEW]: Multi-target translation with 3 languages completes in ≤2× the time of single-target (the overhead is prompt construction and response parsing, not N separate LLM calls).
- **SC-018** [NEW]: Per-language statistics match the actual distribution of translated rows within ±0.5% in 100% of test runs.
- **SC-019** [NEW]: Multilingual dictionary filtering correctly includes/excludes entries based on language pair match in 100% of test cases.
- **SC-020** [NEW]: Inline correction from run results → dictionary submission completes in ≤3 clicks and ≤10 seconds for a single correction.
- **SC-021** [NEW]: Bulk Find & Replace across 100 rows with preview completes in ≤5 seconds.
- **SC-022** [NEW]: Migration of existing single-target jobs and dictionaries completes with zero data loss for 100% of test fixtures.
- **SC-023** [NEW]: Context data from source rows is automatically captured in ≥95% of correction submissions (verified by spot-check audit of 50 corrections).
- **SC-024** [NEW]: Dictionary entries with context_data are rendered with context annotations in ≥98% of generated prompts (verified by prompt inspection in test fixtures).
- **SC-025** [NEW]: LLM correctly prioritizes context-matched dictionary entries over non-context entries in ≥80% of ambiguous-term test cases (where the same source term has different translations for different contexts).
## Assumptions
@@ -330,3 +438,13 @@ A localization manager configures a translation job to run automatically on a sc
- Access control for translation resources uses the existing RBAC infrastructure with the permission matrix defined above.
- Snapshot isolation: in-progress runs use their config snapshot; configuration edits affect only future runs.
- Cumulative metrics survive the 90-day retention window via metric snapshots persisted at pruning time.
- [NEW] Source language auto-detection uses the LLM itself — no separate language detection model. Accuracy depends on the LLM's multilingual capabilities.
- [NEW] Multi-target LLM responses add ~50% more output tokens compared to single-target for the same rows with 3 target languages (approximate, varies by LLM).
- [NEW] The LLM provider must support structured JSON output with multiple translation fields per row (one per target language). OpenAI-compatible APIs with response_format=json_object or json_schema are required.
- [NEW] Existing single-target jobs and dictionaries are automatically migrated to the new multi-language format. Migration is one-time and backward-compatible.
- [NEW] Preview edits are carried forward to full execution as an optimization, not a guarantee. If source data changes between preview and execution, the new value is used and the edit is discarded with a logged warning.
- [NEW] Bulk Find & Replace is a text-level string operation (regex/literal match). It does NOT re-translate via LLM.
- [NEW] Context data from corrections is a best-effort capture. If the source row has context columns configured, they are auto-captured. If not, the entry has no context.
- [NEW] Context-based priority matching is a soft signal — the LLM receives both priority and non-priority entries, and makes the final decision. Accuracy improvement is directional, not guaranteed.
- [NEW] Context rendering in prompts is capped at ~500 tokens per entry to avoid exceeding context windows.
- [NEW] Context data values are compared using Jaccard similarity on tokenized text; 50% overlap threshold is a heuristic that may be tuned per deployment.

View File

@@ -198,7 +198,7 @@
---
## Phase 8: User Story 7 — Schedule Translation Jobs (Priority: P3)
## Phase 8: User Story 10 — Schedule Translation Jobs (Priority: P3)
**Goal**: User configures schedule → system triggers runs → new-key-only translation → optional auto-INSERT → failure notification → pause/resume.
@@ -206,22 +206,22 @@
### Backend — Schedule Management + Trigger Dispatch
- [x] T056 [US7] Implement `TranslationScheduler` class in `backend/src/plugins/translate/scheduler.py`: `create_schedule()`, `update_schedule()`, `delete_schedule()`, `enable_schedule()`, `disable_schedule()`, `get_next_executions(schedule, n=3)` (FR-036). Register schedule with existing `SchedulerService` via `add_job()` with cron/interval/date trigger. `@COMPLEXITY 4` — instrument with `belief_scope`/`reason`/`reflect`. (RATIONALE: C4 because schedule management is stateful with APScheduler integration, concurrency policy enforcement, and trigger dispatch side effects)
- [x] T057 [US7] Implement schedule trigger handler: `_execute_scheduled_translation(job_id)`. Enforce concurrency policy: check if previous run for same job is still `running``skip` (log + event) or `queue` (start after previous completes) per FR-039. If proceeding: create new `TranslationRun` with `trigger_type=scheduled`; fetch source rows; apply new-key-only filter (FR-045) — compare current key values against previous successful run's key values; dispatch to `TranslationOrchestrator`. On failure, send notification via `NotificationService` (FR-041, FR-048). Schedule remains enabled for next trigger (US7 acceptance scenario 6).
- [x] T058 [US7] Implement Superset SQL Lab API submission for all runs: create `SupersetSqlLabExecutor` class in `backend/src/plugins/translate/superset_executor.py`. Submit generated SQL to `/api/v1/sqllab/execute/`, poll execution status, update `TranslationRun.insert_status`, `superset_query_id`, `rows_affected`, error fields. For scheduled runs, this happens automatically; for manual runs, this happens on user trigger. Record `insert_submitted`/`insert_succeeded`/`insert_failed` events.
- [x] T059 [US7] Implement schedule endpoints in `backend/src/api/routes/translate.py`: `PUT /api/translate/jobs/{job_id}/schedule` (create/update), `DELETE /api/translate/jobs/{job_id}/schedule` (remove), `POST /api/translate/jobs/{job_id}/schedule/enable` (FR-040), `POST /api/translate/jobs/{job_id}/schedule/disable` (FR-040). Inject `Depends(require_permission("translate.schedule.manage"))`. Add schedule warning when editing job with active schedule (FR-042).
- [x] T060 [US7] Extend `SchedulerService.load_schedules()` in `backend/src/core/scheduler.py` to discover and register active `TranslationSchedule` rows alongside existing backup schedules (R4).
- [x] T056 [US10] Implement `TranslationScheduler` class in `backend/src/plugins/translate/scheduler.py`: `create_schedule()`, `update_schedule()`, `delete_schedule()`, `enable_schedule()`, `disable_schedule()`, `get_next_executions(schedule, n=3)` (FR-036). Register schedule with existing `SchedulerService` via `add_job()` with cron/interval/date trigger. `@COMPLEXITY 4` — instrument with `belief_scope`/`reason`/`reflect`. (RATIONALE: C4 because schedule management is stateful with APScheduler integration, concurrency policy enforcement, and trigger dispatch side effects)
- [x] T057 [US10] Implement schedule trigger handler: `_execute_scheduled_translation(job_id)`. Enforce concurrency policy: check if previous run for same job is still `running``skip` (log + event) or `queue` (start after previous completes) per FR-039. If proceeding: create new `TranslationRun` with `trigger_type=scheduled`; fetch source rows; apply new-key-only filter (FR-045) — compare current key values against previous successful run's key values; dispatch to `TranslationOrchestrator`. On failure, send notification via `NotificationService` (FR-041, FR-048). Schedule remains enabled for next trigger (US10 acceptance scenario 6).
- [x] T058 [US10] Implement Superset SQL Lab API submission for all runs: create `SupersetSqlLabExecutor` class in `backend/src/plugins/translate/superset_executor.py`. Submit generated SQL to `/api/v1/sqllab/execute/`, poll execution status, update `TranslationRun.insert_status`, `superset_query_id`, `rows_affected`, error fields. For scheduled runs, this happens automatically; for manual runs, this happens on user trigger. Record `insert_submitted`/`insert_succeeded`/`insert_failed` events.
- [x] T059 [US10] Implement schedule endpoints in `backend/src/api/routes/translate.py`: `PUT /api/translate/jobs/{job_id}/schedule` (create/update), `DELETE /api/translate/jobs/{job_id}/schedule` (remove), `POST /api/translate/jobs/{job_id}/schedule/enable` (FR-040), `POST /api/translate/jobs/{job_id}/schedule/disable` (FR-040). Inject `Depends(require_permission("translate.schedule.manage"))`. Add schedule warning when editing job with active schedule (FR-042).
- [x] T060 [US10] Extend `SchedulerService.load_schedules()` in `backend/src/core/scheduler.py` to discover and register active `TranslationSchedule` rows alongside existing backup schedules (R4).
### Frontend — Schedule UI
- [x] T061 [P] [US7] Add schedule API methods to `frontend/src/lib/api/translate.js`: `updateSchedule()`, `deleteSchedule()`, `enableSchedule()`, `disableSchedule()`.
- [x] T062 [US7] Create `ScheduleConfig` component in `frontend/src/lib/components/translate/ScheduleConfig.svelte`: type selector (cron/interval/once), cron expression input with validation, interval input, timezone selector, run-at datetime picker, next-3-executions preview (with timezone), concurrency policy selector (skip/queue), enable/disable toggle with status indicator. Warns if no prior successful manual run exists. `@UX_STATE`: idle, editing, validating, enabled, disabled, no_prior_run_warning. `@UX_REACTIVITY`: next execution times `$derived` from schedule config with timezone display.
- [x] T063 [US7] Integrate `ScheduleConfig` into `TranslationJobConfig` page as the "Schedule" tab.
- [x] T061 [P] [US10] Add schedule API methods to `frontend/src/lib/api/translate.js`: `updateSchedule()`, `deleteSchedule()`, `enableSchedule()`, `disableSchedule()`.
- [x] T062 [US10] Create `ScheduleConfig` component in `frontend/src/lib/components/translate/ScheduleConfig.svelte`: type selector (cron/interval/once), cron expression input with validation, interval input, timezone selector, run-at datetime picker, next-3-executions preview (with timezone), concurrency policy selector (skip/queue), enable/disable toggle with status indicator. Warns if no prior successful manual run exists. `@UX_STATE`: idle, editing, validating, enabled, disabled, no_prior_run_warning. `@UX_REACTIVITY`: next execution times `$derived` from schedule config with timezone display.
- [x] T063 [US10] Integrate `ScheduleConfig` into `TranslationJobConfig` page as the "Schedule" tab.
### Verification — US7
### Verification — US10
- [x] T064 [US7] Write pytest tests for scheduler in `backend/src/plugins/translate/__tests__/test_scheduler.py`: test schedule CRUD, cron expression validation, next-N-executions calculation, trigger dispatch with skip/queue concurrency, new-key-only filter (verify only unseen keys processed), auto-INSERT execution, failure notification, pause/resume, load on SchedulerService start.
- [x] T065 [US7] Verify US7 acceptance scenarios against spec User Story 7 (8 scenarios). Run `cd backend && pytest backend/src/plugins/translate/__tests__/test_scheduler.py -v`.
- [x] T064 [US10] Write pytest tests for scheduler in `backend/src/plugins/translate/__tests__/test_scheduler.py`: test schedule CRUD, cron expression validation, next-N-executions calculation, trigger dispatch with skip/queue concurrency, new-key-only filter (verify only unseen keys processed), auto-INSERT execution, failure notification, pause/resume, load on SchedulerService start.
- [x] T065 [US10] Verify US7 acceptance scenarios against spec User Story 7 (8 scenarios). Run `cd backend && pytest backend/src/plugins/translate/__tests__/test_scheduler.py -v`.
**Checkpoint**: Scheduling complete — jobs can run automatically on schedule with new-key-only incremental translation and failure recovery.
@@ -262,8 +262,8 @@
- [ ] T075 [P] Wire scheduled-run failure notification: ensure `TranslationScheduler` trigger handler calls `NotificationService.send()` when a scheduled run fails (FR-041, FR-048). Test with mock notification provider.
- [x] T076 [P] Instrument remaining C4/C5 Python flows with `belief_scope`/`reason`/`reflect`/`explore` markers where missing: `TranslationOrchestrator.start_run()` (entry/exit), `TranslationExecutor.execute_run()` (batch boundaries + error paths), `DictionaryManager` mutation boundaries, `TranslationScheduler` trigger dispatch. Verify via `axiom_semantic_validation` belief-runtime audit.
- [ ] T077 Run full semantic audit via axiom MCP tools:
- `axiom_semantic_validation audit_contracts --file_path backend/src/plugins/translate/` — verify all `[DEF]` anchors are closed, `@RELATION` targets resolve, no orphan contracts, C4+ contracts have required tag density
- `axiom_semantic_validation audit_belief_protocol --file_path backend/src/plugins/translate/` — verify `@RATIONALE`/`@REJECTED` present on all C5 contracts
- `axiom_semantic_validation audit_contracts --file_path backend/src/plugins/translate/` — verify all `#region`/`#endregion` anchors are properly closed, `@RELATION` targets resolve, no orphan contracts, C4+ contracts have required tag density per GRACE complexity scale
- `axiom_semantic_validation audit_belief_protocol --file_path backend/src/plugins/translate/` — verify `@RATIONALE`/`@REJECTED` present on all C5 contracts only (not on C1-C4)
- `axiom_semantic_validation audit_belief_runtime --file_path backend/src/plugins/translate/` — verify `belief_scope`/`reason`/`reflect`/`explore` markers exist in all C4+ module bodies
- `axiom_semantic_validation impact_analysis --contract_id TranslationOrchestrator:Class` — verify no rejected path is accidentally re-enabled
- [ ] T078 Run quickstart validation: follow `specs/028-llm-datasource-supeset/quickstart.md` end-to-end — create dictionary → create job → preview → execute → verify INSERT SQL → submit correction → schedule → view history → verify metrics. Run `cd backend && pytest -v`, `cd frontend && npm run test -- --run`, `cd backend && ruff check src/plugins/translate/ src/api/routes/translate.py src/models/translate.py src/schemas/translate.py`.
@@ -274,6 +274,93 @@
---
## Phase 11: Multi-Language & Correction Optimization (NEW — 2026-05-14)
**Purpose**: Implement multi-language support (auto-detection, multi-target, per-language storage, multilingual dictionaries, improved correction workflow). All tasks below are NEW additions building on the existing single-target foundation.
### Shared Infrastructure (Migration)
- [ ] T083 [P] Add `TranslationLanguage`, `TranslationPreviewLanguage`, `TranslationRunLanguageStats` models to `backend/src/models/translate.py`. Add Alembic migration. Existing `TranslationRecord` gains `languages` relationship; existing `TranslationPreviewRecord` gains `languages` relationship. Foreign keys and cascades.
- [ ] T084 [P] Add `source_language`, `target_language` columns to `DictionaryEntry` in `backend/src/models/translate.py`. Update unique constraint to `(dictionary_id, source_term_normalized, source_language, target_language)`. Add migration.
- [ ] T085 [P] Migrate `TranslationJob.target_language` (str) → `target_languages` (JSON list) in `backend/src/models/translate.py`. Add migration script for existing jobs: `target_languages = [old_target_language]`. Keep `source_language` as optional fallback hint.
- [ ] T086 [P] Update Pydantic schemas in `backend/src/schemas/translate.py`: `TranslateJobCreate` gets `target_languages: list[str]` (required, min 1), `source_language` optional. Add `TranslationLanguageResponse`, `TranslationPreviewLanguageResponse`, `BulkFindReplaceRequest`, `InlineCorrectionSubmit` schemas.
### US6 (NEW) — Auto-Detected Source Language
- [ ] T087 [P] [US6] Update LLM prompt construction in `backend/src/plugins/translate/executor.py` and `backend/src/plugins/translate/preview.py`: instruct LLM to return `detected_source_language` (BCP-47) alongside each row's translations. Parse and store per-row in `TranslationLanguage.source_language_detected`.
- [ ] T088 [P] [US6] Update `TranslationPreview` to display detected source language per row in preview results. Add `detected_source_language` field to preview response. Flag rows with "und" for manual review.
- [ ] T089 [P] [US6] Update `TranslationExecutor` to store detected source language per `TranslationLanguage` entry. Handle "und" (undetermined) rows — mark as flagged, allow manual override.
- [ ] T090 [US6] Write pytest tests for auto-detection: test with known source languages (en, fr, de, es), test with mixed-language content (expect "und"), test manual override of "und" rows.
### US7 (NEW) — Multilingual Dictionaries
- [ ] T091 [P] [US7] Update `DictionaryManager` (`backend/src/plugins/translate/dictionary.py`): add `source_language` and `target_language` to all CRUD operations. Update unique constraint enforcement. Add language-pair filtering to `filter_for_batch()` method.
- [ ] T092 [P] [US7] Update dictionary import (`frontend/src/routes/translate/dictionaries/[id]/+page.svelte`): CSV/TSV import now accepts `source_language`, `target_language` columns (or defaults from UI). Add language pair columns to the entry table. Add filter bar by language pair.
- [ ] T093 [P] [US7] Update `filter_for_batch()` in `backend/src/plugins/translate/dictionary.py`: accept `source_language` and `target_language` parameters. Only return entries where `source_language` matches row's detected language AND `target_language` matches prompt's target language.
- [ ] T094 [P] [US7] Migrate existing single-language dictionaries: each entry gets `source_language = job.source_language or "und"`, `target_language = dictionary.target_language`. Create Alembic migration data script.
- [ ] T095 [US7] Write pytest tests for multilingual dictionaries: test language-pair filtering, test migration of old dictionaries, test unique constraint with language pairs, test import with language columns.
### US1 (UPDATED) — Multi-Language Job Configuration
- [ ] T096 [P] [US1] Update job configuration endpoints in `backend/src/api/routes/translate/`: `POST/GET/PUT` jobs now handle `target_languages` list. Validate min 1 language. Backward-compatible: single-language requests wrapped into list.
- [ ] T097 [US1] Update `TranslationJobConfig` component (`frontend/src/routes/translate/[id]/+page.svelte`): "Target Language" dropdown → multi-select with search. "Include source language as reference" checkbox. Source language field removed.
- [ ] T098 [US1] Update `TranslationJobList` component (`frontend/src/routes/translate/+page.svelte`): display target languages as badges/tags. Update job card layout.
### US2 (UPDATED) — Multi-Language Preview
- [ ] T099 [P] [US2] Update `TranslationPreview` (`backend/src/plugins/translate/preview.py`): send source text to LLM once, request translations for ALL target languages + detected source language. Parse multi-language response. Create `TranslationPreviewLanguage` rows per language.
- [ ] T100 [P] [US2] Update preview endpoints in `backend/src/api/routes/translate/`: accept `sample_size` parameter (1-100, default 10). Return per-language preview data via `TranslationPreviewLanguageResponse`. Include estimated tokens/cost for multi-target.
- [ ] T101 [US2] Update `TranslationPreview` component (`frontend/src/lib/components/translate/TranslationPreview.svelte`): dynamic per-language columns. Sample size slider (1-100) with cost warning at >30. Per-language accept button.
- [ ] T102 [P] [US2] Implement preview edit carry-forward: when preview is accepted with edited rows, store the edited values per-language. On full execution, check for existing edits by `key_hash` + `language_code` and use edited value instead of re-translating.
- [ ] T103 [US2] Write pytest tests for multi-language preview: test 2-language preview, test 5-language preview, test sample size boundary (1, 100), test cost estimation accuracy.
### US3 (UPDATED) — Multi-Language Execution
- [ ] T104 [P] [US3] Update `TranslationExecutor` (`backend/src/plugins/translate/executor.py`): construct multi-language LLM prompt (one prompt requesting translations for ALL target languages). Parse multi-language JSON response. Create `TranslationLanguage` rows per language per record.
- [ ] T105 [P] [US3] Update `TranslationOrchestrator` (`backend/src/plugins/translate/orchestrator.py`): create `TranslationRunLanguageStats` rows per language on run start. Update statistics per language as batches complete. Track per-language token/cost.
- [ ] T106 [P] [US3] Update run status endpoints in `backend/src/api/routes/translate/`: return `language_stats` array in run detail response. Include per-language progress in progress events.
- [ ] T107 [US3] Update run result rendering in `frontend/src/lib/components/translate/TranslationRunResult.svelte`: show per-language statistics section. Display per-language columns in results table.
- [ ] T108 [US3] Write pytest tests for multi-target execution: test 3-language run, test partial failure (one language fails), test single-language backward compatibility, test per-language statistics accuracy.
### US8 (NEW) — Improved Correction & Preview Workflow
- [ ] T109 [P] [US8] Implement inline correction endpoint in `backend/src/api/routes/translate/`: `PUT /api/translate/runs/{run_id}/records/{record_id}/languages/{language_code}` — update `final_value`, record `user_edit`. Optional `submit_to_dictionary` flag with `dictionary_id` parameter.
- [ ] T110 [P] [US8] Implement `POST /api/translate/runs/{run_id}/bulk-replace` endpoint: accepts `BulkFindReplaceRequest` (find_pattern, regex flag, replacement_text, target_language). Returns affected record list. On apply: updates all matching `TranslationLanguage.final_value`; optionally submits to dictionary.
- [ ] T111 [P] [US8] Add `InlineCorrectionService` in `backend/src/plugins/translate/service.py`: handle dictionary submission from correction — validate language pair, detect conflicts, create/update `DictionaryEntry` with origin tracking.
- [ ] T112 [P] [US8] Add `BulkFindReplaceService` in `backend/src/plugins/translate/service.py`: pattern matching across run records, preview generation, atomic apply with optional dictionary submission.
- [ ] T113 [US8] Create `CorrectionCell` component (`frontend/src/lib/components/translate/CorrectionCell.svelte`): inline editable cell with submit-to-dictionary action. Click → edit → save → "Submit to Dictionary" → popup. Language pair auto-filled.
- [ ] T114 [US8] Create `BulkReplaceModal` component (`frontend/src/lib/components/translate/BulkReplaceModal.svelte`): find/replace pattern inputs, regex toggle, preview table, apply button with confirmation.
- [ ] T115 [US8] Update preview component: add sample size slider (1-100) to preview trigger. Wire cost warning.
- [ ] T116 [US8] Add API methods to `frontend/src/lib/api/translate.js`: `inlineEditCorrection()`, `submitCorrectionToDict()`, `bulkFindReplace()`, `bulkReplacePreview()`.
- [ ] T117 [US8] Write pytest tests for inline correction: test single correction → dictionary, test duplicate detection, test bulk replace preview accuracy, test bulk replace atomic apply.
- [ ] T118 [US8] Write vitest tests for CorrectionCell and BulkReplaceModal components: test edit flow, test submit-to-dictionary, test find/replace preview, test bulk apply.
### US8b (NEW) — Context-Aware Correction & Dictionary [2026-05-14]
**Purpose**: Extend the correction workflow and dictionary with source-row context capture, context editing in popup, usage notes, and context-aware LLM prompt construction.
- [ ] T123 [P] [US8b] Add context fields to `DictionaryEntry` in `backend/src/models/translate.py`: `context_data` (JSON, nullable), `usage_notes` (Text, nullable), `has_context` (Boolean, default=False), `context_source` (String, nullable). Generate Alembic migration.
- [ ] T124 [P] [US8b] Update `InlineCorrectionService` (`backend/src/plugins/translate/service.py`): when a correction is submitted, auto-capture source row's context column values as `context_data`. Accept optional overrides from request. Accept `usage_notes`. Handle `context_source` tagging.
- [ ] T125 [P] [US8b] Create `ContextAwarePromptBuilder` in `backend/src/plugins/translate/prompt_builder.py`: render dictionary entries with context annotations for `has_context=True`. Compute Jaccard similarity between entry `context_data` and incoming row context. Flag >50% overlap as `# PRIORITY`. Cap context at ~500 tokens per entry.
- [ ] T126 [P] [US8b] Update dictionary import/export in API and frontend: CSV/TSV import accepts optional `context_data`, `usage_notes` columns. Export includes these fields.
- [ ] T127 [P] [US8b] Update `BulkFindReplaceService`: when `submit_to_dictionary_with_context=true`, auto-capture context from first matching row per unique term pair. `context_source="bulk"`. Warn if context varies across matched rows for same term.
- [ ] T128 [US8b] Update `CorrectionCell` and `TermCorrectionPopup`: correction popup shows auto-captured context (with Edit/Remove), `usage_notes` textarea, `context_source` badge.
- [ ] T129 [US8b] Update `BulkReplaceModal`: add "Submit to dictionary with context" checkbox + "Usage notes (applied to all)" textarea.
- [ ] T130 [US8b] Update `DictionaryEditor` page: display `context_data` and `usage_notes` per entry in expandable row. Allow inline editing.
- [ ] T131 [US8b] Update `filter_for_batch()` in `backend/src/plugins/translate/dictionary.py`: accept `row_context` parameter. Call `ContextAwarePromptBuilder` for priority flags.
- [ ] T132 [US8b] Write pytest tests for context-aware correction: test context capture, context editing/removal, Jaccard similarity matching, priority flagging, 500-token truncation.
- [ ] T133 [US8b] Write vitest tests for context UI: context display in popup, context editing, usage notes, badge display.
- [ ] T134 [US8b] [P] Update correction API endpoint schema: `POST /api/translate/corrections` now accepts optional `context_data` (JSON override), `usage_notes` (text), `keep_context` (boolean, default=true). Response includes new context fields.
### US4 (UPDATED) — Multi-Language History & Metrics
- [ ] T119 [P] [US4] Update history endpoints: `GET /api/translate/runs` returns per-language stats. Run detail includes `language_stats`. Metrics endpoint returns per-language breakdown.
- [ ] T120 [US4] Update `TranslationHistory` page (`frontend/src/routes/translate/history/+page.svelte`): add per-language columns to run table. Show language badges in run list.
- [ ] T121 [P] [US4] Update `MetricSnapshot` model: add `per_language_metrics: JSON` column. Update pruning logic to persist per-language metrics before pruning.
- [ ] T122 [US4] Write tests for per-language metrics accuracy post-prune.
---
## Dependencies & Execution Order
### Phase Dependencies
@@ -281,13 +368,22 @@
- **Setup (Phase 1)**: No dependencies — can start immediately
- **Foundational (Phase 2)**: Depends on Setup — BLOCKS all user stories
- **US1 (Phase 3)**: Depends on Foundational — no dependencies on other stories. **Recommended start after Foundational.**
- **US5 (Phase 4)**: Depends on Foundational — can run in **parallel with US1**. Dictionary filtering (T020) will be consumed later by US2/US3 but is self-contained.
- **US2 (Phase 5)**: Depends on US1 (needs saved job) + US5 (needs dictionary filtering). Can start integration once US1 backend is stable.
- **US3 (Phase 6)**: Depends on US1 (needs job config) + US2 (preview decisions feed executor). Sequential after US2.
- **US6 (Phase 7)**: Depends on US3 (needs run results) + US5 (needs dictionary). Can run in **parallel with US7** after US3.
- **US7 (Phase 8)**: Depends on US1 (needs job) + US3 (needs execution pipeline). Can run in **parallel with US6** after US3.
- **US4 (Phase 9)**: Depends on US3 (needs run records). Can run in **parallel with US6/US7** after US3.
- **Polish (Phase 10)**: Depends on all desired user stories being complete.
- **US5 (Phase 4)**: Depends on Foundational — can run in **parallel with US1**. Dictionary filtering will be consumed later.
- **US2 (Phase 5)**: Depends on US1 (needs saved job) + US5 (needs dictionary filtering). Can start once US1 backend is stable.
- **US3 (Phase 6)**: Depends on US1 + US2. Sequential after US2.
- **US6/10 (Phase 7/8)**: Original US6+US10 (corrections, scheduling). Depends on US3.
- **US4 (Phase 9)**: Depends on US3.
- **Polish (Phase 10)**: Depends on all desired user stories.
- **Phase 11 (Multi-Language)** [NEW]:
- **Migration (T083-T086)**: Blocks ALL Phase 11 work — models, schemas, migration scripts MUST be done first
- **US6 auto-detection (T087-T090)**: Depends on migration. No dependencies on other Phase 11 stories.
- **US7 multilingual dict (T091-T095)**: Depends on migration. Can run **in parallel** with US6 auto-detection.
- **US1 updated (T096-T098)**: Depends on migration + US7 (for language-aware dict attachment). Can start after migration.
- **US2 preview (T099-T103)**: Depends on migration + US6 (auto-detection for preview display).
- **US3 execution (T104-T108)**: Depends on migration + US6 + US7. Sequential after preview changes.
- **US8 correction (T109-T118)**: Depends on migration + US3 (needs multi-language run results).
- **US8b context (T123-T134)**: Depends on US8 (inline correction exists) + US7 (dictionary filtering). Can start after T109-T114 are stable. Fits in US8's parallel slot.
- **US4 history (T119-T122)**: Depends on US3 (per-language run data). Can run **in parallel** with US8/US8b.
### Parallel Opportunities
@@ -300,15 +396,20 @@
| 5 (US2) | T030 ∥ T031 | API client ∥ Preview component |
| 6 (US3) | T036 ∥ T041 | SQLGenerator ∥ API client |
| 7 (US6) | T050 ∥ T051 ∥ T052 | API client ∥ Popup ∥ Sidebar |
| 8 (US7) | T061 ∥ T062 | API client ∥ ScheduleConfig |
| 8 (US10) | T061 ∥ T062 | API client ∥ ScheduleConfig |
| 9 (US4) | T069 ∥ T070 | API client ∥ History page |
| 10 | T074 ∥ T075 ∥ T076 | Retention, notifications, belief instrumentation |
| 11a (Migration) | T083 ∥ T084 ∥ T085 ∥ T086 | All model/schema changes in parallel [NEW] |
| 11b (US6 + US7) | T087 ∥ T088 ∥ T089 ∥ T091 ∥ T092 ∥ T093 | Auto-detection + multilingual dict in parallel after migration [NEW] |
| 11c (US8) | T109 ∥ T111 ∥ T112 ∥ T113 ∥ T114 | Inline correction backend + frontend components [NEW] |
| 11d (US8b) | T123 ∥ T124 ∥ T125 ∥ T126 ∥ T127 | Context fields, builder, import/export, bulk [NEW] |
### Cross-Story Parallelism
After Foundational (Phase 2):
- **US1 and US5** can proceed in parallel by different developers
- After US3 completes: **US6, US7, and US4** can proceed in parallel
After Migration (Phase 11a):
- **US6 auto-detection** and **US7 multilingual dict** can proceed in parallel
- After US3 original + Phase 11 US3: **US8 correction** and **US4 history update** can proceed in parallel
- **US8b context** can proceed **in parallel** with US8 frontend work once T123-T124 merge
---
@@ -325,13 +426,21 @@ After Foundational (Phase 2):
1. Foundation → US1 + US5 (parallel) → US2 → US3
2. **STOP and VALIDATE**: End-to-end translation flow works: configure → preview → execute → INSERT
3. This is the core feature — all remaining stories add automation (US7), quality improvement (US6), and visibility (US4)
3. This is the core feature — all remaining stories add automation (US10), quality improvement (US6), and visibility (US4)
### Full Feature (All Stories)
1. MVP → US6 + US7 + US4 (parallel after US3) → Polish
1. MVP → US6 + US10 + US4 (parallel after US3) → Polish
2. Scheduled automation, feedback loop, and audit trail all functional
### Multi-Language Extension (Phase 11)
1. **Migration first** → Models, schemas, Alembic, data migration
2. **US6 + US7 in parallel** → Auto-detection + Multilingual dictionaries
3. **US1 US3 sequentially** → Multi-language job config → Preview → Execution
4. **US8 + US4 in parallel** → Inline correction + Per-language history/metrics
5. **Quickstart re-validation** → Update T078 to cover multi-language flow
---
## Notes
@@ -342,4 +451,16 @@ After Foundational (Phase 2):
- Rejected paths are explicitly protected by regression tests in T079.
- `[NEED_CONTEXT]` markers: none — all contract targets resolve to existing or planned modules within this feature.
- The existing `LLMProviderService`, `SupersetClient`, `SchedulerService`, `NotificationService`, and `TaskWebSocket` contracts are reused without modification.
- Quickstart.md (T078) serves as the human-verifiable acceptance test for the full feature.
- Quickstart.md (T078) serves as the human-verifiable acceptance test for the full feature. After Phase 11, update quickstart to cover multi-language flow.
- **Migration considerations**:
- `TranslationJob.target_language``target_languages` migration is one-time. Old API consumers sending single `target_language` are backward-compatible (wrapped into list server-side).
- `TerminologyDictionary.target_language` removal: existing dictionaries get entries migrated with `source_language = job.source_language or "und"` and `target_language = dictionary.target_language`.
- `TranslationRecord``TranslationLanguage` migration: existing single-language records get one `TranslationLanguage` entry per record.
- **LLM prompt change**: Multi-target prompts add non-trivial token overhead (~50% for 3 languages). The prompt structure is: "Translate the following text to languages: [ru, en, de]. Return JSON with detected_source_language and translations keyed by language code."
- **Context-aware prompt format** (US8b):
- Entry without context: `"Voiture" → "Car"`
- Entry with context: `"Voiture" (context: Category=Automotive, Type=Engine) → "Car"`
- Entry with context + notes: `"Voiture" (context: Category=Automotive) → "Car" # Usage: Only for passenger vehicles`
- Priority entry: `"Voiture" (context: Category=Automotive) → "Car" # PRIORITY - context match: 75%`
- Prompt construction caps context at ~500 tokens/entry. Priority entries listed before non-priority entries within the same language pair block.
- **Context similarity heuristic**: Jaccard similarity on tokenized (lowercased, stopwords-removed) text of context column values. Threshold 0.5. Configurable via job settings. Stored per-run in config snapshot.

View File

@@ -241,67 +241,108 @@
6. User resolves conflicts, confirms import → 45 new terms added.
```
### Flow F: Feedback Loop — Correct → Dictionary
### Flow F: Feedback Loop — Correct → Dictionary (Enhanced with Context)
```
1. User is viewing completed run results (Flow D detail view).
2. In the translations table, user notices:
┌────────────────────────────────────────────────────┐
# │ Source │ Translation
├───┼────────────────────┼───────────────────────────┤
3 │ "Monitor Stand" │ "Мониторная стойка"
│ │ [✎ Edit] [📖 Add to Dict]
└───┴────────────────────┴───────────────────────────┘
2. In the translations table (multi-language), user notices:
┌──────────────────────────┬──────────────────┬──────────────────┐
# │ Source (fr) │ Lang │ en │ de
├──────┼────────────────────┼──────────────────┼──────────────────┤
4 │ "Voiture" │ fr │ "Car" [✏️] [📕D] │ "Auto"
│ Cat: Parts │ │ │
└──────┴────────────────────┴──────────────────┴──────────────────┘
Notes: [✏️] = click to edit cell; [📕D] = submit to dictionary
3. User selects the source term "Monitor Stand" and the incorrect
translation "Мониторная" in the result → popup appears:
┌──────────────────────────────────────┐
│ Correct term │
│ │
│ Source term: [Monitor Stand______] │ ← from source column
│ Incorrect: [Мониторная_________] │ ← selected in translation
│ Correct: [Подставка для______] │ ← user types correction
│ │
│ Save to dictionary: [Product Terms ▼]│
│ │
│ [Cancel] [Submit to Dictionary] │
└──────────────────────────────────────┘
3. User clicks [✏️] on "Car" → cell becomes inline input:
┌──────────────────────────────────────────┐
│ [Vehicle___________________________] ✓ │
└──────────────────────────────────────────┘
User types "Vehicle", clicks ✓ → cell saves, shows 📝 badge.
4. User fills the correction, selects "Product Terms" dictionary.
After save, [📕D] button appears → user clicks it.
5. If "Monitor Stand" already exists in that dictionary:
┌──────────────────────────────────────┐
⚠ Term already exists
│ │
"Monitor Stand" already has
translation "Стойка для монитора"
in "Product Terms".
[Overwrite] [Keep Existing] [Cancel]
──────────────────────────────────────
4. Correction popup opens with context:
┌──────────────────────────────────────────────────────────
✏️ Submit to Dictionary
Source term: [Voiture ]
Source language: [French (fr) ▼]
Target language: [English (en) ▼]
New translation: [Vehicle ]
│ ─── Context ─────────────────────────────────────────
│ 🤖 Auto-detected from source row: │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Category: Automotive parts │ │
│ │ Product type: Engine component │ │
│ │ Description: Voiture d'essieu arrière │ │
│ └──────────────────────────────────────────────────┘ │
│ [Edit] [Remove context] │
│ │
│ 📝 Usage notes (optional): │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Use "Vehicle" for entire vehicle assembly. │ │
│ │ "Car" only for passenger automobiles in casual │ │
│ │ contexts. │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ─── Prompt Preview ────────────────────────────────── │
│ "Voiture" (context: Category=Automotive parts, │
│ Product type=Engine component) → "Vehicle" │
│ # Usage: Use "Vehicle" for entire vehicle assembly │
│ │
│ Dictionary: [Main Glossary ▼] │
│ │
│ [Cancel] [Submit to Dictionary] │
└──────────────────────────────────────────────────────────┘
4. User fills the correction and selects "Product Terms" dictionary.
5. User reviews, optionally edits context or adds notes,
then clicks [Submit to Dictionary].
5. If "Monitor Stand" already exists in that dictionary:
┌──────────────────────────────────────┐
│ ⚠ Term already exists │
│ │
│ "Monitor Stand" already has
translation "Стойка для монитора"
in "Product Terms".
[Overwrite] [Keep Existing] [Cancel]
└──────────────────────────────────────┘
6. If "Voiture → Vehicle" (fr→en) already exists:
┌──────────────────────────────────────────────────
│ ⚠ Term already exists
│ "Voiture" → "Vehicle" already exists in
"Main Glossary" (fr → en).
Existing context: Category=Automotive
New context: Category=Automotive parts,
Product type=Engine component
│ │
│ [Overwrite] [Keep Existing] [Cancel] │
└──────────────────────────────────────────────────┘
6. User chooses [Overwrite] → term updated. A small badge appears:
"📖 Added to Product Terms" next to the corrected row.
7. User chooses [Overwrite] → entry updated with new context.
Badge appears: ✅ "Vehicle ✓" on the corrected cell.
7. For bulk corrections:
User Ctrl+clicks multiple problematic words in different rows
→ sidebar opens: "3 terms selected for correction"
→ each line: [source term] → [correction input]
→ [Submit all to: Product Terms ▼]
8. For bulk corrections with context:
User clicks [Bulk Replace...] → modal opens:
┌──────────────────────────────────────────────────────┐
│ 🔄 Bulk Find & Replace │
│ │
│ Find: [Car________________] [🔍 Regex?] │
│ Replace: [Vehicle_____________] │
│ Language: [en ▼] │
│ │
│ ☑ Submit to dictionary with context │
│ Dictionary: [Main Glossary ▼] │
│ Usage notes: [Applied to all entries______] │
│ │
│ [Preview] │
│ ┌──────┬──────────┬──────────┬──────────┬────────┐ │
│ │ # │ Source │ Current │ New │ Context│ │
│ ├──────┼──────────┼──────────┼──────────┼────────┤ │
│ │ 4 │ Voiture │ Car │ Vehicle │ Parts │ │
│ │ 12 │ Voiture │ Car │ Vehicle │ Parts │ │
│ │ 28 │ Voiture │ Car │ Vehicle │ Parts │ │
│ └──────┴──────────┴──────────┴──────────┴────────┘ │
│ ⚠ Same term across rows with different contexts — │
│ only first row's context will be saved. │
│ │
│ [Cancel] [Apply to 3 rows] │
└──────────────────────────────────────────────────────┘
```
### Flow G: Schedule Configuration
@@ -466,20 +507,22 @@
* **Components**:
* `TranslationJobList` — list of saved translation jobs with status and schedule indicators.
* `TranslationJobConfig` — the configuration form (Flow A), with dictionary attachment and schedule tab.
* `TranslationPreview` — the side-by-side preview view (Flow B).
* `TranslationRunProgress` — live progress during execution (Flow C).
* `TranslationRunResult` — completion summary with generated SQL and inline feedback-loop controls (Flow C + Flow F).
* `TranslationHistory` — filterable history of past runs (Flow D).
* `DictionaryList` — list of dictionaries with term counts and attachment info (Flow E).
* `DictionaryEditor` — inline term editor with import/export (Flow E).
* `TermCorrectionPopup` — the feedback-loop popup for submitting corrected terms (Flow F).
* `TranslationPreview` — the side-by-side preview view (Flow B), now with per-language columns and configurable sample size.
* `TranslationRunProgress` — live progress during execution (Flow C), now with per-language progress bars.
* `TranslationRunResult` — completion summary with generated SQL and inline feedback-loop controls (Flow C + Flow F), now with click-to-edit cells and per-language statistics.
* `TranslationHistory` — filterable history of past runs (Flow D), now with per-language columns.
* `DictionaryList` — list of dictionaries with term counts and info (Flow E).
* `DictionaryEditor` — inline term editor with import/export, context display, language pair columns (Flow E).
* `TermCorrectionPopup` — the feedback-loop popup for submitting corrected terms with context capture, usage notes, and prompt preview (Flow F).
* `CorrectionCell` — inline-editable cell component used inside TranslationRunResult (Flow F).
* `BulkReplaceModal` — modal for bulk find-and-replace with context-aware dictionary submission (Flow F).
* `ScheduleConfig` — schedule type selector, cron/interval inputs, upcoming preview, auto-INSERT and concurrency settings (Flow G).
* `BulkCorrectionSidebar` — sidebar for selecting and correcting multiple terms at once (Flow F).
* **State Management**: Job configuration, preview state, run progress, dictionary data, and schedule state are managed through Svelte stores with API-backed persistence.
* **API Surface**: Standard REST endpoints under `/api/translate/` for CRUD on jobs, dictionaries, preview triggering, run execution, schedule management, feedback-loop submission, and history retrieval.
* **WebSocket**: Real-time progress updates during batch translation via existing WebSocket infrastructure (consistent with Task Drawer patterns). Schedule trigger events are logged server-side without WebSocket push (user sees results on next page load or via notification).
* **Contract Mapping**:
* `@UX_STATE`: Enumerate states per component — idle, loading, configured, previewing, running, completed, failed, retrying, scheduled, schedule_paused, dictionary_attached, dictionary_empty, import_preview, import_conflict, correction_selected, correction_submitted.
* `@UX_FEEDBACK`: Loading skeletons during datasource fetch; spinners during LLM calls; toast notifications for save/delete; progress bar during run; syntax-highlighted SQL output; badge for dictionary-attached count; «Added to Dictionary» confirmation badge on corrected rows; schedule status indicator (active/paused); upcoming execution timeline.
* `@UX_RECOVERY`: Retry button for failed batches; datasource replacement flow; NULL-key download; configuration duplication from history; dictionary conflict resolution dialog; import error row download; schedule force-run override; failed-schedule-run retry.
* `@UX_REACTIVITY`: Column list derived from selected datasource (`$derived`); batch progress computed from completed/total counts; cost estimate reactivity on batch size change; next N execution times derived from schedule config; term count reactively updated on add/delete; dictionary attachment list reactivity.
* `@UX_STATE`: Enumerate states per component — idle, loading, configured, previewing, running, completed, failed, retrying, scheduled, schedule_paused, dictionary_attached, dictionary_empty, import_preview, import_conflict, correction_view, correction_editing, correction_saving, correction_submitting_dict, correction_dict_submitted, correction_conflict, context_auto, context_editing, context_removed, context_with_notes, bulk_replace_configuring, bulk_replace_previewing, bulk_replace_applying.
* `@UX_FEEDBACK`: Loading skeletons during datasource fetch; spinners during LLM calls; toast notifications for save/delete; progress bar during run; syntax-highlighted SQL output; badge for dictionary-attached count; «Added to Dictionary» confirmation badge on corrected rows; «Dictionary ✓» badge on saved entries; schedule status indicator (active/paused); upcoming execution timeline; context-source badge (🤖 Auto/✏️ Edited/📝 Manual); prompt preview card in correction popup.
* `@UX_RECOVERY`: Retry button for failed batches; datasource replacement flow; NULL-key download; configuration duplication from history; dictionary conflict resolution dialog; import error row download; schedule force-run override; failed-schedule-run retry; correction save retry; context edit revert.
* `@UX_REACTIVITY`: Column list derived from selected datasource (`$derived`); batch progress computed from completed/total counts; cost estimate reactivity on batch size change; next N execution times derived from schedule config; term count reactively updated on add/delete; dictionary attachment list reactivity; correction context preview `$derived` from context_data + usage_notes; bulk replace affected rows `$derived` from pattern + run data.