use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, LazyLock, Mutex};
use async_trait::async_trait;
use khive_pack_git::ingest::{run_ingest, IngestOptions};
use khive_pack_git::GitPack;
use khive_pack_kg::KgPack;
use khive_runtime::{
AllowAllGate, BackendId, EmbedderProvider, EntityCreateSpec, KhiveRuntime, Namespace,
NamespaceToken, RuntimeConfig, RuntimeError, RuntimeResult, VerbRegistry, VerbRegistryBuilder,
};
use khive_storage::types::{SqlStatement, SqlValue};
use lattice_embed::{EmbedError, EmbeddingModel, EmbeddingService};
use serde_json::{json, Value};
use uuid::Uuid;
fn rt() -> KhiveRuntime {
KhiveRuntime::memory().expect("memory runtime")
}
static ENV_MUTEX: LazyLock<tokio::sync::Mutex<()>> = LazyLock::new(|| tokio::sync::Mutex::new(()));
struct PathGuard {
prior: Option<String>,
}
impl PathGuard {
fn install(bin_dir: &Path) -> Self {
let prior = std::env::var("PATH").ok();
let new_path = match &prior {
Some(p) => format!("{}:{p}", bin_dir.display()),
None => bin_dir.display().to_string(),
};
std::env::set_var("PATH", new_path);
Self { prior }
}
}
impl Drop for PathGuard {
fn drop(&mut self) {
match &self.prior {
Some(p) => std::env::set_var("PATH", p),
None => std::env::remove_var("PATH"),
}
}
}
fn write_fake_gh(bin_dir: &Path, log_dir: &Path, pr_json: &str, issue_json: &str) {
std::fs::write(log_dir.join("pr_response.json"), pr_json).expect("write pr fixture");
std::fs::write(log_dir.join("issue_response.json"), issue_json).expect("write issue fixture");
let script = format!(
r#"#!/bin/sh
printf '%s\n' "$(pwd)" >> '{cwd_log}'
printf '%s\n' "$*" >> '{args_log}'
case "$1" in
--version)
echo "gh version 2.0.0 (fake)"
;;
pr)
cat '{pr_json_path}'
;;
issue)
cat '{issue_json_path}'
;;
*)
echo "fake gh: unsupported args: $*" 1>&2
exit 1
;;
esac
"#,
cwd_log = log_dir.join("cwd.log").display(),
args_log = log_dir.join("args.log").display(),
pr_json_path = log_dir.join("pr_response.json").display(),
issue_json_path = log_dir.join("issue_response.json").display(),
);
let script_path = bin_dir.join("gh");
std::fs::write(&script_path, script).expect("write fake gh script");
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(&script_path)
.expect("fake gh metadata")
.permissions();
perms.set_mode(0o755);
std::fs::set_permissions(&script_path, perms).expect("chmod fake gh");
}
}
async fn fixture() -> (KhiveRuntime, NamespaceToken, VerbRegistry) {
let rt = rt();
let mut builder = VerbRegistryBuilder::new();
builder.register(KgPack::new(rt.clone()));
builder.register(GitPack::new(rt.clone()));
let registry = builder.build().expect("registry builds");
rt.install_edge_rules(registry.all_edge_rules());
registry.call_register_entity_type_validators(&rt);
registry.apply_schema_plans(rt.backend());
let token = rt.authorize(Namespace::local()).expect("authorize local");
(rt, token, registry)
}
async fn create(registry: &VerbRegistry, body: Value) -> Uuid {
let resp = registry.dispatch("create", body).await.expect("create ok");
Uuid::parse_str(resp["id"].as_str().expect("id present")).expect("id is uuid")
}
fn git(repo: &Path, args: &[&str]) {
let out = Command::new("git")
.arg("-C")
.arg(repo)
.args(args)
.output()
.expect("spawn git");
assert!(
out.status.success(),
"git {args:?} failed: {}",
String::from_utf8_lossy(&out.stderr)
);
}
fn init_repo(repo: &Path) {
git(repo, &["init", "-q", "-b", "main"]);
git(repo, &["config", "user.email", "test@example.com"]);
git(repo, &["config", "user.name", "Test User"]);
}
fn write(repo: &Path, rel: &str, contents: &str) {
let path = repo.join(rel);
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(path, contents).unwrap();
}
fn commit(repo: &Path, rel_paths: &[&str], message: &str) {
for p in rel_paths {
git(repo, &["add", p]);
}
git(repo, &["commit", "-q", "-m", message]);
}
fn head_sha(repo: &Path) -> String {
let out = Command::new("git")
.arg("-C")
.arg(repo)
.args(["rev-parse", "HEAD"])
.output()
.expect("rev-parse");
String::from_utf8_lossy(&out.stdout).trim().to_string()
}
const CURSOR_FAIL_SENTINEL: &str = "cursor-fail-sentinel";
struct FailOnceEmbeddingService {
failed_once: AtomicBool,
}
#[async_trait]
impl EmbeddingService for FailOnceEmbeddingService {
async fn embed(
&self,
texts: &[String],
_model: EmbeddingModel,
) -> Result<Vec<Vec<f32>>, EmbedError> {
if texts.iter().any(|t| t.contains(CURSOR_FAIL_SENTINEL))
&& !self.failed_once.swap(true, Ordering::SeqCst)
{
return Err(EmbedError::Internal(
"injected cursor-stall test failure".to_string(),
));
}
Ok(texts.iter().map(|_| vec![0.0_f32; 4]).collect())
}
fn supports_model(&self, _model: EmbeddingModel) -> bool {
true
}
fn name(&self) -> &'static str {
"fail-once-test-embedder"
}
}
struct FailOnceEmbedderProvider;
#[async_trait]
impl EmbedderProvider for FailOnceEmbedderProvider {
fn name(&self) -> &str {
"fail-once-test-embedder"
}
fn dimensions(&self) -> usize {
4
}
async fn build(&self) -> RuntimeResult<Arc<dyn EmbeddingService>> {
Ok(Arc::new(FailOnceEmbeddingService {
failed_once: AtomicBool::new(false),
}))
}
}
#[tokio::test]
async fn ingest_links_commits_to_document_and_pr_by_provenance_query() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "acceptance-repo"}),
)
.await;
let doc_id = create(
®istry,
json!({
"kind": "document",
"name": "ADR-045-test.md",
"properties": {"source_uri": "docs/adr/ADR-045-test.md"},
}),
)
.await;
let pr_id = create(
®istry,
json!({
"kind": "pull_request",
"content": "",
"properties": {"number": 42, "title": "Add ADR-045", "project_id": project_id.to_string()},
"annotates": [project_id.to_string()],
}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo = dir.path();
init_repo(repo);
write(
repo,
"docs/adr/ADR-045-test.md",
"# ADR-045\n\nInitial draft.\n",
);
commit(repo, &["docs/adr/ADR-045-test.md"], "Add ADR-045 (#42)");
let commit1 = head_sha(repo);
write(repo, "src/lib.rs", "// unrelated change\n");
commit(repo, &["src/lib.rs"], "Unrelated source change");
write(repo, "docs/adr/ADR-045-test.md", "# ADR-045\n\nRevised.\n");
commit(repo, &["docs/adr/ADR-045-test.md"], "Revise ADR-045");
let commit3 = head_sha(repo);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.to_path_buf(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(
report.commits_ingested, 3,
"all three commits ingest: {report:?}"
);
let doc_neighbors = registry
.dispatch(
"neighbors",
json!({"id": doc_id.to_string(), "direction": "incoming", "relations": ["annotates"]}),
)
.await
.expect("neighbors ok");
let hits = doc_neighbors.as_array().expect("array");
assert_eq!(
hits.len(),
2,
"exactly the two touching commits annotate the document: {hits:?}"
);
for h in hits {
assert_eq!(h["kind"], "commit");
}
let mut hit_shas: Vec<String> = Vec::new();
for h in hits {
let id = h["id"].as_str().expect("neighbor id");
let got = registry
.dispatch("get", json!({"id": id}))
.await
.expect("get ok");
hit_shas.push(
got["properties"]["sha"]
.as_str()
.expect("commit note has properties.sha")
.to_string(),
);
}
assert!(
hit_shas.contains(&commit1),
"commit1 {commit1} must be among document neighbors: {hit_shas:?}"
);
assert!(
hit_shas.contains(&commit3),
"commit3 {commit3} must be among document neighbors: {hit_shas:?}"
);
let pr_neighbors = registry
.dispatch(
"neighbors",
json!({"id": pr_id.to_string(), "direction": "incoming", "relations": ["annotates"]}),
)
.await
.expect("neighbors ok");
let pr_hits = pr_neighbors.as_array().expect("array");
assert_eq!(
pr_hits.len(),
1,
"exactly one commit annotates the PR: {pr_hits:?}"
);
assert_eq!(pr_hits[0]["kind"], "commit");
let project_neighbors = registry
.dispatch(
"neighbors",
json!({"id": project_id.to_string(), "direction": "incoming", "relations": ["annotates"]}),
)
.await
.expect("neighbors ok");
assert_eq!(
project_neighbors.as_array().expect("array").len(),
4,
"3 commits + 1 pull_request annotate the project: {project_neighbors:?}"
);
}
#[tokio::test]
async fn ingest_masks_secrets_in_commit_message() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(®istry, json!({"kind": "project", "name": "secret-repo"})).await;
let dir = tempfile::tempdir().expect("tempdir");
let repo = dir.path();
init_repo(repo);
let fake_token = "ghp_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
write(repo, "README.md", "hello\n");
commit(
repo,
&["README.md"],
&format!("Rotate deploy key\n\naccidentally committed {fake_token} here"),
);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.to_path_buf(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(report.commits_ingested, 1);
let list = registry
.dispatch("list", json!({"kind": "commit", "limit": 10}))
.await
.expect("list ok");
let items = list.as_array().expect("list returns a plain array");
assert_eq!(items.len(), 1);
let stored_content = items[0]["content"].as_str().expect("content is string");
assert!(
!stored_content.contains(fake_token),
"raw token must not survive into stored content: {stored_content:?}"
);
assert!(
stored_content.contains("***MASKED***") || stored_content.contains("MASKED"),
"masked marker must be present: {stored_content:?}"
);
}
#[tokio::test]
async fn ingest_masks_pr_body_hash_near_token_without_dropping_note() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "pr-hash-near-token-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
let hex64 = "deadbeef".repeat(8);
assert_eq!(hex64.len(), 64, "fixture hash must be exactly 64 hex chars");
let pr_json = json!([{
"number": 42,
"title": "Document the rotation process",
"author": {"login": "octocat"},
"createdAt": "2026-01-01T00:00:00Z",
"mergedAt": null,
"closedAt": null,
"updatedAt": "2026-01-01T00:00:00Z",
"baseRefName": "main",
"headRefName": "docs/rotation",
"mergeCommit": null,
"body": format!("Rotated the deploy token. Old hash was {hex64} before rotation.")
}])
.to_string();
write_fake_gh(&bin_dir, &log_dir, &pr_json, "[]");
let _path_guard = PathGuard::install(&bin_dir);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(
report.prs_ingested, 1,
"the PR must not be dropped: {report:?}"
);
assert!(
report.warnings.iter().all(|w| !w.contains("pull_request")),
"no silent-drop warning may be reported: {:?}",
report.warnings
);
let prs_list = registry
.dispatch("list", json!({"kind": "pull_request", "limit": 10}))
.await
.expect("list prs ok");
let items = prs_list.as_array().expect("array");
assert_eq!(items.len(), 1);
let content = items[0]["content"].as_str().expect("content is string");
assert!(
!content.contains(&hex64),
"raw 64-hex hash must not survive into stored content: {content:?}"
);
let expected = "Rotated the deploy token. Old hash was ***MASKED*** before rotation.";
assert_eq!(
content, expected,
"only the flagged hash span is replaced, surrounding prose is retained exactly: {content:?}"
);
let report_debug = format!("{report:?}");
assert!(
!report_debug.contains(&hex64),
"the report itself must not carry the raw detected hash: {report_debug}"
);
}
#[tokio::test]
async fn ingest_masks_credential_shaped_pr_title_without_dropping_note() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "pr-title-credential-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
let fake_token = "ghp_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
let pr_json = json!([{
"number": 7,
"title": format!("Rotate leaked {fake_token} immediately"),
"author": {"login": "octocat"},
"createdAt": "2026-01-01T00:00:00Z",
"mergedAt": null,
"closedAt": null,
"updatedAt": "2026-01-01T00:00:00Z",
"baseRefName": "main",
"headRefName": "fix/rotate",
"mergeCommit": null,
"body": ""
}])
.to_string();
write_fake_gh(&bin_dir, &log_dir, &pr_json, "[]");
let _path_guard = PathGuard::install(&bin_dir);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(
report.prs_ingested, 1,
"the PR must not be dropped: {report:?}"
);
assert!(
report.warnings.iter().all(|w| !w.contains("pull_request")),
"no silent-drop warning may be reported: {:?}",
report.warnings
);
let prs_list = registry
.dispatch("list", json!({"kind": "pull_request", "limit": 10}))
.await
.expect("list prs ok");
let items = prs_list.as_array().expect("array");
assert_eq!(items.len(), 1);
let item = &items[0];
let stored_name = item["name"].as_str().expect("name is string");
let stored_title = item["properties"]["title"]
.as_str()
.expect("properties.title is string");
assert!(
!stored_name.contains(fake_token) && !stored_title.contains(fake_token),
"raw credential must not survive into name or properties.title: \
name={stored_name:?}, title={stored_title:?}"
);
assert!(
stored_name.contains("***MASKED***") && stored_title.contains("***MASKED***"),
"masked marker must be present in both name and properties.title: \
name={stored_name:?}, title={stored_title:?}"
);
let stored_content = item["content"].as_str().expect("content is string");
assert_eq!(
stored_content, "",
"an empty body must remain empty, not acquire a masking placeholder: {stored_content:?}"
);
}
#[tokio::test]
async fn ingest_masks_credential_shaped_issue_title_without_dropping_note() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "issue-title-credential-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
let fake_token = "ghp_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
let issue_json = json!([{
"number": 11,
"title": format!("Rotate leaked {fake_token} immediately"),
"author": {"login": "octocat"},
"createdAt": "2026-01-01T00:00:00Z",
"closedAt": null,
"updatedAt": "2026-01-01T00:00:00Z",
"labels": [],
"stateReason": "",
"body": ""
}])
.to_string();
write_fake_gh(&bin_dir, &log_dir, "[]", &issue_json);
let _path_guard = PathGuard::install(&bin_dir);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(
report.issues_ingested, 1,
"the issue must not be dropped: {report:?}"
);
assert!(
report.warnings.iter().all(|w| !w.contains("issue #11")),
"no silent-drop warning may be reported: {:?}",
report.warnings
);
let issues_list = registry
.dispatch("list", json!({"kind": "issue", "limit": 10}))
.await
.expect("list issues ok");
let items = issues_list.as_array().expect("array");
assert_eq!(items.len(), 1);
let item = &items[0];
let stored_name = item["name"].as_str().expect("name is string");
let stored_title = item["properties"]["title"]
.as_str()
.expect("properties.title is string");
assert!(
!stored_name.contains(fake_token) && !stored_title.contains(fake_token),
"raw credential must not survive into name or properties.title: \
name={stored_name:?}, title={stored_title:?}"
);
assert!(
stored_name.contains("***MASKED***") && stored_title.contains("***MASKED***"),
"masked marker must be present in both name and properties.title: \
name={stored_name:?}, title={stored_title:?}"
);
}
#[tokio::test]
async fn ingest_leaves_clean_issue_title_unmasked() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "issue-clean-title-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
let issue_json = json!([{
"number": 12,
"title": "Fix the flaky retry test",
"author": {"login": "octocat"},
"createdAt": "2026-01-01T00:00:00Z",
"closedAt": null,
"updatedAt": "2026-01-01T00:00:00Z",
"labels": [],
"stateReason": "",
"body": ""
}])
.to_string();
write_fake_gh(&bin_dir, &log_dir, "[]", &issue_json);
let _path_guard = PathGuard::install(&bin_dir);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(report.issues_ingested, 1, "{report:?}");
let issues_list = registry
.dispatch("list", json!({"kind": "issue", "limit": 10}))
.await
.expect("list issues ok");
let items = issues_list.as_array().expect("array");
assert_eq!(items.len(), 1);
let item = &items[0];
assert_eq!(
item["name"].as_str().unwrap(),
"#12 Fix the flaky retry test"
);
assert_eq!(
item["properties"]["title"].as_str().unwrap(),
"Fix the flaky retry test"
);
}
#[tokio::test]
async fn ingest_masks_credential_shaped_issue_label_without_dropping_note() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "issue-label-credential-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
let fake_token = "ghp_BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
let issue_json = json!([{
"number": 13,
"title": "Rotate the leaked deploy key",
"author": {"login": "octocat"},
"createdAt": "2026-01-01T00:00:00Z",
"closedAt": null,
"updatedAt": "2026-01-01T00:00:00Z",
"labels": [{"name": fake_token}],
"stateReason": "",
"body": ""
}])
.to_string();
write_fake_gh(&bin_dir, &log_dir, "[]", &issue_json);
let _path_guard = PathGuard::install(&bin_dir);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(
report.issues_ingested, 1,
"the issue must not be dropped: {report:?}"
);
assert!(
report.warnings.iter().all(|w| !w.contains("issue #13")),
"no silent-drop warning may be reported: {:?}",
report.warnings
);
let issues_list = registry
.dispatch("list", json!({"kind": "issue", "limit": 10}))
.await
.expect("list issues ok");
let items = issues_list.as_array().expect("array");
assert_eq!(items.len(), 1);
let item = &items[0];
let stored_labels = item["properties"]["labels"]
.as_array()
.expect("properties.labels is array");
assert_eq!(stored_labels.len(), 1);
let stored_label = stored_labels[0].as_str().expect("label is string");
assert!(
!stored_label.contains(fake_token),
"raw credential must not survive into properties.labels: {stored_label:?}"
);
assert!(
stored_label.contains("***MASKED***"),
"masked marker must be present in properties.labels: {stored_label:?}"
);
}
#[tokio::test]
async fn ingest_masks_credential_shaped_issue_author_login_without_dropping_note() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "issue-login-credential-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
let fake_token = "ghp_CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
let issue_json = json!([{
"number": 14,
"title": "Investigate flaky CI runner",
"author": {"login": fake_token},
"createdAt": "2026-01-01T00:00:00Z",
"closedAt": null,
"updatedAt": "2026-01-01T00:00:00Z",
"labels": [],
"stateReason": "",
"body": ""
}])
.to_string();
write_fake_gh(&bin_dir, &log_dir, "[]", &issue_json);
let _path_guard = PathGuard::install(&bin_dir);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(
report.issues_ingested, 1,
"the issue must not be dropped: {report:?}"
);
assert!(
report.warnings.iter().all(|w| !w.contains("issue #14")),
"no silent-drop warning may be reported: {:?}",
report.warnings
);
let issues_list = registry
.dispatch("list", json!({"kind": "issue", "limit": 10}))
.await
.expect("list issues ok");
let items = issues_list.as_array().expect("array");
assert_eq!(items.len(), 1);
let item = &items[0];
let stored_author = item["properties"]["author"]
.as_str()
.expect("properties.author is string");
assert!(
!stored_author.contains(fake_token),
"raw credential must not survive into properties.author: {stored_author:?}"
);
assert!(
stored_author.contains("***MASKED***"),
"masked marker must be present in properties.author: {stored_author:?}"
);
}
#[tokio::test]
async fn ingest_rejects_credential_shaped_issue_created_at_without_dropping_note() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "issue-created-at-credential-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
let fake_token = "ghp_DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD";
let issue_json = json!([{
"number": 20,
"title": "Investigate credential-shaped createdAt",
"author": {"login": "octocat"},
"createdAt": fake_token,
"closedAt": null,
"updatedAt": "2026-01-01T00:00:00Z",
"labels": [],
"stateReason": "",
"body": ""
}])
.to_string();
write_fake_gh(&bin_dir, &log_dir, "[]", &issue_json);
let _path_guard = PathGuard::install(&bin_dir);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(
report.issues_ingested, 1,
"an unparseable createdAt must not drop the issue: {report:?}"
);
let issues_list = registry
.dispatch("list", json!({"kind": "issue", "limit": 10}))
.await
.expect("list issues ok");
let items = issues_list.as_array().expect("array");
assert_eq!(items.len(), 1);
let item = &items[0];
assert!(
item["properties"]["created_at"].is_null(),
"an unparseable createdAt must be rejected, not persisted raw: {:?}",
item["properties"]["created_at"]
);
let dumped = item.to_string();
assert!(
!dumped.contains(fake_token),
"raw credential-shaped createdAt must not survive anywhere in the stored record: {dumped}"
);
}
#[tokio::test]
async fn ingest_rejects_credential_shaped_issue_closed_at_without_dropping_note() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "issue-closed-at-credential-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
let fake_token = "ghp_FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF";
let issue_json = json!([{
"number": 21,
"title": "Investigate credential-shaped closedAt",
"author": {"login": "octocat"},
"createdAt": "2026-01-01T00:00:00Z",
"closedAt": fake_token,
"updatedAt": "2026-01-01T00:00:00Z",
"labels": [],
"stateReason": "",
"body": ""
}])
.to_string();
write_fake_gh(&bin_dir, &log_dir, "[]", &issue_json);
let _path_guard = PathGuard::install(&bin_dir);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(
report.issues_ingested, 1,
"an unparseable closedAt must not drop the issue: {report:?}"
);
let issues_list = registry
.dispatch("list", json!({"kind": "issue", "limit": 10}))
.await
.expect("list issues ok");
let items = issues_list.as_array().expect("array");
assert_eq!(items.len(), 1);
let item = &items[0];
assert!(
item["properties"]["closed_at"].is_null(),
"an unparseable closedAt must be rejected, not persisted raw: {:?}",
item["properties"]["closed_at"]
);
let dumped = item.to_string();
assert!(
!dumped.contains(fake_token),
"raw credential-shaped closedAt must not survive anywhere in the stored record: {dumped}"
);
}
#[tokio::test]
async fn ingest_rejects_credential_shaped_issue_updated_at_and_cursor_never_persists_raw_value() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "issue-updated-at-credential-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
let fake_token = "ghp_EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE";
let issue_json = json!([
{
"number": 22,
"title": "Credential-shaped updatedAt",
"author": {"login": "octocat"},
"createdAt": "2026-01-01T00:00:00Z",
"closedAt": null,
"updatedAt": fake_token,
"labels": [],
"stateReason": "",
"body": ""
},
{
"number": 23,
"title": "Clean sibling issue",
"author": {"login": "octocat"},
"createdAt": "2026-01-02T00:00:00Z",
"closedAt": null,
"updatedAt": "2026-01-02T00:00:00Z",
"labels": [],
"stateReason": "",
"body": ""
}
])
.to_string();
write_fake_gh(&bin_dir, &log_dir, "[]", &issue_json);
let _path_guard = PathGuard::install(&bin_dir);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(
report.issues_ingested, 2,
"an unparseable updatedAt must not drop the issue: {report:?}"
);
let cursor = read_git_cursor(&rt, project_id, "issues")
.await
.expect("cursor must be written from the sibling issue's valid updatedAt");
assert!(
!cursor.contains(fake_token),
"the paging cursor must never persist a raw credential-shaped updatedAt: {cursor:?}"
);
assert!(
cursor.starts_with("2026-01-02"),
"the cursor must advance to the sibling issue's valid, canonicalized updatedAt: {cursor:?}"
);
}
#[tokio::test]
async fn ingest_masks_multiple_credential_spans_in_pr_title_and_body() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "pr-multi-span-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
let title_token = "ghp_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
let body_token = "ghp_BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
let pr_json = json!([{
"number": 13,
"title": format!("Purge {title_token} from history"),
"author": {"login": "octocat"},
"createdAt": "2026-01-01T00:00:00Z",
"mergedAt": null,
"closedAt": null,
"updatedAt": "2026-01-01T00:00:00Z",
"baseRefName": "main",
"headRefName": "fix/purge",
"mergeCommit": null,
"body": format!("Also found {body_token} committed earlier, and again {body_token} in a second commit.")
}])
.to_string();
write_fake_gh(&bin_dir, &log_dir, &pr_json, "[]");
let _path_guard = PathGuard::install(&bin_dir);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(
report.prs_ingested, 1,
"exactly one PR note written: {report:?}"
);
let prs_list = registry
.dispatch("list", json!({"kind": "pull_request", "limit": 10}))
.await
.expect("list prs ok");
let items = prs_list.as_array().expect("array");
assert_eq!(items.len(), 1, "no duplicate PR notes: {items:?}");
let item = &items[0];
let stored_name = item["name"].as_str().expect("name is string");
let stored_title = item["properties"]["title"]
.as_str()
.expect("properties.title is string");
let stored_content = item["content"].as_str().expect("content is string");
assert!(
!stored_name.contains(title_token)
&& !stored_title.contains(title_token)
&& !stored_content.contains(body_token),
"no raw credential span may survive in any surface: \
name={stored_name:?}, title={stored_title:?}, content={stored_content:?}"
);
assert_eq!(
stored_content.matches("***MASKED***").count(),
2,
"both body occurrences of the credential must be masked independently: {stored_content:?}"
);
assert!(
stored_title.contains("***MASKED***"),
"title span must be masked: {stored_title:?}"
);
}
#[tokio::test]
async fn ingest_leaves_clean_pr_title_and_null_body_unmasked() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "pr-clean-title-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
let hex64 = "deadbeef".repeat(8);
let title = format!("Document the {hex64} commit reference");
let pr_json = json!([{
"number": 9,
"title": title,
"author": {"login": "octocat"},
"createdAt": "2026-01-01T00:00:00Z",
"mergedAt": null,
"closedAt": null,
"updatedAt": "2026-01-01T00:00:00Z",
"baseRefName": "main",
"headRefName": "docs/reference",
"mergeCommit": null,
"body": null
}])
.to_string();
write_fake_gh(&bin_dir, &log_dir, &pr_json, "[]");
let _path_guard = PathGuard::install(&bin_dir);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(report.prs_ingested, 1, "the PR must land: {report:?}");
assert_eq!(
report.prs_skipped_existing, 0,
"a first-seen PR is never counted as skipped-existing: {report:?}"
);
let prs_list = registry
.dispatch("list", json!({"kind": "pull_request", "limit": 10}))
.await
.expect("list prs ok");
let items = prs_list.as_array().expect("array");
assert_eq!(items.len(), 1);
let item = &items[0];
let stored_name = item["name"].as_str().expect("name is string");
let stored_title = item["properties"]["title"]
.as_str()
.expect("properties.title is string");
let stored_content = item["content"].as_str().expect("content is string");
assert_eq!(
stored_title, title,
"a clean title must be stored byte-for-byte unchanged"
);
assert_eq!(
stored_name,
format!("#9 {title}"),
"the name must be the unmodified `#<number> <title>` form"
);
assert_eq!(
stored_content, "",
"a null body must serialize as an empty, unmasked content string"
);
assert!(
!stored_title.contains("***MASKED***") && !stored_name.contains("***MASKED***"),
"no masking marker may appear on clean input: title={stored_title:?}, name={stored_name:?}"
);
let project_neighbors = registry
.dispatch(
"neighbors",
json!({"id": project_id.to_string(), "direction": "incoming", "relations": ["annotates"]}),
)
.await
.expect("neighbors ok");
let pr_neighbor_count = project_neighbors
.as_array()
.expect("array")
.iter()
.filter(|h| h["kind"] == "pull_request")
.count();
assert_eq!(
pr_neighbor_count, 1,
"the PR's project annotation edge must still materialize for clean input: {project_neighbors:?}"
);
}
#[tokio::test]
async fn ingest_masks_pr_title_before_truncating_name_to_max_chars() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "pr-mask-before-truncate-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
let filler = "x".repeat(80);
let fake_token = "ghp_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
let title = format!("{filler} {fake_token} TAIL_SURVIVED_MARKER");
let raw_name_len = format!("#1 {title}").len();
assert!(
raw_name_len > 120,
"fixture must exceed NAME_MAX_CHARS pre-mask: {raw_name_len}"
);
let pr_json = json!([{
"number": 1,
"title": title,
"author": {"login": "octocat"},
"createdAt": "2026-01-01T00:00:00Z",
"mergedAt": null,
"closedAt": null,
"updatedAt": "2026-01-01T00:00:00Z",
"baseRefName": "main",
"headRefName": "fix/order",
"mergeCommit": null,
"body": ""
}])
.to_string();
write_fake_gh(&bin_dir, &log_dir, &pr_json, "[]");
let _path_guard = PathGuard::install(&bin_dir);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(
report.prs_ingested, 1,
"the PR must not be dropped: {report:?}"
);
let prs_list = registry
.dispatch("list", json!({"kind": "pull_request", "limit": 10}))
.await
.expect("list prs ok");
let items = prs_list.as_array().expect("array");
assert_eq!(items.len(), 1);
let item = &items[0];
let stored_name = item["name"].as_str().expect("name is string");
let stored_title = item["properties"]["title"]
.as_str()
.expect("properties.title is string");
assert!(
stored_name.chars().count() <= 120,
"the stored name must respect NAME_MAX_CHARS: {stored_name:?}"
);
assert!(
!stored_name.contains(fake_token) && !stored_title.contains(fake_token),
"raw credential must not survive: name={stored_name:?}, title={stored_title:?}"
);
assert!(
stored_name.contains("***MASKED***"),
"masked marker must be present in the truncated name: {stored_name:?}"
);
assert!(
stored_name.contains("TAIL_SURVIVED_MARKER"),
"masking must run before truncation so the trailing marker, which would be \
cut away if the raw (unmasked) name were truncated first, survives: {stored_name:?}"
);
}
#[tokio::test]
async fn ingest_masks_pr_body_credential_without_breaking_fixes_reference() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "pr-mask-and-reference-repo"}),
)
.await;
let issue_id = create(
®istry,
json!({
"kind": "issue",
"content": "",
"properties": {"number": 42, "title": "Some bug", "project_id": project_id.to_string()},
"annotates": [project_id.to_string()],
}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
let fake_token = "ghp_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
let pr_json = json!([{
"number": 7,
"title": "Rotate the leaked credential",
"author": {"login": "octocat"},
"createdAt": "2026-01-01T00:00:00Z",
"mergedAt": null,
"closedAt": null,
"updatedAt": "2026-01-01T00:00:00Z",
"baseRefName": "main",
"headRefName": "fix/rotate",
"mergeCommit": null,
"body": format!("Rotated {fake_token} out of the config. Fixes #42.")
}])
.to_string();
write_fake_gh(&bin_dir, &log_dir, &pr_json, "[]");
let _path_guard = PathGuard::install(&bin_dir);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(
report.prs_ingested, 1,
"the PR must not be dropped: {report:?}"
);
assert_eq!(
report.reference_edges_created, 1,
"the Fixes #42 reference must resolve even though the body was masked: {report:?}"
);
let prs_list = registry
.dispatch("list", json!({"kind": "pull_request", "limit": 10}))
.await
.expect("list prs ok");
let items = prs_list.as_array().expect("array");
assert_eq!(items.len(), 1);
let content = items[0]["content"].as_str().expect("content is string");
assert!(
!content.contains(fake_token),
"raw credential must not survive: {content:?}"
);
assert!(
content.contains("***MASKED***"),
"masked marker must be present: {content:?}"
);
assert!(
content.contains("Fixes #42"),
"the reference text itself must be retained in stored content: {content:?}"
);
let issue_neighbors = registry
.dispatch(
"neighbors",
json!({"id": issue_id.to_string(), "direction": "incoming", "relations": ["annotates"]}),
)
.await
.expect("neighbors ok");
let hits = issue_neighbors.as_array().expect("array");
assert_eq!(
hits.len(),
1,
"exactly one PR annotates the referenced issue: {hits:?}"
);
assert_eq!(hits[0]["kind"], "pull_request");
}
#[tokio::test]
async fn commit_hook_rejects_bad_sha() {
let (_rt, _token, registry) = fixture().await;
let err = registry
.dispatch(
"create",
json!({
"kind": "commit",
"content": "bad commit",
"properties": {"sha": "not-a-sha"},
}),
)
.await
.expect_err("bad sha must be rejected");
let msg = format!("{err}");
assert!(
msg.contains("40-character hex"),
"error must name the valid shape: {msg}"
);
}
#[tokio::test]
async fn issue_hook_rejects_ungoverned_state_reason() {
let (_rt, _token, registry) = fixture().await;
let project_id = create(®istry, json!({"kind": "project", "name": "hook-repo"})).await;
let err = registry
.dispatch(
"create",
json!({
"kind": "issue",
"content": "bad issue",
"properties": {
"number": 7,
"state_reason": "wontfix",
"project_id": project_id.to_string(),
},
}),
)
.await
.expect_err("ungoverned state_reason must be rejected");
let msg = format!("{err}");
assert!(
msg.contains("completed") && msg.contains("not_planned"),
"error must list the governed set: {msg}"
);
}
#[tokio::test]
async fn issue_ingest_never_echoes_credential_shaped_state_reason() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "state-reason-leak-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
const CREDENTIAL: &str = "ghp_FAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKE1";
let issue_json = json!([
{"number": 42, "title": "credential-shaped stateReason", "author": {"login": "a"},
"createdAt": "2026-01-01T00:00:00Z", "closedAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-01-01T00:00:00Z", "labels": [], "stateReason": CREDENTIAL, "body": ""}
])
.to_string();
write_fake_gh(&bin_dir, &log_dir, "[]", &issue_json);
let _path_guard = PathGuard::install(&bin_dir);
let mut opts = IngestOptions::unbounded(repo.clone(), project_id.to_string());
opts.include.pull_requests = false;
opts.include.commits = false;
let report = run_ingest(&rt, &token, ®istry, opts)
.await
.expect("ingest ok");
assert_eq!(
report.issues_ingested, 0,
"the ungoverned-stateReason record must be rejected, not created: {report:?}"
);
assert!(
!report.warnings.is_empty(),
"the rejection must be reported as a warning: {report:?}"
);
assert!(
report.warnings.iter().any(|w| w.contains("issue #42")),
"the warning must name the rejected record: {:?}",
report.warnings
);
assert!(
report.warnings.iter().all(|w| !w.contains(CREDENTIAL)),
"the raw credential-shaped stateReason must never appear in report.warnings: {:?}",
report.warnings
);
let issues_list = registry
.dispatch("list", json!({"kind": "issue", "limit": 10}))
.await
.expect("list issues ok");
let items = issues_list.as_array().expect("array");
assert!(
items.is_empty(),
"the ungoverned record must never land: {items:?}"
);
}
#[tokio::test]
async fn issue_hook_requires_properties_project_id() {
let (_rt, _token, registry) = fixture().await;
let err = registry
.dispatch(
"create",
json!({
"kind": "issue",
"content": "no project_id",
"properties": {"number": 8},
}),
)
.await
.expect_err("missing project_id must be rejected");
assert!(format!("{err}").contains("project_id"));
}
#[tokio::test]
async fn commit_hook_requires_properties_sha() {
let (_rt, _token, registry) = fixture().await;
let err = registry
.dispatch(
"create",
json!({"kind": "commit", "content": "no sha", "properties": {}}),
)
.await
.expect_err("missing sha must be rejected");
assert!(format!("{err}").contains("sha"));
}
#[tokio::test]
async fn issue_and_pr_idempotency_is_scoped_per_project() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_a = create(®istry, json!({"kind": "project", "name": "repo-a"})).await;
let project_b = create(®istry, json!({"kind": "project", "name": "repo-b"})).await;
let pr_a = create(
®istry,
json!({
"kind": "pull_request",
"content": "",
"properties": {"number": 1, "title": "A#1", "project_id": project_a.to_string()},
"annotates": [project_a.to_string()],
}),
)
.await;
let issue_a = create(
®istry,
json!({
"kind": "issue",
"content": "",
"properties": {"number": 1, "title": "issue A#1", "project_id": project_a.to_string()},
"annotates": [project_a.to_string()],
}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo_b: PathBuf = dir.path().to_path_buf();
init_repo(&repo_b);
write(&repo_b, "README.md", "b\n");
commit(&repo_b, &["README.md"], "Add repo-b feature (#1)");
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo_b.clone(), project_b.to_string()),
)
.await
.expect("ingest ok (pass 1)");
assert_eq!(report.commits_ingested, 1);
let pr_a_neighbors = registry
.dispatch(
"neighbors",
json!({"id": pr_a.to_string(), "direction": "incoming", "relations": ["annotates"]}),
)
.await
.expect("neighbors ok");
assert_eq!(
pr_a_neighbors.as_array().expect("array").len(),
0,
"project B's commit must never attach to project A's PR #1: {pr_a_neighbors:?}"
);
let pr_b = create(
®istry,
json!({
"kind": "pull_request",
"content": "",
"properties": {"number": 1, "title": "B#1", "project_id": project_b.to_string()},
"annotates": [project_b.to_string()],
}),
)
.await;
let issue_b = create(
®istry,
json!({
"kind": "issue",
"content": "",
"properties": {"number": 1, "title": "issue B#1", "project_id": project_b.to_string()},
"annotates": [project_b.to_string()],
}),
)
.await;
assert_ne!(pr_a, pr_b, "both projects' #1 PRs are distinct records");
assert_ne!(
issue_a, issue_b,
"both projects' #1 issues are distinct records"
);
write(&repo_b, "src/lib.rs", "// b2\n");
commit(&repo_b, &["src/lib.rs"], "Add another repo-b feature (#1)");
run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo_b.clone(), project_b.to_string()),
)
.await
.expect("ingest ok (pass 2)");
let pr_b_neighbors = registry
.dispatch(
"neighbors",
json!({"id": pr_b.to_string(), "direction": "incoming", "relations": ["annotates"]}),
)
.await
.expect("neighbors ok");
assert_eq!(
pr_b_neighbors.as_array().expect("array").len(),
1,
"project B's own PR #1 resolves the squash-merge fallback: {pr_b_neighbors:?}"
);
let pr_a_neighbors_after = registry
.dispatch(
"neighbors",
json!({"id": pr_a.to_string(), "direction": "incoming", "relations": ["annotates"]}),
)
.await
.expect("neighbors ok");
assert_eq!(
pr_a_neighbors_after.as_array().expect("array").len(),
0,
"project A's PR #1 remains untouched: {pr_a_neighbors_after:?}"
);
}
#[tokio::test]
async fn gh_boundary_contract_and_partial_ingest_failure() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "gh-boundary-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let repo_canon = repo.canonicalize().expect("canonicalize repo");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
let pr_json = json!([{
"number": 99,
"title": "Add feature",
"author": {"login": "octocat"},
"createdAt": "2026-01-01T00:00:00Z",
"mergedAt": "2026-01-02T00:00:00Z",
"closedAt": "2026-01-02T00:00:00Z",
"updatedAt": "2026-01-02T00:00:00Z",
"baseRefName": "main",
"headRefName": "feature/x",
"mergeCommit": null,
"body": "PR body"
}])
.to_string();
let issue_json = json!([
{"number": 1, "title": "i1", "author": {"login": "a"}, "createdAt": "2026-01-01T00:00:00Z", "closedAt": null, "updatedAt": "2026-01-01T00:00:01Z", "labels": [], "stateReason": "", "body": ""},
{"number": 2, "title": "i2", "author": {"login": "a"}, "createdAt": "2026-01-01T00:00:00Z", "closedAt": "2026-01-01T00:00:02Z", "updatedAt": "2026-01-01T00:00:02Z", "labels": [], "stateReason": "NOT_PLANNED", "body": ""},
{"number": 3, "title": "i3-bad", "author": {"login": "a"}, "createdAt": "2026-01-01T00:00:00Z", "closedAt": "2026-01-01T00:00:03Z", "updatedAt": "2026-01-01T00:00:03Z", "labels": [], "stateReason": "WONTFIX", "body": ""},
{"number": 4, "title": "i4", "author": {"login": "a"}, "createdAt": "2026-01-01T00:00:00Z", "closedAt": "2026-01-01T00:00:04Z", "updatedAt": "2026-01-01T00:00:04Z", "labels": [], "stateReason": "COMPLETED", "body": ""},
{"number": 5, "title": "i5", "author": {"login": "a"}, "createdAt": "2026-01-01T00:00:00Z", "closedAt": "2026-01-01T00:00:05Z", "updatedAt": "2026-01-01T00:00:05Z", "labels": [], "stateReason": "REOPENED", "body": ""},
{"number": 6, "title": "i6", "author": {"login": "a"}, "createdAt": "2026-01-01T00:00:00Z", "closedAt": "2026-01-01T00:00:06Z", "updatedAt": "2026-01-01T00:00:06Z", "labels": [], "stateReason": "DUPLICATE", "body": ""}
])
.to_string();
write_fake_gh(&bin_dir, &log_dir, &pr_json, &issue_json);
let _path_guard = PathGuard::install(&bin_dir);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok (pass 1)");
assert!(
report.gh_available,
"fake gh must be found on PATH: {report:?}"
);
assert_eq!(report.prs_ingested, 1, "{report:?}");
assert_eq!(
report.issues_ingested, 5,
"5 of 6 issues land, #3 warns-and-skips: {report:?}"
);
assert_eq!(
report
.warnings
.iter()
.filter(|w| w.contains("issue #3"))
.count(),
1,
"exactly one warning names the ungoverned record: {:?}",
report.warnings
);
let args_log = std::fs::read_to_string(log_dir.join("args.log")).expect("read args.log");
assert!(
!args_log.contains("-C"),
"gh must never receive an unsupported -C flag: {args_log}"
);
let pr_and_issue_invocations: Vec<&str> = args_log
.lines()
.filter(|l| l.starts_with("pr ") || l.starts_with("issue "))
.collect();
assert!(
!pr_and_issue_invocations.is_empty(),
"expected at least one gh pr/issue list invocation: {args_log}"
);
for line in &pr_and_issue_invocations {
assert!(
line.contains("--state all"),
"every gh pr/issue list invocation must request --state all: {line:?}"
);
}
let cwd_log = std::fs::read_to_string(log_dir.join("cwd.log")).expect("read cwd.log");
let cwd_lines: Vec<&str> = cwd_log.lines().filter(|l| !l.is_empty()).collect();
assert!(
!cwd_lines.is_empty(),
"gh must have been invoked at least once"
);
for line in &cwd_lines {
let logged = Path::new(line)
.canonicalize()
.expect("canonicalize logged cwd");
assert_eq!(
logged, repo_canon,
"every gh invocation must run with the repo as its current_dir"
);
}
let issues_list = registry
.dispatch("list", json!({"kind": "issue", "limit": 20}))
.await
.expect("list issues ok");
let issue_items = issues_list.as_array().expect("array");
let issue_by_number = |n: u64| -> &Value {
issue_items
.iter()
.find(|i| i["properties"]["number"].as_u64() == Some(n))
.unwrap_or_else(|| panic!("issue #{n} must be stored: {issue_items:?}"))
};
assert!(
issue_by_number(1)["properties"]
.get("state_reason")
.is_none(),
"empty stateReason must be omitted, not stored as \"\""
);
assert_eq!(
issue_by_number(2)["properties"]["state_reason"],
"not_planned"
);
assert_eq!(
issue_by_number(4)["properties"]["state_reason"],
"completed"
);
assert_eq!(issue_by_number(5)["properties"]["state_reason"], "reopened");
assert_eq!(
issue_by_number(6)["properties"]["state_reason"],
"duplicate"
);
assert!(
!issue_items
.iter()
.any(|i| i["properties"]["number"].as_u64() == Some(3)),
"the ungoverned record must never land: {issue_items:?}"
);
let prs_list = registry
.dispatch("list", json!({"kind": "pull_request", "limit": 10}))
.await
.expect("list prs ok");
let pr_items = prs_list.as_array().expect("array");
let pr99 = pr_items
.iter()
.find(|i| i["properties"]["number"].as_u64() == Some(99))
.expect("PR #99 must be stored");
assert_eq!(pr99["properties"]["base_ref"], "main");
assert_eq!(pr99["properties"]["head_ref"], "feature/x");
assert!(
pr99["properties"].get("base").is_none() && pr99["properties"].get("head").is_none(),
"ADR-088 names these base_ref/head_ref, not base/head: {pr99:?}"
);
let report2 = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok (pass 2)");
assert_eq!(
report2.issues_ingested, 0,
"already-landed issues are found by natural key, not re-created: {report2:?}"
);
assert_eq!(
report2
.warnings
.iter()
.filter(|w| w.contains("issue #3"))
.count(),
1,
"the frozen cursor retries the failed record every pass, not just once: {:?}",
report2.warnings
);
}
#[tokio::test]
async fn issue_full_page_never_leaks_raw_updated_at_into_paging_floor() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "full-page-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
const PAGE_LIMIT: usize = 1000;
const CREDENTIAL: &str = "sk-ant-api03-FAKE1234567890FAKE1234567890FAKE1234567890FAKE";
let mut issues: Vec<Value> = (1..PAGE_LIMIT)
.map(|i| {
let minute = i / 60;
let second = i % 60;
json!({
"number": i,
"title": format!("issue {i}"),
"author": {"login": "a"},
"createdAt": "2026-01-01T00:00:00Z",
"closedAt": null,
"updatedAt": format!("2026-01-01T{minute:02}:{second:02}:00Z"),
"labels": [],
"stateReason": "",
"body": ""
})
})
.collect();
issues.push(json!({
"number": PAGE_LIMIT,
"title": "issue with malformed updatedAt",
"author": {"login": "a"},
"createdAt": "2026-01-01T00:00:00Z",
"closedAt": null,
"updatedAt": CREDENTIAL,
"labels": [],
"stateReason": "",
"body": ""
}));
assert_eq!(
issues.len(),
PAGE_LIMIT,
"page must be exactly PAGE_LIMIT-sized to force the continuation branch"
);
let issue_json = Value::Array(issues).to_string();
write_fake_gh(&bin_dir, &log_dir, "[]", &issue_json);
let _path_guard = PathGuard::install(&bin_dir);
let mut opts = IngestOptions::unbounded(repo.clone(), project_id.to_string());
opts.include.pull_requests = false;
opts.include.commits = false;
let report = run_ingest(&rt, &token, ®istry, opts)
.await
.expect("ingest ok");
assert_eq!(
report.issues_ingested, PAGE_LIMIT as u64,
"every record lands -- the malformed record only loses its updated_at field: {report:?}"
);
let args_log = std::fs::read_to_string(log_dir.join("args.log")).expect("read args.log");
assert!(
!args_log.contains(CREDENTIAL),
"the raw credential-shaped updatedAt must never reach a gh invocation's argv \
(paging floor leak): {args_log}"
);
let issue_invocations = args_log.lines().filter(|l| l.starts_with("issue ")).count();
assert!(
issue_invocations >= 2,
"a full page must be followed by a continuation fetch: {args_log}"
);
let cursor = read_git_cursor(&rt, project_id, "issues")
.await
.expect("cursor must be written");
assert!(
!cursor.contains(CREDENTIAL),
"the persisted paging cursor must never contain the raw credential value: {cursor}"
);
let mut scanned = 0usize;
let mut offset = 0u64;
loop {
let page = registry
.dispatch(
"list",
json!({"kind": "issue", "limit": (PAGE_LIMIT + 10) as u64, "offset": offset}),
)
.await
.expect("list issues ok");
let items = match page.as_array() {
Some(items) => items.clone(),
None => page
.get("items")
.and_then(Value::as_array)
.expect("clamped list response must carry an items array")
.clone(),
};
assert!(
items.iter().all(|i| !i.to_string().contains(CREDENTIAL)),
"no persisted issue record may contain the raw credential value"
);
scanned += items.len();
if items.is_empty() {
break;
}
offset += items.len() as u64;
}
assert!(
scanned >= PAGE_LIMIT,
"paging scan must cover every persisted issue: scanned {scanned}"
);
}
async fn read_git_cursor(rt: &KhiveRuntime, project_id: Uuid, kind: &str) -> Option<String> {
use khive_storage::types::{SqlStatement, SqlValue};
let sql = rt.sql();
let mut r = sql.reader().await.expect("sql reader");
let row = r
.query_row(SqlStatement {
sql: "SELECT cursor_value FROM git_mirror_cursor WHERE project_id=?1 AND kind=?2"
.into(),
params: vec![
SqlValue::Text(project_id.to_string()),
SqlValue::Text(kind.to_string()),
],
label: Some("test_read_git_cursor".into()),
})
.await
.expect("query cursor row");
row.and_then(|r| match r.get("cursor_value") {
Some(SqlValue::Text(s)) => Some(s.clone()),
_ => None,
})
}
#[tokio::test]
async fn issue_ingest_sorts_by_updated_at_so_frozen_cursor_survives_out_of_order_listing() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "issue-order-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
let bad_issue_json = |state_reason: &str| {
json!([
{"number": 10, "title": "i10-newest-good", "author": {"login": "a"}, "createdAt": "2026-01-01T00:00:00Z", "closedAt": null, "updatedAt": "2026-01-03T00:00:00Z", "labels": [], "stateReason": "completed", "body": ""},
{"number": 20, "title": "i20-older-bad", "author": {"login": "a"}, "createdAt": "2026-01-01T00:00:00Z", "closedAt": null, "updatedAt": "2026-01-01T00:00:00Z", "labels": [], "stateReason": state_reason, "body": ""},
{"number": 5, "title": "i5-oldest-good", "author": {"login": "a"}, "createdAt": "2026-01-01T00:00:00Z", "closedAt": null, "updatedAt": "2025-12-01T00:00:00Z", "labels": [], "stateReason": "", "body": ""}
])
.to_string()
};
write_fake_gh(&bin_dir, &log_dir, "[]", &bad_issue_json("WONTFIX"));
let _path_guard = PathGuard::install(&bin_dir);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok (pass 1)");
assert_eq!(
report.issues_ingested, 2,
"#5 and #10 both land, #20 warns-and-skips: {report:?}"
);
assert_eq!(
report
.warnings
.iter()
.filter(|w| w.contains("issue #20"))
.count(),
1,
"exactly one warning names the ungoverned record: {:?}",
report.warnings
);
let cursor_after_pass1 = read_git_cursor(&rt, project_id, "issues")
.await
.expect("cursor must be written (#5 landed before the stall)");
assert_eq!(
cursor_after_pass1, "2025-12-01T00:00:00Z",
"cursor must freeze at #5's timestamp (at/below failed #20's \
2026-01-01T00:00:00Z), not advance to #10's later 2026-01-03T00:00:00Z \
just because #10 appeared earlier in gh's raw (unsorted) output: {cursor_after_pass1:?}"
);
std::fs::write(
log_dir.join("issue_response.json"),
bad_issue_json("completed"),
)
.expect("rewrite issue fixture with corrected stateReason");
let report2 = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok (pass 2)");
assert_eq!(
report2.issues_ingested, 1,
"only #20 (now corrected) is newly created on pass 2: {report2:?}"
);
assert_eq!(
report2.issues_skipped_existing, 2,
"#5 and #10 are found by natural key, not duplicated: {report2:?}"
);
assert!(
report2.warnings.iter().all(|w| !w.contains("issue #20")),
"#20 must not warn once its stateReason is corrected: {:?}",
report2.warnings
);
let issues_list = registry
.dispatch("list", json!({"kind": "issue", "limit": 20}))
.await
.expect("list issues ok");
let numbers: Vec<u64> = issues_list
.as_array()
.expect("array")
.iter()
.filter_map(|i| i["properties"]["number"].as_u64())
.collect();
assert_eq!(
numbers.len(),
3,
"exactly #5, #10, #20 — no duplicates: {numbers:?}"
);
let cursor_after_pass2 = read_git_cursor(&rt, project_id, "issues")
.await
.expect("cursor must be written");
assert_eq!(cursor_after_pass2, "2026-01-03T00:00:00Z");
}
#[tokio::test]
async fn pr_ingest_sorts_by_updated_at_so_frozen_cursor_survives_out_of_order_listing() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
rt.register_embedder(FailOnceEmbedderProvider);
let project_id = create(
®istry,
json!({"kind": "project", "name": "pr-order-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
let bad_pr_json = json!([
{"number": 10, "title": "pr10-newest-good", "author": {"login": "a"}, "createdAt": "2026-01-01T00:00:00Z", "mergedAt": null, "closedAt": null, "updatedAt": "2026-01-03T00:00:00Z", "baseRefName": "main", "headRefName": "f10", "mergeCommit": null, "body": ""},
{"number": 20, "title": "pr20-older-bad", "author": {"login": "a"}, "createdAt": "2026-01-01T00:00:00Z", "mergedAt": null, "closedAt": null, "updatedAt": "2026-01-01T00:00:00Z", "baseRefName": "main", "headRefName": "f20", "mergeCommit": null, "body": CURSOR_FAIL_SENTINEL},
{"number": 5, "title": "pr5-oldest-good", "author": {"login": "a"}, "createdAt": "2026-01-01T00:00:00Z", "mergedAt": null, "closedAt": null, "updatedAt": "2025-12-01T00:00:00Z", "baseRefName": "main", "headRefName": "f5", "mergeCommit": null, "body": ""}
])
.to_string();
write_fake_gh(&bin_dir, &log_dir, &bad_pr_json, "[]");
let _path_guard = PathGuard::install(&bin_dir);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok (pass 1)");
assert_eq!(
report.prs_ingested, 2,
"#5 and #10 both land, #20 (injected embedder failure) warns-and-skips: {report:?}"
);
assert_eq!(
report
.warnings
.iter()
.filter(|w| w.contains("pull_request #20"))
.count(),
1,
"exactly one warning names the rejected record: {:?}",
report.warnings
);
let cursor_after_pass1 = read_git_cursor(&rt, project_id, "prs")
.await
.expect("cursor must be written (#5 landed before the stall)");
assert_eq!(
cursor_after_pass1, "2025-12-01T00:00:00Z",
"cursor must freeze at #5's timestamp (at/below failed #20's \
2026-01-01T00:00:00Z), not advance to #10's later 2026-01-03T00:00:00Z \
just because #10 appeared earlier in gh's raw (unsorted) output: {cursor_after_pass1:?}"
);
let report2 = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok (pass 2)");
assert_eq!(
report2.prs_ingested, 1,
"only #20 (embedder fuse now spent) is newly created on pass 2: {report2:?}"
);
assert_eq!(
report2.prs_skipped_existing, 2,
"#5 and #10 are found by natural key, not duplicated: {report2:?}"
);
assert!(
report2
.warnings
.iter()
.all(|w| !w.contains("pull_request #20")),
"#20 must not warn once the embedder fuse is spent: {:?}",
report2.warnings
);
let prs_list = registry
.dispatch("list", json!({"kind": "pull_request", "limit": 20}))
.await
.expect("list prs ok");
let numbers: Vec<u64> = prs_list
.as_array()
.expect("array")
.iter()
.filter_map(|i| i["properties"]["number"].as_u64())
.collect();
assert_eq!(
numbers.len(),
3,
"exactly #5, #10, #20 — no duplicates: {numbers:?}"
);
let cursor_after_pass2 = read_git_cursor(&rt, project_id, "prs")
.await
.expect("cursor must be written");
assert_eq!(cursor_after_pass2, "2026-01-03T00:00:00Z");
}
#[tokio::test]
async fn issue_ingest_retries_tie_at_cursor_timestamp() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "issue-tie-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
const TIE_AT: &str = "2026-02-01T00:00:00Z";
let issue_json = |state_reason: &str| {
json!([
{"number": 5, "title": "i5-good", "author": {"login": "a"}, "createdAt": "2026-01-01T00:00:00Z", "closedAt": null, "updatedAt": TIE_AT, "labels": [], "stateReason": "", "body": ""},
{"number": 20, "title": "i20-bad-tied", "author": {"login": "a"}, "createdAt": "2026-01-01T00:00:00Z", "closedAt": null, "updatedAt": TIE_AT, "labels": [], "stateReason": state_reason, "body": ""}
])
.to_string()
};
write_fake_gh(&bin_dir, &log_dir, "[]", &issue_json("WONTFIX"));
let _path_guard = PathGuard::install(&bin_dir);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok (pass 1)");
assert_eq!(
report.issues_ingested, 1,
"#5 lands, #20 (tied timestamp, ungoverned stateReason) warns-and-skips: {report:?}"
);
assert_eq!(
report
.warnings
.iter()
.filter(|w| w.contains("issue #20"))
.count(),
1,
"exactly one warning names the ungoverned record: {:?}",
report.warnings
);
let cursor_after_pass1 = read_git_cursor(&rt, project_id, "issues")
.await
.expect("cursor must be written (#5 landed before the stall)");
assert_eq!(
cursor_after_pass1, TIE_AT,
"cursor freezes at #5's timestamp, which is the SAME as failed #20's — \
the exact tie the exclusive `updated > cursor` check used to strand \
#20 on: {cursor_after_pass1:?}"
);
std::fs::write(log_dir.join("issue_response.json"), issue_json("completed"))
.expect("rewrite issue fixture with corrected stateReason");
let report2 = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok (pass 2)");
assert_eq!(
report2.issues_ingested, 1,
"only #20 (now corrected) is newly created on pass 2, proving the \
tied timestamp did not strand it: {report2:?}"
);
assert_eq!(
report2.issues_skipped_existing, 1,
"#5 is found by natural key, not duplicated, even though it is \
re-examined every pass at the tied cursor timestamp: {report2:?}"
);
assert!(
report2.warnings.iter().all(|w| !w.contains("issue #20")),
"#20 must not warn once its stateReason is corrected: {:?}",
report2.warnings
);
let issues_list = registry
.dispatch("list", json!({"kind": "issue", "limit": 20}))
.await
.expect("list issues ok");
let numbers: Vec<u64> = issues_list
.as_array()
.expect("array")
.iter()
.filter_map(|i| i["properties"]["number"].as_u64())
.collect();
assert_eq!(
numbers.len(),
2,
"exactly #5, #20 — no duplicates: {numbers:?}"
);
}
#[tokio::test]
async fn pr_ingest_retries_tie_at_cursor_timestamp() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
rt.register_embedder(FailOnceEmbedderProvider);
let project_id = create(®istry, json!({"kind": "project", "name": "pr-tie-repo"})).await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
const TIE_AT: &str = "2026-02-01T00:00:00Z";
let pr_json = json!([
{"number": 5, "title": "pr5-good", "author": {"login": "a"}, "createdAt": "2026-01-01T00:00:00Z", "mergedAt": null, "closedAt": null, "updatedAt": TIE_AT, "baseRefName": "main", "headRefName": "f5", "mergeCommit": null, "body": ""},
{"number": 20, "title": "pr20-bad-tied", "author": {"login": "a"}, "createdAt": "2026-01-01T00:00:00Z", "mergedAt": null, "closedAt": null, "updatedAt": TIE_AT, "baseRefName": "main", "headRefName": "f20", "mergeCommit": null, "body": CURSOR_FAIL_SENTINEL}
])
.to_string();
write_fake_gh(&bin_dir, &log_dir, &pr_json, "[]");
let _path_guard = PathGuard::install(&bin_dir);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok (pass 1)");
assert_eq!(
report.prs_ingested, 1,
"#5 lands, #20 (tied timestamp, injected embedder failure) warns-and-skips: {report:?}"
);
assert_eq!(
report
.warnings
.iter()
.filter(|w| w.contains("pull_request #20"))
.count(),
1,
"exactly one warning names the rejected record: {:?}",
report.warnings
);
let cursor_after_pass1 = read_git_cursor(&rt, project_id, "prs")
.await
.expect("cursor must be written (#5 landed before the stall)");
assert_eq!(
cursor_after_pass1, TIE_AT,
"cursor freezes at #5's timestamp, which is the SAME as failed #20's — \
the exact tie the exclusive `updated > cursor` check used to strand \
#20 on: {cursor_after_pass1:?}"
);
let report2 = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok (pass 2)");
assert_eq!(
report2.prs_ingested, 1,
"only #20 (embedder fuse now spent) is newly created on pass 2, proving the \
tied timestamp did not strand it: {report2:?}"
);
assert_eq!(
report2.prs_skipped_existing, 1,
"#5 is found by natural key, not duplicated, even though it is \
re-examined every pass at the tied cursor timestamp: {report2:?}"
);
assert!(
report2
.warnings
.iter()
.all(|w| !w.contains("pull_request #20")),
"#20 must not warn once the embedder fuse is spent: {:?}",
report2.warnings
);
let prs_list = registry
.dispatch("list", json!({"kind": "pull_request", "limit": 20}))
.await
.expect("list prs ok");
let numbers: Vec<u64> = prs_list
.as_array()
.expect("array")
.iter()
.filter_map(|i| i["properties"]["number"].as_u64())
.collect();
assert_eq!(
numbers.len(),
2,
"exactly #5, #20 — no duplicates: {numbers:?}"
);
}
#[tokio::test]
async fn digest_verb_auto_creates_project_and_enriches_references() {
let _guard = ENV_MUTEX.lock().await;
let (_rt, _token, registry) = fixture().await;
let dir = tempfile::tempdir().expect("tempdir");
let repo = dir.path();
init_repo(repo);
write(repo, "README.md", "hello\n");
commit(repo, &["README.md"], "Initial commit");
let first = registry
.dispatch(
"git.digest",
json!({"source": repo.to_str().unwrap(), "max_items": 10}),
)
.await
.expect("digest ok (pass 1)");
assert_eq!(first["done"], true, "{first}");
assert_eq!(first["project_created"], true, "{first}");
assert_eq!(first["commits_ingested"], 1, "{first}");
let project_id = first["project_id"]
.as_str()
.expect("project_id present")
.to_string();
let second = registry
.dispatch(
"git.digest",
json!({"source": repo.to_str().unwrap(), "max_items": 10}),
)
.await
.expect("digest ok (pass 2, no new commits)");
assert_eq!(second["project_created"], false, "{second}");
assert_eq!(second["project_id"], project_id, "{second}");
assert_eq!(
second["commits_ingested"], 0,
"no new commits since pass 1: {second}"
);
let issue_id = create(
®istry,
json!({
"kind": "issue",
"content": "",
"properties": {"number": 42, "title": "Some bug", "project_id": project_id},
"annotates": [project_id],
}),
)
.await;
write(repo, "src/lib.rs", "// fix\n");
commit(repo, &["src/lib.rs"], "Fix the bug\n\nCloses #42");
let commit2_sha = head_sha(repo);
let third = registry
.dispatch(
"git.digest",
json!({"source": repo.to_str().unwrap(), "project": project_id, "max_items": 10}),
)
.await
.expect("digest ok (pass 3)");
assert_eq!(third["commits_ingested"], 1, "{third}");
assert_eq!(
third["reference_edges_created"], 1,
"the Closes #42 reference resolves: {third}"
);
assert_eq!(
third["parent_edges_created"], 1,
"the second commit's parent link to the first: {third}"
);
let issue_neighbors = registry
.dispatch(
"neighbors",
json!({"id": issue_id.to_string(), "direction": "incoming", "relations": ["annotates"]}),
)
.await
.expect("neighbors ok");
let hits = issue_neighbors.as_array().expect("array");
assert_eq!(hits.len(), 1, "{hits:?}");
assert_eq!(hits[0]["kind"], "commit");
let commit_note = registry
.dispatch("list", json!({"kind": "commit", "limit": 10}))
.await
.expect("list ok");
let items = commit_note.as_array().expect("array");
let closing = items
.iter()
.find(|c| c["properties"]["sha"] == commit2_sha)
.expect("closing commit note present");
let name = closing["name"].as_str().expect("name present");
assert!(
name.contains("Fix the bug"),
"commit name must carry the subject: {name:?}"
);
let first_commit = items
.iter()
.find(|c| c["properties"]["sha"] != commit2_sha)
.expect("first commit note present");
let first_commit_id = first_commit["id"].as_str().expect("id present");
let precedes_neighbors = registry
.dispatch(
"neighbors",
json!({"id": first_commit_id, "direction": "outgoing", "relations": ["precedes"]}),
)
.await
.expect("neighbors ok");
let precedes_hits = precedes_neighbors.as_array().expect("array");
assert_eq!(
precedes_hits.len(),
1,
"the first commit precedes exactly the second: {precedes_hits:?}"
);
assert_eq!(
precedes_hits[0]["id"].as_str().unwrap(),
closing["id"].as_str().unwrap()
);
}
#[tokio::test]
async fn digest_verb_max_items_is_bounded_and_resumable() {
let _guard = ENV_MUTEX.lock().await;
let (_rt, _token, registry) = fixture().await;
let dir = tempfile::tempdir().expect("tempdir");
let repo = dir.path();
init_repo(repo);
for i in 0..3 {
write(repo, "f.txt", &format!("v{i}\n"));
commit(repo, &["f.txt"], &format!("commit {i}"));
}
let mut total_ingested = 0u64;
let mut project_id: Option<String> = None;
let mut calls = 0;
loop {
calls += 1;
assert!(calls <= 10, "must converge well within 10 calls");
let mut args =
json!({"source": repo.to_str().unwrap(), "max_items": 1, "include": ["commits"]});
if let Some(p) = &project_id {
args["project"] = json!(p);
}
let resp = registry
.dispatch("git.digest", args)
.await
.expect("digest ok");
project_id = Some(resp["project_id"].as_str().unwrap().to_string());
total_ingested += resp["commits_ingested"].as_u64().unwrap();
if resp["done"].as_bool().unwrap() {
break;
}
}
assert_eq!(total_ingested, 3, "all three commits eventually ingest");
let list = registry
.dispatch("list", json!({"kind": "commit", "limit": 10}))
.await
.expect("list ok");
assert_eq!(
list.as_array().expect("array").len(),
3,
"no duplicates across the bounded/resumed calls"
);
}
#[tokio::test]
async fn digest_verb_rejects_ssh_source() {
let (_rt, _token, registry) = fixture().await;
let err = registry
.dispatch(
"git.digest",
json!({"source": "ssh://git@github.com/org/repo.git"}),
)
.await
.expect_err("ssh source must be rejected");
assert!(format!("{err}").contains("SSH"), "{err}");
}
#[tokio::test]
async fn digest_verb_max_items_negative_and_zero_clamp_to_one() {
let _guard = ENV_MUTEX.lock().await;
for requested in [-1, 0] {
let (_rt, _token, registry) = fixture().await;
let dir = tempfile::tempdir().expect("tempdir");
let repo = dir.path();
init_repo(repo);
for i in 0..3 {
write(repo, "f.txt", &format!("v{i}\n"));
commit(repo, &["f.txt"], &format!("commit {i}"));
}
let resp = registry
.dispatch(
"git.digest",
json!({"source": repo.to_str().unwrap(), "max_items": requested, "include": ["commits"]}),
)
.await
.unwrap_or_else(|e| panic!("digest ok for max_items={requested}: {e}"));
assert_eq!(
resp["commits_ingested"].as_u64().unwrap(),
1,
"max_items={requested} must clamp to the lower bound (1 item this call): {resp:?}"
);
assert!(
!resp["done"].as_bool().unwrap(),
"2 commits remain after a 1-item pass: {resp:?}"
);
}
}
#[tokio::test]
async fn digest_verb_max_items_above_cap_clamps_to_two_thousand() {
let _guard = ENV_MUTEX.lock().await;
let (_rt, _token, registry) = fixture().await;
let dir = tempfile::tempdir().expect("tempdir");
let repo = dir.path();
init_repo(repo);
write(repo, "f.txt", "v\n");
commit(repo, &["f.txt"], "only commit");
let resp = registry
.dispatch(
"git.digest",
json!({"source": repo.to_str().unwrap(), "max_items": 2001, "include": ["commits"]}),
)
.await
.expect("digest ok");
assert_eq!(resp["commits_ingested"].as_u64().unwrap(), 1);
assert!(
resp["done"].as_bool().unwrap(),
"a single-commit repo finishes in one call however large max_items clamps to: {resp:?}"
);
}
#[tokio::test]
async fn digest_verb_max_items_at_boundary_values() {
let _guard = ENV_MUTEX.lock().await;
for (requested, expected_ingested) in [(1i64, 1u64), (2000i64, 1u64)] {
let (_rt, _token, registry) = fixture().await;
let dir = tempfile::tempdir().expect("tempdir");
let repo = dir.path();
init_repo(repo);
write(repo, "f.txt", "v\n");
commit(repo, &["f.txt"], "only commit");
let resp = registry
.dispatch(
"git.digest",
json!({"source": repo.to_str().unwrap(), "max_items": requested, "include": ["commits"]}),
)
.await
.unwrap_or_else(|e| panic!("digest ok for max_items={requested}: {e}"));
assert_eq!(
resp["commits_ingested"].as_u64().unwrap(),
expected_ingested,
"max_items={requested}: {resp:?}"
);
}
}
#[tokio::test]
async fn digest_verb_rejects_non_integer_max_items() {
let _guard = ENV_MUTEX.lock().await;
let (_rt, _token, registry) = fixture().await;
let dir = tempfile::tempdir().expect("tempdir");
let repo = dir.path();
init_repo(repo);
write(repo, "f.txt", "v\n");
commit(repo, &["f.txt"], "only commit");
let err = registry
.dispatch(
"git.digest",
json!({"source": repo.to_str().unwrap(), "max_items": "lots"}),
)
.await
.expect_err("a non-integer max_items must be a hard error, not a silent default");
assert!(
format!("{err}").contains("max_items"),
"error must name the offending field: {err}"
);
}
struct CapturingEmbedService {
captured: Arc<Mutex<Vec<String>>>,
dims: usize,
}
#[async_trait]
impl EmbeddingService for CapturingEmbedService {
async fn embed(
&self,
texts: &[String],
_model: EmbeddingModel,
) -> std::result::Result<Vec<Vec<f32>>, EmbedError> {
self.captured
.lock()
.expect("captured mutex")
.extend(texts.iter().cloned());
Ok(texts.iter().map(|_| vec![1.0_f32; self.dims]).collect())
}
fn supports_model(&self, _model: EmbeddingModel) -> bool {
true
}
fn name(&self) -> &'static str {
"capturing-test-embedder"
}
}
struct CapturingEmbedProvider {
captured: Arc<Mutex<Vec<String>>>,
dims: usize,
}
#[async_trait]
impl EmbedderProvider for CapturingEmbedProvider {
fn name(&self) -> &str {
"capturing-test-embedder"
}
fn dimensions(&self) -> usize {
self.dims
}
async fn build(&self) -> RuntimeResult<Arc<dyn EmbeddingService>> {
Ok(Arc::new(CapturingEmbedService {
captured: Arc::clone(&self.captured),
dims: self.dims,
}))
}
}
#[tokio::test]
async fn ingest_truncates_over_cap_commit_embedding_and_reports_it() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "big-commit-repo"}),
)
.await;
let captured: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
rt.register_embedder(CapturingEmbedProvider {
captured: Arc::clone(&captured),
dims: 8,
});
let dir = tempfile::tempdir().expect("tempdir");
let repo = dir.path();
init_repo(repo);
write(repo, "README.md", "hello\n");
let filler = "x".repeat(51_648 - "head-term-unique ".len() - " tail-term-unique".len());
let message = format!("head-term-unique {filler} tail-term-unique");
assert!(message.len() > 32_768, "fixture must exceed the cap");
commit(repo, &["README.md"], &message);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.to_path_buf(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(report.commits_ingested, 1);
assert_eq!(
report.commit_embeddings_truncated, 1,
"over-cap commit must be reported as truncated"
);
let list = registry
.dispatch("list", json!({"kind": "commit", "limit": 10}))
.await
.expect("list ok");
let items = list.as_array().expect("array");
assert_eq!(items.len(), 1);
let stored_content = items[0]["content"].as_str().expect("content is string");
assert!(stored_content.contains("head-term-unique"));
assert!(
stored_content.contains("tail-term-unique"),
"full commit message, including the tail beyond the cap, must be stored"
);
let seen = captured.lock().expect("captured mutex").clone();
assert_eq!(seen.len(), 1, "exactly one embed call: {seen:?}");
let embedded_text = &seen[0];
assert!(
embedded_text.len() <= 32_768,
"embedder input must be at or under the cap: {} bytes",
embedded_text.len()
);
assert!(
embedded_text.contains("head-term-unique"),
"embedder input must retain the head term"
);
assert!(
!embedded_text.contains("tail-term-unique"),
"embedder input must not reach past the cap into the tail"
);
assert!(
stored_content.starts_with(embedded_text.as_str()),
"embedder input must be a proper prefix of the stored content"
);
let vectors = rt
.vectors_for_model(&token, "capturing-test-embedder")
.expect("vector store for capturing embedder");
assert_eq!(
vectors.count().await.expect("vector count"),
1,
"the capped head must have produced exactly one vector row"
);
let head_hits = registry
.dispatch(
"search",
json!({"kind": "commit", "query": "head term unique"}),
)
.await
.expect("search ok");
let head_hits = head_hits.as_array().expect("array");
assert!(
head_hits.iter().any(|h| h["id"] == items[0]["id"]),
"the head term must resolve the truncated commit note via search: {head_hits:?}"
);
let tail_hits = registry
.dispatch(
"search",
json!({"kind": "commit", "query": "tail term unique"}),
)
.await
.expect("search ok");
let tail_hits = tail_hits.as_array().expect("array");
assert!(
tail_hits.iter().any(|h| h["id"] == items[0]["id"]),
"the tail term, beyond the embedding cap, must still resolve via FTS on the \
complete stored content: {tail_hits:?}"
);
let sql = rt.sql();
let mut w = sql.writer().await.expect("sql writer");
w.execute(SqlStatement {
sql: "DELETE FROM git_mirror_cursor WHERE project_id=?1 AND kind='commits'".into(),
params: vec![SqlValue::Text(project_id.to_string())],
label: Some("test_reset_commits_cursor".into()),
})
.await
.expect("reset cursor");
drop(w);
let second_report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.to_path_buf(), project_id.to_string()),
)
.await
.expect("second ingest ok");
assert_eq!(
second_report.commits_ingested, 0,
"no new commits on the re-walked pass"
);
assert_eq!(
second_report.commits_skipped_existing, 1,
"the already-ingested over-cap commit must be a natural-key skip"
);
assert_eq!(
second_report.commit_embeddings_truncated, 0,
"a re-walked pass must not re-report truncation for an existing commit"
);
}
struct FailingEmbedService;
#[async_trait]
impl EmbeddingService for FailingEmbedService {
async fn embed(
&self,
_texts: &[String],
_model: EmbeddingModel,
) -> std::result::Result<Vec<Vec<f32>>, EmbedError> {
Err(EmbedError::InferenceFailed(
"simulated inference failure (issue #764 regression)".into(),
))
}
fn supports_model(&self, _model: EmbeddingModel) -> bool {
true
}
fn name(&self) -> &'static str {
"failing-test-embedder"
}
}
struct FailingEmbedProvider;
#[async_trait]
impl EmbedderProvider for FailingEmbedProvider {
fn name(&self) -> &str {
"failing-test-embedder"
}
fn dimensions(&self) -> usize {
8
}
async fn build(&self) -> RuntimeResult<Arc<dyn EmbeddingService>> {
Ok(Arc::new(FailingEmbedService))
}
}
#[tokio::test]
async fn ingest_over_cap_commit_with_failing_embedder_creates_nothing_and_warns() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "failing-embed-repo"}),
)
.await;
rt.register_embedder(FailingEmbedProvider);
let dir = tempfile::tempdir().expect("tempdir");
let repo = dir.path();
init_repo(repo);
write(repo, "README.md", "hello\n");
let filler = "x".repeat(51_648 - "head-term-unique ".len() - " tail-term-unique".len());
let message = format!("head-term-unique {filler} tail-term-unique");
assert!(message.len() > 32_768, "fixture must exceed the cap");
commit(repo, &["README.md"], &message);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.to_path_buf(), project_id.to_string()),
)
.await
.expect("ingest ok -- a per-commit create failure is a warning, not a hard pass error");
assert_eq!(
report.commits_ingested, 0,
"the failed create must not be counted as ingested"
);
assert_eq!(
report.commit_embeddings_truncated, 0,
"truncation is only reported on the successful-create arm, which a failed \
embed never reaches"
);
assert!(
report.warnings.iter().any(|w| w.contains("create commit")
&& w.contains("embedding inference failed")
&& w.contains("simulated inference failure")),
"the embed failure must surface as an explicit create-commit warning: {:?}",
report.warnings
);
let list = registry
.dispatch("list", json!({"kind": "commit", "limit": 10}))
.await
.expect("list ok");
assert_eq!(
list.as_array().expect("array").len(),
0,
"a compensated create must leave no commit note row behind"
);
let head_hits = registry
.dispatch(
"search",
json!({"kind": "commit", "query": "head term unique"}),
)
.await
.expect("search ok");
assert_eq!(
head_hits.as_array().expect("array").len(),
0,
"no FTS document must survive the compensated create"
);
}
#[tokio::test]
async fn ingest_over_cap_commit_embedding_is_semantically_retrievable() {
const MODEL: EmbeddingModel = EmbeddingModel::BgeSmallEnV15;
let dims = MODEL.dimensions();
struct FixtureEmbedService {
dims: usize,
}
#[async_trait]
impl EmbeddingService for FixtureEmbedService {
async fn embed(
&self,
texts: &[String],
_model: EmbeddingModel,
) -> std::result::Result<Vec<Vec<f32>>, EmbedError> {
Ok(texts.iter().map(|_| vec![1.0_f32; self.dims]).collect())
}
fn supports_model(&self, _model: EmbeddingModel) -> bool {
true
}
fn name(&self) -> &'static str {
"fixture-semantic-embedder"
}
}
struct FixtureEmbedProvider {
dims: usize,
}
#[async_trait]
impl EmbedderProvider for FixtureEmbedProvider {
fn name(&self) -> &str {
"bge-small-en-v1.5"
}
fn dimensions(&self) -> usize {
self.dims
}
async fn build(&self) -> RuntimeResult<Arc<dyn EmbeddingService>> {
Ok(Arc::new(FixtureEmbedService { dims: self.dims }))
}
}
let _guard = ENV_MUTEX.lock().await;
let rt = KhiveRuntime::new(RuntimeConfig {
git_write: Default::default(),
db_path: None,
default_namespace: Namespace::local(),
embedding_model: Some(MODEL),
additional_embedding_models: vec![],
gate: Arc::new(AllowAllGate),
packs: vec!["kg".to_string()],
backend_id: BackendId::main(),
brain_profile: None,
visible_namespaces: vec![],
allowed_outbound_namespaces: vec![],
actor_id: None,
})
.expect("runtime with a configured default model");
rt.register_embedder(FixtureEmbedProvider { dims });
let mut builder = VerbRegistryBuilder::new();
builder.register(KgPack::new(rt.clone()));
builder.register(GitPack::new(rt.clone()));
let registry = builder.build().expect("registry builds");
rt.install_edge_rules(registry.all_edge_rules());
registry.apply_schema_plans(rt.backend());
let token = rt.authorize(Namespace::local()).expect("authorize local");
let project_id = create(
®istry,
json!({"kind": "project", "name": "semantic-retrieval-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo = dir.path();
init_repo(repo);
write(repo, "README.md", "hello\n");
let filler = "x".repeat(51_648 - "head-term-unique ".len() - " tail-term-unique".len());
let message = format!("head-term-unique {filler} tail-term-unique");
assert!(message.len() > 32_768, "fixture must exceed the cap");
commit(repo, &["README.md"], &message);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.to_path_buf(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(report.commits_ingested, 1);
assert_eq!(report.commit_embeddings_truncated, 1);
let list = registry
.dispatch("list", json!({"kind": "commit", "limit": 10}))
.await
.expect("list ok");
let items = list.as_array().expect("array");
assert_eq!(items.len(), 1);
let commit_note_id =
Uuid::parse_str(items[0]["id"].as_str().expect("commit id")).expect("commit id is uuid");
let semantic_hits = rt
.search_notes(
&token,
"lexically-absent-query-term-zzz",
Some(vec![1.0_f32; dims]),
10,
Some("commit"),
false,
&[],
None,
)
.await
.expect("semantic search ok");
assert!(
semantic_hits.iter().any(|h| h.note_id == commit_note_id),
"an explicit deterministic query vector matching the embedded head must \
retrieve the commit note by ID even though the query text is lexically \
absent from its content: {semantic_hits:?}"
);
}
#[tokio::test]
async fn ingest_does_not_truncate_under_cap_commit_embedding() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "small-commit-repo"}),
)
.await;
let captured: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
rt.register_embedder(CapturingEmbedProvider {
captured: Arc::clone(&captured),
dims: 8,
});
let dir = tempfile::tempdir().expect("tempdir");
let repo = dir.path();
init_repo(repo);
write(repo, "README.md", "hello\n");
commit(repo, &["README.md"], "A short, unremarkable commit message");
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.to_path_buf(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(report.commits_ingested, 1);
assert_eq!(
report.commit_embeddings_truncated, 0,
"an under-cap commit must not be reported as truncated"
);
let seen = captured.lock().expect("captured mutex").clone();
assert_eq!(seen.len(), 1);
assert!(
seen[0].contains("A short, unremarkable commit message"),
"the embedder must receive the full message unchanged: {:?}",
seen[0]
);
}
#[tokio::test]
async fn ingest_does_not_truncate_exact_cap_commit_embedding() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "exact-cap-commit-repo"}),
)
.await;
let captured: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
rt.register_embedder(CapturingEmbedProvider {
captured: Arc::clone(&captured),
dims: 8,
});
let dir = tempfile::tempdir().expect("tempdir");
let repo = dir.path();
init_repo(repo);
write(repo, "README.md", "hello\n");
let message = "x".repeat(32_768);
assert_eq!(
message.len(),
32_768,
"fixture must land exactly on the cap"
);
commit(repo, &["README.md"], &message);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.to_path_buf(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(report.commits_ingested, 1);
assert_eq!(
report.commit_embeddings_truncated, 0,
"an exact-cap commit must not be reported as truncated"
);
let seen = captured.lock().expect("captured mutex").clone();
assert_eq!(seen.len(), 1);
assert_eq!(
seen[0].len(),
32_768,
"the embedder must receive the full, untruncated exact-cap message"
);
}
#[tokio::test]
async fn ingest_reports_truncation_even_with_no_embedder_configured() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "no-embedder-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo = dir.path();
init_repo(repo);
write(repo, "README.md", "hello\n");
let message = format!("head-of-message {}", "y".repeat(40_000));
commit(repo, &["README.md"], &message);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.to_path_buf(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(report.commits_ingested, 1);
assert_eq!(report.commit_embeddings_truncated, 1);
let list = registry
.dispatch("list", json!({"kind": "commit", "limit": 10}))
.await
.expect("list ok");
let stored_content = list[0]["content"].as_str().expect("content is string");
assert!(stored_content.contains(&message));
}
#[tokio::test]
async fn ingest_resolves_over_cap_commit_reference_beyond_the_embedding_cap() {
let _guard = ENV_MUTEX.lock().await;
let (_rt, _token, registry) = fixture().await;
let dir = tempfile::tempdir().expect("tempdir");
let repo = dir.path();
init_repo(repo);
write(repo, "README.md", "hello\n");
commit(repo, &["README.md"], "Initial commit");
let first = registry
.dispatch(
"git.digest",
json!({"source": repo.to_str().unwrap(), "max_items": 10}),
)
.await
.expect("digest ok (pass 1)");
let project_id = first["project_id"]
.as_str()
.expect("project_id present")
.to_string();
let issue_id = create(
®istry,
json!({
"kind": "issue",
"content": "",
"properties": {"number": 4242, "title": "Tail-referenced bug", "project_id": project_id},
"annotates": [project_id],
}),
)
.await;
let filler = "z".repeat(40_000);
let message = format!("Fix the tail bug\n\n{filler}\n\nCloses #4242");
assert!(
message.rfind("Closes #4242").unwrap() > 32_768,
"fixture must place the reference beyond the embedding cap"
);
write(repo, "src/lib.rs", "// fix\n");
commit(repo, &["src/lib.rs"], &message);
let second = registry
.dispatch(
"git.digest",
json!({"source": repo.to_str().unwrap(), "project": project_id, "max_items": 10}),
)
.await
.expect("digest ok (pass 2)");
assert_eq!(second["commits_ingested"], 1, "{second}");
assert_eq!(
second["commit_embeddings_truncated"], 1,
"the referencing commit is itself over-cap: {second}"
);
assert_eq!(
second["reference_edges_created"], 1,
"the beyond-cap Closes #4242 reference must still resolve: {second}"
);
let issue_neighbors = registry
.dispatch(
"neighbors",
json!({"id": issue_id.to_string(), "direction": "incoming", "relations": ["annotates"]}),
)
.await
.expect("neighbors ok");
let hits = issue_neighbors.as_array().expect("array");
assert_eq!(hits.len(), 1, "{hits:?}");
assert_eq!(hits[0]["kind"], "commit");
}
#[tokio::test]
async fn git_pack_adr_entity_type_validates_through_create() {
let (_rt, _token, registry) = fixture().await;
let resp = registry
.dispatch(
"create",
json!({"kind": "document", "entity_type": "adr", "name": "ADR-001: example"}),
)
.await
.expect("create must accept the git-pack-declared adr Document subtype");
assert_eq!(resp["entity_type"], "adr", "{resp}");
let resp_alias = registry
.dispatch(
"create",
json!({
"kind": "document",
"entity_type": "architecture_decision_record",
"name": "ADR-002: example",
}),
)
.await
.expect("create must accept the adr alias");
assert_eq!(resp_alias["entity_type"], "adr", "{resp_alias}");
let resp_builtin = registry
.dispatch(
"create",
json!({"kind": "document", "entity_type": "paper", "name": "Some paper"}),
)
.await
.expect("builtin paper subtype must remain resolvable when git pack adds adr");
assert_eq!(resp_builtin["entity_type"], "paper", "{resp_builtin}");
}
#[tokio::test]
async fn adr_entity_type_rejected_without_git_pack_loaded() {
let rt = rt();
let mut builder = VerbRegistryBuilder::new();
builder.register(KgPack::new(rt.clone()));
let registry = builder.build().expect("registry builds");
rt.install_edge_rules(registry.all_edge_rules());
registry.apply_schema_plans(rt.backend());
let err = registry
.dispatch(
"create",
json!({"kind": "document", "entity_type": "adr", "name": "ADR-001: example"}),
)
.await
.expect_err("adr must be rejected when the declaring pack is not loaded");
assert!(
err.to_string().contains("adr"),
"error must name the rejected value: {err}"
);
}
#[tokio::test]
async fn git_pack_adr_entity_type_validates_through_runtime_create_many() {
let (rt, token, _registry) = fixture().await;
let created = rt
.create_many(
&token,
vec![EntityCreateSpec {
kind: "document".to_string(),
entity_type: Some("adr".to_string()),
name: "ADR-003: runtime layer".to_string(),
description: None,
properties: None,
tags: vec![],
}],
)
.await
.expect("runtime-layer create_many must accept the git-pack-declared adr subtype");
assert_eq!(created.len(), 1, "{created:?}");
assert_eq!(
created[0].entity_type.as_deref(),
Some("adr"),
"{created:?}"
);
let err = rt
.create_many(
&token,
vec![EntityCreateSpec {
kind: "document".to_string(),
entity_type: Some("not_a_registered_subtype".to_string()),
name: "not an ADR".to_string(),
description: None,
properties: None,
tags: vec![],
}],
)
.await
.expect_err(
"runtime-layer create_many must reject an unregistered Document subtype when the \
aggregate entity-type validator is composed",
);
assert!(
matches!(err, RuntimeError::InvalidInput(_)),
"unregistered entity_type must fail as InvalidInput, got: {err:?}"
);
assert!(
err.to_string().contains("not_a_registered_subtype"),
"error must name the rejected value: {err}"
);
}
#[tokio::test]
async fn ingest_masks_credential_shaped_commit_subject_in_name_without_dropping_note() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "commit-subject-credential-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo = dir.path();
init_repo(repo);
let fake_token = "ghp_EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE";
write(repo, "README.md", "hello\n");
commit(repo, &["README.md"], fake_token);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.to_path_buf(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(
report.commits_ingested, 1,
"the commit must not be dropped: {report:?}"
);
assert!(
report.warnings.iter().all(|w| !w.contains("create commit")),
"no silent-drop warning may be reported: {:?}",
report.warnings
);
let list = registry
.dispatch("list", json!({"kind": "commit", "limit": 10}))
.await
.expect("list ok");
let items = list.as_array().expect("array");
assert_eq!(items.len(), 1);
let stored_name = items[0]["name"].as_str().expect("name is string");
assert!(
!stored_name.contains(fake_token),
"raw token must not survive into the stored name: {stored_name:?}"
);
assert!(
stored_name.contains("***MASKED***"),
"masked marker must be present in name: {stored_name:?}"
);
}
#[tokio::test]
async fn ingest_masks_credential_shaped_commit_author_without_dropping_note() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "commit-author-credential-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo = dir.path();
init_repo(repo);
let fake_token = "ghp_FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF";
git(repo, &["config", "user.name", fake_token]);
write(repo, "README.md", "hello\n");
commit(repo, &["README.md"], "Normal commit message");
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.to_path_buf(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(
report.commits_ingested, 1,
"the commit must not be dropped: {report:?}"
);
assert!(
report.warnings.iter().all(|w| !w.contains("create commit")),
"no silent-drop warning may be reported: {:?}",
report.warnings
);
let list = registry
.dispatch("list", json!({"kind": "commit", "limit": 10}))
.await
.expect("list ok");
let items = list.as_array().expect("array");
assert_eq!(items.len(), 1);
let stored_author = items[0]["properties"]["author"]
.as_str()
.expect("properties.author is string");
assert!(
!stored_author.contains(fake_token),
"raw token must not survive into properties.author: {stored_author:?}"
);
assert!(
stored_author.contains("***MASKED***"),
"masked marker must be present in properties.author: {stored_author:?}"
);
}
#[tokio::test]
async fn ingest_masks_credential_shaped_commit_author_email_without_dropping_note() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "commit-author-email-credential-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo = dir.path();
init_repo(repo);
let fake_token = "ghp_GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG";
git(
repo,
&["config", "user.email", &format!("{fake_token}@example.com")],
);
write(repo, "README.md", "hello\n");
commit(repo, &["README.md"], "Normal commit message");
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.to_path_buf(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(
report.commits_ingested, 1,
"the commit must not be dropped: {report:?}"
);
assert!(
report.warnings.iter().all(|w| !w.contains("create commit")),
"no silent-drop warning may be reported: {:?}",
report.warnings
);
let list = registry
.dispatch("list", json!({"kind": "commit", "limit": 10}))
.await
.expect("list ok");
let items = list.as_array().expect("array");
assert_eq!(items.len(), 1);
let stored_email = items[0]["properties"]["author_email"]
.as_str()
.expect("properties.author_email is string");
assert!(
!stored_email.contains(fake_token),
"raw token must not survive into properties.author_email: {stored_email:?}"
);
assert!(
stored_email.contains("***MASKED***"),
"masked marker must be present in properties.author_email: {stored_email:?}"
);
}
#[tokio::test]
async fn ingest_leaves_clean_commit_author_and_subject_unmasked() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "commit-clean-fields-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo = dir.path();
init_repo(repo);
git(repo, &["config", "user.name", "Ada Lovelace"]);
git(repo, &["config", "user.email", "ada@example.com"]);
write(repo, "README.md", "hello\n");
commit(repo, &["README.md"], "Add README with usage notes");
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.to_path_buf(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(report.commits_ingested, 1, "{report:?}");
let list = registry
.dispatch("list", json!({"kind": "commit", "limit": 10}))
.await
.expect("list ok");
let items = list.as_array().expect("array");
assert_eq!(items.len(), 1);
let item = &items[0];
let stored_author = item["properties"]["author"]
.as_str()
.expect("author is string");
let stored_email = item["properties"]["author_email"]
.as_str()
.expect("author_email is string");
let stored_name = item["name"].as_str().expect("name is string");
assert_eq!(
stored_author, "Ada Lovelace",
"clean author must be stored unchanged"
);
assert_eq!(
stored_email, "ada@example.com",
"clean author_email must be stored unchanged"
);
assert!(
stored_name.ends_with("Add README with usage notes"),
"clean subject must survive unmodified in name: {stored_name:?}"
);
assert!(
!stored_author.contains("***MASKED***")
&& !stored_email.contains("***MASKED***")
&& !stored_name.contains("***MASKED***"),
"no masking marker may appear on clean input: author={stored_author:?} email={stored_email:?} name={stored_name:?}"
);
}
#[tokio::test]
async fn ingest_masks_credential_shaped_pr_author_login_without_dropping_note() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "pr-author-credential-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
let fake_token = "ghp_HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH";
let pr_json = json!([{
"number": 55,
"title": "Fix pagination edge case",
"author": {"login": fake_token},
"createdAt": "2026-01-01T00:00:00Z",
"mergedAt": null,
"closedAt": null,
"updatedAt": "2026-01-01T00:00:00Z",
"baseRefName": "main",
"headRefName": "fix/pagination",
"mergeCommit": null,
"body": ""
}])
.to_string();
write_fake_gh(&bin_dir, &log_dir, &pr_json, "[]");
let _path_guard = PathGuard::install(&bin_dir);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(
report.prs_ingested, 1,
"the PR must not be dropped: {report:?}"
);
assert!(
report.warnings.iter().all(|w| !w.contains("pull_request")),
"no silent-drop warning may be reported: {:?}",
report.warnings
);
let prs_list = registry
.dispatch("list", json!({"kind": "pull_request", "limit": 10}))
.await
.expect("list prs ok");
let items = prs_list.as_array().expect("array");
assert_eq!(items.len(), 1);
let stored_author = items[0]["properties"]["author"]
.as_str()
.expect("properties.author is string");
assert!(
!stored_author.contains(fake_token),
"raw credential must not survive into properties.author: {stored_author:?}"
);
assert!(
stored_author.contains("***MASKED***"),
"masked marker must be present in properties.author: {stored_author:?}"
);
}
#[tokio::test]
async fn ingest_masks_credential_shaped_pr_base_ref_without_dropping_note() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "pr-base-ref-credential-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
let fake_token = "ghp_IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII";
let pr_json = json!([{
"number": 56,
"title": "Retarget release branch",
"author": {"login": "octocat"},
"createdAt": "2026-01-01T00:00:00Z",
"mergedAt": null,
"closedAt": null,
"updatedAt": "2026-01-01T00:00:00Z",
"baseRefName": fake_token,
"headRefName": "release/next",
"mergeCommit": null,
"body": ""
}])
.to_string();
write_fake_gh(&bin_dir, &log_dir, &pr_json, "[]");
let _path_guard = PathGuard::install(&bin_dir);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(
report.prs_ingested, 1,
"the PR must not be dropped: {report:?}"
);
assert!(
report.warnings.iter().all(|w| !w.contains("pull_request")),
"no silent-drop warning may be reported: {:?}",
report.warnings
);
let prs_list = registry
.dispatch("list", json!({"kind": "pull_request", "limit": 10}))
.await
.expect("list prs ok");
let items = prs_list.as_array().expect("array");
assert_eq!(items.len(), 1);
let stored_base_ref = items[0]["properties"]["base_ref"]
.as_str()
.expect("properties.base_ref is string");
assert!(
!stored_base_ref.contains(fake_token),
"raw credential must not survive into properties.base_ref: {stored_base_ref:?}"
);
assert!(
stored_base_ref.contains("***MASKED***"),
"masked marker must be present in properties.base_ref: {stored_base_ref:?}"
);
}
#[tokio::test]
async fn ingest_masks_credential_shaped_pr_head_ref_without_dropping_note() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "pr-head-ref-credential-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
let fake_token = "ghp_JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ";
let pr_json = json!([{
"number": 57,
"title": "Fork contribution",
"author": {"login": "octocat"},
"createdAt": "2026-01-01T00:00:00Z",
"mergedAt": null,
"closedAt": null,
"updatedAt": "2026-01-01T00:00:00Z",
"baseRefName": "main",
"headRefName": fake_token,
"mergeCommit": null,
"body": ""
}])
.to_string();
write_fake_gh(&bin_dir, &log_dir, &pr_json, "[]");
let _path_guard = PathGuard::install(&bin_dir);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(
report.prs_ingested, 1,
"the PR must not be dropped: {report:?}"
);
assert!(
report.warnings.iter().all(|w| !w.contains("pull_request")),
"no silent-drop warning may be reported: {:?}",
report.warnings
);
let prs_list = registry
.dispatch("list", json!({"kind": "pull_request", "limit": 10}))
.await
.expect("list prs ok");
let items = prs_list.as_array().expect("array");
assert_eq!(items.len(), 1);
let stored_head_ref = items[0]["properties"]["head_ref"]
.as_str()
.expect("properties.head_ref is string");
assert!(
!stored_head_ref.contains(fake_token),
"raw credential must not survive into properties.head_ref: {stored_head_ref:?}"
);
assert!(
stored_head_ref.contains("***MASKED***"),
"masked marker must be present in properties.head_ref: {stored_head_ref:?}"
);
}
#[tokio::test]
async fn ingest_leaves_clean_pr_author_and_refs_unmasked() {
let _guard = ENV_MUTEX.lock().await;
let (rt, token, registry) = fixture().await;
let project_id = create(
®istry,
json!({"kind": "project", "name": "pr-clean-author-refs-repo"}),
)
.await;
let dir = tempfile::tempdir().expect("tempdir");
let repo: PathBuf = dir.path().join("repo");
std::fs::create_dir_all(&repo).expect("mk repo dir");
init_repo(&repo);
write(&repo, "README.md", "hello\n");
commit(&repo, &["README.md"], "Initial commit");
let bin_dir = dir.path().join("bin");
std::fs::create_dir_all(&bin_dir).expect("mk bin dir");
let log_dir = dir.path().join("log");
std::fs::create_dir_all(&log_dir).expect("mk log dir");
let pr_json = json!([{
"number": 58,
"title": "Improve README clarity",
"author": {"login": "octocat"},
"createdAt": "2026-01-01T00:00:00Z",
"mergedAt": null,
"closedAt": null,
"updatedAt": "2026-01-01T00:00:00Z",
"baseRefName": "main",
"headRefName": "docs/readme-clarity",
"mergeCommit": null,
"body": ""
}])
.to_string();
write_fake_gh(&bin_dir, &log_dir, &pr_json, "[]");
let _path_guard = PathGuard::install(&bin_dir);
let report = run_ingest(
&rt,
&token,
®istry,
IngestOptions::unbounded(repo.clone(), project_id.to_string()),
)
.await
.expect("ingest ok");
assert_eq!(report.prs_ingested, 1, "{report:?}");
let prs_list = registry
.dispatch("list", json!({"kind": "pull_request", "limit": 10}))
.await
.expect("list prs ok");
let items = prs_list.as_array().expect("array");
assert_eq!(items.len(), 1);
let item = &items[0];
let stored_author = item["properties"]["author"]
.as_str()
.expect("author is string");
let stored_base_ref = item["properties"]["base_ref"]
.as_str()
.expect("base_ref is string");
let stored_head_ref = item["properties"]["head_ref"]
.as_str()
.expect("head_ref is string");
assert_eq!(stored_author, "octocat", "clean author must be unchanged");
assert_eq!(stored_base_ref, "main", "clean base_ref must be unchanged");
assert_eq!(
stored_head_ref, "docs/readme-clarity",
"clean head_ref must be unchanged"
);
assert!(
!stored_author.contains("***MASKED***")
&& !stored_base_ref.contains("***MASKED***")
&& !stored_head_ref.contains("***MASKED***"),
"no masking marker may appear on clean input: author={stored_author:?} base_ref={stored_base_ref:?} head_ref={stored_head_ref:?}"
);
}