1 parent
fb9eab87eb
commit
d91212d24c
40 files changed
+585
-380
No files matched your search
@@ -84,10 +84,10 @@ reader_js = read("src/web/js/40-messages-reader.js")
|
||||
compose_js = read("src/web/js/50-compose.js")
|
||||
compose_css = read("src/web/css/40-compose.css")
|
||||
mail_css = read("src/web/css/30-mail.css")
|
||||
require("perPage: 300" in bootstrap_js and "messages_per_page: 300" in bootstrap_js, "browser mailbox default is 300 messages")
|
||||
require("perPage: 300" in bootstrap_js and "messages_per_page: 300" in bootstrap_js and 'message_loading_mode: "pagination"' in bootstrap_js, "browser mailbox defaults to 300-message pagination")
|
||||
require(re.search(r"default_per_page\(\)\s*->\s*u32\s*\{\s*300\s*\}", read("src/routes/messages/types.rs")) is not None, "folder message API default page size is 300")
|
||||
require('1..=300' in read("src/routes/messages/mod.rs"), "folder message API accepts up to 300 messages per page")
|
||||
require('20..=300' in read("src/db/mail_preferences.rs"), "mailbox preference accepts 300 messages per page")
|
||||
require('20..=300' in read("src/db/mail_preferences.rs") and '"pagination" | "progressive" | "all"' in read("src/db/mail_preferences.rs"), "mailbox preferences accept 300-message pages and the three loading modes")
|
||||
require('const appShortcut = keyboardIdle && !event.ctrlKey && !event.metaKey && !event.altKey;' in bindings_js, "single-key shortcuts preserve Ctrl/Cmd/Alt browser shortcuts")
|
||||
require('realtimeNotificationsReady' in realtime_js and 'suppressRealtimeNotificationsForFolder' in realtime_js, "realtime new-mail notification gating is present")
|
||||
require('realtimeFolderIsDrafts' in realtime_js and 'if (realtimeFolderIsDrafts(data?.folder)) return true;' in realtime_js, "realtime new-mail notifications are suppressed for Drafts folders")
|
||||
@@ -105,12 +105,13 @@ require('clipboardData' in bindings_js and 'uploadInlineImage(images, range)' in
|
||||
require('.compose-attachment-kind' in compose_css and 'Image attachment' in compose_js and 'Inline image' in compose_js, "composer distinguishes embedded images from regular attachments")
|
||||
require("function mergeComposeAttachments" in compose_js and "await refreshComposeAttachmentsFromServer(id);" in compose_js and "attachments: mergeComposeAttachments(local.attachments, server.attachments)" in compose_js, "composer preserves multiple attachments and draft recovery merges authoritative server attachment metadata")
|
||||
require("const attachments = mergeComposeAttachments(state.composeAttachments);" in compose_js and "Inline link" in compose_js and "detachComposeAttachmentFromBody" in compose_js, "composer attachment strip keeps inline and regular attachments visible together")
|
||||
require('grid-template-columns: repeat(2, minmax(0, 1fr));' in mail_css and '.load-more {' in mail_css and 'width: 100%;' in mail_css.split('.load-more {', 1)[1].split('}', 1)[0], "message load actions share the full available list width")
|
||||
require('.message-pagination {' in mail_css and 'grid-template-columns: 30px auto 30px;' in mail_css and '.pagination-arrow {' in mail_css and '.load-more {' in mail_css and 'width: 100%;' in mail_css.split('.load-more {', 1)[1].split('}', 1)[0], "message pagination uses compact SVG arrows while progressive-load controls span the message list width")
|
||||
require('id="compose-fields-toggle"' in read("src/web/index.html") and 'function setComposeFieldsCollapsed' in compose_js and 'function toggleComposeFields' in compose_js and '.compose-dialog.compose-fields-collapsed .compose-addresses' in compose_css and 'setComposeFieldsCollapsed(false)' in bindings_js, "composer addressing fields can collapse to maximize editor space and reopen on invalid recipient input")
|
||||
require('id="compose-save-draft"' in read("src/web/index.html") and '$("#compose-save-draft").addEventListener("click", saveComposeDraftToImap);' in bindings_js and 'saveCurrentDraft(false, true, true)' in compose_js, "composer exposes an explicit manual IMAP draft save before cancel")
|
||||
|
||||
require('rememberSignatureRange' in bindings_js and 'selectedSignatureRange' in bindings_js and 'if (!range || range.collapsed) return null;' in bindings_js and 'state.identitySignatureRange = null;' in bindings_js and '.signature-editor-toolbar button' in bindings_js and 'event.preventDefault();' in bindings_js and 'restoreEditorRange(range, signatureEditor);' in bindings_js, "signature formatting preserves real selections and refuses collapsed-caret commands that would enable sticky typing styles")
|
||||
index_html = read("src/web/index.html")
|
||||
require('<div class="folder-quota-label"><span>Mailbox storage</span>' in index_html and '<span id="mail-server-label">RustBox</span><span>__RUSTBOX_VERSION__</span>' in index_html, "mailbox storage and footer labels stay protocol-neutral")
|
||||
require('<div class="identity-signature-field"><span id="identity-signature-label">HTML signature / footer</span>' in index_html and 'aria-labelledby="identity-signature-label"' in index_html and '<label class="identity-signature-field">' not in index_html, "HTML signature editor is not wrapped in a label that can implicitly activate the Bold toolbar button")
|
||||
require('function ensureComposeEntryBeforeSignature' in read("src/web/js/72-openpgp-identities.js") and 'data-rustbox-signature-entry' in read("src/web/js/72-openpgp-identities.js") and 'compose-body-entry' in compose_css, "new visible signatures keep an editable compose body entry above the signature")
|
||||
require('.compose-signature.compose-signature-new' in compose_css and 'margin-top: 2.4em;' in compose_css and 'border-top: 1px solid color-mix(in srgb, var(--line) 58%, transparent);' in compose_css and 'removeAttribute("data-rustbox-signature-entry")' in compose_js, "new-message signature spacing is scoped, the separator is subdued, and editor-only scaffolding is stripped from outgoing HTML")
|
||||
@@ -121,6 +122,8 @@ require('bindPaneResizer("#message-resizer", "messagePaneWidth", 300, 760)' in b
|
||||
require('bindPaneResizer("#folder-resizer", "folderPaneWidth", 180, 420)' in bindings_js and 'value.clamp(180, 420).to_db_value()' in display_prefs_rs and 'id="pref-folder-width" type="range"\n min="180" max="420"' in index_html, "folder pane resize limits are aligned at 180..420 px and normalized server-side")
|
||||
require('suppressPaneWidthValidationError' in auth_js and 'temporarilyDisablePanePreference' in auth_js and 'data.temporarilyDisabled' not in auth_js and 'handle.dataset.temporarilyDisabled = "true"' in auth_js and 'if (!suppressPaneWidthValidationError(error)) toast(error.message, "error")' in auth_js, "pane-width validation incompatibilities disable the in-view resizer without a user-facing error")
|
||||
require('id="identity-signature-mode"' in read("src/web/index.html") and 'value="silent"' in read("src/web/index.html") and 'value="visible"' in read("src/web/index.html") and 'signature_insert_mode' in read("src/web/js/72-openpgp-identities.js"), "identity UI exposes silent and visible signature insertion modes")
|
||||
require('id="pref-adaptive-email-colors"' in read("src/web/index.html") and 'adaptive_email_colors' in display_prefs_rs and 'adaptiveEmailColors' in auth_js and 'adaptMessagePreviewColors' in reader_js, "Appearance exposes persisted adaptive HTML-mail colors and the reader applies them")
|
||||
require('id="mail-pref-loading-mode"' in index_html and 'id="message-pagination"' in index_html and 'function changeMailboxPage(delta)' in reader_js and 'function loadNextProgressivePage()' in reader_js and 'function loadAllMessages()' in reader_js and 'handleMessageListScroll' in bindings_js, "Mailbox settings expose pagination, progressive loading and load-all behavior")
|
||||
require('identity.signature_insert_mode != "visible"' in read("src/routes/send.rs") and 'data-rustbox-signature' in read("src/web/js/72-openpgp-identities.js"), "silent signatures append server-side while visible signatures remain composer-owned and de-duplicatable")
|
||||
require('id="compose-delete-draft"' in read("src/web/index.html") and '$("#compose-delete-draft").addEventListener("click", deleteComposeDraft);' in bindings_js and 'function deleteComposeDraft()' in compose_js and 'imap_uid_validity' in compose_js, "composer exposes permanent draft deletion with current IMAP coordinates")
|
||||
require('if req.sync_imap {' in read("src/routes/drafts.rs") and 'Failed to save draft to IMAP' in read("src/routes/drafts.rs") and 'AppError::ServiceUnavailable' in read("src/routes/drafts.rs"), "manual IMAP draft save fails visibly when the IMAP snapshot cannot be appended")
|
||||
@@ -138,8 +141,9 @@ require('encoding_rs::Encoding::for_label' in read("src/imap/parse.rs") and 'dec
|
||||
require('let is_pdf = response_content_type_is(&response, "application/pdf");' in read("src/routes/mod.rs") and 'HeaderValue::from_static("SAMEORIGIN")' in read("src/routes/mod.rs") and "HeaderValue::from_static(\"frame-ancestors 'self'\")" in read("src/routes/mod.rs"), "security headers allow same-origin framing for inline PDF previews only")
|
||||
index_html = read("src/web/index.html")
|
||||
require('class="compose-limit-summary"' in index_html and 'Per-file upload limit set with' not in index_html and 'Hard limit for the complete RFC822/MIME message' not in index_html, "Composing shows compact raw size limits without implementation details")
|
||||
require("V050" in read("RELEASE-MANIFEST.txt") and "V051" in read("RELEASE-MANIFEST.txt"), "release manifest documents V050 and reserves V051")
|
||||
require("V050" in read("migrations/README.md") and "V051" in read("migrations/README.md"), "migration guide documents V050 and reserves V051")
|
||||
require("V049" in read("RELEASE-MANIFEST.txt") and "V050" in read("RELEASE-MANIFEST.txt") and "V051" in read("RELEASE-MANIFEST.txt") and "V052" not in read("RELEASE-MANIFEST.txt"), "release manifest documents V049 baseline, V050 current migration and next V051")
|
||||
require("V049" in read("migrations/README.md") and "V050" in read("migrations/README.md") and "V051" in read("migrations/README.md") and "V052" not in read("migrations/README.md"), "migration guide documents V049 baseline, V050 current migration and next V051")
|
||||
require("versions = [50]" in read("scripts/migrate_roundcube.py"), "Roundcube provisioning fallback follows the V050 consolidated baseline")
|
||||
require((ROOT / "docs/STORAGE_SEARCH_OPERATIONS.md").is_file(), "storage/search operations guide exists")
|
||||
require('imap_host = ""' not in read("config.dev.toml"), "development config does not override IMAP host with an invalid empty value")
|
||||
require(not re.search(r"(?m)^IMAP_HOST=\s*$", read(".env.example")), "environment example does not set an invalid empty IMAP host")
|
||||
@@ -548,114 +552,76 @@ try:
|
||||
require(sqlite_generation == ("2",), "SQLite header-sync UPSERT remains unambiguous and increments generation")
|
||||
account_default = conn.execute("PRAGMA table_info(rustbox_accounts)").fetchall()
|
||||
version_default = next((row[4] for row in account_default if row[1] == "schema_version"), None)
|
||||
require(str(version_default).strip("'\"") == "46", "fresh global compatibility baseline remains V046 before tenant registration")
|
||||
require(str(version_default).strip("'\"") == "50", "fresh account schema version defaults to current V050")
|
||||
display_columns = {row[1]: row for row in conn.execute("PRAGMA table_info(u_test__display_preferences)")}
|
||||
require("adaptive_email_colors" in display_columns and str(display_columns["adaptive_email_colors"][4]) == "0", "V050 defaults adaptive HTML-mail colors off")
|
||||
mail_columns = {row[1]: row for row in conn.execute("PRAGMA table_info(u_test__mail_preferences)")}
|
||||
require("message_loading_mode" in mail_columns and str(mail_columns["message_loading_mode"][4]).strip("\'\"") == "pagination", "V050 defaults mailbox loading mode to pagination")
|
||||
conn.close()
|
||||
except Exception as exc:
|
||||
errors.append(f"fresh SQLite schema execution failed: {exc}")
|
||||
|
||||
# V047 upgrade execution from a minimal V046 attachment table, including data preservation.
|
||||
try:
|
||||
conn = sqlite3.connect(":memory:")
|
||||
conn.executescript(
|
||||
"CREATE TABLE u_test__draft_attachments ("
|
||||
"id TEXT PRIMARY KEY, draft_id TEXT NOT NULL, filename TEXT NOT NULL, "
|
||||
"content_type TEXT NOT NULL, size INTEGER NOT NULL, file_path TEXT NOT NULL, "
|
||||
"created_at TEXT NOT NULL);"
|
||||
"INSERT INTO u_test__draft_attachments "
|
||||
"(id, draft_id, filename, content_type, size, file_path, created_at) VALUES "
|
||||
"('att-1', 'draft-1', 'report.pdf', 'application/pdf', 1234, '/legacy/report.pdf', '2026-01-01T00:00:00Z');"
|
||||
)
|
||||
migration = read("migrations/sqlite/V047__blob_keys_and_search_outbox.sql").replace("{{p}}", "u_test__")
|
||||
conn.executescript(migration)
|
||||
columns = {row[1] for row in conn.execute("PRAGMA table_info(u_test__draft_attachments)")}
|
||||
require("checksum_sha256" in columns, "V047 adds checksum_sha256 to SQLite attachments")
|
||||
row = conn.execute(
|
||||
"SELECT id, draft_id, filename, content_type, size, file_path, created_at, checksum_sha256 "
|
||||
"FROM u_test__draft_attachments WHERE id='att-1'"
|
||||
).fetchone()
|
||||
require(
|
||||
row == ('att-1', 'draft-1', 'report.pdf', 'application/pdf', 1234, '/legacy/report.pdf', '2026-01-01T00:00:00Z', None),
|
||||
"V047 preserves existing attachment rows and initializes checksum as NULL",
|
||||
)
|
||||
require(conn.execute("SELECT 1 FROM sqlite_master WHERE type='table' AND name='u_test__search_outbox'").fetchone() is not None, "V047 creates SQLite search_outbox")
|
||||
outbox_columns = {row[1] for row in conn.execute("PRAGMA table_info(u_test__search_outbox)")}
|
||||
require("claimed_until" in outbox_columns, "V047 creates an independent SQLite outbox lease column")
|
||||
conn.close()
|
||||
except Exception as exc:
|
||||
errors.append(f"SQLite V047 migration execution failed: {exc}")
|
||||
|
||||
# V048 upgrade execution from a V047-style folders table, preserving existing counters.
|
||||
try:
|
||||
conn = sqlite3.connect(":memory:")
|
||||
conn.executescript(
|
||||
"CREATE TABLE u_test__folders ("
|
||||
"name TEXT PRIMARY KEY, delimiter TEXT, parent TEXT, flags TEXT NOT NULL DEFAULT '', "
|
||||
"is_subscribed INTEGER NOT NULL DEFAULT 1, total_count INTEGER NOT NULL DEFAULT 0, "
|
||||
"unread_count INTEGER NOT NULL DEFAULT 0, uid_validity INTEGER NOT NULL DEFAULT 0, "
|
||||
"highest_modseq INTEGER NOT NULL DEFAULT 0, updated_at TEXT NOT NULL, messages_updated_at TEXT);"
|
||||
"INSERT INTO u_test__folders "
|
||||
"(name, total_count, unread_count, uid_validity, highest_modseq, updated_at) VALUES "
|
||||
"('INBOX', 3444, 12, 77, 9001, '2026-11-01T00:00:00Z');"
|
||||
)
|
||||
migration = read("migrations/sqlite/V048__folder_counter_generation.sql").replace("{{p}}", "u_test__")
|
||||
conn.executescript(migration)
|
||||
columns = {row[1] for row in conn.execute("PRAGMA table_info(u_test__folders)")}
|
||||
require("counter_generation" in columns, "V048 adds counter_generation to SQLite folders")
|
||||
row = conn.execute(
|
||||
"SELECT name, total_count, unread_count, uid_validity, highest_modseq, counter_generation "
|
||||
"FROM u_test__folders WHERE name='INBOX'"
|
||||
).fetchone()
|
||||
require(
|
||||
row == ('INBOX', 3444, 12, 77, 9001, 0),
|
||||
"V048 preserves existing folder counters and initializes generation at zero",
|
||||
)
|
||||
conn.close()
|
||||
except Exception as exc:
|
||||
errors.append(f"SQLite V048 migration execution failed: {exc}")
|
||||
|
||||
# V049 creates the durable blob deletion outbox.
|
||||
try:
|
||||
conn = sqlite3.connect(":memory:")
|
||||
migration = read("migrations/sqlite/V049__blob_delete_outbox.sql").replace("{{p}}", "u_test__")
|
||||
conn.executescript(migration)
|
||||
columns = {row[1] for row in conn.execute("PRAGMA table_info(u_test__blob_delete_outbox)")}
|
||||
require({"id", "storage_key", "attempts", "available_at", "claimed_until", "last_error", "created_at", "updated_at"}.issubset(columns), "V049 creates SQLite blob_delete_outbox with durable retry state")
|
||||
indexes = {row[1] for row in conn.execute("PRAGMA index_list(u_test__blob_delete_outbox)")}
|
||||
require(any("blob_delete_outbox_ready" in name for name in indexes), "V049 creates SQLite blob cleanup readiness index")
|
||||
conn.close()
|
||||
except Exception as exc:
|
||||
errors.append(f"SQLite V049 migration execution failed: {exc}")
|
||||
|
||||
# V050 adds compact-wide-row and signature insertion preferences.
|
||||
# V047-V049 are squashed into the V049 compatibility baseline. V050 is the single runtime migration.
|
||||
# V050 upgrade execution and migration/query parity across SQL engines.
|
||||
# V050 upgrades a V049-compatible account schema with all unreleased 0.2.15 preferences.
|
||||
try:
|
||||
conn = sqlite3.connect(":memory:")
|
||||
conn.executescript(
|
||||
"CREATE TABLE u_test__display_preferences (id INTEGER PRIMARY KEY);"
|
||||
"CREATE TABLE u_test__mail_preferences (id INTEGER PRIMARY KEY);"
|
||||
"CREATE TABLE u_test__identities (id INTEGER PRIMARY KEY);"
|
||||
)
|
||||
migration = read("migrations/sqlite/V050__compact_rows_and_signature_mode.sql").replace("{{p}}", "u_test__")
|
||||
conn.executescript(migration)
|
||||
display_columns = {row[1]: row for row in conn.execute("PRAGMA table_info(u_test__display_preferences)")}
|
||||
mail_columns = {row[1]: row for row in conn.execute("PRAGMA table_info(u_test__mail_preferences)")}
|
||||
identity_columns = {row[1]: row for row in conn.execute("PRAGMA table_info(u_test__identities)")}
|
||||
require("adaptive_email_colors" in display_columns and str(display_columns["adaptive_email_colors"][4]) == "0", "V050 defaults adaptive HTML-mail colors off in SQLite")
|
||||
require("message_loading_mode" in mail_columns and str(mail_columns["message_loading_mode"][4]).strip("\'\"") == "pagination", "V050 defaults mailbox loading mode to pagination in SQLite")
|
||||
require("compact_wide_rows" in mail_columns and str(mail_columns["compact_wide_rows"][4]) == "1", "V050 defaults compact wide rows on in SQLite")
|
||||
require("signature_insert_mode" in identity_columns and identity_columns["signature_insert_mode"][4] == "'silent'", "V050 defaults identity signature insertion to silent in SQLite")
|
||||
conn.execute("INSERT INTO u_test__display_preferences(id) VALUES (1)")
|
||||
conn.execute("INSERT INTO u_test__mail_preferences(id) VALUES (1)")
|
||||
conn.execute("INSERT INTO u_test__identities(id) VALUES (1)")
|
||||
require(conn.execute("SELECT compact_wide_rows FROM u_test__mail_preferences WHERE id=1").fetchone()[0] == 1, "V050 applies compact-row default to existing/new SQLite rows")
|
||||
require(conn.execute("SELECT signature_insert_mode FROM u_test__identities WHERE id=1").fetchone()[0] == "silent", "V050 applies silent-signature default to existing/new SQLite rows")
|
||||
require(conn.execute("SELECT adaptive_email_colors FROM u_test__display_preferences WHERE id=1").fetchone()[0] == 0, "V050 applies adaptive-color default to SQLite rows")
|
||||
require(conn.execute("SELECT message_loading_mode, compact_wide_rows FROM u_test__mail_preferences WHERE id=1").fetchone() == ("pagination", 1), "V050 applies mailbox pagination and compact-row defaults to SQLite rows")
|
||||
require(conn.execute("SELECT signature_insert_mode FROM u_test__identities WHERE id=1").fetchone()[0] == "silent", "V050 applies silent-signature default to SQLite rows")
|
||||
conn.close()
|
||||
except Exception as exc:
|
||||
errors.append(f"SQLite V050 migration execution failed: {exc}")
|
||||
|
||||
# Migration/query parity across SQL engines.
|
||||
for dialect in ("sqlite", "mysql", "pgsql"):
|
||||
require((ROOT / f"migrations/{dialect}/V047__blob_keys_and_search_outbox.sql").is_file(), f"V047 migration exists for {dialect}")
|
||||
require((ROOT / f"migrations/{dialect}/V048__folder_counter_generation.sql").is_file(), f"V048 migration exists for {dialect}")
|
||||
require((ROOT / f"migrations/{dialect}/V049__blob_delete_outbox.sql").is_file(), f"V049 migration exists for {dialect}")
|
||||
require((ROOT / f"migrations/{dialect}/V050__compact_rows_and_signature_mode.sql").is_file(), f"V050 migration exists for {dialect}")
|
||||
for retired in (
|
||||
"V047__blob_keys_and_search_outbox.sql",
|
||||
"V048__folder_counter_generation.sql",
|
||||
"V049__blob_delete_outbox.sql",
|
||||
"V051__adaptive_email_colors.sql",
|
||||
):
|
||||
require(not (ROOT / f"migrations/{dialect}/{retired}").exists(), f"consolidated/unreleased migration {retired} is absent for {dialect}")
|
||||
require((ROOT / f"migrations/{dialect}/V050__compact_rows_and_signature_mode.sql").is_file(), f"V050 runtime migration exists for {dialect}")
|
||||
migration_text = read(f"migrations/{dialect}/V050__compact_rows_and_signature_mode.sql")
|
||||
require(
|
||||
"adaptive_email_colors" in migration_text
|
||||
and "message_loading_mode" in migration_text
|
||||
and "compact_wide_rows" in migration_text
|
||||
and "signature_insert_mode" in migration_text,
|
||||
f"V050 runtime migration contains all unreleased preference columns for {dialect}",
|
||||
)
|
||||
schema_text = read(f"migrations/{dialect}/schema.sql")
|
||||
require("Current runtime schema: V050" in schema_text and "counter_generation" in schema_text and "blob_delete_outbox" in schema_text and "compact_wide_rows" in schema_text and "signature_insert_mode" in schema_text, f"fresh {dialect} schema includes V050 compact-row/signature preferences")
|
||||
require(
|
||||
"Squashed compatibility baseline: V049. Current schema: V050." in schema_text
|
||||
and "counter_generation" in schema_text
|
||||
and "blob_delete_outbox" in schema_text
|
||||
and "compact_wide_rows" in schema_text
|
||||
and "signature_insert_mode" in schema_text
|
||||
and "adaptive_email_colors" in schema_text
|
||||
and "message_loading_mode" in schema_text
|
||||
and "pagination" in schema_text,
|
||||
f"fresh {dialect} schema contains current V050 schema on the V049 compatibility baseline",
|
||||
)
|
||||
queries = read(f"src/queries/{dialect}/queries.rs")
|
||||
require("adaptive_email_colors" in queries, f"display-preference query includes adaptive_email_colors for {dialect}")
|
||||
require("message_loading_mode" in queries, f"mail-preference query includes message_loading_mode for {dialect}")
|
||||
folder_block = re.search(r"pub mod db_folders\s*\{(.*?)\n\}", queries, re.S)
|
||||
require(folder_block is not None and "Q_032" in folder_block.group(1) and "counter_generation = counter_generation + 1" in folder_block.group(1), f"folder counter CAS increments generation for {dialect}")
|
||||
block_match = re.search(r"pub mod db_search_outbox\s*\{(.*?)\n\}", queries, re.S)
|
||||
@@ -671,16 +637,14 @@ for dialect in ("sqlite", "mysql", "pgsql"):
|
||||
|
||||
pool = read("src/db/pool.rs")
|
||||
for dialect in ("SQLITE", "MYSQL", "PGSQL"):
|
||||
require(f"{dialect}_MIGRATIONS" in pool and "V047__blob_keys_and_search_outbox.sql" in pool, f"runtime migration catalog includes V047 ({dialect.lower()})")
|
||||
require(f"{dialect}_MIGRATIONS" in pool and "V048__folder_counter_generation.sql" in pool, f"runtime migration catalog includes V048 ({dialect.lower()})")
|
||||
require(f"{dialect}_MIGRATIONS" in pool and "V049__blob_delete_outbox.sql" in pool, f"runtime migration catalog includes V049 ({dialect.lower()})")
|
||||
require(f"{dialect}_MIGRATIONS" in pool and "V050__compact_rows_and_signature_mode.sql" in pool, f"runtime migration catalog includes V050 ({dialect.lower()})")
|
||||
require("const BASE_SCHEMA_VERSION: u32 = 46" in pool, "legacy squashed baseline stays V046")
|
||||
require(f"const {dialect}_MIGRATIONS: &[(u32, &str)]" in pool and "V050__compact_rows_and_signature_mode.sql" in pool, f"runtime migration catalog includes V050 ({dialect.lower()})")
|
||||
require("pub const BASE_SCHEMA_VERSION: u32 = 49" in pool, "consolidated squashed baseline is V049")
|
||||
require("V047__blob_keys_and_search_outbox.sql" not in pool and "V048__folder_counter_generation.sql" not in pool and "V049__blob_delete_outbox.sql" not in pool and "V051__adaptive_email_colors.sql" not in pool, "retired standalone migrations are absent from the runtime catalog")
|
||||
require("latest_schema_version" in pool, "runtime schema version is computed from migrations")
|
||||
require(
|
||||
"latest_supported_sqlite = SQLITE_MIGRATIONS" in pool
|
||||
and "if version > latest_supported_sqlite" in pool,
|
||||
"legacy SQLite import accepts runtime migrations up to this build instead of stopping at V046",
|
||||
"legacy SQLite import accepts the V049 baseline, applies V050 and rejects schemas newer than this build",
|
||||
)
|
||||
|
||||
# Tenant/import table parity and transaction-bound mutation helpers.
|
||||
@@ -777,11 +741,11 @@ require('json!({"term": {"user_hash": user_hash}})' in search_rs, "OpenSearch qu
|
||||
require("process_search_outbox" in search_rs and "claim(&conn" in search_rs, "OpenSearch worker uses durable claimed outbox events")
|
||||
require("fn event_lease_secs(&self) -> u64" in search_rs and "let lease_secs = backend.event_lease_secs();" in search_rs and "claim(&conn, &event, 60)" not in search_rs, "OpenSearch outbox lease is derived from HTTP timeout/failover budget instead of a fixed 60 seconds")
|
||||
require("opensearch_event_lease_covers_timeout_and_failover_budget" in search_rs, "OpenSearch lease budget has a source-level unit regression test")
|
||||
require("claimed_until" in read("migrations/sqlite/V047__blob_keys_and_search_outbox.sql") and "reenqueue_preserves_active_claim_lease" in read("src/db/search_outbox.rs"), "outbox re-enqueue cannot steal an active worker lease")
|
||||
require("claimed_until" in read("migrations/sqlite/schema.sql") and "reenqueue_preserves_active_claim_lease" in read("src/db/search_outbox.rs"), "outbox re-enqueue cannot steal an active worker lease")
|
||||
blob_delete_rs = read("src/db/blob_delete_outbox.rs")
|
||||
attachments_rs = read("src/routes/attachments.rs")
|
||||
send_rs = read("src/routes/send.rs")
|
||||
require("mark_failure" in blob_delete_rs and "min(3600)" in blob_delete_rs and "max_attempts" not in blob_delete_rs and "dead_letter" not in read("migrations/sqlite/V049__blob_delete_outbox.sql"), "blob deletion outbox retries indefinitely with capped backoff and no permanent dead-letter")
|
||||
require("mark_failure" in blob_delete_rs and "min(3600)" in blob_delete_rs and "max_attempts" not in blob_delete_rs and "dead_letter" not in read("migrations/sqlite/schema.sql").split("CREATE TABLE {{p}}blob_delete_outbox", 1)[1].split("CREATE TABLE {{p}}search_outbox", 1)[0], "blob deletion outbox retries indefinitely with capped backoff and no permanent dead-letter")
|
||||
require("unchecked_transaction()" in attachments_rs and "blob_delete_outbox::enqueue(&transaction" in attachments_rs and "delete_draft_attachment(&transaction" in attachments_rs, "single attachment deletion atomically queues blob cleanup with metadata deletion")
|
||||
require("blob_delete_outbox::enqueue(&transaction" in drafts_rs and "delete_draft(&transaction" in drafts_rs, "whole-draft deletion atomically queues every attachment blob before metadata cascade")
|
||||
require("blob_delete_outbox::enqueue(&transaction" in send_rs and "delete_draft(&transaction" in send_rs, "post-send draft cleanup atomically queues attachment blobs")
|
||||
|
||||
Reference in new issue
Block a user