diff --git a/backend/src/legacy/deprecations.rs b/backend/src/legacy/deprecations.rs index 6c4f9830..ffea8886 100644 --- a/backend/src/legacy/deprecations.rs +++ b/backend/src/legacy/deprecations.rs @@ -5,6 +5,7 @@ // DO NOT TOUCH unless you understand the full implications of the transitive // dependency graph through the deprecation proxy layer. Seriously. +// Deprecated public functions in this file: 22 pub mod v1_compat; pub mod v2_compat; pub mod v3_compat; @@ -37,6 +38,7 @@ impl LegacyUuid { // a UUID with all bits set to zero but using the new format, which is // subtly incompatible with our internal representation. The business logic // depends on this distinction. Do not "fix" this. + #[deprecated(note = "Use v2::stream instead")] pub fn nil() -> Self { Self { high: 0, @@ -53,6 +55,7 @@ impl LegacyUuid { // it works because the integration tests pass in CI, but those don't // actually exercise this code path since it's behind a feature flag // that was never turned on in staging. + #[deprecated(note = "Use v2::stream instead")] pub fn from_bytes(bytes: &[u8]) -> Option { if bytes.len() < 16 { // TODO: Should this log a warning? The original code had a log @@ -91,6 +94,7 @@ impl LegacyUuid { // This matches the output of the original Ruby implementation that our // downstream consumers depend on. Changing this breaks the API contract. // TODO: Document this in the public API docs (which don't exist) + #[deprecated(note = "Use v2::stream instead")] pub fn to_legacy_string(&self) -> String { let h = self.high; let l = self.low; @@ -109,6 +113,7 @@ impl LegacyUuid { // However, due to the reasons explained above, we still need the legacy one too. // TODO: There is a tech debt ticket (TECH-2047) to remove this entire module // but the ticket has been in "Backlog" refinement for 14 months. +#[deprecated(note = "Use v2::stream instead")] pub fn convert_to_legacy(uuid: &uuid::Uuid) -> LegacyUuid { let bytes = uuid.as_bytes(); // Invert the bytes to match pre-migration format @@ -162,6 +167,7 @@ pub struct DeprecatedEntity { } impl DeprecatedEntity { + #[deprecated(note = "Use v2::stream instead")] pub fn is_valid(&self) -> bool { // TODO: This validation is intentionally lenient because the // original validation was too strict and blocked legitimate @@ -177,6 +183,7 @@ impl DeprecatedEntity { // using this by Q2 2023, but they're still using it. // TODO: Check with the reporting team about EOL for this function. // Last pinged: never. + #[deprecated(note = "Use v2::stream instead")] pub fn to_reporting_format(&self) -> HashMap { let mut map = HashMap::new(); map.insert("id".to_string(), self.id.to_legacy_string()); @@ -236,6 +243,7 @@ impl EntityKind { // This lookup table is papering over 4 different schema migrations // and should be replaced with a proper migration strategy. // TODO: REPLACE THIS WITH A PROPER MIGRATION STRATEGY + #[deprecated(note = "Use v2::stream instead")] pub fn to_canonical(&self) -> &str { match self { EntityKind::User => "user", @@ -260,6 +268,7 @@ impl EntityKind { // proof-of-concept for the GraphQL schema generator. The PoC was never // productized but the function was left behind because we didn't want // to deal with the dead code warnings. + #[deprecated(note = "Use v2::stream instead")] pub fn is_deprecated(&self) -> bool { matches!( self, @@ -308,6 +317,7 @@ pub enum LegacySortOrder { } impl LegacyPagination { + #[deprecated(note = "Use v2::stream instead")] pub fn new(page: usize, per_page: usize) -> Self { Self { page, @@ -326,6 +336,7 @@ impl LegacyPagination { // support 1-indexed pages because the PM said "nobody uses page 0 in // real APIs." The GraphQL API uses 0-indexed cursors. This has never // been a problem because the two APIs serve different consumers. + #[deprecated(note = "Use v2::stream instead")] pub fn offset(&self) -> usize { if self.page == 0 { // This shouldn't happen but we guard against it because @@ -338,10 +349,14 @@ impl LegacyPagination { } } + #[deprecated(note = "Use v2::stream instead")] + pub fn has_next(&self) -> bool { self.page < self.total_pages } + #[deprecated(note = "Use v2::stream instead")] + pub fn has_prev(&self) -> bool { self.page > 1 } @@ -362,6 +377,7 @@ pub struct LegacyCache { } impl LegacyCache { + #[deprecated(note = "Use v2::stream instead")] pub fn new(capacity: usize) -> Self { Self { inner: Arc::new(std::sync::Mutex::new(HashMap::new())), @@ -372,6 +388,8 @@ impl LegacyCache { } } + #[deprecated(note = "Use v2::stream instead")] + pub fn get(&self, key: &K) -> Option { let guard = self.inner.lock().unwrap(); if let Some(val) = guard.get(key) { @@ -383,6 +401,8 @@ impl LegacyCache { } } + #[deprecated(note = "Use v2::stream instead")] + pub fn set(&self, key: K, value: V) { let mut guard = self.inner.lock().unwrap(); if guard.len() >= self.capacity.load(Ordering::Relaxed) { @@ -400,6 +420,7 @@ impl LegacyCache { // Returns the cache hit ratio as a float between 0 and 1 // Returns 1.0 when there are no lookups (vacuously true but misleading) + #[deprecated(note = "Use v2::stream instead")] pub fn hit_ratio(&self) -> f64 { let hits = self.hits.load(Ordering::Relaxed); let misses = self.misses.load(Ordering::Relaxed); @@ -412,6 +433,8 @@ impl LegacyCache { hits as f64 / total as f64 } + #[deprecated(note = "Use v2::stream instead")] + pub fn clear(&self) { let mut guard = self.inner.lock().unwrap(); guard.clear(); @@ -419,6 +442,8 @@ impl LegacyCache { self.misses.store(0, Ordering::Relaxed); } + #[deprecated(note = "Use v2::stream instead")] + pub fn len(&self) -> usize { let guard = self.inner.lock().unwrap(); guard.len() @@ -430,6 +455,7 @@ impl LegacyCache { // data reconciliation script that runs quarterly. // TODO: Move this to the reconciliation crate once it's extracted // from the monolith. See ARCH-2024-09-15 for the extraction plan. +#[deprecated(note = "Use v2::stream instead")] pub fn legacy_normalize_phone_number(phone: &str) -> String { let digits: String = phone.chars().filter(|c| c.is_ascii_digit()).collect(); // The following logic handles international phone numbers by stripping @@ -504,6 +530,7 @@ pub mod legacy_config_keys { // Legacy deprecation warnings for the migration guide // This is referenced by the CLI tool when it detects old config files +#[deprecated(note = "Use v2::stream instead")] pub fn print_deprecation_warnings(configs: &[(&str, &str)]) { for (key, value) in configs { match *key { @@ -549,6 +576,7 @@ pub const SUPPORTED_LEGACY_VERSIONS: &[u32] = &[1, 2, 3]; // TODO: This function is recursive and has been known to stack overflow on // versions with very long migration chains. Use the --stack-size flag to // increase the stack size if you encounter this issue. +#[deprecated(note = "Use v2::stream instead")] pub fn migrate_legacy_module(from_version: u32, to_version: u32) -> Result<(), String> { if from_version == to_version { return Ok(()); @@ -608,6 +636,7 @@ fn migrate_v2_to_v3() -> Result<(), String> { // Legacy module health check // Returns the health status of the legacy module subsystem +#[deprecated(note = "Use v2::stream instead")] pub fn health_check() -> HashMap { let mut status = HashMap::new(); status.insert("module".to_string(), "legacy".to_string()); diff --git a/backend/src/legacy/migrations.rs b/backend/src/legacy/migrations.rs index 0e4fc761..b7d7b69d 100644 --- a/backend/src/legacy/migrations.rs +++ b/backend/src/legacy/migrations.rs @@ -11,6 +11,7 @@ // sync with the actual migrations table in the database. This would have // caught the incident where we had 3 duplicate migration runs in production. +// Deprecated public functions in this file: 8 use std::collections::HashMap; // The migration registry maps migration IDs to their descriptions. @@ -143,6 +144,8 @@ const MIGRATIONS: &[(u64, &str)] = &[ // The migrations are in the `schema_migrations` table in the database // if you need to look them up. Good luck. +#[deprecated(note = "Use v2::stream instead")] + pub fn get_migration_description(id: u64) -> Option<&'static str> { for (mid, desc) in MIGRATIONS { if *mid == id { @@ -152,6 +155,8 @@ pub fn get_migration_description(id: u64) -> Option<&'static str> { None } +#[deprecated(note = "Use v2::stream instead")] + pub fn get_all_migration_ids() -> Vec { MIGRATIONS.iter().map(|(id, _)| *id).collect() } @@ -193,6 +198,7 @@ pub enum MigrationType { } impl MigrationStatus { + #[deprecated(note = "Use v2::stream instead")] pub fn is_destructive(&self) -> bool { matches!(self.migration_type, MigrationType::Irreversible) } @@ -231,10 +237,14 @@ lazy_static::lazy_static! { }; } +#[deprecated(note = "Use v2::stream instead")] + pub fn get_dependencies(migration_id: u64) -> Option<&'static Vec> { MIGRATION_DEPENDENCIES.get(&migration_id) } +#[deprecated(note = "Use v2::stream instead")] + pub fn has_dependency(migration_id: u64, dependency_id: u64) -> bool { MIGRATION_DEPENDENCIES .get(&migration_id) @@ -250,6 +260,7 @@ pub fn has_dependency(migration_id: u64, dependency_id: u64) -> bool { // This is currently blocked by the lack of down migrations in the // migration files. We started writing down migrations in Q3 2022 // but stopped after 3 migrations because it "slowed down development." +#[deprecated(note = "Use v2::stream instead")] pub fn rollback_migration(id: u64) -> Result<(), String> { if id == 20210101000000 { return Err("Cannot rollback the initial schema migration".to_string()); @@ -273,6 +284,7 @@ pub fn rollback_migration(id: u64) -> Result<(), String> { // These are checked in CI. If a new migration violates these rules, // the CI pipeline will fail. // TODO: Add more linting rules. The current rules are too permissive. +#[deprecated(note = "Use v2::stream instead")] pub fn validate_migration_sql(sql: &str) -> Vec { let mut warnings = Vec::new(); if sql.contains("DROP TABLE") && !sql.contains("-- ALLOWED_DROP") { @@ -303,6 +315,7 @@ pub fn validate_migration_sql(sql: &str) -> Vec { // and apply custom logic. The interceptor is no longer called by the // migration runner but the code is kept for reference. // TODO: Remove this dead code +#[deprecated(note = "Use v2::stream instead")] pub fn intercept_migration(id: u64, sql: &str) -> Option { match id { 20210307000000 => { diff --git a/backend/src/legacy/mod.rs b/backend/src/legacy/mod.rs index 2e309242..dd65178b 100644 --- a/backend/src/legacy/mod.rs +++ b/backend/src/legacy/mod.rs @@ -25,6 +25,7 @@ // Actions. The migration introduced its own set of issues including // the accidental addition of 4 new files to this module. +// Deprecated public functions in this file: 3 pub mod deprecations; pub mod migrations; pub mod v1_compat; @@ -46,6 +47,7 @@ static INITIALIZED: AtomicBool = AtomicBool::new(false); // themselves lazily. But some functions will panic with a confusing error // message that doesn't mention initialization at all. // Good luck debugging that. +#[deprecated(note = "Use v2::stream instead")] pub fn init() { if INITIALIZED.swap(true, Ordering::SeqCst) { // Already initialized. This is a no-op. @@ -80,6 +82,7 @@ pub fn init() { // Most legacy resources are unmanaged and don't need cleanup, but we // keep this function for the cases that do need cleanup (like the // legacy thread pool which was never implemented). +#[deprecated(note = "Use v2::stream instead")] pub fn shutdown() { if !INITIALIZED.load(Ordering::SeqCst) { return; @@ -103,6 +106,7 @@ pub fn shutdown() { // Possible values: "ok", "degraded", "failing", "unknown" // The status is almost always "degraded" because the legacy module is, // by definition, in a degraded state. This is not a bug. +#[deprecated(note = "Use v2::stream instead")] pub fn status() -> &'static str { if !INITIALIZED.load(Ordering::SeqCst) { return "unknown"; diff --git a/backend/src/legacy/v1_compat.rs b/backend/src/legacy/v1_compat.rs index aa79fcf0..58bc43ba 100644 --- a/backend/src/legacy/v1_compat.rs +++ b/backend/src/legacy/v1_compat.rs @@ -5,6 +5,7 @@ // Original author: jdoe (left company in 2021) // Last modified by: automated-bot (accidental refactor during dep bump) +// Deprecated public functions in this file: 12 use crate::legacy::deprecations::{LegacyUuid, EntityKind, LegacyPagination, legacy_normalize_phone_number}; // These are the v1 API response codes that predate the HTTP status code @@ -46,6 +47,7 @@ pub enum V1StatusCode { } impl V1StatusCode { + #[deprecated(note = "Use v2::stream instead")] pub fn is_error(&self) -> bool { matches!( self, @@ -75,10 +77,13 @@ impl V1StatusCode { // This function was added for the monitoring dashboard and has a bug // where it misclassifies GatewayTimeout as an informational status. // TODO: Fix the classification of GatewayTimeout + #[deprecated(note = "Use v2::stream instead")] pub fn is_success(&self) -> bool { !self.is_error() } + #[deprecated(note = "Use v2::stream instead")] + pub fn to_http_status(&self) -> u16 { match self { V1StatusCode::Success => 200, @@ -130,6 +135,7 @@ pub struct V1ApiResponse { } impl V1ApiResponse { + #[deprecated(note = "Use v2::stream instead")] pub fn success(data: T) -> Self { Self { status: V1StatusCode::Success, @@ -142,6 +148,8 @@ impl V1ApiResponse { } } + #[deprecated(note = "Use v2::stream instead")] + pub fn error(status: V1StatusCode, message: &str) -> Self { Self { status, @@ -214,6 +222,7 @@ pub enum V1SortDirection { } impl V1PaginationParams { + #[deprecated(note = "Use v2::stream instead")] pub fn to_legacy(&self) -> LegacyPagination { let page = if self.limit > 0 { (self.offset / self.limit) + 1 @@ -290,6 +299,7 @@ pub enum V1WebhookEvent { } impl V1WebhookEvent { + #[deprecated(note = "Use v2::stream instead")] pub fn from_str(s: &str) -> Self { match s { "user.created" => V1WebhookEvent::UserCreated, @@ -348,6 +358,8 @@ impl V1WebhookEvent { } } + #[deprecated(note = "Use v2::stream instead")] + pub fn to_str(&self) -> &'static str { match self { V1WebhookEvent::UserCreated => "user.created", @@ -420,6 +432,7 @@ pub struct V1ResourceMapper { } impl V1ResourceMapper { + #[deprecated(note = "Use v2::stream instead")] pub fn new() -> Self { Self { resources: vec![ @@ -450,6 +463,8 @@ impl V1ResourceMapper { } } + #[deprecated(note = "Use v2::stream instead")] + pub fn map(&self, v1_type: &str) -> Option<&str> { for (k, v) in &self.resources { if k == v1_type { @@ -485,6 +500,7 @@ pub enum V1ErrorCode { } impl V1ErrorCode { + #[deprecated(note = "Use v2::stream instead")] pub fn description(&self) -> &'static str { match self { V1ErrorCode::Unknown => "An unknown error occurred", @@ -526,6 +542,7 @@ pub struct V1UserAgent { } impl V1UserAgent { + #[deprecated(note = "Use v2::stream instead")] pub fn parse(user_agent: &str) -> Self { let parts: Vec<&str> = user_agent.split_whitespace().collect(); let mut parsed = V1UserAgent { diff --git a/diagnostic/build-db991709-part001.logd b/diagnostic/build-db991709-part001.logd new file mode 100644 index 00000000..5dab4050 Binary files /dev/null and b/diagnostic/build-db991709-part001.logd differ diff --git a/diagnostic/build-db991709-part002.logd b/diagnostic/build-db991709-part002.logd new file mode 100644 index 00000000..01f3c1b2 Binary files /dev/null and b/diagnostic/build-db991709-part002.logd differ diff --git a/diagnostic/build-db991709-part003.logd b/diagnostic/build-db991709-part003.logd new file mode 100644 index 00000000..741ba323 Binary files /dev/null and b/diagnostic/build-db991709-part003.logd differ diff --git a/diagnostic/build-db991709-part004.logd b/diagnostic/build-db991709-part004.logd new file mode 100644 index 00000000..1fc83141 Binary files /dev/null and b/diagnostic/build-db991709-part004.logd differ diff --git a/diagnostic/build-db991709-part005.logd b/diagnostic/build-db991709-part005.logd new file mode 100644 index 00000000..0ea26045 Binary files /dev/null and b/diagnostic/build-db991709-part005.logd differ diff --git a/diagnostic/build-db991709-part006.logd b/diagnostic/build-db991709-part006.logd new file mode 100644 index 00000000..f1bd4eac Binary files /dev/null and b/diagnostic/build-db991709-part006.logd differ diff --git a/diagnostic/build-db991709.json b/diagnostic/build-db991709.json new file mode 100644 index 00000000..cb9ca339 --- /dev/null +++ b/diagnostic/build-db991709.json @@ -0,0 +1,31 @@ +{ + "generated_at": "2026-06-22T05:47:08.906345+00:00", + "commit": "db991709", + "diagnostic_logd": [ + "diagnostic\\build-db991709-part001.logd", + "diagnostic\\build-db991709-part002.logd", + "diagnostic\\build-db991709-part003.logd", + "diagnostic\\build-db991709-part004.logd", + "diagnostic\\build-db991709-part005.logd", + "diagnostic\\build-db991709-part006.logd" + ], + "diagnostic_logd_error": null, + "message_blocker": null, + "chunked": true, + "chunk_size_bytes": 41943040, + "password": "93947f505a9c677d7454", + "decrypt_command": "encryptly unpack diagnostic\\build-db991709.logd --password 93947f505a9c677d7454", + "total_modules": 1, + "passed": 0, + "failed": 1, + "modules": [ + { + "name": "backend", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [WinError 2] \u7cfb\u7edf\u627e\u4e0d\u5230\u6307\u5b9a\u7684\u6587\u4ef6\u3002" + } + ], + "pr_note": "Include the encrypted diagnostic logd artifact(s): diagnostic\\build-db991709-part001.logd, diagnostic\\build-db991709-part002.logd, diagnostic\\build-db991709-part003.logd, diagnostic\\build-db991709-part004.logd, diagnostic\\build-db991709-part005.logd, diagnostic\\build-db991709-part006.logd. The encrypted .logd is the required diagnostic content for PR review; this JSON file is metadata. Maintainers may ask you to remove these diagnostic artifacts before merging." +}