lib

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

service_source_lock_v3.rs (20647B)


      1 use std::fmt;
      2 
      3 use serde::Deserialize;
      4 
      5 use crate::service_source_lock::{ContractVersions, LIB_REPOSITORY};
      6 
      7 pub(crate) const LOCK_FILENAME: &str = "radroots.service.source-lock.v3.toml";
      8 pub(crate) const PREDECESSOR_LOCK_FILENAME: &str = "radroots.service.source-lock.v2.toml";
      9 const MAX_LOCK_BYTES: usize = 16 * 1024;
     10 const ARCHITECTURE: &str = "radroots.crates.release.v2";
     11 const LIB_VERSION: &str = "0.1.0-alpha";
     12 const RUST_VERSION: &str = "1.97.1";
     13 const HOST_FEATURE_PROFILE: &str = "service-host";
     14 
     15 #[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
     16 #[serde(deny_unknown_fields)]
     17 struct RawSourceArchiveContract {
     18     binding: String,
     19     format: String,
     20     compression: String,
     21     compression_timestamp: String,
     22     entry_order: String,
     23     path_prefix: String,
     24     file_mode: String,
     25     uid: u32,
     26     gid: u32,
     27     uname: String,
     28     gname: String,
     29     mtime: String,
     30     pax_headers: String,
     31     directory_entries: String,
     32     symlinks: String,
     33     hardlinks: String,
     34     submodules: String,
     35     trailer: String,
     36 }
     37 
     38 #[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
     39 #[serde(deny_unknown_fields)]
     40 struct RawPublicInputLock {
     41     path: String,
     42     sha256: String,
     43     binding: String,
     44     mutable_reference: String,
     45     lib_input: String,
     46 }
     47 
     48 #[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
     49 #[serde(deny_unknown_fields)]
     50 struct RawParentResult {
     51     embedded_in_public_input_lock: bool,
     52     embedded_in_source_lock: bool,
     53     storage: String,
     54 }
     55 
     56 #[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
     57 #[serde(deny_unknown_fields)]
     58 struct RawQualifiedNix {
     59     material: String,
     60     lib_revision: String,
     61     supported_systems: Vec<String>,
     62     public_input_lock: RawPublicInputLock,
     63     parent_result: RawParentResult,
     64 }
     65 
     66 #[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
     67 #[serde(deny_unknown_fields)]
     68 struct RawArtifactContract {
     69     path: String,
     70     sha256: String,
     71     binding: String,
     72 }
     73 
     74 #[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
     75 #[serde(deny_unknown_fields)]
     76 struct RawSqliteContract {
     77     high_level_authority: String,
     78     second_pool_connection_query_transaction_migration_authority: String,
     79     incremental_backup_adapter: String,
     80     native_linkage_count: u32,
     81 }
     82 
     83 #[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
     84 #[serde(deny_unknown_fields)]
     85 struct RawServiceSourceLockV3 {
     86     schema: String,
     87     contract_version: u32,
     88     service: String,
     89     repository: String,
     90     revision: String,
     91     architecture: String,
     92     workspace_catalog_sha256: String,
     93     version: String,
     94     source_archive_sha256: String,
     95     source_archive_contract: RawSourceArchiveContract,
     96     cargo_lock_sha256: String,
     97     rust_version: String,
     98     host_feature_profile: String,
     99     nix: RawQualifiedNix,
    100     artifact_contract: RawArtifactContract,
    101     sqlite: RawSqliteContract,
    102     contract_versions: ContractVersions,
    103 }
    104 
    105 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    106 pub(crate) enum ServiceSourceLockV3Error {
    107     TooLarge,
    108     Malformed,
    109     Noncanonical,
    110     Invalid,
    111 }
    112 
    113 impl fmt::Display for ServiceSourceLockV3Error {
    114     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    115         formatter.write_str(match self {
    116             Self::TooLarge => "service source lock v3 exceeds its byte limit",
    117             Self::Malformed => "service source lock v3 is malformed",
    118             Self::Noncanonical => "service source lock v3 is not canonical",
    119             Self::Invalid => "service source lock v3 is invalid",
    120         })
    121     }
    122 }
    123 
    124 impl std::error::Error for ServiceSourceLockV3Error {}
    125 
    126 #[derive(Clone, Eq, PartialEq)]
    127 pub(crate) struct ServiceSourceLockV3 {
    128     raw: RawServiceSourceLockV3,
    129     canonical: Box<[u8]>,
    130 }
    131 
    132 #[cfg(test)]
    133 pub(crate) struct FixtureParts<'a> {
    134     pub(crate) service: &'a str,
    135     pub(crate) revision: &'a str,
    136     pub(crate) workspace_catalog_sha256: &'a str,
    137     pub(crate) source_archive_sha256: &'a str,
    138     pub(crate) cargo_lock_sha256: &'a str,
    139     pub(crate) flake_lock_sha256: &'a str,
    140     pub(crate) artifact_contract_sha256: &'a str,
    141     pub(crate) contract_versions: ContractVersions,
    142 }
    143 
    144 impl fmt::Debug for ServiceSourceLockV3 {
    145     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    146         formatter
    147             .debug_struct("ServiceSourceLockV3")
    148             .finish_non_exhaustive()
    149     }
    150 }
    151 
    152 impl ServiceSourceLockV3 {
    153     pub(crate) fn from_canonical_bytes(bytes: &[u8]) -> Result<Self, ServiceSourceLockV3Error> {
    154         if bytes.len() > MAX_LOCK_BYTES {
    155             return Err(ServiceSourceLockV3Error::TooLarge);
    156         }
    157         let text = std::str::from_utf8(bytes).map_err(|_| ServiceSourceLockV3Error::Malformed)?;
    158         let raw = toml::from_str::<RawServiceSourceLockV3>(text)
    159             .map_err(|_| ServiceSourceLockV3Error::Malformed)?;
    160         validate(&raw)?;
    161         let canonical = canonical_bytes(&raw);
    162         if bytes != canonical.as_slice() {
    163             return Err(ServiceSourceLockV3Error::Noncanonical);
    164         }
    165         Ok(Self {
    166             raw,
    167             canonical: canonical.into_boxed_slice(),
    168         })
    169     }
    170 
    171     #[cfg(test)]
    172     pub(crate) fn canonical_bytes(&self) -> &[u8] {
    173         &self.canonical
    174     }
    175 
    176     pub(crate) fn service(&self) -> &str {
    177         &self.raw.service
    178     }
    179 
    180     pub(crate) fn revision(&self) -> &str {
    181         &self.raw.revision
    182     }
    183 
    184     pub(crate) fn workspace_catalog_sha256(&self) -> &str {
    185         &self.raw.workspace_catalog_sha256
    186     }
    187 
    188     pub(crate) fn source_archive_sha256(&self) -> &str {
    189         &self.raw.source_archive_sha256
    190     }
    191 
    192     pub(crate) fn cargo_lock_sha256(&self) -> &str {
    193         &self.raw.cargo_lock_sha256
    194     }
    195 
    196     pub(crate) fn flake_lock_sha256(&self) -> &str {
    197         &self.raw.nix.public_input_lock.sha256
    198     }
    199 
    200     pub(crate) fn artifact_contract_path(&self) -> &str {
    201         &self.raw.artifact_contract.path
    202     }
    203 
    204     pub(crate) fn artifact_contract_sha256(&self) -> &str {
    205         &self.raw.artifact_contract.sha256
    206     }
    207 
    208     pub(crate) const fn contract_versions(&self) -> ContractVersions {
    209         self.raw.contract_versions
    210     }
    211 
    212     #[cfg(test)]
    213     pub(crate) fn fixture(parts: FixtureParts<'_>) -> Result<Self, ServiceSourceLockV3Error> {
    214         let FixtureParts {
    215             service,
    216             revision,
    217             workspace_catalog_sha256,
    218             source_archive_sha256,
    219             cargo_lock_sha256,
    220             flake_lock_sha256,
    221             artifact_contract_sha256,
    222             contract_versions,
    223         } = parts;
    224         let artifact_path = format!("contracts/release/{service}-artifact-contract.v3.json");
    225         let raw = RawServiceSourceLockV3 {
    226             schema: "radroots.service.source-lock.v3".to_owned(),
    227             contract_version: 3,
    228             service: service.to_owned(),
    229             repository: LIB_REPOSITORY.to_owned(),
    230             revision: revision.to_owned(),
    231             architecture: ARCHITECTURE.to_owned(),
    232             workspace_catalog_sha256: workspace_catalog_sha256.to_owned(),
    233             version: LIB_VERSION.to_owned(),
    234             source_archive_sha256: source_archive_sha256.to_owned(),
    235             source_archive_contract: RawSourceArchiveContract {
    236                 binding: "sha256_of_canonical_exact_lib_revision_tree_archive".to_owned(),
    237                 format: "ustar".to_owned(),
    238                 compression: "none".to_owned(),
    239                 compression_timestamp: "not_applicable".to_owned(),
    240                 entry_order: "bytewise_git_path".to_owned(),
    241                 path_prefix: "none".to_owned(),
    242                 file_mode: "git_index_100644_or_100755".to_owned(),
    243                 uid: 0,
    244                 gid: 0,
    245                 uname: String::new(),
    246                 gname: String::new(),
    247                 mtime: "lib_revision_commit_timestamp".to_owned(),
    248                 pax_headers: "forbidden".to_owned(),
    249                 directory_entries: "omitted".to_owned(),
    250                 symlinks: "forbidden".to_owned(),
    251                 hardlinks: "forbidden".to_owned(),
    252                 submodules: "forbidden".to_owned(),
    253                 trailer: "two_zero_blocks".to_owned(),
    254             },
    255             cargo_lock_sha256: cargo_lock_sha256.to_owned(),
    256             rust_version: RUST_VERSION.to_owned(),
    257             host_feature_profile: HOST_FEATURE_PROFILE.to_owned(),
    258             nix: RawQualifiedNix {
    259                 material: "qualified".to_owned(),
    260                 lib_revision: revision.to_owned(),
    261                 supported_systems: vec!["aarch64-darwin".to_owned(), "x86_64-linux".to_owned()],
    262                 public_input_lock: RawPublicInputLock {
    263                     path: "flake.lock".to_owned(),
    264                     sha256: flake_lock_sha256.to_owned(),
    265                     binding: "exact_regular_file_bytes".to_owned(),
    266                     mutable_reference: "forbidden".to_owned(),
    267                     lib_input: "lib".to_owned(),
    268                 },
    269                 parent_result: RawParentResult {
    270                     embedded_in_public_input_lock: false,
    271                     embedded_in_source_lock: false,
    272                     storage: "separate_generation_scoped_evidence".to_owned(),
    273                 },
    274             },
    275             artifact_contract: RawArtifactContract {
    276                 path: artifact_path,
    277                 sha256: artifact_contract_sha256.to_owned(),
    278                 binding: "exact_regular_file_bytes_in_same_source_revision".to_owned(),
    279             },
    280             sqlite: RawSqliteContract {
    281                 high_level_authority: "sqlx_only".to_owned(),
    282                 second_pool_connection_query_transaction_migration_authority: "forbidden"
    283                     .to_owned(),
    284                 incremental_backup_adapter: "sealed_native_sqlx_owned_locked_handle_only"
    285                     .to_owned(),
    286                 native_linkage_count: 1,
    287             },
    288             contract_versions,
    289         };
    290         validate(&raw)?;
    291         let canonical = canonical_bytes(&raw).into_boxed_slice();
    292         Ok(Self { raw, canonical })
    293     }
    294 }
    295 
    296 fn validate(raw: &RawServiceSourceLockV3) -> Result<(), ServiceSourceLockV3Error> {
    297     let expected_artifact = match raw.service.as_str() {
    298         "myc" => "contracts/release/myc-artifact-contract.v3.json",
    299         "rhi" => "contracts/release/rhi-artifact-contract.v3.json",
    300         _ => return Err(ServiceSourceLockV3Error::Invalid),
    301     };
    302     let archive = &raw.source_archive_contract;
    303     if raw.schema != "radroots.service.source-lock.v3"
    304         || raw.contract_version != 3
    305         || raw.repository != LIB_REPOSITORY
    306         || !valid_lower_hex(&raw.revision, 40)
    307         || raw.architecture != ARCHITECTURE
    308         || !valid_lower_hex(&raw.workspace_catalog_sha256, 64)
    309         || raw.version != LIB_VERSION
    310         || !valid_lower_hex(&raw.source_archive_sha256, 64)
    311         || !valid_lower_hex(&raw.cargo_lock_sha256, 64)
    312         || raw.rust_version != RUST_VERSION
    313         || raw.host_feature_profile != HOST_FEATURE_PROFILE
    314         || archive.binding != "sha256_of_canonical_exact_lib_revision_tree_archive"
    315         || archive.format != "ustar"
    316         || archive.compression != "none"
    317         || archive.compression_timestamp != "not_applicable"
    318         || archive.entry_order != "bytewise_git_path"
    319         || archive.path_prefix != "none"
    320         || archive.file_mode != "git_index_100644_or_100755"
    321         || archive.uid != 0
    322         || archive.gid != 0
    323         || !archive.uname.is_empty()
    324         || !archive.gname.is_empty()
    325         || archive.mtime != "lib_revision_commit_timestamp"
    326         || archive.pax_headers != "forbidden"
    327         || archive.directory_entries != "omitted"
    328         || archive.symlinks != "forbidden"
    329         || archive.hardlinks != "forbidden"
    330         || archive.submodules != "forbidden"
    331         || archive.trailer != "two_zero_blocks"
    332         || raw.nix.material != "qualified"
    333         || raw.nix.lib_revision != raw.revision
    334         || raw.nix.supported_systems != ["aarch64-darwin", "x86_64-linux"]
    335         || raw.nix.public_input_lock.path != "flake.lock"
    336         || !valid_lower_hex(&raw.nix.public_input_lock.sha256, 64)
    337         || raw.nix.public_input_lock.binding != "exact_regular_file_bytes"
    338         || raw.nix.public_input_lock.mutable_reference != "forbidden"
    339         || raw.nix.public_input_lock.lib_input != "lib"
    340         || raw.nix.parent_result.embedded_in_public_input_lock
    341         || raw.nix.parent_result.embedded_in_source_lock
    342         || raw.nix.parent_result.storage != "separate_generation_scoped_evidence"
    343         || raw.artifact_contract.path != expected_artifact
    344         || !valid_lower_hex(&raw.artifact_contract.sha256, 64)
    345         || raw.artifact_contract.binding != "exact_regular_file_bytes_in_same_source_revision"
    346         || raw.sqlite.high_level_authority != "sqlx_only"
    347         || raw
    348             .sqlite
    349             .second_pool_connection_query_transaction_migration_authority
    350             != "forbidden"
    351         || raw.sqlite.incremental_backup_adapter != "sealed_native_sqlx_owned_locked_handle_only"
    352         || raw.sqlite.native_linkage_count != 1
    353         || !contract_versions_valid(raw.contract_versions)
    354     {
    355         Err(ServiceSourceLockV3Error::Invalid)
    356     } else {
    357         Ok(())
    358     }
    359 }
    360 
    361 fn contract_versions_valid(versions: ContractVersions) -> bool {
    362     versions.config() != 0
    363         && versions.state() != 0
    364         && versions.admin() != 0
    365         && versions.status() != 0
    366         && versions.provider() != 0
    367 }
    368 
    369 fn canonical_bytes(raw: &RawServiceSourceLockV3) -> Vec<u8> {
    370     format!(
    371         concat!(
    372             "schema = \"radroots.service.source-lock.v3\"\n",
    373             "contract_version = 3\n",
    374             "service = \"{}\"\n",
    375             "repository = \"https://github.com/radrootslabs/lib\"\n",
    376             "revision = \"{}\"\n",
    377             "architecture = \"radroots.crates.release.v2\"\n",
    378             "workspace_catalog_sha256 = \"{}\"\n",
    379             "version = \"0.1.0-alpha\"\n",
    380             "source_archive_sha256 = \"{}\"\n",
    381             "cargo_lock_sha256 = \"{}\"\n",
    382             "rust_version = \"1.97.1\"\n",
    383             "host_feature_profile = \"service-host\"\n\n",
    384             "[source_archive_contract]\n",
    385             "binding = \"sha256_of_canonical_exact_lib_revision_tree_archive\"\n",
    386             "format = \"ustar\"\n",
    387             "compression = \"none\"\n",
    388             "compression_timestamp = \"not_applicable\"\n",
    389             "entry_order = \"bytewise_git_path\"\n",
    390             "path_prefix = \"none\"\n",
    391             "file_mode = \"git_index_100644_or_100755\"\n",
    392             "uid = 0\n",
    393             "gid = 0\n",
    394             "uname = \"\"\n",
    395             "gname = \"\"\n",
    396             "mtime = \"lib_revision_commit_timestamp\"\n",
    397             "pax_headers = \"forbidden\"\n",
    398             "directory_entries = \"omitted\"\n",
    399             "symlinks = \"forbidden\"\n",
    400             "hardlinks = \"forbidden\"\n",
    401             "submodules = \"forbidden\"\n",
    402             "trailer = \"two_zero_blocks\"\n\n",
    403             "[nix]\n",
    404             "material = \"qualified\"\n",
    405             "lib_revision = \"{}\"\n",
    406             "supported_systems = [\"aarch64-darwin\", \"x86_64-linux\"]\n\n",
    407             "[nix.public_input_lock]\n",
    408             "path = \"flake.lock\"\n",
    409             "sha256 = \"{}\"\n",
    410             "binding = \"exact_regular_file_bytes\"\n",
    411             "mutable_reference = \"forbidden\"\n",
    412             "lib_input = \"lib\"\n\n",
    413             "[nix.parent_result]\n",
    414             "embedded_in_public_input_lock = false\n",
    415             "embedded_in_source_lock = false\n",
    416             "storage = \"separate_generation_scoped_evidence\"\n\n",
    417             "[artifact_contract]\n",
    418             "path = \"{}\"\n",
    419             "sha256 = \"{}\"\n",
    420             "binding = \"exact_regular_file_bytes_in_same_source_revision\"\n\n",
    421             "[sqlite]\n",
    422             "high_level_authority = \"sqlx_only\"\n",
    423             "second_pool_connection_query_transaction_migration_authority = \"forbidden\"\n",
    424             "incremental_backup_adapter = \"sealed_native_sqlx_owned_locked_handle_only\"\n",
    425             "native_linkage_count = 1\n\n",
    426             "[contract_versions]\n",
    427             "config = {}\n",
    428             "state = {}\n",
    429             "admin = {}\n",
    430             "status = {}\n",
    431             "provider = {}\n"
    432         ),
    433         raw.service,
    434         raw.revision,
    435         raw.workspace_catalog_sha256,
    436         raw.source_archive_sha256,
    437         raw.cargo_lock_sha256,
    438         raw.nix.lib_revision,
    439         raw.nix.public_input_lock.sha256,
    440         raw.artifact_contract.path,
    441         raw.artifact_contract.sha256,
    442         raw.contract_versions.config(),
    443         raw.contract_versions.state(),
    444         raw.contract_versions.admin(),
    445         raw.contract_versions.status(),
    446         raw.contract_versions.provider(),
    447     )
    448     .into_bytes()
    449 }
    450 
    451 fn valid_lower_hex(value: &str, length: usize) -> bool {
    452     value.len() == length
    453         && value
    454             .bytes()
    455             .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
    456 }
    457 
    458 #[cfg(test)]
    459 mod tests {
    460     use super::*;
    461 
    462     const MYC_LOCK: &[u8] = br#"schema = "radroots.service.source-lock.v3"
    463 contract_version = 3
    464 service = "myc"
    465 repository = "https://github.com/radrootslabs/lib"
    466 revision = "1111111111111111111111111111111111111111"
    467 architecture = "radroots.crates.release.v2"
    468 workspace_catalog_sha256 = "2222222222222222222222222222222222222222222222222222222222222222"
    469 version = "0.1.0-alpha"
    470 source_archive_sha256 = "3333333333333333333333333333333333333333333333333333333333333333"
    471 cargo_lock_sha256 = "4444444444444444444444444444444444444444444444444444444444444444"
    472 rust_version = "1.97.1"
    473 host_feature_profile = "service-host"
    474 
    475 [source_archive_contract]
    476 binding = "sha256_of_canonical_exact_lib_revision_tree_archive"
    477 format = "ustar"
    478 compression = "none"
    479 compression_timestamp = "not_applicable"
    480 entry_order = "bytewise_git_path"
    481 path_prefix = "none"
    482 file_mode = "git_index_100644_or_100755"
    483 uid = 0
    484 gid = 0
    485 uname = ""
    486 gname = ""
    487 mtime = "lib_revision_commit_timestamp"
    488 pax_headers = "forbidden"
    489 directory_entries = "omitted"
    490 symlinks = "forbidden"
    491 hardlinks = "forbidden"
    492 submodules = "forbidden"
    493 trailer = "two_zero_blocks"
    494 
    495 [nix]
    496 material = "qualified"
    497 lib_revision = "1111111111111111111111111111111111111111"
    498 supported_systems = ["aarch64-darwin", "x86_64-linux"]
    499 
    500 [nix.public_input_lock]
    501 path = "flake.lock"
    502 sha256 = "5555555555555555555555555555555555555555555555555555555555555555"
    503 binding = "exact_regular_file_bytes"
    504 mutable_reference = "forbidden"
    505 lib_input = "lib"
    506 
    507 [nix.parent_result]
    508 embedded_in_public_input_lock = false
    509 embedded_in_source_lock = false
    510 storage = "separate_generation_scoped_evidence"
    511 
    512 [artifact_contract]
    513 path = "contracts/release/myc-artifact-contract.v3.json"
    514 sha256 = "6666666666666666666666666666666666666666666666666666666666666666"
    515 binding = "exact_regular_file_bytes_in_same_source_revision"
    516 
    517 [sqlite]
    518 high_level_authority = "sqlx_only"
    519 second_pool_connection_query_transaction_migration_authority = "forbidden"
    520 incremental_backup_adapter = "sealed_native_sqlx_owned_locked_handle_only"
    521 native_linkage_count = 1
    522 
    523 [contract_versions]
    524 config = 1
    525 state = 2
    526 admin = 3
    527 status = 4
    528 provider = 5
    529 "#;
    530 
    531     #[test]
    532     fn canonical_v3_lock_round_trips_and_exposes_exact_bindings() {
    533         let lock = ServiceSourceLockV3::from_canonical_bytes(MYC_LOCK).expect("v3 lock");
    534         assert_eq!(lock.canonical_bytes(), MYC_LOCK);
    535         assert_eq!(lock.service(), "myc");
    536         assert_eq!(lock.revision(), "1".repeat(40));
    537         assert_eq!(
    538             lock.artifact_contract_path(),
    539             "contracts/release/myc-artifact-contract.v3.json"
    540         );
    541         assert_eq!(
    542             lock.contract_versions(),
    543             ContractVersions::new(1, 2, 3, 4, 5)
    544         );
    545     }
    546 
    547     #[test]
    548     fn v3_lock_rejects_noncanonical_and_independent_semantic_drift() {
    549         let noncanonical = String::from_utf8(MYC_LOCK.to_vec())
    550             .expect("UTF-8")
    551             .replace("schema =", "schema  =");
    552         assert!(ServiceSourceLockV3::from_canonical_bytes(noncanonical.as_bytes()).is_err());
    553         for (from, to) in [
    554             ("service = \"myc\"", "service = \"other\""),
    555             ("material = \"qualified\"", "material = \"deferred\""),
    556             ("native_linkage_count = 1", "native_linkage_count = 2"),
    557             ("trailer = \"two_zero_blocks\"", "trailer = \"other\""),
    558         ] {
    559             let drifted = String::from_utf8(MYC_LOCK.to_vec())
    560                 .expect("UTF-8")
    561                 .replace(from, to);
    562             assert!(
    563                 ServiceSourceLockV3::from_canonical_bytes(drifted.as_bytes()).is_err(),
    564                 "{from}"
    565             );
    566         }
    567     }
    568 }