feat: establish first-class lexical language parity #2
5 changed files with 59 additions and 6 deletions
feat(model): retain all dependency evidence locations
commit
5f3b2484cc
|
|
@ -26,7 +26,10 @@ Every edge has:
|
|||
- lane: syntactic, symbolic, imported, or heuristic;
|
||||
- backend: producer and mode;
|
||||
- confidence: producer-calibrated value;
|
||||
- provenance: source location or report artifact.
|
||||
- provenance: the first source location or report artifact, retained for v0.1
|
||||
consumers;
|
||||
- locations: every distinct source location contributing to a deduplicated
|
||||
edge.
|
||||
|
||||
## Health indicator
|
||||
|
||||
|
|
@ -50,4 +53,3 @@ should map Fold function/block/operation/value fact IDs directly to Brick
|
|||
identities and retain source/binary lanes. Source proved, latent, heuristic, and
|
||||
concrete-witness confidence must remain distinguishable. Binary evidence must
|
||||
not be promoted to symbolic evidence without corroboration.
|
||||
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ All extractors must satisfy these rules before first-class parity is claimed:
|
|||
or calls.
|
||||
5. Duplicate names use stable qualified identities and do not resolve
|
||||
ambiguously.
|
||||
6. Every edge retains backend, confidence, and source evidence. If multiple
|
||||
source locations are collapsed, the evidence must say so.
|
||||
6. Every edge retains backend, confidence, its first provenance value, and all
|
||||
distinct source locations contributing to a deduplicated relationship.
|
||||
|
||||
## Parity test contract
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ pub fn import_findings(building: &mut Building, report: &Path) -> Result<usize>
|
|||
lines_of_code: 0,
|
||||
attributes,
|
||||
});
|
||||
let provenance = format!("{}:{}", report.display(), finding.line);
|
||||
building.edges.push(Edge {
|
||||
id: stable_id("edge", &format!("{node_id}:flags:{file_id}")),
|
||||
source: node_id,
|
||||
|
|
@ -70,7 +71,8 @@ pub fn import_findings(building: &mut Building, report: &Path) -> Result<usize>
|
|||
},
|
||||
backend: format!("fold:discover:{}", finding.backend),
|
||||
confidence: confidence_value(&finding.confidence),
|
||||
provenance: format!("{}:{}", report.display(), finding.line),
|
||||
locations: vec![provenance.clone()],
|
||||
provenance,
|
||||
},
|
||||
});
|
||||
imported += 1;
|
||||
|
|
|
|||
|
|
@ -72,6 +72,8 @@ pub struct Evidence {
|
|||
pub backend: String,
|
||||
pub confidence: f64,
|
||||
pub provenance: String,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub locations: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
|
@ -142,10 +144,12 @@ pub fn stable_id(kind: &str, identity: &str) -> String {
|
|||
}
|
||||
|
||||
pub fn evidence(backend: &str, confidence: f64, provenance: impl Into<String>) -> Evidence {
|
||||
let provenance = provenance.into();
|
||||
Evidence {
|
||||
lane: EvidenceLane::Syntactic,
|
||||
backend: backend.into(),
|
||||
confidence,
|
||||
provenance: provenance.into(),
|
||||
locations: vec![provenance.clone()],
|
||||
provenance,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
45
src/scan.rs
45
src/scan.rs
|
|
@ -531,8 +531,17 @@ impl ScanState {
|
|||
confidence: f64,
|
||||
provenance: impl Into<String>,
|
||||
) {
|
||||
let provenance = provenance.into();
|
||||
let key = (source.clone(), target.clone(), kind);
|
||||
if !self.edge_keys.insert(key) {
|
||||
if let Some(edge) = self
|
||||
.edges
|
||||
.iter_mut()
|
||||
.find(|edge| edge.source == source && edge.target == target && edge.kind == kind)
|
||||
&& !edge.evidence.locations.contains(&provenance)
|
||||
{
|
||||
edge.evidence.locations.push(provenance);
|
||||
}
|
||||
return;
|
||||
}
|
||||
let identity = format!("{source}:{}:{target}", edge_kind_name(kind));
|
||||
|
|
@ -1038,6 +1047,42 @@ mod tests {
|
|||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retains_every_location_for_a_deduplicated_dependency_edge() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
fs::write(
|
||||
dir.path().join("calls.rs"),
|
||||
"fn helper() {}\nfn caller() {\n helper();\n helper();\n}\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let building = scan(dir.path()).unwrap();
|
||||
let caller = building
|
||||
.nodes
|
||||
.iter()
|
||||
.find(|node| node.kind == NodeKind::Function && node.name == "caller")
|
||||
.unwrap();
|
||||
let helper = building
|
||||
.nodes
|
||||
.iter()
|
||||
.find(|node| node.kind == NodeKind::Function && node.name == "helper")
|
||||
.unwrap();
|
||||
let call = building
|
||||
.edges
|
||||
.iter()
|
||||
.find(|edge| {
|
||||
edge.kind == EdgeKind::Calls && edge.source == caller.id && edge.target == helper.id
|
||||
})
|
||||
.unwrap();
|
||||
let serialized = serde_json::to_value(&call.evidence).unwrap();
|
||||
|
||||
assert_eq!(call.evidence.provenance, "calls.rs:3");
|
||||
assert_eq!(
|
||||
serialized["locations"],
|
||||
serde_json::json!(["calls.rs:3", "calls.rs:4"])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scans_dart_structure_without_promoting_control_flow_or_strings() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue