Resolve the revision-graph conflict introduced by the release line.
Conflicts
- tests/integration/persistence/test_rag_document_identity.py: master had
independently introduced the same dynamic-head helper (`current_head`) plus
a `DOCUMENT_IDENTITY_REVISION` constant, and it explicitly upgrades to that
revision. Keep master's semantics (the explicit revision matters because
upgrading to the head now also traverses the TOTP branch) while keeping the
module-level alembic imports on this branch.
New migration
- 0030_merge_totp_into_release joins the TOTP branch's own merge revision
(0026_merge_totp_and_rag_identity) with the release head
(0029_merge_rag_identity). Both reached 0025_rag_document_identity without
including each other, which left Alembic with two heads and made
`upgrade head` fail with "Multiple head revisions are present".
Verification
- Single Alembic head confirmed (0030_merge_totp_into_release).
- tests/integration/persistence/test_rag_document_identity.py: 34 passed.
- Full fast integration suite: 356 passed, 84 skipped.
Address the migration and formatting failures reported on the TOTP branch.
Migrations
- Register totp_credentials and totp_recovery_codes in
_ALEMBIC_TENANT_TABLES. On a legacy PostgreSQL install these two tables
reference users.uuid, which only exists after 0009, so create_all() must
not run ahead of Alembic the way it did for the other tenant tables.
Without this the PostgreSQL migration test failed with
"column uuid referenced in foreign key constraint does not exist".
Tests
- Resolve the Alembic head dynamically in the RAG document identity
regression instead of pinning 0025_rag_document_identity. The TOTP and
RAG branches now meet at a merge revision, so the pinned value was no
longer the head. This matches the convention already used by
test_migrations_postgres.
Formatting
- Apply ruff format to the new backend modules and prettier to the locale
files and TOTP components, so the lint jobs pass.
Add a per-Account time-based one-time password (TOTP) second sign-in
factor, plus the owner/admin tooling needed to operate it.
Backend
- TotpService: enrolment, constant-time verification with a +/- one step
drift window, single-use recovery codes and disablement.
* shared secret is only ever persisted as a Fernet token whose key is
derived (HKDF-SHA256) from the instance JWT secret and gated by a
key_version epoch;
* recovery codes are only ever persisted as salted
PBKDF2-HMAC-SHA256 digests (600k iterations) and are single-use;
* the consumed counter advances monotonically so a captured code cannot
be replayed inside the same time window.
- New persistence entities and alembic migrations for credentials and
recovery codes.
- Login second-factor challenge, bound to the Account that passed the
password step.
- Owner/admin oversight endpoints to inspect and re-bind the second
factor of any Account.
Frontend
- Account settings panel for enrolling, managing, re-binding and
disabling the second factor.
- Login second-factor step and the matching client methods.
- Strings for all eight locales.
The shared secret never crosses the API boundary: enrolment returns a
server-rendered QR code (as a data: URL) and the plaintext secret is
discarded as soon as the image is produced.
- Safely resolve media_cache via getattr in cleanup_expired_files to prevent AttributeError when storage_mgr is unset in cloud/test fixtures
- Only attach 'media_files' in cleanup return dictionary when media cache is active on singleton, preserving exact return contract for cloud maintenance tests
- Add explanatory comments to exception suppression in MediaCache.touch and cleanup loops to address code-quality review findings
- Add MediaCache using xxHash3-128 (with sha256 fallback) content-addressable storage
- Externalize message chain image payloads before recording monitoring and discarded messages
- Strip base64 payloads to null in SQLite monitoring_messages, dropping row size from megabytes to hundreds of bytes
- Add GET /api/v1/files/media/<filename> route with immutable HTTP cache headers to serve cached media
- Integrate age-based retention (default 30 days) and configurable disk quota with MaintenanceService cleanup loop
- Add defensive sanitizer in MonitoringService.record_message against oversized raw base64 payloads
- Add comprehensive unit tests and end-to-end verification covering CAS deduplication, route serving, and LRU pruning
* feat(mcp): surface OAuth-required server tests
* fix(mcp): show connection failure details in status cards
---------
Co-authored-by: RockChinQ <[email protected]>
create_bot inserts the Bot row first and only then instantiates the
adapter via platform_mgr.load_bot. When the adapter constructor raises
(e.g. KeyError on a missing credential key), the insert is already
committed and nothing removes the row: the HTTP layer returns 500 but
a permanently disabled orphan bot stays in the DB. Callers never
receive the bot uuid, so they cannot compensate by deleting it, and
load_bots_from_db skips enable=False bots, so the orphan is never
loaded or surfaced anywhere.
Wrap load_bot in try/except and delete the inserted row before
re-raising. Add a regression test asserting the DELETE is issued when
the adapter constructor fails.
* fix(cloud): provision login workspace just in time
* fix(oauth): send callback URI during code exchange
* fix(oauth): preserve callback URI through browser exchange
* fix(oauth): negotiate redirect-bound codes
---------
Co-authored-by: dadachann <[email protected]>
* fix(cloud): launch new accounts through Space
* style: format Cloud entry URL
* fix(cloud): wait for launch workspace projection
---------
Co-authored-by: dadachann <[email protected]>