mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-11-07 09:45:36 +00:00
fix: close the remaining review findings (cross-process lock, partials)
Second Codex (gpt-6.1-sol, xhigh) pass over 60634d3, each item verified
before fixing:
- new: the sync recheck hashed an existing empty database before schema
setup, so the first sync falsely reported an external change. Hash
after setup; a published snapshot already has the schema and keeps
its bytes.
- sync, import, and entity edits now hold a cross-process lock
(<db>.lock, std File::lock), so a sync cannot replace the database
under an import running in another process. Import validates and
downloads before taking the lock, keeping its error order. MSRV
1.85 -> 1.89 for File::lock.
- the row-count rebuild branch also never downgrades history.
- cache reads no longer delete corrupt entries (a concurrent writer may
have just replaced them); the next put overwrites atomically.
- idx-mcp validates the changes limit before querying and runs each
requested section or analysis once.
- XLSX parsing bounds total cell text, so one large shared string
referenced from many cells cannot exhaust memory.
- PDF/archive percentages must be 0-100%; HHI uses saturating math.
CLI goldens unchanged. Live A/B vs 0.2.4: 16 passed, discover hits the
IDX 403 on both builds (exit 4 vs 1, intended).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
60634d3aef
commit
1e551385d0
10 changed files with 117 additions and 34 deletions
|
|
@ -47,6 +47,29 @@ impl Investor {
|
|||
}
|
||||
}
|
||||
|
||||
/// Cross-process lock held for every write (sync, import, entity edits),
|
||||
/// so a sync cannot replace the database under a concurrent import, or the
|
||||
/// other way around. Released when the returned file is dropped.
|
||||
fn write_lock(&self) -> Result<fs::File, IdxError> {
|
||||
let db_path = self.db_path()?;
|
||||
if let Some(parent) = db_path.parent()
|
||||
&& !parent.as_os_str().is_empty()
|
||||
{
|
||||
fs::create_dir_all(parent).map_err(|e| IdxError::Io(e.to_string()))?;
|
||||
}
|
||||
let mut lock_name = db_path.as_os_str().to_os_string();
|
||||
lock_name.push(".lock");
|
||||
let file = fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(false)
|
||||
.write(true)
|
||||
.open(PathBuf::from(lock_name))
|
||||
.map_err(|e| IdxError::Io(format!("failed to open ownership lock: {e}")))?;
|
||||
file.lock()
|
||||
.map_err(|e| IdxError::Io(format!("failed to lock ownership database: {e}")))?;
|
||||
Ok(file)
|
||||
}
|
||||
|
||||
fn connect(&self) -> Result<Connection, IdxError> {
|
||||
db::open_db_at(&self.db_path()?)
|
||||
}
|
||||
|
|
@ -172,11 +195,13 @@ impl Investor {
|
|||
}
|
||||
|
||||
pub fn map_alias(&self, alias: &str, entity: &str) -> Result<(), IdxError> {
|
||||
let _lock = self.write_lock()?;
|
||||
db::manual_map(&self.connect()?, alias, entity)
|
||||
}
|
||||
|
||||
/// Merges entity `merge` into `keep`, reporting how many rows moved.
|
||||
pub fn merge_entities(&self, keep: i64, merge: i64) -> Result<MergeResult, IdxError> {
|
||||
let _lock = self.write_lock()?;
|
||||
let conn = self.connect()?;
|
||||
let count = |table: &str| -> Result<i64, IdxError> {
|
||||
conn.query_row(
|
||||
|
|
@ -217,13 +242,17 @@ impl Investor {
|
|||
) -> Result<OwnershipSyncResult, IdxError> {
|
||||
let manifest_source = snapshot::resolve_manifest_source(manifest)?;
|
||||
let db_path = self.db_path()?;
|
||||
let _lock = self.write_lock()?;
|
||||
snapshot::sync_snapshot(&manifest_source, &db_path, force)
|
||||
}
|
||||
|
||||
/// Imports one report. A release already imported (same SHA-256) is
|
||||
/// skipped unless `force`, which replaces it.
|
||||
pub fn import(&self, source: &ImportSource, force: bool) -> Result<ImportResult, IdxError> {
|
||||
// Validate and download first (unchanged error order), then lock
|
||||
// only around the database writes.
|
||||
let input = self.resolve_import_input(source)?;
|
||||
let _lock = self.write_lock()?;
|
||||
let conn = self.connect()?;
|
||||
|
||||
let sha256 = sha256_file(&input.path)?;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue