use ggen_utils::error::{Error, Result};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
use tokio::fs;
use tracing::{debug, info, instrument, warn};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PackageMetadata {
pub name: String,
pub versions: Vec<VersionMetadata>,
pub description: String,
pub author: Option<String>,
pub category: Option<String>,
pub tags: Vec<String>,
pub repository: Option<String>,
pub license: Option<String>,
pub homepage: Option<String>,
#[serde(default)]
pub is_8020: bool,
#[serde(default)]
pub is_8020_certified: bool,
#[serde(default)]
pub dark_matter_reduction_target: Option<String>,
#[serde(default)]
pub sector: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct VersionMetadata {
pub version: String,
pub download_url: String,
pub checksum: String,
pub dependencies: Vec<Dependency>,
pub published_at: String,
pub size_bytes: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Dependency {
pub name: String,
pub version_req: String,
pub optional: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegistryIndex {
pub version: String,
pub updated_at: String,
pub packages: HashMap<String, PackageMetadata>,
}
impl RegistryIndex {
pub fn new() -> Self {
Self {
version: "1.0.0".to_string(),
updated_at: chrono::Utc::now().to_rfc3339(),
packages: HashMap::new(),
}
}
pub fn add_package(&mut self, metadata: PackageMetadata) {
if let Some(existing) = self.packages.get_mut(&metadata.name) {
for new_version in &metadata.versions {
if existing
.versions
.iter()
.any(|v| v.version == new_version.version)
{
tracing::warn!(
"Package {} version {} already exists in registry - skipping duplicate",
metadata.name,
new_version.version
);
continue;
}
existing.versions.push(new_version.clone());
}
self.updated_at = chrono::Utc::now().to_rfc3339();
} else {
self.packages.insert(metadata.name.clone(), metadata);
self.updated_at = chrono::Utc::now().to_rfc3339();
}
}
pub fn get_package(&self, name: &str) -> Option<&PackageMetadata> {
self.packages.get(name)
}
pub fn list_packages(&self) -> Vec<String> {
self.packages.keys().cloned().collect()
}
}
impl Default for RegistryIndex {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
struct CacheEntry {
key: String,
value: PackageMetadata,
}
#[derive(Debug, Clone)]
pub struct CacheManager {
capacity: usize,
cache: Arc<RwLock<HashMap<String, PackageMetadata>>>,
lru_queue: Arc<RwLock<VecDeque<String>>>,
}
impl CacheManager {
pub fn new(capacity: usize) -> Self {
Self {
capacity,
cache: Arc::new(RwLock::new(HashMap::new())),
lru_queue: Arc::new(RwLock::new(VecDeque::new())),
}
}
#[instrument(skip(self))]
pub fn get(&self, name: &str) -> Option<PackageMetadata> {
let cache = self.cache.read().ok()?;
let metadata = cache.get(name).cloned();
if metadata.is_some() {
if let Ok(mut queue) = self.lru_queue.write() {
queue.retain(|k| k != name);
queue.push_back(name.to_string());
}
debug!("Cache hit for package: {}", name);
} else {
debug!("Cache miss for package: {}", name);
}
metadata
}
#[instrument(skip(self, metadata))]
pub fn put(&self, name: String, metadata: PackageMetadata) {
let mut cache = match self.cache.write() {
Ok(c) => c,
Err(e) => {
warn!("Failed to acquire cache write lock: {}", e);
return;
}
};
let mut queue = match self.lru_queue.write() {
Ok(q) => q,
Err(e) => {
warn!("Failed to acquire LRU queue write lock: {}", e);
return;
}
};
queue.retain(|k| k != &name);
if cache.len() >= self.capacity && !cache.contains_key(&name) {
if let Some(lru_key) = queue.pop_front() {
cache.remove(&lru_key);
debug!("Evicted LRU package: {}", lru_key);
}
}
cache.insert(name.clone(), metadata);
queue.push_back(name.clone());
debug!("Cached package: {}", name);
}
pub fn clear(&self) {
if let Ok(mut cache) = self.cache.write() {
cache.clear();
}
if let Ok(mut queue) = self.lru_queue.write() {
queue.clear();
}
debug!("Cache cleared");
}
pub fn size(&self) -> usize {
self.cache.read().map(|c| c.len()).unwrap_or(0)
}
pub fn capacity(&self) -> usize {
self.capacity
}
}
#[derive(Debug, Clone)]
pub struct Registry {
index_path: PathBuf,
index: Arc<RwLock<Option<RegistryIndex>>>,
cache: CacheManager,
}
impl Registry {
pub fn new() -> Result<Self> {
let home_dir =
dirs::home_dir().ok_or_else(|| Error::new("Failed to determine home directory"))?;
let index_path = home_dir.join(".ggen").join("registry").join("index.json");
Ok(Self {
index_path,
index: Arc::new(RwLock::new(None)),
cache: CacheManager::new(100), })
}
pub fn with_path(index_path: PathBuf) -> Self {
Self {
index_path,
index: Arc::new(RwLock::new(None)),
cache: CacheManager::new(100),
}
}
pub fn with_cache_capacity(capacity: usize) -> Result<Self> {
let home_dir =
dirs::home_dir().ok_or_else(|| Error::new("Failed to determine home directory"))?;
let index_path = home_dir.join(".ggen").join("registry").join("index.json");
Ok(Self {
index_path,
index: Arc::new(RwLock::new(None)),
cache: CacheManager::new(capacity),
})
}
#[instrument(skip(self))]
pub async fn load(&self) -> Result<()> {
info!("Loading registry index from: {}", self.index_path.display());
if let Some(parent) = self.index_path.parent() {
fs::create_dir_all(parent).await?;
}
let index = if self.index_path.exists() {
let contents = fs::read_to_string(&self.index_path).await.map_err(|e| {
Error::new(&format!(
"Failed to read registry index from {}: {}. Registry may be corrupted.",
self.index_path.display(),
e
))
})?;
serde_json::from_str::<RegistryIndex>(&contents).map_err(|e| {
Error::new(&format!(
"Failed to parse registry index from {} - invalid JSON: {}. Registry is corrupted. Delete {} and re-sync.",
self.index_path.display(),
e,
self.index_path.display()
))
})?
} else {
return Err(Error::new(&format!(
"Registry index not found at {}. Run 'ggen marketplace sync' to download the registry.",
self.index_path.display()
)));
};
let mut guard = self
.index
.write()
.map_err(|e| Error::new(&format!("Failed to acquire index write lock: {}", e)))?;
*guard = Some(index);
info!("Registry index loaded successfully");
Ok(())
}
#[instrument(skip(self))]
pub async fn save(&self) -> Result<()> {
let index_data = {
let guard = self
.index
.read()
.map_err(|e| Error::new(&format!("Failed to acquire index read lock: {}", e)))?;
guard
.as_ref()
.ok_or_else(|| Error::new("Registry index not loaded"))?
.clone()
};
if let Some(parent) = self.index_path.parent() {
fs::create_dir_all(parent).await?;
}
let lock_path = self.index_path.with_extension("index.lock");
let lock_file = tokio::task::spawn_blocking({
let lock_path = lock_path.clone();
move || {
use std::fs::OpenOptions;
use std::io::Write;
let mut lock_file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(&lock_path)
.map_err(|e| {
Error::new(&format!(
"Failed to create registry lock at {}: {}. Another process may be writing.",
lock_path.display(),
e
))
})?;
let pid = std::process::id();
writeln!(lock_file, "{}", pid).map_err(|e| {
Error::new(&format!("Failed to write to registry lock: {}", e))
})?;
lock_file.sync_all().map_err(|e| {
Error::new(&format!("Failed to sync registry lock: {}", e))
})?;
Ok::<std::fs::File, Error>(lock_file)
}
})
.await
.map_err(|e| Error::new(&format!("Task join error acquiring lock: {}", e)))??;
let _lock_guard = lock_file;
let contents = serde_json::to_string_pretty(&index_data)?;
let temp_path = self.index_path.with_extension("index.tmp");
fs::write(&temp_path, contents)
.await
.map_err(|e| Error::new(&format!("Failed to write registry temp file: {}", e)))?;
fs::rename(&temp_path, &self.index_path)
.await
.map_err(|e| {
std::mem::drop(fs::remove_file(&temp_path));
Error::new(&format!("Failed to atomically update registry: {}", e))
})?;
let verify_content = fs::read_to_string(&self.index_path).await.map_err(|e| {
Error::new(&format!(
"Failed to verify registry write: {}. Registry may be corrupted.",
e
))
})?;
serde_json::from_str::<RegistryIndex>(&verify_content).map_err(|e| {
Error::new(&format!(
"Registry verification failed - saved file is invalid JSON: {}. Registry may be corrupted.",
e
))
})?;
info!(
"Registry index saved and verified at: {}",
self.index_path.display()
);
Ok(())
}
#[instrument(skip(self))]
pub async fn get_package(&self, name: &str) -> Result<Option<PackageMetadata>> {
if let Some(cached) = self.cache.get(name) {
return Ok(Some(cached));
}
let guard = self
.index
.read()
.map_err(|e| Error::new(&format!("Failed to acquire index read lock: {}", e)))?;
let index = guard
.as_ref()
.ok_or_else(|| Error::new("Registry index not loaded"))?;
if let Some(metadata) = index.get_package(name) {
let metadata = metadata.clone();
self.cache.put(name.to_string(), metadata.clone());
Ok(Some(metadata))
} else {
Ok(None)
}
}
#[instrument(skip(self))]
pub async fn list_versions(&self, name: &str) -> Result<Vec<String>> {
let metadata = self
.get_package(name)
.await?
.ok_or_else(|| Error::new(&format!("Package not found: {}", name)))?;
Ok(metadata
.versions
.iter()
.map(|v| v.version.clone())
.collect())
}
#[instrument(skip(self))]
pub async fn get_version(&self, name: &str, version: &str) -> Result<Option<VersionMetadata>> {
let metadata = self.get_package(name).await?;
Ok(metadata.and_then(|m| m.versions.iter().find(|v| v.version == version).cloned()))
}
#[instrument(skip(self))]
pub async fn list_packages(&self) -> Result<Vec<String>> {
let guard = self
.index
.read()
.map_err(|e| Error::new(&format!("Failed to acquire index read lock: {}", e)))?;
let index = guard
.as_ref()
.ok_or_else(|| Error::new("Registry index not loaded"))?;
Ok(index.list_packages())
}
#[instrument(skip(self, metadata))]
pub async fn add_package(&self, metadata: PackageMetadata) -> Result<()> {
let mut guard = self
.index
.write()
.map_err(|e| Error::new(&format!("Failed to acquire index write lock: {}", e)))?;
let index = guard
.as_mut()
.ok_or_else(|| Error::new("Registry index not loaded"))?;
if let Some(existing) = index.get_package(&metadata.name) {
for new_version in &metadata.versions {
if existing
.versions
.iter()
.any(|v| v.version == new_version.version)
{
return Err(Error::new(&format!(
"❌ Package {} version {} already exists in registry. Cannot add duplicate version.",
metadata.name, new_version.version
)));
}
}
}
let name = metadata.name.clone();
index.add_package(metadata.clone());
self.cache.put(name.clone(), metadata);
info!("Added package to registry: {}", name);
Ok(())
}
pub fn cache(&self) -> &CacheManager {
&self.cache
}
pub fn index_path(&self) -> &Path {
&self.index_path
}
pub async fn validate(&self) -> Result<()> {
let guard = self
.index
.read()
.map_err(|e| Error::new(&format!("Failed to acquire index read lock: {}", e)))?;
let index = guard
.as_ref()
.ok_or_else(|| Error::new("Registry index not loaded"))?;
if index.packages.is_empty() {
return Err(Error::new(
"Registry index is empty. Run 'ggen marketplace sync' to download the registry.",
));
}
for (name, metadata) in &index.packages {
if metadata.versions.is_empty() {
return Err(Error::new(&format!(
"Package {} has no versions defined - registry is corrupted",
name
)));
}
for version in &metadata.versions {
if version.download_url.is_empty() {
return Err(Error::new(&format!(
"Package {}@{} has empty download URL - registry is corrupted",
name, version.version
)));
}
if version.checksum.is_empty() {
return Err(Error::new(&format!(
"Package {}@{} has empty checksum - registry is corrupted",
name, version.version
)));
}
}
}
Ok(())
}
}
impl Default for Registry {
fn default() -> Self {
Self::new().unwrap_or_else(|_| {
let temp_path = std::env::temp_dir()
.join("ggen")
.join("registry")
.join("index.json");
Self::with_path(temp_path)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn create_test_package(name: &str, version: &str) -> PackageMetadata {
PackageMetadata {
name: name.to_string(),
versions: vec![VersionMetadata {
version: version.to_string(),
download_url: format!("https://example.com/{}/{}.tar.gz", name, version),
checksum: "abcd1234".to_string(),
dependencies: vec![],
published_at: chrono::Utc::now().to_rfc3339(),
size_bytes: 1024,
}],
description: format!("Test package {}", name),
author: Some("Test Author".to_string()),
category: Some("testing".to_string()),
tags: vec!["test".to_string()],
repository: Some("https://github.com/test/repo".to_string()),
license: Some("MIT".to_string()),
homepage: Some("https://example.com".to_string()),
is_8020: false,
is_8020_certified: false,
dark_matter_reduction_target: None,
sector: None,
}
}
#[test]
fn test_registry_index_creation() {
let index = RegistryIndex::new();
assert_eq!(index.version, "1.0.0");
assert!(index.packages.is_empty());
}
#[test]
fn test_registry_index_add_package() {
let mut index = RegistryIndex::new();
let package = create_test_package("test-pkg", "1.0.0");
index.add_package(package.clone());
assert_eq!(index.packages.len(), 1);
assert_eq!(index.get_package("test-pkg"), Some(&package));
}
#[test]
fn test_cache_manager_basic_operations() {
let cache = CacheManager::new(3);
let pkg1 = create_test_package("pkg1", "1.0.0");
let pkg2 = create_test_package("pkg2", "1.0.0");
assert_eq!(cache.size(), 0);
cache.put("pkg1".to_string(), pkg1.clone());
cache.put("pkg2".to_string(), pkg2.clone());
assert_eq!(cache.size(), 2);
assert_eq!(cache.get("pkg1"), Some(pkg1));
assert_eq!(cache.get("pkg2"), Some(pkg2));
assert_eq!(cache.get("pkg3"), None);
}
#[test]
fn test_cache_manager_lru_eviction() {
let cache = CacheManager::new(2);
let pkg1 = create_test_package("pkg1", "1.0.0");
let pkg2 = create_test_package("pkg2", "1.0.0");
let pkg3 = create_test_package("pkg3", "1.0.0");
cache.put("pkg1".to_string(), pkg1.clone());
cache.put("pkg2".to_string(), pkg2.clone());
assert_eq!(cache.size(), 2);
let _ = cache.get("pkg1");
cache.put("pkg3".to_string(), pkg3.clone());
assert_eq!(cache.size(), 2);
assert_eq!(cache.get("pkg1"), Some(pkg1)); assert_eq!(cache.get("pkg2"), None); assert_eq!(cache.get("pkg3"), Some(pkg3)); }
#[test]
fn test_cache_manager_clear() {
let cache = CacheManager::new(5);
cache.put("pkg1".to_string(), create_test_package("pkg1", "1.0.0"));
cache.put("pkg2".to_string(), create_test_package("pkg2", "1.0.0"));
assert_eq!(cache.size(), 2);
cache.clear();
assert_eq!(cache.size(), 0);
assert_eq!(cache.get("pkg1"), None);
}
#[tokio::test]
async fn test_registry_load_and_save_real_filesystem() {
let temp_dir = TempDir::new().unwrap();
let index_path = temp_dir.path().join("index.json");
let empty_index =
r#"{"version":"1.0.0","updated_at":"2024-01-01T00:00:00Z","packages":{}}"#;
std::fs::write(&index_path, empty_index).unwrap();
let registry = Registry::with_path(index_path.clone());
registry.load().await.unwrap();
let package = create_test_package("real-pkg", "2.0.0");
registry.add_package(package.clone()).await.unwrap();
registry.save().await.unwrap();
assert!(index_path.exists());
let registry2 = Registry::with_path(index_path);
registry2.load().await.unwrap();
let loaded = registry2.get_package("real-pkg").await.unwrap();
assert_eq!(loaded, Some(package));
}
#[tokio::test]
async fn test_registry_get_package_with_cache() {
let temp_dir = TempDir::new().unwrap();
let index_path = temp_dir.path().join("index.json");
let empty_index =
r#"{"version":"1.0.0","updated_at":"2024-01-01T00:00:00Z","packages":{}}"#;
std::fs::write(&index_path, empty_index).unwrap();
let registry = Registry::with_path(index_path);
registry.load().await.unwrap();
let package = create_test_package("cached-pkg", "1.5.0");
registry.add_package(package.clone()).await.unwrap();
let result1 = registry.get_package("cached-pkg").await.unwrap();
assert_eq!(result1, Some(package.clone()));
let result2 = registry.get_package("cached-pkg").await.unwrap();
assert_eq!(result2, Some(package));
assert_eq!(registry.cache().size(), 1);
}
#[tokio::test]
async fn test_registry_list_versions() {
let temp_dir = TempDir::new().unwrap();
let index_path = temp_dir.path().join("index.json");
let empty_index =
r#"{"version":"1.0.0","updated_at":"2024-01-01T00:00:00Z","packages":{}}"#;
std::fs::write(&index_path, empty_index).unwrap();
let registry = Registry::with_path(index_path);
registry.load().await.unwrap();
let mut package = create_test_package("multi-ver", "1.0.0");
package.versions.push(VersionMetadata {
version: "1.1.0".to_string(),
download_url: "https://example.com/multi-ver/1.1.0.tar.gz".to_string(),
checksum: "efgh5678".to_string(),
dependencies: vec![],
published_at: chrono::Utc::now().to_rfc3339(),
size_bytes: 2048,
});
package.versions.push(VersionMetadata {
version: "2.0.0".to_string(),
download_url: "https://example.com/multi-ver/2.0.0.tar.gz".to_string(),
checksum: "ijkl9012".to_string(),
dependencies: vec![],
published_at: chrono::Utc::now().to_rfc3339(),
size_bytes: 4096,
});
registry.add_package(package).await.unwrap();
let versions = registry.list_versions("multi-ver").await.unwrap();
assert_eq!(versions.len(), 3);
assert!(versions.contains(&"1.0.0".to_string()));
assert!(versions.contains(&"1.1.0".to_string()));
assert!(versions.contains(&"2.0.0".to_string()));
}
#[tokio::test]
async fn test_registry_get_specific_version() {
let temp_dir = TempDir::new().unwrap();
let index_path = temp_dir.path().join("index.json");
let empty_index =
r#"{"version":"1.0.0","updated_at":"2024-01-01T00:00:00Z","packages":{}}"#;
std::fs::write(&index_path, empty_index).unwrap();
let registry = Registry::with_path(index_path);
registry.load().await.unwrap();
let package = create_test_package("versioned-pkg", "3.2.1");
registry.add_package(package).await.unwrap();
let version = registry
.get_version("versioned-pkg", "3.2.1")
.await
.unwrap();
assert!(version.is_some());
assert_eq!(version.unwrap().version, "3.2.1");
let nonexistent = registry
.get_version("versioned-pkg", "9.9.9")
.await
.unwrap();
assert!(nonexistent.is_none());
}
#[tokio::test]
async fn test_registry_list_packages() {
let temp_dir = TempDir::new().unwrap();
let index_path = temp_dir.path().join("index.json");
let empty_index =
r#"{"version":"1.0.0","updated_at":"2024-01-01T00:00:00Z","packages":{}}"#;
std::fs::write(&index_path, empty_index).unwrap();
let registry = Registry::with_path(index_path);
registry.load().await.unwrap();
registry
.add_package(create_test_package("pkg-a", "1.0.0"))
.await
.unwrap();
registry
.add_package(create_test_package("pkg-b", "2.0.0"))
.await
.unwrap();
registry
.add_package(create_test_package("pkg-c", "3.0.0"))
.await
.unwrap();
let packages = registry.list_packages().await.unwrap();
assert_eq!(packages.len(), 3);
assert!(packages.contains(&"pkg-a".to_string()));
assert!(packages.contains(&"pkg-b".to_string()));
assert!(packages.contains(&"pkg-c".to_string()));
}
#[tokio::test]
async fn test_registry_persistence_across_instances() {
let temp_dir = TempDir::new().unwrap();
let index_path = temp_dir.path().join("shared-index.json");
let empty_index =
r#"{"version":"1.0.0","updated_at":"2024-01-01T00:00:00Z","packages":{}}"#;
std::fs::write(&index_path, empty_index).unwrap();
{
let registry1 = Registry::with_path(index_path.clone());
registry1.load().await.unwrap();
registry1
.add_package(create_test_package("persistent-pkg", "1.0.0"))
.await
.unwrap();
registry1.save().await.unwrap();
}
{
let registry2 = Registry::with_path(index_path);
registry2.load().await.unwrap();
let loaded = registry2.get_package("persistent-pkg").await.unwrap();
assert!(loaded.is_some());
assert_eq!(loaded.unwrap().name, "persistent-pkg");
}
}
#[tokio::test]
async fn test_registry_with_dependencies() {
let temp_dir = TempDir::new().unwrap();
let index_path = temp_dir.path().join("index.json");
let empty_index =
r#"{"version":"1.0.0","updated_at":"2024-01-01T00:00:00Z","packages":{}}"#;
std::fs::write(&index_path, empty_index).unwrap();
let registry = Registry::with_path(index_path);
registry.load().await.unwrap();
let mut package = create_test_package("dep-pkg", "1.0.0");
package.versions[0].dependencies = vec![
Dependency {
name: "dep1".to_string(),
version_req: "^1.0.0".to_string(),
optional: false,
},
Dependency {
name: "dep2".to_string(),
version_req: ">=2.0.0".to_string(),
optional: true,
},
];
registry.add_package(package).await.unwrap();
let loaded = registry.get_package("dep-pkg").await.unwrap().unwrap();
assert_eq!(loaded.versions[0].dependencies.len(), 2);
assert_eq!(loaded.versions[0].dependencies[0].name, "dep1");
assert!(!loaded.versions[0].dependencies[0].optional);
assert_eq!(loaded.versions[0].dependencies[1].name, "dep2");
assert!(loaded.versions[0].dependencies[1].optional);
}
#[tokio::test]
async fn test_registry_load_corrupted_index_fails() {
let temp_dir = TempDir::new().unwrap();
let index_path = temp_dir.path().join("index.json");
std::fs::write(&index_path, "{ invalid json }").unwrap();
let registry = Registry::with_path(index_path);
assert!(registry.load().await.is_err());
}
#[tokio::test]
async fn test_registry_load_missing_index_fails() {
let temp_dir = TempDir::new().unwrap();
let index_path = temp_dir.path().join("index.json");
let registry = Registry::with_path(index_path);
assert!(registry.load().await.is_err());
}
#[tokio::test]
async fn test_registry_validate_empty_fails() {
let temp_dir = TempDir::new().unwrap();
let registry = Registry::with_path(temp_dir.path().join("index.json"));
let index = RegistryIndex::new();
let mut guard = registry.index.write().unwrap();
*guard = Some(index);
drop(guard);
assert!(registry.validate().await.is_err());
}
#[tokio::test]
async fn test_registry_validate_success_with_valid_packages() {
let temp_dir = TempDir::new().unwrap();
let registry = Registry::with_path(temp_dir.path().join("index.json"));
let mut index = RegistryIndex::new();
let package = create_test_package("test-pkg", "1.0.0");
index.add_package(package);
let mut guard = registry.index.write().unwrap();
*guard = Some(index);
drop(guard);
assert!(registry.validate().await.is_ok());
}
#[tokio::test]
async fn test_registry_validate_detects_missing_checksum() {
let temp_dir = TempDir::new().unwrap();
let registry = Registry::with_path(temp_dir.path().join("index.json"));
let mut index = RegistryIndex::new();
let mut package = create_test_package("invalid-pkg", "1.0.0");
package.versions[0].checksum = String::new();
index.add_package(package);
let mut guard = registry.index.write().unwrap();
*guard = Some(index);
drop(guard);
assert!(registry.validate().await.is_err());
}
}