Per molecular CoT protocol: REASON and REFLECT are primary reasoning
bonds and must be visible at INFO level in production. DEBUG is
reserved for high-frequency loops.
- reason(): self.debug() → self.info()
- reflect(): self.debug() → self.info()
- explore(): stays at warning() (WARNING level)
- Also removed redundant logger.info() calls in lifespan (now reason()
itself provides visible startup logging)
- Simplified init_db() to only call Base.metadata.create_all() on all
three engines — no more _ensure_*() inline additive migrations
- run_alembic_migrations() now raises on failure (no safety net to
fall back to)
- All schema changes (add/drop/rename columns, data migrations) must
go through Alembic. create_all() handles new tables only.
@REJECTED _ensure_*() inline migrations removed because they:
- Duplicated Alembic logic without versioning
- Created hidden schema drift (columns existed in DB but had no
corresponding Alembic migration)
- Made audits and fresh DB provisioning unreliable
- Added 4 missing columns to _ensure_translation_jobs_columns() inline
migration: target_language_column, target_source_column,
target_source_language_column, disable_reasoning — these were in the
SQLAlchemy model but had no Alembic migration or _ensure_*() coverage
- Added missing api_key, assistant, clean_release model imports to
alembic/env.py so autogenerate can see all tables
The inline migration _ensure_roles_is_admin_column() was defined but
never called from init_db(). This caused 'column roles.is_admin does
not exist' errors when logging in against databases with an older
schema. Added the missing call to ensure the column is created during
startup.
Alembic migration 86c7b1d6a710 adds is_admin BOOLEAN DEFAULT FALSE
to the roles table. Fixes login crash after M-3 change:
psycopg2.errors.UndefinedColumn: column roles.is_admin does not exist
Also adds _ensure_roles_is_admin_column() as an additive migration
safety net for environments that don't run alembic automatically.
To apply: cd backend && source .venv/bin/activate && alembic upgrade head
C-2 (CRITICAL): JWT decode_token now validates aud, iss, iat, jti claims.
Tokens without these claims are rejected. Audience 'ss-tools-api' and
issuer 'ss-tools' prevent cross-service token reuse.
H-2 (HIGH): Server-side JWT revocation via token_blacklist table.
- New TokenBlacklist model (SHA-256 hash only, never raw token)
- blacklist_token() on logout — revokes current session
- is_token_blacklisted() check in get_current_user
- Expired entries auto-pruned via _prune_blacklist()
H-3 (HIGH): Encryption key auto-generation removed — crash-early instead.
Previously, ensure_encryption_key() would auto-generate a Fernet key
on first run and write it to .env. This made encrypted data unrecoverable
when the key changed between deployments. Now raises RuntimeError with
a clear command to generate the key explicitly.
Also: update orthogonal security test for crash-early behavior.
Introduce a new API key authentication mechanism to support service-to-service
communication (e.g., Airflow, CI/CD) without browser-based JWT login.
- Add `APIKey` model and `APIKeyPrincipal` dependency for RBAC.
- Implement `require_api_key_or_jwt` dependency with environment scoping.
- Add admin CRUD endpoints for API key lifecycle management.
- Add API key management UI in System Settings.
- Update maintenance API to support explicit `environment_id` and API key auth.
- Add i18n support for API keys and connection settings.
- Include Python and Bash usage examples in the `examples/` directory.
- Update technical specifications and documentation.
StatsBar — переделаны огромные карточки-кнопки в компактные пилюли
(rounded-full px-3 py-1 text-sm вместо p-3 grid grid-cols-4).
DatasetList — экшн-кнопки из bg-primary в outline-стиль, карточки
плотнее (p-2.5, py-0.5), ESLint-фиксы (#each key).
Backend — linked_dashboard_count теперь заполняется в списке датасетов
через /dataset/{id}/related_objects (конкурентно, Semaphore=3, timeout=10s).
Ранее поле было только в get_dataset_detail и StatsBar показывал 0.
Translations:
- FR-045: new-key-only execution mode — translate only rows with unseen keys
- Compare source row keys against TranslationRecord.source_data from last succeeded run
- Baseline expired (>90 days) fallback to full mode with baseline_expired event
- run_noop early return when zero new keys (skip LLM + SQL)
Scheduler reliability:
- Stale PENDING protection: runs older than 1h auto-marked FAILED, no longer block schedule
- load_schedules() reloads active translation schedules from DB on restart
- add_translation_job/remove_translation_job register/unregister with APScheduler
- execution_mode column on translation_schedules with additive DB migration
Automation view:
- GET /settings/automation/translation-schedules endpoint
- Translation schedules displayed on Automation page below validation policies
Trace propagation:
- seed_trace_id() in all background entry points:
TaskManager._run_task/_flusher_loop, SchedulerService._trigger_backup,
websocket_endpoint, IdMappingService, all standalone scripts
Migrations:
- dictionary_entries: origin_run_id, origin_row_key, origin_user_id
- translation_schedules: execution_mode
Tests:
- 3 new test modules (22 tests): core_scheduler, executor_filter, scheduler_execution+guard
- Fix pre-existing translate test isolation (conftest.py)
- Fix test_list_runs_filter_status parameter
- Fix ModuleNotFoundError: 4 lazy imports in _schedule_routes.py changed
from 3-dot to 4-dot relative imports (src.api.dependencies -> src.dependencies)
- Fix TypeError: update_schedule() param name mismatch (timezone -> timezone_str)
- Add add_translation_job/remove_translation_job to SchedulerService
to register translation schedules with APScheduler via execute_scheduled_translation
- Add GET /settings/automation/translation-schedules endpoint
returning all translation schedules with joined job names
- Update Automation settings page to display translation schedules
(cron, timezone, active badge, last run) below validation policies
Replace [Entry]/[Exit]/[Action]/[COHERENCE:OK] with [REASON]/[REFLECT]/[EXPLORE]
in belief_scope context manager, BeliefFormatter, and 34 source files.
Per molecular-cot-logging skill invariants.
- Add environment_id to TranslationJob model/schema/API + DB migration
- Pass environmentId from page to TranslationPreview and fetchPreview
- Fix _fetch_sample_rows: use result_type='samples' to include virtual cols
- Add 'kilo' and 'openrouter' to supported LLM provider types
- Make response_format conditional (skip for non-OpenAI upstream providers)
- Remove source_table field from form (redundant with datasource)
- Restore datasourceSearch display from saved job on page load
- Add request/response logging for LLM API calls
Move dataset review clarification into the assistant workspace and
rework the review page into a chat-centric layout with execution rails.
Add session-scoped assistant actions for mappings, semantic fields,
and SQL preview generation. Introduce optimistic locking for dataset
review mutations, propagate session versions through API responses,
and mask imported filter values before assistant exposure.
Refresh tests, i18n, and spec artifacts to match the new workflow.
BREAKING CHANGE: dataset review mutation endpoints now require the
X-Session-Version header, and clarification is no longer handled
through ClarificationDialog-based flows