Files
alpha-id/tests/pack_cli_tests.rs
T
Brummel 27e6a67388 audit: packed-versioned-bundle cycle close
Cycle-close tidy for the packed-versioned-index-bundle cycle (issues #3 + #4,
both shipped and closed). Architect drift review over d83b10f..HEAD against the
spec + glossary (no paths.design_ledger / project CLAUDE.md configured, so spec
+ glossary are the only references; the regression gate is empty —
commands.regression: [] — so architect was the sole cycle-close gate).

Architect verdict: drift_found, minor — no spec contract violated. Format,
header schema, on-disk layout, and the load_packed_or_store validation order
(n_rows → model → corpus hash) match the spec exactly; both #3 and #4
acceptance criteria are test-evidenced; no glossary term drift; the
IndexStatus / degraded orthogonality is honored.

Resolutions:
- [medium] Missing positional-alignment test. The spec § Testing strategy
  promised a "row i == store.get(entries[i].text)" guard, and it had not
  shipped — the load-bearing invariant that licenses dropping the per-row
  SHA256 key (row i = corpus entry i) was unprotected. FIXED: added
  tests/pack_cli_tests.rs::cli_packed_rows_are_in_corpus_order, which packs a
  store with order-revealing orthogonal vectors via the CLI, reads the packed
  file back, and asserts the rows follow corpus-load order (swapped order
  fails the test).
- [low] Stale IndexStatus doc comments still said "structurally valid/invalid"
  though the variants now also carry semantic model/corpus mismatch. FIXED:
  refreshed the Ok / Mismatch comments in src/model.rs.
- [low] The `index --pack` writer logic lives inline in main rather than in the
  packed module, leaving its header construction outside library test reach.
  CARRY-ON: filed as backlog issue #5 (idea/debt) — behaviour is correct and
  CLI-test-covered; the refactor is a self-contained testability improvement,
  not forced into this tidy.
- [note] No design ledger / project CLAUDE.md configured; drift is measurable
  only against spec + glossary. Recorded as an infrastructure observation.

Regression: no scripts configured (no-op). Full suite green (68 tests, +1 for
the alignment guard). cycle packed-versioned-bundle tidy (drift-clean).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 01:06:05 +02:00

157 lines
6.6 KiB
Rust

