lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit 540e3195cf280da694baa340512769a13541b635
parent da49136dc0f2b34720ffc3db349b8e63e2f62fd5
Author: triesap <tyson@radroots.org>
Date:   Sun,  1 Mar 2026 16:16:20 +0000

refactor: harden replica naming and legacy identifier guard

Diffstat:
M.github/workflows/sdk-contract-ci.yml | 3+++
Mcontract/manifest.toml | 2+-
Mcontract/replica.toml | 2+-
Mcrates/replica-db-wasm/src/wasm_impl.rs | 10+++++-----
Mcrates/replica-db/src/backup.rs | 16++++++++--------
Mcrates/replica-db/src/export.rs | 14+++++++-------
Mcrates/replica-db/src/lib.rs | 20++++++++++----------
Mcrates/replica-db/tests/full_mode.rs | 6+++---
Mcrates/replica-sync/src/error.rs | 20++++++++++----------
Mcrates/replica-sync/src/ingest.rs | 4++--
Mcrates/sql-core/src/export_lock.rs | 2+-
Ascripts/ci/guard_no_legacy_identifiers.sh | 17+++++++++++++++++
12 files changed, 68 insertions(+), 48 deletions(-)

diff --git a/.github/workflows/sdk-contract-ci.yml b/.github/workflows/sdk-contract-ci.yml @@ -16,6 +16,9 @@ jobs: - name: guard committed ts artifacts run: ./scripts/ci/guard_committed_ts_artifacts.sh + - name: guard legacy identifiers + run: ./scripts/ci/guard_no_legacy_identifiers.sh + - name: install rust toolchain uses: dtolnay/rust-toolchain@stable with: diff --git a/contract/manifest.toml b/contract/manifest.toml @@ -41,6 +41,6 @@ require_reproducible_exports = true require_conformance_vectors = true [policy.replica] -forbid_legacy_tangle_identifiers = true +forbid_legacy_alias_identifiers = true require_transport_agnostic_sync_contract = true require_deterministic_emit_ingest = true diff --git a/contract/replica.toml b/contract/replica.toml @@ -14,4 +14,4 @@ sync_wasm = "radroots-replica-sync-wasm" transport_agnostic_sync_core = true deterministic_emit_and_ingest = true wasm_exports_prefix = "replica_" -forbid_legacy_tangle_identifiers = true +forbid_legacy_alias_identifiers = true diff --git a/crates/replica-db-wasm/src/wasm_impl.rs b/crates/replica-db-wasm/src/wasm_impl.rs @@ -3,7 +3,7 @@ use radroots_sql_core::{ }; use radroots_sql_wasm_core::{err_js, parse_json}; use radroots_replica_db::migrations; -use radroots_replica_db::{TangleDbExportManifestRs, export_manifest}; +use radroots_replica_db::{ReplicaDbExportManifestRs, export_manifest}; use radroots_replica_sync::radroots_replica_sync_status; use wasm_bindgen::JsValue; use wasm_bindgen::prelude::*; @@ -141,7 +141,7 @@ fn export_snapshot(exec: &WasmSqlExecutor) -> Result<JsValue, JsValue> { if status.pending_count > 0 { return Err(err_js(radroots_sql_core::SqlError::InvalidArgument( format!( - "tangle db export requires synced state (pending {}/{})", + "replica db export requires synced state (pending {}/{})", status.pending_count, status.expected_count ), ))); @@ -150,13 +150,13 @@ fn export_snapshot(exec: &WasmSqlExecutor) -> Result<JsValue, JsValue> { export_snapshot_value(manifest) } -fn export_snapshot_value(manifest: TangleDbExportManifestRs) -> Result<JsValue, JsValue> { +fn export_snapshot_value(manifest: ReplicaDbExportManifestRs) -> Result<JsValue, JsValue> { let bytes_js = radroots_sql_wasm_core::export_bytes(); export_snapshot_value_with_bytes(manifest, bytes_js) } fn export_snapshot_value_with_bytes( - manifest: TangleDbExportManifestRs, + manifest: ReplicaDbExportManifestRs, bytes_js: JsValue, ) -> Result<JsValue, JsValue> { let manifest_js = serde_wasm_bindgen::to_value(&manifest).map_err(|err| { @@ -180,7 +180,7 @@ mod tests { #[wasm_bindgen_test::wasm_bindgen_test] fn export_snapshot_value_includes_fields() { - let manifest = radroots_replica_db::TangleDbExportManifestRs { + let manifest = radroots_replica_db::ReplicaDbExportManifestRs { export_version: "1".to_string(), replica_db_version: "0.0.0".to_string(), backup_format_version: "0.0.0".to_string(), diff --git a/crates/replica-db/src/backup.rs b/crates/replica-db/src/backup.rs @@ -4,7 +4,7 @@ use serde_json::{Map, Value}; use std::collections::{BTreeMap, HashMap}; pub const DATABASE_BACKUP_VERSION: &str = "1.0.0"; -pub const TANGLE_DB_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const REPLICA_DB_VERSION: &str = env!("CARGO_PKG_VERSION"); #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SchemaEntry { @@ -44,7 +44,7 @@ pub fn export_database_backup<E: SqlExecutor>(executor: &E) -> Result<DatabaseBa let migrations = export_migrations(); Ok(DatabaseBackup { format_version: DATABASE_BACKUP_VERSION.to_string(), - replica_db_version: TANGLE_DB_VERSION.to_string(), + replica_db_version: REPLICA_DB_VERSION.to_string(), schema, migrations, data, @@ -277,10 +277,10 @@ fn validate_backup_version(backup: &DatabaseBackup) -> Result<(), SqlError> { backup.format_version, DATABASE_BACKUP_VERSION ))); } - if backup.replica_db_version != TANGLE_DB_VERSION { + if backup.replica_db_version != REPLICA_DB_VERSION { return Err(SqlError::InvalidArgument(format!( "unsupported replica-db version {}, expected {}", - backup.replica_db_version, TANGLE_DB_VERSION + backup.replica_db_version, REPLICA_DB_VERSION ))); } Ok(()) @@ -394,7 +394,7 @@ mod tests { ); let backup = DatabaseBackup { format_version: DATABASE_BACKUP_VERSION.to_string(), - replica_db_version: TANGLE_DB_VERSION.to_string(), + replica_db_version: REPLICA_DB_VERSION.to_string(), schema: vec![SchemaEntry { object_type: String::from("table"), name: String::from("fail_table"), @@ -517,7 +517,7 @@ mod tests { row.insert(String::from("co\"l"), Value::from(7)); let backup = DatabaseBackup { format_version: DATABASE_BACKUP_VERSION.to_string(), - replica_db_version: TANGLE_DB_VERSION.to_string(), + replica_db_version: REPLICA_DB_VERSION.to_string(), schema: vec![ SchemaEntry { object_type: String::from("table"), @@ -581,7 +581,7 @@ mod tests { #[test] fn validate_backup_version_rejects_invalid_versions() { - let wrong_format = backup_with_versions("0.0.1", TANGLE_DB_VERSION); + let wrong_format = backup_with_versions("0.0.1", REPLICA_DB_VERSION); let err = validate_backup_version(&wrong_format).expect_err("format version must fail"); assert!(matches!(err, SqlError::InvalidArgument(_))); @@ -599,7 +599,7 @@ mod tests { )], None, ); - let backup = backup_with_versions(DATABASE_BACKUP_VERSION, TANGLE_DB_VERSION); + let backup = backup_with_versions(DATABASE_BACKUP_VERSION, REPLICA_DB_VERSION); let matched = executor .query_raw("select type, name from sqlite_master", "[]") diff --git a/crates/replica-db/src/export.rs b/crates/replica-db/src/export.rs @@ -3,11 +3,11 @@ use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use crate::backup::{ - DATABASE_BACKUP_VERSION, MigrationBackup, SchemaEntry, TANGLE_DB_VERSION, escape_identifier, + DATABASE_BACKUP_VERSION, MigrationBackup, SchemaEntry, REPLICA_DB_VERSION, escape_identifier, export_migrations, load_schema, }; -pub const TANGLE_DB_EXPORT_VERSION: &str = "1"; +pub const REPLICA_DB_EXPORT_VERSION: &str = "1"; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TableCount { @@ -16,7 +16,7 @@ pub struct TableCount { } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TangleDbExportManifestRs { +pub struct ReplicaDbExportManifestRs { pub export_version: String, pub replica_db_version: String, pub backup_format_version: String, @@ -26,14 +26,14 @@ pub struct TangleDbExportManifestRs { pub table_counts: Vec<TableCount>, } -pub fn export_manifest<E: SqlExecutor>(executor: &E) -> Result<TangleDbExportManifestRs, SqlError> { +pub fn export_manifest<E: SqlExecutor>(executor: &E) -> Result<ReplicaDbExportManifestRs, SqlError> { let schema = load_schema(executor)?; let migrations = export_migrations(); let table_counts = load_table_counts(executor, &schema)?; let schema_hash = schema_hash(&schema)?; - Ok(TangleDbExportManifestRs { - export_version: TANGLE_DB_EXPORT_VERSION.to_string(), - replica_db_version: TANGLE_DB_VERSION.to_string(), + Ok(ReplicaDbExportManifestRs { + export_version: REPLICA_DB_EXPORT_VERSION.to_string(), + replica_db_version: REPLICA_DB_VERSION.to_string(), backup_format_version: DATABASE_BACKUP_VERSION.to_string(), schema_hash, schema, diff --git a/crates/replica-db/src/lib.rs b/crates/replica-db/src/lib.rs @@ -121,22 +121,22 @@ pub mod models; #[cfg(not(feature = "coverage-minimal"))] pub use backup::{DatabaseBackup, MigrationBackup, SchemaEntry}; #[cfg(not(feature = "coverage-minimal"))] -pub use export::{TANGLE_DB_EXPORT_VERSION, TableCount, TangleDbExportManifestRs, export_manifest}; +pub use export::{REPLICA_DB_EXPORT_VERSION, TableCount, ReplicaDbExportManifestRs, export_manifest}; #[cfg(not(feature = "coverage-minimal"))] pub use models::*; -pub struct TangleSql<E: SqlExecutor> { +pub struct ReplicaSql<E: SqlExecutor> { executor: E, } -impl<E: SqlExecutor> TangleSql<E> { +impl<E: SqlExecutor> ReplicaSql<E> { pub fn coverage_branch_probe(enabled: bool) -> &'static str { if enabled { "enabled" } else { "disabled" } } } #[cfg(not(feature = "coverage-minimal"))] -impl<E: SqlExecutor> TangleSql<E> { +impl<E: SqlExecutor> ReplicaSql<E> { pub fn new(executor: E) -> Self { Self { executor } } @@ -720,7 +720,7 @@ impl<E: SqlExecutor> TangleSql<E> { } #[cfg(feature = "coverage-minimal")] -impl<E: SqlExecutor> TangleSql<E> { +impl<E: SqlExecutor> ReplicaSql<E> { pub fn new(executor: E) -> Self { Self { executor } } @@ -732,7 +732,7 @@ impl<E: SqlExecutor> TangleSql<E> { #[cfg(test)] mod tests { - use super::TangleSql; + use super::ReplicaSql; use radroots_sql_core::{ExecOutcome, SqlError, SqlExecutor}; struct ProbeExecutor; @@ -763,8 +763,8 @@ mod tests { } #[test] - fn tangle_sql_constructor_and_executor_access_are_supported() { - let db = TangleSql::new(ProbeExecutor); + fn replica_sql_constructor_and_executor_access_are_supported() { + let db = ReplicaSql::new(ProbeExecutor); let exec = db.executor(); assert!(exec.exec("select 1", "[]").is_ok()); assert!(exec.query_raw("select 1", "[]").is_ok()); @@ -772,11 +772,11 @@ mod tests { assert!(exec.commit().is_ok()); assert!(exec.rollback().is_ok()); assert_eq!( - TangleSql::<ProbeExecutor>::coverage_branch_probe(true), + ReplicaSql::<ProbeExecutor>::coverage_branch_probe(true), "enabled" ); assert_eq!( - TangleSql::<ProbeExecutor>::coverage_branch_probe(false), + ReplicaSql::<ProbeExecutor>::coverage_branch_probe(false), "disabled" ); } diff --git a/crates/replica-db/tests/full_mode.rs b/crates/replica-db/tests/full_mode.rs @@ -1,5 +1,5 @@ use radroots_sql_core::{SqlError, SqliteExecutor}; -use radroots_replica_db::{TangleSql, export_manifest}; +use radroots_replica_db::{ReplicaSql, export_manifest}; use radroots_replica_db_schema::farm::{ IFarmCreate, IFarmDelete, IFarmFindMany, IFarmFindOne, IFarmUpdate, }; @@ -87,9 +87,9 @@ fn assert_not_found<T>(result: Result<T, IError<SqlError>>) { assert!(matches!(err.err, SqlError::NotFound(_))); } -fn open_db() -> TangleSql<SqliteExecutor> { +fn open_db() -> ReplicaSql<SqliteExecutor> { let exec = SqliteExecutor::open_memory().expect("open sqlite memory"); - let db = TangleSql::new(exec); + let db = ReplicaSql::new(exec); db.migrate_up().expect("migrate up"); db } diff --git a/crates/replica-sync/src/error.rs b/crates/replica-sync/src/error.rs @@ -19,11 +19,11 @@ pub enum RadrootsReplicaEventsError { impl fmt::Display for RadrootsReplicaEventsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Sql(err) => write!(f, "tangle_events.sql: {}", err.err.to_string()), - Self::Encode(err) => write!(f, "tangle_events.encode: {err}"), - Self::Parse(err) => write!(f, "tangle_events.parse: {err}"), - Self::InvalidSelector(msg) => write!(f, "tangle_events.selector: {msg}"), - Self::InvalidData(msg) => write!(f, "tangle_events.data: {msg}"), + Self::Sql(err) => write!(f, "replica_sync.sql: {}", err.err.to_string()), + Self::Encode(err) => write!(f, "replica_sync.encode: {err}"), + Self::Parse(err) => write!(f, "replica_sync.parse: {err}"), + Self::InvalidSelector(msg) => write!(f, "replica_sync.selector: {msg}"), + Self::InvalidData(msg) => write!(f, "replica_sync.data: {msg}"), } } } @@ -59,20 +59,20 @@ mod tests { #[test] fn display_formats_all_error_variants() { let sql_err = RadrootsReplicaEventsError::Sql(IError::from(SqlError::Internal)); - assert!(sql_err.to_string().contains("tangle_events.sql")); + assert!(sql_err.to_string().contains("replica_sync.sql")); let encode_err = RadrootsReplicaEventsError::Encode(EventEncodeError::InvalidField("name")); - assert!(encode_err.to_string().contains("tangle_events.encode")); + assert!(encode_err.to_string().contains("replica_sync.encode")); let parse_err = RadrootsReplicaEventsError::Parse(EventParseError::InvalidTag("d")); - assert!(parse_err.to_string().contains("tangle_events.parse")); + assert!(parse_err.to_string().contains("replica_sync.parse")); let selector_err = RadrootsReplicaEventsError::InvalidSelector("selector missing".to_string()); - assert!(selector_err.to_string().contains("tangle_events.selector")); + assert!(selector_err.to_string().contains("replica_sync.selector")); let data_err = RadrootsReplicaEventsError::InvalidData("bad data".to_string()); - assert!(data_err.to_string().contains("tangle_events.data")); + assert!(data_err.to_string().contains("replica_sync.data")); } #[test] diff --git a/crates/replica-sync/src/ingest.rs b/crates/replica-sync/src/ingest.rs @@ -1948,10 +1948,10 @@ mod tests { fn create_gcs_location_error_mapping_helpers_are_covered() { let point_json_err = serde_json::from_str::<Value>("{").expect_err("invalid json"); let point_err = map_gcs_point_serialize_error(point_json_err); - assert_eq!(point_err.to_string(), "tangle_events.data: gcs.point"); + assert_eq!(point_err.to_string(), "replica_sync.data: gcs.point"); let polygon_json_err = serde_json::from_str::<Value>("{").expect_err("invalid json"); let polygon_err = map_gcs_polygon_serialize_error(polygon_json_err); - assert_eq!(polygon_err.to_string(), "tangle_events.data: gcs.polygon"); + assert_eq!(polygon_err.to_string(), "replica_sync.data: gcs.polygon"); } } diff --git a/crates/sql-core/src/export_lock.rs b/crates/sql-core/src/export_lock.rs @@ -5,7 +5,7 @@ use crate::error::SqlError; use std::cell::Cell; use std::sync::atomic::{AtomicBool, Ordering}; -pub(crate) const EXPORT_LOCK_ERR: &str = "tangle db export in progress"; +pub(crate) const EXPORT_LOCK_ERR: &str = "replica db export in progress"; static EXPORT_LOCK_ACTIVE: AtomicBool = AtomicBool::new(false); diff --git a/scripts/ci/guard_no_legacy_identifiers.sh b/scripts/ci/guard_no_legacy_identifiers.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +matches="$( + git grep -nI 'tangle' -- . \ + ':(exclude)AGENTS.md' \ + ':(exclude)scripts/ci/guard_no_legacy_identifiers.sh' \ + || true +)" + +if [[ -n "$matches" ]]; then + echo "legacy identifier 'tangle' is forbidden in tracked oss files" + echo "$matches" + exit 1 +fi + +echo "no legacy 'tangle' identifiers found in tracked oss files"