//! Golden-repository integration tests (docs/TEST_PLAN.md §3): the
//! http-service-python fixture must produce the expected System IR.
//!
//! Helpers live in `tests/common/` so other integration binaries can share
//! `copy_fixture` / `run_ok` without compiling this file's `#[test]`s.
mod common;
use common::*;
// trace:v1 id=test.scc.golden verifies=REQ-SCC-IR exercises=impl.scc.cli,impl.scc.store
#[test]
// trace:v1 id=test.crates-scc-cli-tests-golden.http-service-produces-expected-ir
fn http_service_produces_expected_ir() {
let repo = copy_fixture("http-service-python");
run_ok(&workdir(repo.path()), &["index", "--quiet"]);
let status = run_ok(&workdir(repo.path()), &["status"]);
assert!(status.contains("components:"), "{status}");
assert!(status.contains("flows:"), "{status}");
// components: root (main.py), services, tests
let comps = run_ok(&workdir(repo.path()), &["components"]);
assert!(comps.contains("root"), "{comps}");
assert!(comps.contains("services"), "{comps}");
// routes + flows
let flows = run_ok(&workdir(repo.path()), &["flows"]);
assert!(flows.contains("get-/api/transcripts"), "{flows}");
assert!(flows.contains("GET /api/transcripts"), "{flows}");
// data ownership: services owns db
let flow = run_ok(&workdir(repo.path()), &["context", "flow", "get-/api/transcripts"]);
assert!(flow.contains("services"), "{flow}");
assert!(flow.contains("TranscriptRepository"), "{flow}");
// task context finds the normalization code and its test
let task = run_ok(
&workdir(repo.path()),
&["context", "task", "change transcript normalization"],
);
assert!(task.contains("Normalizer"), "{task}");
assert!(task.contains("test_normalization_preserves_raw"), "{task}");
assert!(task.contains("PRIMARY FLOW"), "{task}");
// verify is clean on this repo
let verify = run_ok(&workdir(repo.path()), &["verify"]);
assert!(verify.contains("VERIFIED"), "{verify}");
assert!(verify.contains("Fresh"), "{verify}");
// CI invariant check passes
let out = run(&workdir(repo.path()), &["check-invariants"]);
assert!(out.status.success(), "check-invariants must pass");
}
#[test]
// trace:v1 id=test.crates-scc-cli-tests-golden.stale-worktree-never-serves-cached-pack
fn stale_worktree_never_serves_cached_pack() {
// P0 trust contract (§7): a pack cached under a clean revision must not
// be returned after a working-tree file changed WITHOUT re-indexing.
// The rebuild excludes the stale facts and warns instead.
let repo = copy_fixture("http-service-python");
let dir = workdir(repo.path());
run_ok(&dir, &["index", "--quiet"]);
let goal = "change transcript normalization";
let first = run_ok(&dir, &["context", "task", goal]);
assert!(first.contains("Normalizer"), "{first}");
assert!(!first.contains("changed since indexing"), "{first}");
// modify a source file WITHOUT indexing
let f = dir.join("services/transcripts.py");
let mut src = std::fs::read_to_string(&f).unwrap();
src.push_str("\n# working-tree change\n");
std::fs::write(&f, src).unwrap();
let second = run_ok(&dir, &["context", "task", goal]);
assert!(
second.contains("stale: services/transcripts.py"),
"stale facts must be excluded and warned: {second}"
);
// the exact same request after a re-index returns a fresh pack again
run_ok(&dir, &["index", "--quiet"]);
let third = run_ok(&dir, &["context", "task", goal]);
assert!(!third.contains("stale: services/transcripts.py"), "{third}");
}
#[test]
// trace:v1 id=test.crates-scc-cli-tests-golden.canonical-flow-graph-preserves-topology
fn canonical_flow_graph_preserves_topology() {
// Wave 3 exit condition: the canonical FlowGraph preserves branches,
// retry, and fanout exactly — alternate execution paths are never
// flattened into false sequential causality, and branches come from
// evidence (call fanout, @tenacity.retry decorators), never text
// heuristics.
let repo = copy_fixture("py-queue-service");
let dir = workdir(repo.path());
run_ok(&dir, &["index", "--quiet"]);
let out = run_ok(&dir, &["export", "flow-graphs.json"]);
let graphs: serde_json::Value = serde_json::from_str(&out).unwrap();
let graphs = graphs.as_array().unwrap();
assert!(!graphs.is_empty(), "at least one canonical graph");
let graph = graphs
.iter()
.find(|g| g["name"].as_str().unwrap_or("").contains("consume"))
.or_else(|| graphs.first())
.unwrap();
let kinds: Vec<&str> = graph["edges"]
.as_array()
.unwrap()
.iter()
.filter_map(|e| e["kind"].as_str())
.collect();
// retry: the @tenacity.retry decorator must produce Retry edges
assert!(
kinds.contains(&"retry"),
"retry edges from decorator evidence: {kinds:?}"
);
// P1 §19: plain call fanout (consume -> process_incident AND
// IncidentStore) is NOT a branch — those are Next edges; only the call
// inside the try/except (classify) is a Branch edge.
let branch_edges: Vec<&serde_json::Value> = graph["edges"]
.as_array()
.unwrap()
.iter()
.filter(|e| e["kind"] == "branch")
.collect();
assert_eq!(
branch_edges.len(),
1,
"exactly one conditional call becomes a branch: {kinds:?}"
);
assert!(
is_cfg_condition(branch_edges[0]["condition"].as_str().unwrap_or("")),
"branch condition names the control-block evidence: {branch_edges:?}"
);
assert!(
kinds.iter().filter(|k| *k == &"next").count() >= 2,
"plain fanout stays Next edges (unordered calls, not alternatives): {kinds:?}"
);
// no false convergence either: this fixture has no join point, so no
// Join edge is invented (the old branch-artifact join is gone)
// branch edges carry evidence conditions (block kind or the legacy
// "conditional: <op>" format), never comma-split operation lists from
// generated text
for e in graph["edges"].as_array().unwrap() {
if e["kind"] == "branch" {
if let Some(c) = e["condition"].as_str() {
assert!(
is_cfg_condition(c),
"branch conditions name the CFG evidence: {c}"
);
}
}
}
// exits detected
assert!(
graph["exits"].as_array().map(|a| !a.is_empty()).unwrap_or(false),
"exits detected: {graph}"
);
// provenance recorded per edge
assert!(
graph["provenance_summary"]
.as_object()
.map(|o| !o.is_empty())
.unwrap_or(false),
"provenance summary present"
);
}
#[test]
// trace:v1 id=test.crates-scc-cli-tests-golden.checkpoint-captures-goal-from-active-bead
fn checkpoint_captures_goal_from_active_bead() {
// §126: checkpoint goal/bead are populated from active task state.
let repo = copy_fixture("http-service-python");
let dir = workdir(repo.path());
std::fs::create_dir_all(dir.join(".beads")).unwrap();
std::fs::write(
dir.join(".beads/issues.jsonl"),
"{\"id\":\"b7\",\"title\":\"Fix transcript normalization retry\",\"status\":\"in_progress\",\"dependencies\":[]}\n",
)
.unwrap();
run_ok(&dir, &["index", "--quiet"]);
let out = run_ok(&dir, &["checkpoint", "save", "--json"]);
let cp: serde_json::Value = serde_json::from_str(&out).unwrap();
assert_eq!(cp["task"]["goal"], "Fix transcript normalization retry", "{out}");
assert_eq!(cp["task"]["bead"], "b7", "{out}");
// capture pins a durable snapshot for semantic rehydration
assert!(
cp["snapshot_id"].as_str().map(|s| s.starts_with("snap-")).unwrap_or(false),
"capture must pin a snapshot id: {out}"
);
// rehydration renders the goal plus the snapshot validity verdict
let loaded = run_ok(&dir, &["checkpoint", "load", "--inject"]);
assert!(loaded.contains("Fix transcript normalization retry"), "{loaded}");
assert!(loaded.contains("Snapshot validity"), "{loaded}");
assert!(loaded.contains("still valid"), "{loaded}");
}
#[test]
// trace:v1 id=test.crates-scc-cli-tests-golden.atlas-describes-system-accurately
fn atlas_describes_system_accurately() {
// Wave 2 QA: the agent should be able to explain the system from the
// atlas alone — purpose, architecture, flows, ownership, contracts,
// freshness — with no source exploration.
let repo = copy_fixture("http-service-python");
let dir = workdir(repo.path());
run_ok(&dir, &["index", "--quiet"]);
let atlas = run_ok(&dir, &["atlas"]);
assert!(atlas.contains("SYSTEM PURPOSE"), "{atlas}");
assert!(atlas.contains("normalized radio transcripts"), "purpose: {atlas}");
assert!(atlas.contains("ARCHITECTURE"), "{atlas}");
assert!(atlas.contains("SERVICES"), "{atlas}");
assert!(atlas.contains("TranscriptRepository"), "component purpose: {atlas}");
assert!(atlas.contains("get-/api/transcripts"), "flow: {atlas}");
assert!(atlas.contains("handle_transcripts"), "flow step: {atlas}");
assert!(atlas.contains("DATA OWNERSHIP"), "{atlas}");
assert!(atlas.contains("services owns db"), "ownership: {atlas}");
assert!(atlas.contains("CONTRACTS"), "{atlas}");
assert!(atlas.contains("GET /api/transcripts"), "contract: {atlas}");
assert!(atlas.contains("EVIDENCE STATUS"), "{atlas}");
assert!(atlas.contains("FRESH"), "freshness: {atlas}");
assert!(atlas.contains("Raw transcripts are immutable"), "invariant/README purpose: {atlas}");
}
#[test]
// trace:v1 id=test.crates-scc-cli-tests-golden.atlas-excludes-stale-facts-and-warns
fn atlas_excludes_stale_facts_and_warns() {
let repo = copy_fixture("http-service-python");
let dir = workdir(repo.path());
run_ok(&dir, &["index", "--quiet"]);
// modify without re-indexing
let f = dir.join("services/transcripts.py");
let mut src = std::fs::read_to_string(&f).unwrap();
src.push_str("\n# worktree edit\n");
std::fs::write(&f, src).unwrap();
let atlas = run_ok(&dir, &["atlas"]);
assert!(
atlas.contains("services/transcripts.py changed since indexing"),
"stale warning must surface: {atlas}"
);
// re-index -> fresh again
run_ok(&dir, &["index", "--quiet"]);
let atlas2 = run_ok(&dir, &["atlas"]);
assert!(!atlas2.contains("changed since indexing"), "{atlas2}");
}
#[test]
// trace:v1 id=test.crates-scc-cli-tests-golden.atlas-runtime-section-shows-observed-paths-and-drift
fn atlas_runtime_section_shows_observed_paths_and_drift() {
// Wave 6: the atlas RUNTIME section surfaces observed trace signatures
// and three-way drift findings (declared vs static vs observed).
let repo = copy_fixture("http-service-python");
let dir = workdir(repo.path());
run_ok(&dir, &["index", "--quiet"]);
// No runtime data yet: the section renders but says (none).
let atlas = run_ok(&dir, &["atlas"]);
assert!(atlas.contains("# RUNTIME\n(none)"), "{atlas}");
// Ingest a 3-span OTLP trace: an `api` root span with two `db` children
// dedupes to one "root -> api -> db" signature.
let trace = r#"{"resourceSpans":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"api"}}]},"scopeSpans":[{"spans":[{"traceId":"t1","spanId":"a","name":"GET /x","startTimeUnixNano":"0","endTimeUnixNano":"10000000","status":{"code":0}}]}]},{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"db"}}]},"scopeSpans":[{"spans":[{"traceId":"t1","spanId":"b","parentSpanId":"a","name":"SELECT 1","startTimeUnixNano":"1000000","endTimeUnixNano":"6000000","status":{"code":0}},{"traceId":"t1","spanId":"c","parentSpanId":"a","name":"SELECT 2","startTimeUnixNano":"2000000","endTimeUnixNano":"7000000","status":{"code":0}}]}]}]}"#;
run_ok(&dir, &["ingest", trace]);
let atlas = run_ok(&dir, &["atlas"]);
assert!(atlas.contains("# RUNTIME"), "{atlas}");
assert!(atlas.contains("OBSERVED PATH"), "{atlas}");
assert!(atlas.contains("root -> api -> db (1 reqs"), "{atlas}");
// Reconcile writes the three-way drift: the observed edges are
// undeclared (the fixture declares no flows) and the fixture's static
// edges that were never observed are flagged. The epoch bump makes the
// cached atlas re-render with the drift lines.
run_ok(&dir, &["runtime", "reconcile"]);
let atlas = run_ok(&dir, &["atlas"]);
assert!(
atlas.contains("DRIFT undeclared observed: root -> api")
&& atlas.contains("DRIFT undeclared observed: api -> db"),
"atlas must surface undeclared_observed drift: {atlas}"
);
assert!(
atlas.contains("DRIFT static unobserved:"),
"atlas must surface static_unobserved drift: {atlas}"
);
}
#[test]
// trace:v1 id=test.crates-scc-cli-tests-golden.atlas-budget-accounting-is-honest
fn atlas_budget_accounting_is_honest() {
let repo = copy_fixture("http-service-python");
let dir = workdir(repo.path());
run_ok(&dir, &["index", "--quiet"]);
// tight budget: the hard renderer fits the budget exactly — sections
// may drop or line-truncate (even critical ones at 200 tokens), but
// every cut is recorded in dropped_sections, never silent
let out = run_ok(&dir, &["atlas", "--budget", "200", "--json"]);
let v: serde_json::Value = serde_json::from_str(&out).unwrap();
assert_eq!(v["budget"], 200);
assert!(
v["truncated"].as_bool().unwrap(),
"tight budget must report truncation: {out}"
);
assert!(
v["tokens"].as_u64().unwrap() <= 200,
"hard cap violated: {}",
v["tokens"]
);
assert!(
!v["exceeded_soft_budget"].as_bool().unwrap(),
"fit must hold, no soft excess: {out}"
);
let dropped: Vec<&str> = v["dropped_sections"]
.as_array()
.map(|a| {
a.iter()
.filter_map(|x| x.as_str())
.collect::<Vec<_>>()
})
.unwrap_or_default();
assert!(
!dropped.is_empty(),
"cuts must be recorded: {out}"
);
}
#[test]
// trace:v1 id=test.crates-scc-cli-tests-golden.atlas-unbounded-human-mode verifies=REQ-SI-503JSBGP exercises=impl.crates-scc-cli-src-commands.cmd-atlas
fn atlas_unbounded_is_soft_and_reported() {
// Human `--unbounded`: the legacy soft render returns (excess reported
// via exceeded_soft_budget, critical sections kept); the default stays
// hard-capped. Guards the cache-key separation: bounded and unbounded
// packs must never share entries.
let repo = copy_fixture("http-service-python");
let dir = workdir(repo.path());
run_ok(&dir, &["index", "--quiet"]);
let full: serde_json::Value = serde_json::from_str(&run_ok(
&dir,
&["atlas", "--budget", "60", "--unbounded", "--json"],
))
.unwrap();
assert!(
full["exceeded_soft_budget"].as_bool().unwrap(),
"unbounded excess must be reported: {full}"
);
assert!(
full["content"].as_str().unwrap().contains("# CONTRACTS"),
"unbounded keeps critical sections: {full}"
);
let hard: serde_json::Value =
serde_json::from_str(&run_ok(&dir, &["atlas", "--budget", "60", "--json"])).unwrap();
assert!(
hard["tokens"].as_u64().unwrap() <= 60,
"default stays hard-capped: {}",
hard["tokens"]
);
assert!(
!hard["exceeded_soft_budget"].as_bool().unwrap(),
"hard fit must hold: {hard}"
);
}
#[test]
// trace:v1 id=test.crates-scc-cli-tests-golden.task-cache-hits-within-an-epoch-and-misses-across
fn task_cache_hits_within_an_epoch_and_misses_across() {
let repo = copy_fixture("http-service-python");
let dir = workdir(repo.path());
run_ok(&dir, &["index", "--quiet"]);
let goal = "rename the transcript field in the api response";
let a = run_ok(&dir, &["context", "task", "--json", goal]);
let b = run_ok(&dir, &["context", "task", "--json", goal]);
// The artifact is {pack, delta, delta_ids}: the PACK must be cached
// byte-identically; the delta intentionally evolves with the ledger
// (run 1 recorded its rendered ids, so run 2's delta suppresses them —
// that suppression IS the Wave-14E novelty contract).
let pack_of = |s: &str| -> String {
let v: serde_json::Value = serde_json::from_str(s).unwrap();
serde_json::to_string(&v["pack"]).unwrap()
};
assert_eq!(pack_of(&a), pack_of(&b), "same model state must serve the cached pack");
// identical re-index: rebuild is deterministic — same state yields the
// same pack AND the same full artifact (the epoch change invalidated
// the cache key and reset the per-epoch ledger, not the truth)
run_ok(&dir, &["index", "--quiet"]);
let c = run_ok(&dir, &["context", "task", "--json", goal]);
assert_eq!(a, c, "deterministic rebuild for identical state");
// a real source change (re-indexed) produces a genuinely new pack
let f = dir.join("services/transcripts.py");
let mut src = std::fs::read_to_string(&f).unwrap();
src.push_str("\ndef new_helper():\n return 2\n");
std::fs::write(&f, src).unwrap();
run_ok(&dir, &["index", "--quiet"]);
let d = run_ok(&dir, &["context", "task", "--json", goal]);
assert_ne!(a, d, "changed source must produce a different pack");
}
#[test]
// trace:v1 id=test.crates-scc-cli-tests-golden.incremental-refresh-matches-cold-cli
fn incremental_refresh_matches_cold_cli() {
let repo = copy_fixture("http-service-python");
run_ok(&workdir(repo.path()), &["index", "--quiet"]);
let cold = run_ok(&workdir(repo.path()), &["export", "system-ir.json"]);
// edit a file, then refresh just that path
std::fs::write(
workdir(repo.path()).join("services/transcripts.py"),
"def helper():\n return 1\n",
)
.unwrap();
run_ok(&workdir(repo.path()), &["index", "--paths", "services/transcripts.py", "--quiet"]);
// fresh cold index of the final state must agree with the incremental one
let repo2 = copy_fixture("http-service-python");
std::fs::write(
workdir(repo2.path()).join("services/transcripts.py"),
"def helper():\n return 1\n",
)
.unwrap();
run_ok(&workdir(repo2.path()), &["index", "--quiet"]);
let cold2 = run_ok(&workdir(repo2.path()), &["export", "system-ir.json"]);
let incr = run_ok(&workdir(repo.path()), &["export", "system-ir.json"]);
// equivalence is about FACTS — normalize the wall-clock indexed_at
let norm = |s: &str| {
let mut v: serde_json::Value = serde_json::from_str(s).unwrap();
v["snapshot"]["indexed_at"] = serde_json::Value::String("X".into());
v
};
assert_eq!(norm(&cold2), norm(&incr), "full vs incremental equivalence (CLI)");
assert_ne!(norm(&cold), norm(&incr), "the edit must change the IR");
}
#[test]
// trace:v1 id=test.crates-scc-cli-tests-golden.stale-detection-and-verify
fn stale_detection_and_verify() {
let repo = copy_fixture("http-service-python");
run_ok(&workdir(repo.path()), &["index", "--quiet"]);
// modify without re-indexing
std::fs::write(workdir(repo.path()).join("main.py"), "def changed():\n pass\n").unwrap();
let status = run_ok(&workdir(repo.path()), &["status"]);
assert!(status.contains("STALE"), "{status}");
let task = run_ok(&workdir(repo.path()), &["context", "task", "transcripts"]);
assert!(task.contains("stale"), "{task}");
assert!(task.contains("WARNING"), "{task}");
let verify = run_ok(&workdir(repo.path()), &["verify"]);
assert!(verify.contains("STALE"), "{verify}");
assert!(verify.contains("ISSUES FOUND"), "{verify}");
// re-index restores freshness
run_ok(&workdir(repo.path()), &["index", "--quiet"]);
let verify = run_ok(&workdir(repo.path()), &["verify"]);
assert!(verify.contains("Fresh"), "{verify}");
}
#[test]
// trace:v1 id=test.crates-scc-cli-tests-golden.added-file-makes-verify-stale verifies=REQ-SI-NX53P4B7 exercises=impl.crates-scc-cli-src-lib.stale-paths
fn added_file_makes_verify_stale() {
let repo = copy_fixture("http-service-python");
run_ok(&workdir(repo.path()), &["index", "--quiet"]);
let verify = run_ok(&workdir(repo.path()), &["verify"]);
assert!(verify.contains("Fresh"), "{verify}");
// skip accounting: counts recorded at index time, visible in verify
assert!(verify.contains("discovered="), "{verify}");
assert!(verify.contains("indexed="), "{verify}");
let status = run_ok(&workdir(repo.path()), &["status"]);
assert!(status.contains("discovered="), "{status}");
// add without re-indexing: the scan sees it, the inventory does not
std::fs::write(workdir(repo.path()).join("services/new_endpoint.py"), "def new_endpoint():\n return 'new'\n").unwrap();
let verify = run_ok(&workdir(repo.path()), &["verify"]);
assert!(verify.contains("STALE"), "{verify}");
assert!(verify.contains("new_endpoint.py"), "{verify}");
// re-index restores freshness
run_ok(&workdir(repo.path()), &["index", "--quiet"]);
let verify = run_ok(&workdir(repo.path()), &["verify"]);
assert!(verify.contains("Fresh"), "{verify}");
// delete without re-indexing
std::fs::remove_file(workdir(repo.path()).join("services/new_endpoint.py")).unwrap();
let verify = run_ok(&workdir(repo.path()), &["verify"]);
assert!(verify.contains("STALE"), "{verify}");
}
#[test]
// trace:v1 id=test.crates-scc-cli-tests-golden.secret-redaction-end-to-end
fn secret_redaction_end_to_end() {
let repo = tempfile::TempDir::new().unwrap();
std::fs::create_dir_all(workdir(repo.path())).unwrap();
std::fs::write(
workdir(repo.path()).join(".env"),
"DATABASE_URL=postgres://user:hunter2secret@db:5432/x\nPORT=8080\n",
)
.unwrap();
std::fs::write(workdir(repo.path()).join("app.py"), "def main():\n pass\n").unwrap();
run_ok(&workdir(repo.path()), &["index", "--quiet"]);
let ir = run_ok(&workdir(repo.path()), &["export", "system-ir.json"]);
assert!(!ir.contains("hunter2secret"), "secret value leaked");
assert!(!ir.contains("postgres://user:"), "DSN leaked");
assert!(ir.contains("DATABASE_URL"), "reference kept");
}
#[test]
// trace:v1 id=test.crates-scc-cli-tests-golden.check-invariants-fails-on-dangling-refs
fn check_invariants_fails_on_dangling_refs() {
let repo = tempfile::TempDir::new().unwrap();
std::fs::create_dir_all(workdir(repo.path())).unwrap();
std::fs::write(workdir(repo.path()).join("a.py"), "def a():\n pass\n").unwrap();
run_ok(&workdir(repo.path()), &["index", "--quiet"]);
// sabotage: drop an entity referenced by a relationship
let db =
scc_store::Store::open(&workdir(repo.path()).join(".scc/scc.db"), &workdir(repo.path()))
.unwrap();
if let Some(e) = db.entities_by_kind("symbol").unwrap().into_iter().next() {
db.delete_entity(&e.id).unwrap();
}
drop(db);
let out = run(&workdir(repo.path()), &["check-invariants"]);
assert!(!out.status.success(), "dangling refs must fail CI check");
}
#[test]
// trace:v1 id=test.crates-scc-cli-tests-golden.cold-index-verifies-clean verifies=REQ-SI-NX53P4B7 exercises=impl.crates-scc-cli-src-commands.cmd-verify
fn cold_index_verifies_clean() {
// A fresh cold index must not report SCC-created graph corruption:
// zero dangling internal references, fresh files, VERIFIED verdict.
let repo = copy_fixture("http-service-python");
run_ok(&workdir(repo.path()), &["index", "--quiet"]);
let verify = run_ok(&workdir(repo.path()), &["verify"]);
assert!(verify.contains("Dangling references: 0"), "{verify}");
assert!(verify.contains("Fresh:"), "{verify}");
assert!(verify.contains("VERIFIED"), "{verify}");
}
#[test]
// trace:v1 id=test.crates-scc-cli-tests-golden.query-and-export-formats
fn query_and_export_formats() {
let repo = copy_fixture("http-service-python");
run_ok(&workdir(repo.path()), &["index", "--quiet"]);
let q = run_ok(&workdir(repo.path()), &["query", "transcript"]);
assert!(q.contains("transcript") || q.contains("Transcript"), "{q}");
let jsonl = run_ok(&workdir(repo.path()), &["export", "system-ir.jsonl"]);
assert!(jsonl.lines().count() > 5, "jsonl has many records");
assert!(jsonl.contains("\"type\":\"entity\""), "{jsonl}");
let ccg = run_ok(&workdir(repo.path()), &["export", "ccg"]);
let v: serde_json::Value = serde_json::from_str(&ccg).unwrap();
assert_eq!(v["schema"], "ccg");
assert!(v["layers"]["L0"].is_object());
assert!(v["layers"]["L1"]["architecture"].is_array());
}
#[test]
// trace:v1 id=test.scc-cli.golden-not-reexported verifies=REQ-implement-stop-re-running-the-golden-integration-suite-in-every-scc-cl exercises=impl.scc-cli.tests.common-helpers
fn other_integration_binaries_do_not_mod_golden() {
let tests_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests");
let mut offenders = Vec::new();
for ent in std::fs::read_dir(&tests_dir).unwrap() {
let path = ent.unwrap().path();
if path.extension().and_then(|e| e.to_str()) != Some("rs") {
continue;
}
if path.file_name().unwrap() == "golden.rs" {
continue;
}
let text = std::fs::read_to_string(&path).unwrap();
if text.contains("mod golden;") {
offenders.push(path.display().to_string());
}
}
assert!(
offenders.is_empty(),
"mod golden re-runs the golden suite in each binary: {offenders:?}"
);
}
#[test]
// trace:v1 id=test.crates-scc-cli-tests-golden.multi-repo-system-stitches verifies=REQ-SI-503JSBGP exercises=impl.crates-scc-cli-src-commands.cmd-system
fn multi_repo_system_stitches_contracts_topics_and_packages() {
use std::path::PathBuf;
let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.parent()
.unwrap()
.join("fixtures");
let tmp = tempfile::TempDir::new().unwrap();
let member = |fixture: &str, name: &str| -> PathBuf {
let dst = tmp.path().join(name);
std::fs::create_dir_all(&dst).unwrap();
copy_tree(&fixtures.join(fixture), &dst);
dst
};
let svc_a = member("http-service-python", "svc-a");
let web_b = member("ts-api-web", "web-b");
let hub = member("stitch_hub", "stitch_hub");
let spoke = member("stitch_spoke", "spoke");
let prod = member("event_producer", "prod");
let cons = member("event_consumer", "cons");
let amb = member("stitch_ambiguous", "amb");
for m in [&svc_a, &web_b, &hub, &spoke, &prod, &cons, &amb] {
run_ok(m, &["index", "--quiet"]);
}
let sys = |ms: &[PathBuf]| -> serde_json::Value {
let mut args = vec!["system".to_string(), "--json".to_string()];
for m in ms {
args.push("--member".to_string());
args.push(m.display().to_string());
}
let argrefs: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
let raw = run_ok(tmp.path(), &argrefs);
serde_json::from_str(&raw).unwrap_or_else(|e| panic!("system parse failed: {e}; raw={raw:?}"))
};
// contract stitch: same verb + path in two languages, both servers.
// Same shape, no evidenced direction: MatchingContract, never Exact.
let routes = sys(&[svc_a.clone(), web_b.clone()]);
let health: Vec<&serde_json::Value> = routes
.as_array()
.unwrap()
.iter()
.filter(|s| s["key"] == "GET /health")
.collect();
assert_eq!(health.len(), 1, "{routes}");
assert_eq!(health[0]["match_kind"], "MatchingContract");
assert_eq!(health[0]["ends"].as_array().unwrap().len(), 2);
// provenance survives stitching: every end names its file
for e in health[0]["ends"].as_array().unwrap() {
assert!(!e["sources"].as_array().unwrap().is_empty(), "{e}");
assert_eq!(e["role"], "Server", "{e}");
}
// standalone-vs-system stability: svc-a ends identical in both systems
let routes2 = sys(&[svc_a.clone(), amb.clone()]);
let ids = |v: &serde_json::Value, key: &str| -> Vec<String> {
v.as_array()
.unwrap()
.iter()
.find(|s| s["key"] == key)
.unwrap()["ends"]
.as_array()
.unwrap()
.iter()
.map(|e| e["entity_id"].as_str().unwrap().to_string())
.collect()
};
// same svc-a end id in the exact and the ambiguous view: system context
// never rewrites member identity
let amb_svc: Vec<String> = routes2
.as_array()
.unwrap()
.iter()
.find(|s| s["key"] == "/health")
.unwrap()["ends"]
.as_array()
.unwrap()
.iter()
.filter(|e| e["repo_id"] == "svc-a")
.map(|e| e["entity_id"].as_str().unwrap().to_string())
.collect();
let exact_svc: Vec<String> = ids(&routes, "GET /health")
.into_iter()
.filter(|id| id.starts_with("repo://svc-a/"))
.collect();
assert_eq!(exact_svc, amb_svc);
// ambiguity stays ambiguous, never joined
let amb_routes: Vec<&serde_json::Value> = routes2
.as_array()
.unwrap()
.iter()
.filter(|s| s["key"] == "/health")
.collect();
assert_eq!(amb_routes.len(), 1, "{routes2}");
assert_eq!(amb_routes[0]["match_kind"], "Ambiguous");
assert_eq!(amb_routes[0]["ends"].as_array().unwrap().len(), 2);
// event stitch across producer/consumer
let topics = sys(&[prod.clone(), cons.clone()]);
let orders: Vec<&serde_json::Value> = topics
.as_array()
.unwrap()
.iter()
.filter(|s| s["key"] == "orders.created")
.collect();
assert_eq!(orders.len(), 1, "{topics}");
assert_eq!(orders[0]["match_kind"], "Exact");
// direction evidenced per end: producer publishes, consumer subscribes
let role_of = |ends: &serde_json::Value, repo: &str| {
ends.as_array()
.unwrap()
.iter()
.find(|e| e["repo_id"] == repo)
.unwrap()["role"]
.as_str()
.unwrap()
.to_string()
};
assert_eq!(role_of(&orders[0]["ends"], "prod"), "Publisher", "{topics}");
assert_eq!(role_of(&orders[0]["ends"], "cons"), "Subscriber", "{topics}");
// package stitch: declared repo + symbol binding
let pkgs = sys(&[hub.clone(), spoke.clone()]);
assert_eq!(pkgs.as_array().unwrap().len(), 1, "{pkgs}");
assert_eq!(pkgs[0]["match_kind"], "Declared");
assert_eq!(pkgs[0]["ends"].as_array().unwrap().len(), 2);
// checkout move keeps identity: rename svc-a, stitches unchanged
let moved = tmp.path().join("svc-a-moved");
std::fs::rename(&svc_a, &moved).unwrap();
let routes3 = sys(&[moved, web_b]);
assert_eq!(ids(&routes3, "GET /health"), ids(&routes, "GET /health"));
// incremental == cold for stitches: unrelated file addition changes nothing
std::fs::write(tmp.path().join("svc-a-moved").join("notes.txt"), "hello").unwrap();
run_ok(&tmp.path().join("svc-a-moved"), &["index", "--quiet"]);
let routes4 = sys(&[tmp.path().join("svc-a-moved"), tmp.path().join("web-b")]);
assert_eq!(routes4, routes3);
}
#[test]
// trace:v1 id=test.crates-scc-cli-tests-golden.graph-history-and-snapshot verifies=REQ-SI-503JSBGP exercises=impl.crates-scc-cli-src-commands.cmd-history
fn graph_history_records_and_snapshots_diff() {
let repo = copy_fixture("http-service-python");
let dir = workdir(repo.path());
run_ok(&dir, &["index", "--quiet"]);
// durable revision with source-hash/extractor provenance
let hist: serde_json::Value =
serde_json::from_str(&run_ok(&dir, &["history", "--json"])).unwrap();
assert_eq!(hist.as_array().unwrap().len(), 1, "{hist}");
assert_eq!(hist[0]["rev"], 1);
assert!(!hist[0]["source_hash"].as_str().unwrap().is_empty());
assert!(!hist[0]["extractor_version"].as_str().unwrap().is_empty());
// content-identical re-index: no duplicate revision
run_ok(&dir, &["index", "--quiet"]);
let hist2: serde_json::Value =
serde_json::from_str(&run_ok(&dir, &["history", "--json"])).unwrap();
assert_eq!(hist2.as_array().unwrap().len(), 1);
// change the model: new field entity appears in the delta
std::fs::write(dir.join("main.py"), "x = 1\n").unwrap();
run_ok(&dir, &["index", "--quiet"]);
let diff: serde_json::Value =
serde_json::from_str(&run_ok(&dir, &["diff", "--from", "1", "--to", "2", "--json"])).unwrap();
assert!(
!diff["added_entities"].as_array().unwrap().is_empty(),
"{diff}"
);
// snapshot persists the render with epoch + revision
let snap: serde_json::Value = serde_json::from_str(&run_ok(
&dir,
&["snapshot", "save", "--task", "change transcript normalization", "--json"],
))
.unwrap();
assert_eq!(snap["revision"], 2);
assert!(!snap["epoch"].as_str().unwrap().is_empty());
assert!(!snap["entity_ids"].as_array().unwrap().is_empty());
let id = snap["id"].as_str().unwrap().to_string();
// resume works: the stored render comes back intact
let shown = run_ok(&dir, &["snapshot", "show", &id]);
assert!(shown.contains("transcript") || shown.contains("Transcript"), "{shown}");
// clean diff while the model is unchanged
let d0: serde_json::Value =
serde_json::from_str(&run_ok(&dir, &["snapshot", "diff", &id, "--json"])).unwrap();
assert_eq!(d0["current_revision"], 2);
assert!(d0["invalidated"].as_array().unwrap().is_empty(), "{d0}");
// remove a rendered symbol: diff names the invalidated fact
let main = std::fs::read_to_string(dir.join("main.py")).unwrap();
std::fs::write(dir.join("main.py"), main.replace("x = 1\n", "")).unwrap();
run_ok(&dir, &["index", "--quiet"]);
let d1: serde_json::Value =
serde_json::from_str(&run_ok(&dir, &["snapshot", "diff", &id, "--json"])).unwrap();
assert_eq!(d1["current_revision"], 3);
assert!(
!d1["invalidated"].as_array().unwrap().is_empty(),
"{d1}"
);
assert!(
!d1["still_valid"].as_array().unwrap().is_empty(),
"{d1}"
);
}
#[test]
// trace:v1 id=test.crates-scc-cli-tests-golden.state-flow-write-edge verifies=REQ-SI-503JSBGP exercises=impl.scc.graph.state-flow-read-write
fn state_authority_write_surfaces_as_flow_edge() {
let repo = copy_fixture("http-service-python");
run_ok(&workdir(repo.path()), &["index", "--quiet"]);
let out = run_ok(&workdir(repo.path()), &["export", "flow-graphs.json"]);
let graphs: serde_json::Value = serde_json::from_str(&out).unwrap();
let kinds: Vec<String> = graphs
.as_array()
.unwrap()
.iter()
.flat_map(|g| g["edges"].as_array().unwrap().iter())
.map(|e| e["kind"].as_str().unwrap().to_string())
.collect();
assert!(
kinds.iter().any(|k| k == "write"),
"state WRITES must compile to a flow Write edge: {kinds:?}"
);
}
#[test]
// trace:v1 id=test.crates-scc-cli-tests-golden.deleted-symbol-never-haunts-packs verifies=REQ-SI-503JSBGP exercises=impl.crates-scc-cli-src-commands.build-task-context
fn deleted_symbol_never_haunts_task_packs() {
// Falsifiable hallucination probe (§41): a symbol whose file is gone
// must not appear in a task pack — neither via the stale path (before
// re-index) nor after re-indexing. A resolver/retriever that sprays
// same-name edges or ignores freshness fails this test.
let repo = copy_fixture("http-service-python");
let dir = workdir(repo.path());
run_ok(&dir, &["index", "--quiet"]);
let before = run_ok(&dir, &["context", "task", "change transcript normalization"]);
assert!(before.contains("Normalizer"), "{before}");
// delete the file WITHOUT re-indexing: its exact source must vanish
// (live files may still reference the name — those are honest).
// NOTE: flow/surface entries still name stale symbols (known gap, see
// mission report); exact-source exclusion is the load-bearing invariant.
std::fs::remove_file(dir.join("services").join("transcripts.py")).unwrap();
let stale = run_ok(&dir, &["context", "task", "change transcript normalization"]);
assert!(
!stale.contains("services%2Ftranscripts.py")
&& !stale.contains("# services/transcripts.py"),
"deleted file served as exact source: {stale}"
);
// re-index: the fact is invalidated, still absent
run_ok(&dir, &["index", "--quiet"]);
let after = run_ok(&dir, &["context", "task", "change transcript normalization"]);
assert!(
!after.contains("services%2Ftranscripts.py")
&& !after.contains("# services/transcripts.py"),
"deleted file served as exact source: {after}"
);
}