use alpha_id::model::{AlphaIdEntry, Config, IndexStatus};
use alpha_id::pipeline::load_packed_or_store;
use std::path::Path;
use std::process::Command;
// Write a minimal config TOML pointing the corpus + index dir at temp paths.
// The `--pack` branch only reads `alpha_id_path` (corpus) and `index_dir`
// (store); `claml_path` etc. are present for parse but unused on this path.
fn write_config(dir: &Path, corpus: &Path, index_dir: &Path) -> std::path::PathBuf {
let toml = format!(
r#"ionos_base_url = "http://127.0.0.1:1"
token_path = "/nonexistent"
embed_model = "test/model"
rerank_model = "test/rerank"
alpha_id_path = "{corpus}"
claml_path = "/nonexistent.xml"
index_dir = "{index}"
pool_size = 10
top_k = 10
"#,
corpus = corpus.to_str().unwrap(),
index = index_dir.to_str().unwrap(),
);
let p = dir.join("config.toml");
std::fs::write(&p, toml).unwrap();
p
}
fn corpus_line(text: &str) -> String {
// Alpha-ID format: valid|alpha_id|primary|star|addon|primary2|orpha|text
format!("1|A0001|A00|||||{text}")
}
#[test]
fn pack_refuses_incomplete_store_exit_2() {
let dir = tempfile::tempdir().unwrap();
let corpus = dir.path().join("corpus.txt");
std::fs::write(&corpus, format!("{}\n{}\n", corpus_line("alpha"), corpus_line("beta"))).unwrap();
let index_dir = dir.path().join("index");
std::fs::create_dir_all(&index_dir).unwrap();
let config = write_config(dir.path(), &corpus, &index_dir);
let out = Command::new(env!("CARGO_BIN_EXE_alpha-id"))
.args(["--config", config.to_str().unwrap(), "index", "--pack"])
.output()
.unwrap();
assert_eq!(out.status.code(), Some(2), "incomplete store must exit 2");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(stderr.contains("store incomplete"), "stderr was: {stderr}");
}
#[test]
fn pack_writes_file_from_complete_store() {
let dir = tempfile::tempdir().unwrap();
let corpus = dir.path().join("corpus.txt");
std::fs::write(&corpus, format!("{}\n{}\n", corpus_line("alpha"), corpus_line("beta"))).unwrap();
let index_dir = dir.path().join("index");
std::fs::create_dir_all(&index_dir).unwrap();
// Populate the per-file store for both entry texts so the pack succeeds.
let store = alpha_id::embed::EmbeddingStore::open(index_dir.to_str().unwrap(), "test/model").unwrap();
store.put("alpha", &[1.0f32, 0.0]).unwrap();
store.put("beta", &[0.0f32, 1.0]).unwrap();
let config = write_config(dir.path(), &corpus, &index_dir);
let out = Command::new(env!("CARGO_BIN_EXE_alpha-id"))
.args(["--config", config.to_str().unwrap(), "index", "--pack"])
.output()
.unwrap();
assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr));
let packed = index_dir.join("packed.test_model.bin");
assert!(packed.exists(), "packed file should be written at {}", packed.display());
}
fn entry(text: &str) -> AlphaIdEntry {
AlphaIdEntry {
alpha_id: "A0001".to_string(),
valid: true,
icd_primary: "A00".to_string(),
icd_star: String::new(),
icd_addon: String::new(),
icd_primary2: String::new(),
orpha: String::new(),
text: text.to_string(),
}
}
/// End-to-end seam: a packed file produced by the `index --pack` CLI loads back
/// through the production `load_packed_or_store` path as `IndexStatus::Ok` with a
/// usable index, with no per-file store consulted. This pins the cross-boundary
/// round trip (CLI writer ↔ pipeline reader) that the two halves' unit tests
/// each cover only one side of.
#[test]
fn cli_packed_file_loads_back_as_ok_index() {
let dir = tempfile::tempdir().unwrap();
let corpus = dir.path().join("corpus.txt");
std::fs::write(&corpus, format!("{}\n{}\n", corpus_line("alpha"), corpus_line("beta"))).unwrap();
let index_dir = dir.path().join("index");
std::fs::create_dir_all(&index_dir).unwrap();
let store = alpha_id::embed::EmbeddingStore::open(index_dir.to_str().unwrap(), "test/model").unwrap();
store.put("alpha", &[1.0f32, 0.0]).unwrap();
store.put("beta", &[0.0f32, 1.0]).unwrap();
let config = write_config(dir.path(), &corpus, &index_dir);
let out = Command::new(env!("CARGO_BIN_EXE_alpha-id"))
.args(["--config", config.to_str().unwrap(), "index", "--pack"])
.output()
.unwrap();
assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr));
// Load the CLI-written packed file through the production path. The store
// directory is removed first to prove the index comes from the packed file
// alone, not the per-file store.
std::fs::remove_dir_all(index_dir.join("embeddings")).unwrap();
let mut cfg = Config::load(config.to_str().unwrap()).unwrap();
cfg.index_dir = index_dir.to_str().unwrap().to_string();
let entries = vec![entry("alpha"), entry("beta")];
let (vector, status) = load_packed_or_store(&cfg, &entries);
assert!(vector.is_some(), "packed file written by the CLI must yield an index");
assert_eq!(status, IndexStatus::Ok);
}
/// Positional alignment (spec § Testing strategy): the packed matrix stores one
/// row per corpus entry in corpus-load order, so row `i` is the embedding of
/// `entries[i].text`. The two entries carry order-revealing orthogonal vectors;
/// if packing did not preserve corpus order the rows would come back swapped.
#[test]
fn cli_packed_rows_are_in_corpus_order() {
use alpha_id::packed::read_packed;
let dir = tempfile::tempdir().unwrap();
let corpus = dir.path().join("corpus.txt");
// alpha is line 1 (corpus entry 0), beta is line 2 (corpus entry 1).
std::fs::write(&corpus, format!("{}\n{}\n", corpus_line("alpha"), corpus_line("beta"))).unwrap();
let index_dir = dir.path().join("index");
std::fs::create_dir_all(&index_dir).unwrap();
let store = alpha_id::embed::EmbeddingStore::open(index_dir.to_str().unwrap(), "test/model").unwrap();
store.put("alpha", &[1.0f32, 0.0]).unwrap();
store.put("beta", &[0.0f32, 1.0]).unwrap();
let config = write_config(dir.path(), &corpus, &index_dir);
let out = Command::new(env!("CARGO_BIN_EXE_alpha-id"))
.args(["--config", config.to_str().unwrap(), "index", "--pack"])
.output()
.unwrap();
assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr));
let (_header, rows) = read_packed(&index_dir.join("packed.test_model.bin")).unwrap();
// Row i is the embedding of corpus entry i: alpha→[1,0] at row 0, beta→[0,1] at row 1.
assert_eq!(
rows,
vec![vec![1.0f32, 0.0], vec![0.0f32, 1.0]],
"packed rows must follow corpus-load order"
);
}