1 parent
12e0c99850
commit
edda5fc9e4
42 files changed
+733
-122
No files matched your search
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Static/offline release checks for RustBox 0.2.13.
|
||||
"""Static/offline release checks for RustBox 0.2.14.
|
||||
|
||||
These checks intentionally avoid pretending to replace `cargo test`: they validate
|
||||
configuration, SQL migrations, Docker examples, query-catalog parity and release
|
||||
@@ -49,14 +49,14 @@ def extract_section(text: str, begin: str, end: str) -> str:
|
||||
|
||||
# Release/toolchain metadata.
|
||||
cargo = tomllib.loads(read("Cargo.toml"))
|
||||
require(cargo["package"]["version"] == "0.2.13", "Cargo package version is 0.2.13")
|
||||
require(cargo["package"]["version"] == "0.2.14", "Cargo package version is 0.2.14")
|
||||
require(cargo["package"]["rust-version"] == "1.99.1", "Cargo rust-version is 1.99.1")
|
||||
toolchain = tomllib.loads(read("rust-toolchain.toml"))
|
||||
require(toolchain["toolchain"]["channel"] == "1.99.1", "rust-toolchain pins Rust 1.99.1")
|
||||
require((ROOT / "Cargo.lock").is_file(), "Cargo.lock is retained for reproducible application builds")
|
||||
lock = tomllib.loads(read("Cargo.lock"))
|
||||
lock_rustbox = next((pkg for pkg in lock.get("package", []) if pkg.get("name") == "rustbox"), {})
|
||||
require(lock_rustbox.get("version") == "0.2.13", "Cargo.lock RustBox package version is 0.2.13")
|
||||
require(lock_rustbox.get("version") == "0.2.14", "Cargo.lock RustBox package version is 0.2.14")
|
||||
require(cargo["dependencies"].get("encoding_rs") == "0.8", "direct charset decoder dependency is pinned")
|
||||
require("encoding_rs" in lock_rustbox.get("dependencies", []), "Cargo.lock records direct charset decoder dependency")
|
||||
require("cargo generate-lockfile" in read("scripts/build-release.sh"), "release script generates Cargo.lock when missing")
|
||||
@@ -71,10 +71,10 @@ require("make_zip.py" in package_script and "sha256sum" in package_script, "rele
|
||||
require("cargo generate-lockfile" in read(".github/workflows/ci.yml"), "CI regenerates Cargo.lock before --locked checks")
|
||||
require("validate-release-static.py" in read("scripts/build-release.sh"), "release script runs offline/static release gate")
|
||||
require("validate-release-static.py" in read(".github/workflows/ci.yml"), "CI runs offline/static release gate")
|
||||
require(read("README.md").startswith("# RustBox 0.2.13"), "README identifies RustBox 0.2.13")
|
||||
require(read("RELEASE-MANIFEST.txt").startswith("RustBox 0.2.13"), "release manifest identifies RustBox 0.2.13")
|
||||
require(read("RELEASE-VALIDATION.txt").startswith("RustBox 0.2.13"), "release validation identifies RustBox 0.2.13")
|
||||
require(read("docs/IMPLEMENTATION_STATUS.md").startswith("# RustBox 0.2.13"), "implementation status identifies RustBox 0.2.13")
|
||||
require(read("README.md").startswith("# RustBox 0.2.14"), "README identifies RustBox 0.2.14")
|
||||
require(read("RELEASE-MANIFEST.txt").startswith("RustBox 0.2.14"), "release manifest identifies RustBox 0.2.14")
|
||||
require(read("RELEASE-VALIDATION.txt").startswith("RustBox 0.2.14"), "release validation identifies RustBox 0.2.14")
|
||||
require(read("docs/IMPLEMENTATION_STATUS.md").startswith("# RustBox 0.2.14"), "implementation status identifies RustBox 0.2.14")
|
||||
bootstrap_js = read("src/web/js/00-bootstrap.js")
|
||||
bindings_js = read("src/web/js/95-bindings-boot.js")
|
||||
realtime_js = read("src/web/js/80-settings-realtime.js")
|
||||
@@ -108,6 +108,14 @@ require("const attachments = mergeComposeAttachments(state.composeAttachments);"
|
||||
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('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('data-panel="appearance"' in read("src/web/index.html") and 'id="pref-compact-wide-rows"' in read("src/web/index.html") and 'id="mail-pref-compact-wide-rows"' not in read("src/web/index.html") and 'saveCompactWideRowsAppearance' in auth_js and 'compact_wide_rows: true' in bootstrap_js and 'data-compact-wide-rows="true"' not in read("src/web/index.html") and '@container message-pane (min-width: 520px)' in mail_css, "wide message-row compaction is an Appearance preference, persisted, default-on, and width-gated")
|
||||
display_prefs_rs = read("src/db/display_preferences.rs")
|
||||
index_html = read("src/web/index.html")
|
||||
require('bindPaneResizer("#message-resizer", "messagePaneWidth", 300, 760)' in bindings_js and 'value.clamp(300, 760).to_db_value()' in display_prefs_rs and 'id="pref-message-width" type="range"\n min="300" max="760"' in index_html, "message pane resize limits are aligned at 300..760 px and normalized server-side")
|
||||
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('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")
|
||||
drafts_rs = read("src/routes/drafts.rs")
|
||||
@@ -124,8 +132,8 @@ 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("V049" in read("RELEASE-MANIFEST.txt") and "V050" in read("RELEASE-MANIFEST.txt"), "release manifest documents V049 and reserves V050")
|
||||
require("V049" in read("migrations/README.md") and "V050" in read("migrations/README.md"), "migration guide documents V049 and reserves V050")
|
||||
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((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")
|
||||
@@ -612,13 +620,35 @@ try:
|
||||
except Exception as exc:
|
||||
errors.append(f"SQLite V049 migration execution failed: {exc}")
|
||||
|
||||
# V050 adds compact-wide-row and signature insertion preferences.
|
||||
try:
|
||||
conn = sqlite3.connect(":memory:")
|
||||
conn.executescript(
|
||||
"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)
|
||||
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("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__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")
|
||||
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}")
|
||||
schema_text = read(f"migrations/{dialect}/schema.sql")
|
||||
require("Current runtime schema: V049" in schema_text and "counter_generation" in schema_text and "blob_delete_outbox" in schema_text, f"fresh {dialect} schema includes V049 blob deletion outbox")
|
||||
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")
|
||||
queries = read(f"src/queries/{dialect}/queries.rs")
|
||||
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}")
|
||||
@@ -638,6 +668,7 @@ 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("latest_schema_version" in pool, "runtime schema version is computed from migrations")
|
||||
require(
|
||||
|
||||
Reference in new issue
Block a user