Bugs
This commit is contained in:
@@ -4,4 +4,4 @@
|
||||
server.url=http://localhost:8787
|
||||
# Stamped by manage-ac.sh (stamp_cli_version) from ac-code-server's agenticcode.version
|
||||
# at build time. "dev" means this jar wasn't built via manage-ac.sh.
|
||||
version=73
|
||||
version=77
|
||||
|
||||
@@ -14,6 +14,10 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -184,6 +188,23 @@ public class ProjectIngestService {
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Item 24: reads and parses one candidate file, applying the shell/user-exit metric enrichment.
|
||||
* Pure per-file work (no shared mutable state), so it is safe to run on a virtual thread per file.
|
||||
*/
|
||||
private Parsed parseCandidate(Candidate candidate, Path root, boolean coarse,
|
||||
CopycodeLibrary copycodes, Map<String, LocMetrics> userExit,
|
||||
ProjectInfo project) throws IOException {
|
||||
String content = SourceFiles.read(candidate.file());
|
||||
String sourceFile = relativeSourceFile(root, candidate.file());
|
||||
ParseResult result = coarse
|
||||
? astIngestService.coarseScan(candidate.kind().language(), sourceFile, content, copycodes)
|
||||
: withShellMetrics(astIngestService.parse(candidate.kind().language(), sourceFile, content, copycodes),
|
||||
content, candidate.kind().language());
|
||||
result = withUserExitMetrics(result, project.generatedDir(), userExit);
|
||||
return new Parsed(candidate, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identity used for cross-file duplicate detection: the fully-qualified name for Java modules
|
||||
* (so {@code com.a.Foo} and {@code com.b.Foo} are distinct), the bare name otherwise.
|
||||
@@ -364,20 +385,28 @@ public class ProjectIngestService {
|
||||
List<String> examinedFiles = candidates.stream()
|
||||
.map(c -> relativeSourceFile(root, c.file()))
|
||||
.toList();
|
||||
// Item 24: parse+read each file on its own virtual thread. Reading source is IO-bound and
|
||||
// parsing is CPU-bound but per-file independent (the JavaParser/NaturalParser instances hold no
|
||||
// mutable state; copycodes/userExit are read-only), so this scales the parse phase across cores.
|
||||
// Results are collected in candidate order (futures list is parallel to candidates), so the
|
||||
// downstream duplicate detection and persist order stay deterministic.
|
||||
List<Parsed> parsed = new ArrayList<>();
|
||||
List<IngestSummary.Failure> failed = new ArrayList<>();
|
||||
for (Candidate candidate : candidates) {
|
||||
try {
|
||||
String content = SourceFiles.read(candidate.file());
|
||||
String sourceFile = relativeSourceFile(root, candidate.file());
|
||||
ParseResult result = coarse
|
||||
? astIngestService.coarseScan(candidate.kind().language(), sourceFile, content, copycodes)
|
||||
: withShellMetrics(astIngestService.parse(candidate.kind().language(), sourceFile, content, copycodes),
|
||||
content, candidate.kind().language());
|
||||
result = withUserExitMetrics(result, project.generatedDir(), userExit);
|
||||
parsed.add(new Parsed(candidate, result));
|
||||
} catch (RuntimeException | IOException e) {
|
||||
failed.add(new IngestSummary.Failure(candidate.file().toString(), e.toString()));
|
||||
try (ExecutorService parseExecutor = Executors.newVirtualThreadPerTaskExecutor()) {
|
||||
List<Future<Parsed>> futures = candidates.stream()
|
||||
.map(candidate -> parseExecutor.submit(
|
||||
() -> parseCandidate(candidate, root, coarse, copycodes, userExit, project)))
|
||||
.toList();
|
||||
for (int i = 0; i < futures.size(); i++) {
|
||||
try {
|
||||
parsed.add(futures.get(i).get());
|
||||
} catch (ExecutionException e) {
|
||||
Throwable cause = e.getCause() != null ? e.getCause() : e;
|
||||
failed.add(new IngestSummary.Failure(candidates.get(i).file().toString(), cause.toString()));
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
failed.add(new IngestSummary.Failure(candidates.get(i).file().toString(), e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ quarkus.http.port=8787
|
||||
# AgenticCode's own release counter (not the Maven project version) — bump this by hand for each
|
||||
# release. Single source of truth for the startup log line, GET /api/version, and the MCP
|
||||
# 'version' tool/server-info (referenced below via property expression, not duplicated).
|
||||
agenticcode.version=73
|
||||
agenticcode.version=77
|
||||
|
||||
# MCP server (HTTP/SSE transport) — tools exposed at http://<host>:8787/mcp/sse
|
||||
quarkus.mcp.server.server-info.name=agenticcode
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.agenticcode.codeserver.api;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.restassured.RestAssured;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.Session;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static io.restassured.RestAssured.given;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* Item 81 — a leaf qualified by a <em>group</em> whose name is not a {@code USING} member must resolve
|
||||
* by using the group to disambiguate an otherwise-ambiguous leaf name. `WGEAGB0S` reads
|
||||
* {@code #MAP-T.V-ID}, where {@code #MAP-T} is a group inside the included {@code BGEAGA01.pda} and
|
||||
* {@code V-ID} occurs in dozens of PDAs, so the bare resolver's {@code size(matches)=1} guard left it
|
||||
* unresolved.
|
||||
*
|
||||
* <p>{@code MG81} includes both {@code GRPPDA} (whose {@code MAPT} group holds {@code FLD-X}) and
|
||||
* {@code AMBIG} (which also declares {@code FLD-X}), then reads {@code MAPT.FLD-X}. The bare leaf
|
||||
* {@code FLD-X} is ambiguous across the two includes, but the group qualifier {@code MAPT} pins it to
|
||||
* {@code GRPPDA.lda}. Asserted against the raw {@code READS} target. Written RED.
|
||||
*/
|
||||
@QuarkusTest
|
||||
class GroupQualifiedLeafResolveIT {
|
||||
|
||||
private static final String PROJECT = "nat-group-qualifier";
|
||||
|
||||
@TempDir
|
||||
static Path root;
|
||||
|
||||
@Inject
|
||||
Driver driver;
|
||||
|
||||
@BeforeAll
|
||||
static void ingest() {
|
||||
RestAssured.port = Integer.getInteger("quarkus.http.test-port", 8081);
|
||||
for (String f : List.of("GRPPDA.lda", "AMBIG.lda", "MG81.nat")) {
|
||||
copyFixture("fixtures/natural/groupqual/" + f);
|
||||
}
|
||||
given().contentType("application/json")
|
||||
.body(new ProjectResource.ProjectRequest(null, root.toString(), null, "natural", null, null))
|
||||
.when().post("/api/projects/" + PROJECT).then().statusCode(201);
|
||||
given().when().post("/api/projects/" + PROJECT + "/refresh?deep=true").then().statusCode(200);
|
||||
}
|
||||
|
||||
private static void copyFixture(String cp) {
|
||||
String fileName = cp.substring(cp.lastIndexOf('/') + 1);
|
||||
try (InputStream in = GroupQualifiedLeafResolveIT.class.getClassLoader().getResourceAsStream(cp)) {
|
||||
if (in == null) {
|
||||
throw new IllegalStateException("Resource not found: " + cp);
|
||||
}
|
||||
Files.write(root.resolve(fileName), in.readAllBytes());
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void groupQualifierDisambiguatesAnAmbiguousLeaf() {
|
||||
try (Session session = driver.session()) {
|
||||
session.run("CALL db.awaitIndexes(120)").consume();
|
||||
List<String> targets = session.run("""
|
||||
MATCH (m:AstNode {project:$p, type:'MODULE', name:'MG81'})
|
||||
-[:CONTAINS*0..]->(s)-[:READS]->(v:AstNode {name:'FLD-X'})
|
||||
WHERE v.sourceFile <> ""
|
||||
RETURN DISTINCT v.sourceFile AS f ORDER BY f
|
||||
""", Map.of("p", PROJECT))
|
||||
.list(r -> r.get("f").asString());
|
||||
|
||||
assertEquals(List.of("GRPPDA.lda"), targets,
|
||||
"MG81 reads MAPT.FLD-X — the MAPT group pins the ambiguous FLD-X to GRPPDA.lda, not "
|
||||
+ "AMBIG.lda, and it must not stay unresolved. Targets: " + targets);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.agenticcode.codeserver.api;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.restassured.RestAssured;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.Session;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static io.restassured.RestAssured.given;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* Item 80 — a qualified reference may name a <em>group</em> (a {@code DATA_STRUCTURE}), not only a leaf
|
||||
* field. The qualifier-scoped field resolver matched targets of type {@code VARIABLE}/{@code CONSTANT}
|
||||
* only, so a write to a group stayed unresolved ({@code sourceFile=""}) — e.g. `WGEAGB0S` writing
|
||||
* {@code BGEAGBA0.#P-DESC-NAME}, a level-1 group of {@code BGEAGBA0.pda}.
|
||||
*
|
||||
* <p>{@code MGRP} includes {@code GRPPA} and writes {@code GRPPA.GRP-FLD}, where {@code GRP-FLD} is a
|
||||
* level-2 group (it has children {@code SUB-A}/{@code SUB-B}). The write must resolve into
|
||||
* {@code GRPPA.lda}, not stay a placeholder. Asserted against the raw {@code WRITES} target. Written RED.
|
||||
*/
|
||||
@QuarkusTest
|
||||
class QualifiedGroupTargetResolveIT {
|
||||
|
||||
private static final String PROJECT = "nat-group-target";
|
||||
|
||||
@TempDir
|
||||
static Path root;
|
||||
|
||||
@Inject
|
||||
Driver driver;
|
||||
|
||||
@BeforeAll
|
||||
static void ingest() {
|
||||
RestAssured.port = Integer.getInteger("quarkus.http.test-port", 8081);
|
||||
for (String f : List.of("GRPPA.lda", "MGRP.nat")) {
|
||||
copyFixture("fixtures/natural/groupfield/" + f);
|
||||
}
|
||||
given().contentType("application/json")
|
||||
.body(new ProjectResource.ProjectRequest(null, root.toString(), null, "natural", null, null))
|
||||
.when().post("/api/projects/" + PROJECT).then().statusCode(201);
|
||||
given().when().post("/api/projects/" + PROJECT + "/refresh?deep=true").then().statusCode(200);
|
||||
}
|
||||
|
||||
private static void copyFixture(String cp) {
|
||||
String fileName = cp.substring(cp.lastIndexOf('/') + 1);
|
||||
try (InputStream in = QualifiedGroupTargetResolveIT.class.getClassLoader().getResourceAsStream(cp)) {
|
||||
if (in == null) {
|
||||
throw new IllegalStateException("Resource not found: " + cp);
|
||||
}
|
||||
Files.write(root.resolve(fileName), in.readAllBytes());
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void qualifiedWriteToAGroupResolvesIntoTheDataArea() {
|
||||
try (Session session = driver.session()) {
|
||||
session.run("CALL db.awaitIndexes(120)").consume();
|
||||
List<String> targets = session.run("""
|
||||
MATCH (m:AstNode {project:$p, type:'MODULE', name:'MGRP'})
|
||||
-[:CONTAINS*0..]->(s)-[:WRITES]->(v:AstNode {name:'GRP-FLD'})
|
||||
RETURN DISTINCT v.sourceFile AS f ORDER BY f
|
||||
""", Map.of("p", PROJECT))
|
||||
.list(r -> r.get("f").asString());
|
||||
|
||||
assertEquals(List.of("GRPPA.lda"), targets,
|
||||
"MGRP writes GRPPA.GRP-FLD (a group), so it must resolve into GRPPA.lda, not stay an "
|
||||
+ "unresolved placeholder. Targets: " + targets);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.agenticcode.codeserver.api;
|
||||
|
||||
import com.agenticcode.neo4jstore.graph.CypherQueries;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.Session;
|
||||
import org.neo4j.driver.summary.Plan;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* Item 75 (read path) — the runtime read queries traversed a module's statements with an unbounded
|
||||
* {@code (m)-[:CONTAINS*0..]->(src)}. {@code CONTAINS} is not acyclic (shared copycode nodes, parser
|
||||
* line-range artefacts), so on a heavy module that expansion blew up and {@code callees}/{@code digest}/
|
||||
* {@code context} hung (measured: {@code ACCNPE01 callees} timed out > 2 min). Every edge source is a
|
||||
* {@code MODULE} (depth 0) or a {@code FUNCTION} (a direct {@code CONTAINS} child, depth 1) — verified 0
|
||||
* sources deeper — so the traversal is bounded to {@code *0..1}, which cannot walk the cycles.
|
||||
*
|
||||
* <p>This guards the fix at the plan level (like {@code StaleFileSweepIndexIT}): every variable-length
|
||||
* {@code CONTAINS} expansion in the {@code callees} plan must carry an upper bound, so a revert to
|
||||
* {@code *0..} is caught. {@code EXPLAIN} plans without executing.
|
||||
*/
|
||||
@QuarkusTest
|
||||
class ReadPathBoundedTraversalIT {
|
||||
|
||||
/**
|
||||
* A var-length CONTAINS expansion with no upper bound, e.g. {@code CONTAINS*} or {@code CONTAINS*0..}.
|
||||
*/
|
||||
private static final Pattern UNBOUNDED_CONTAINS = Pattern.compile("CONTAINS\\*(\\d*\\.\\.)?(?![\\d.])");
|
||||
@Inject
|
||||
Driver driver;
|
||||
|
||||
private static void collectOperators(Plan plan, List<String> into) {
|
||||
Object details = plan.arguments().get("Details");
|
||||
into.add(plan.operatorType() + "(" + (details == null ? "" : details.toString()) + ")");
|
||||
plan.children().forEach(child -> collectOperators(child, into));
|
||||
}
|
||||
|
||||
@Test
|
||||
void calleesBoundsItsContainsTraversal() {
|
||||
try (Session session = driver.session()) {
|
||||
Plan plan = session.run("EXPLAIN " + CypherQueries.callees(null, false),
|
||||
Map.of("name", "ANY", "project", "any-project"))
|
||||
.consume().plan();
|
||||
|
||||
List<String> operators = new ArrayList<>();
|
||||
collectOperators(plan, operators);
|
||||
|
||||
// Guard against a vacuous pass: the plan must actually expand CONTAINS.
|
||||
assertTrue(operators.stream().anyMatch(op -> op.contains("CONTAINS")),
|
||||
"callees plan should traverse CONTAINS; plan format may have changed: " + operators);
|
||||
|
||||
List<String> unbounded = operators.stream()
|
||||
.filter(op -> UNBOUNDED_CONTAINS.matcher(op).find())
|
||||
.toList();
|
||||
|
||||
assertTrue(unbounded.isEmpty(),
|
||||
"callees must bound every CONTAINS traversal to *0..1 (item 75 read path); an "
|
||||
+ "unbounded expansion explodes on the cyclic copycode region. Offending plan "
|
||||
+ "operators: " + unbounded + " | full plan: " + operators);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
*-----------------------------------------------------------------
|
||||
* Item 80: data area whose GRP-FLD is a level-2 GROUP (DATA_STRUCTURE),
|
||||
* not a leaf field — it has children SUB-A / SUB-B.
|
||||
*-----------------------------------------------------------------
|
||||
1GRPPA
|
||||
2GRP-FLD
|
||||
A 5 3SUB-A
|
||||
A 5 3SUB-B
|
||||
@@ -0,0 +1,9 @@
|
||||
DEFINE DATA
|
||||
LOCAL USING GRPPA
|
||||
LOCAL
|
||||
1 #V (A10)
|
||||
END-DEFINE
|
||||
*
|
||||
MOVE #V TO GRPPA.GRP-FLD
|
||||
*
|
||||
END
|
||||
@@ -0,0 +1,6 @@
|
||||
*-----------------------------------------------------------------
|
||||
* Item 81: an unrelated area that also declares FLD-X, so the bare
|
||||
* leaf name is ambiguous across MG81's two includes.
|
||||
*-----------------------------------------------------------------
|
||||
1AMBIG
|
||||
A 10 2FLD-X
|
||||
@@ -0,0 +1,6 @@
|
||||
*-----------------------------------------------------------------
|
||||
* Item 81: data area whose MAPT group contains the leaf FLD-X.
|
||||
*-----------------------------------------------------------------
|
||||
1GRPPDA
|
||||
2MAPT
|
||||
A 10 3FLD-X
|
||||
@@ -0,0 +1,10 @@
|
||||
DEFINE DATA
|
||||
LOCAL USING GRPPDA
|
||||
LOCAL USING AMBIG
|
||||
LOCAL
|
||||
1 #V (A10)
|
||||
END-DEFINE
|
||||
*
|
||||
MOVE MAPT.FLD-X TO #V
|
||||
*
|
||||
END
|
||||
File diff suppressed because it is too large
Load Diff
@@ -248,9 +248,17 @@ public final class NaturalParser implements LanguageParser {
|
||||
// graph links the include. Model FIELD as a bare included-field placeholder so
|
||||
// CypherQueries.resolveBareIncludedFieldTargets redirects it to the real PDA field, instead of
|
||||
// dropping the access entirely. Gated exactly like the bare path above.
|
||||
if (allowIncludePlaceholder && scope.hasIncludes() && IDENTIFIER_TOKEN.matcher(fieldName).matches()) {
|
||||
return scope.bareFields().computeIfAbsent(fieldName, key -> {
|
||||
AstNode field = node(NodeType.VARIABLE, key, "", lineNo, lineNo, null, null);
|
||||
//
|
||||
// Item 81: keep the group qualifier (STRUCT) as a `qualifierGroup` property so the bare-included
|
||||
// resolver can pick the one included field that sits under a group of that name — otherwise a
|
||||
// leaf like V-ID (in dozens of PDAs) is project-wide-ambiguous and stays unresolved. Cache under
|
||||
// the compound "STRUCT.FIELD" key so a group-qualified ref and a truly-bare one to the same leaf
|
||||
// are distinct placeholders.
|
||||
if (allowIncludePlaceholder && scope.hasIncludes() && IDENTIFIER_TOKEN.matcher(fieldName).matches()
|
||||
&& IDENTIFIER_TOKEN.matcher(structName).matches()) {
|
||||
return scope.bareFields().computeIfAbsent(structName + "." + fieldName, key -> {
|
||||
AstNode field = node(NodeType.VARIABLE, fieldName, "", lineNo, lineNo, null, null,
|
||||
Map.of("qualifierGroup", structName));
|
||||
scope.nodes().add(field);
|
||||
scope.edges().add(edge(EdgeType.CONTAINS, scope.module().id(), field.id(), lineNo));
|
||||
return field;
|
||||
|
||||
@@ -2121,3 +2121,55 @@ live server); full `ac-code-server` IT suite green (190/0/0) with no regressions
|
||||
them, so a module that changes which area it includes does not keep the old resolved edge. Scoped to
|
||||
`VARIABLE`/`CONSTANT` and cross-file targets; gated on `reconcile`. Two-phase test
|
||||
`QualifiedWriteReconcileIT.reIngestDeletesTheStaleResolvedFieldEdge`.
|
||||
|
||||
## Parallel parse phase (item 24) — 2026-07-18
|
||||
|
||||
The ingest parse phase (read file + parse + shell/user-exit metric enrichment) ran as a sequential loop
|
||||
over all candidate files. It is per-file independent — the `JavaParser`/`NaturalParser` instances hold no
|
||||
mutable state, and `copycodes`/`userExit` are read-only — so `ProjectIngestService.ingestRoot` now submits
|
||||
one task per file to `Executors.newVirtualThreadPerTaskExecutor()` (extracted helper `parseCandidate`).
|
||||
Results are collected in candidate order (the futures list is parallel to `candidates`), so cross-file
|
||||
duplicate detection and persist order stay deterministic; a per-file parse/read failure is still recorded
|
||||
as an `IngestSummary.Failure` for that file only. Full IT suite green (192/0/0).
|
||||
|
||||
## Qualified group-target resolution (item 80) — 2026-07-18
|
||||
|
||||
A Natural qualified reference can name a *group* (a `DATA_STRUCTURE`), not only a leaf field — e.g.
|
||||
`WGEAGB0S` writes `BGEAGBA0.#P-DESC-NAME`, a level-1 group of `BGEAGBA0.pda`. The qualifier-scoped field
|
||||
resolvers matched a real target of type `VARIABLE`/`CONSTANT` only, so such writes/reads stayed
|
||||
unresolved (`sourceFile=""`). Fix: the two qualifier-scoped (INCLUDES-gated) resolvers
|
||||
(`buildResolvePlaceholderFieldTargetQueries` + `…ScopedQueries`) now accept a `DATA_STRUCTURE` target as
|
||||
well (`realv.type IN ['VARIABLE','CONSTANT','DATA_STRUCTURE']`). Left the bare-field resolvers (which
|
||||
carry a `size(matches)=1` guard) leaf-only, so adding groups cannot turn a previously-unique bare match
|
||||
ambiguous. Test `QualifiedGroupTargetResolveIT.qualifiedWriteToAGroupResolvesIntoTheDataArea`. *(The
|
||||
`#MAP-T.*` reads that were unresolved in the same `WGEAGB0S` snapshot are leaves, a separate concern, not
|
||||
covered here.)*
|
||||
|
||||
## Group-qualifier field resolution (item 81) — 2026-07-18
|
||||
|
||||
A qualified reference can name a leaf via a *group* the parser cannot see as a `USING` member — e.g.
|
||||
`WGEAGB0S` reads `#MAP-T.V-ID`, where `#MAP-T` is a group inside the included `BGEAGA01.pda` and the leaf
|
||||
`V-ID` also occurs in dozens of other PDAs. After items 78/79 the read was captured but fell to the
|
||||
bare-field resolver, whose `size(matches)=1` guard rightly refused the project-wide-ambiguous leaf, so it
|
||||
stayed `sourceFile=""`. Fix: `NaturalParser.lookupVariable` now keeps the group qualifier as a
|
||||
`qualifierGroup` property on the bare placeholder (cached under the compound `STRUCT.FIELD` key so a
|
||||
group-qualified and a truly-bare reference to the same leaf are distinct); the two bare-included resolvers
|
||||
(`buildResolveBareIncludedFieldQueries` + `…ScopedQueries`) then keep only a candidate nested under a group
|
||||
of that name, so the qualifier pins the leaf to the one right PDA. The placeholder is a bare `VARIABLE`
|
||||
under the module (not a `DATA_STRUCTURE` area), so the by-name resolvers never see it — no
|
||||
`#74`-style misattribution risk. Test `GroupQualifiedLeafResolveIT.groupQualifierDisambiguatesAnAmbiguousLeaf`;
|
||||
full IT suite green (193/0/0).
|
||||
|
||||
## Read-path CONTAINS bounding (item 75 read path) — 2026-07-19
|
||||
|
||||
The runtime read queries traversed a module's statements with an unbounded `(m)-[:CONTAINS*0..]->(src)`.
|
||||
`CONTAINS` is not acyclic (shared copycode `CONTROL_FLOW` nodes, parser line-range artefacts), so on a
|
||||
heavy module (`ACCNPE01`) that expansion blew up and `callees`/`digest`/`context` hung (callees >2 min).
|
||||
Every edge source is a `MODULE` (depth 0) or a `FUNCTION` that is a *direct* `CONTAINS` child (depth 1) —
|
||||
verified corpus-wide (0 sources deeper; `DB_ACCESS` parents only `FUNCTION`/`MODULE`) — so the traversal is
|
||||
provably equivalent when bounded to `*0..1`, which cannot walk the cycles. 20 read-side traversals updated
|
||||
(`callees`, `MODULE_HOP_OUT`, `DISPATCH_TABLE`, `EGO_NEIGHBORS_*`, `VARIABLE_ACCESSES`, `DB_ACCESSES`,
|
||||
`SQL_STATEMENTS`, `FUNCTION_CALLERS`, `SEARCH_BY_VALUE`, `fieldFlow`, `BUILD_CALLS_MODULE`, and the
|
||||
`*_FOR_MODULES` batch variants). Query-only change (no `recreate`). Guard `ReadPathBoundedTraversalIT`
|
||||
(EXPLAIN asserts no unbounded CONTAINS expand); full IT suite 193/0/0. Live (v76): `ACCNPE01 callees`
|
||||
> 2 min → 0.16 s, `digest` >10 s → 3.3 s, `context` >10 s → 3.1 s.
|
||||
|
||||
@@ -72,70 +72,11 @@ with stale check, the `refresh` surface, and hash-based auto-invalidation) — s
|
||||
bottleneck — and 25 — batch persist, found already implemented — completed 2026-07-16 and moved to
|
||||
`x-docs/features.md`. A full `upms` call-graph refresh (6311 files) now takes **179 s** end-to-end
|
||||
(~63 s parse, 104.5 s persist across 32 batches, ~12 s finalize), against ~2.5–2.8 min **per batch**
|
||||
before. **No open items remain in this track**, with one optional idea parked below.)*
|
||||
|
||||
- [ ] **Parallel parse phase (OPTIONAL, unmeasured — was the original item 24)** — parse is ~63 s of a
|
||||
179 s Natural refresh, so parallelising it on virtual threads (`JavaParser.parse()` /
|
||||
`NaturalParser.parse()` are stateless and safely parallelisable) would buy at most ~35 % and cost
|
||||
deterministic log ordering. **Possibly worth it for a large *Java* project**, where building a
|
||||
`CompilationUnit` is genuinely CPU-heavy — upms is Natural. Revisit only with a timing against
|
||||
`pur` (Java) showing parse is a real share there.
|
||||
before. The parked "parallel parse phase" idea was implemented 2026-07-18 (item 24) — see
|
||||
`x-docs/features.md`. **No open items remain in this track.**)*
|
||||
|
||||
## Known bugs
|
||||
|
||||
- [ ] **80. A qualified assignment/read of a *group* (not a leaf field) stays unresolved** (found
|
||||
2026-07-18, `WGEAGB0S` post-fix audit). The field resolvers (finalize 18–25) match a real target with
|
||||
`realv.type IN ['VARIABLE','CONSTANT']`, so a reference to a `DATA_STRUCTURE` **group** never resolves.
|
||||
`WGEAGB0S` writes `BGEAGBA0.#P-DESC-NAME` (lines 529/1350) and reads it (622), but `#P-DESC-NAME` is a
|
||||
level-1 *group* in `BGEAGBA0.pda` (a `DATA_STRUCTURE` node), so all three stay `sourceFile=""`. Same
|
||||
class for the `#MAP-T.V-ID`/`LOCK-ID`/`LOG-TIME`/`LOG-DATE`/`LOG-USER` reads and `P-REST-POINT-OBJ` — 9
|
||||
unresolved in `WGEAGB0S` after items 74/78/79. Independent of those fixes (it is a target-type gap, not
|
||||
a qualifier/scoping bug). A fix would let a group placeholder resolve to a `DATA_STRUCTURE` of the same
|
||||
name under the included area (and, for a `MOVE BY NAME`, arguably fan out to the group's leaves). Not
|
||||
yet fixed; needs its own design decision on group-level edge semantics.
|
||||
|
||||
- [x] **76. A bare unresolved field is ONE node per (name, project) — shared by up to 916 modules**
|
||||
(fixed 2026-07-18 — field placeholders now carry a per-module identity)
|
||||
(found 2026-07-17 while root-causing item 74). `MERGE_NODES` (`CypherQueries.java:40`) keys a node on
|
||||
`{type, name, sourceFile, project}`. An unresolved bare field has `sourceFile=""`, so the key collapses
|
||||
to `{VARIABLE, "P-DESCRIPTION", "", upms}` — **one** node for the whole project, `CONTAINS`ed by every
|
||||
module that mentions the name. Measured in `upms`: **7,262** such nodes, **532** owned by more than one
|
||||
module, **max 916 owners**; the `P-DESCRIPTION` placeholder has 39 owners, 70 `READS` + 35 `WRITES`
|
||||
edges from 8 different origin files.
|
||||
This is the *shared root* of item 74 and of the resolver defect below it: the resolver's per-module
|
||||
reasoning is done on a node that is not per-module. It also leaks into the API — `variable_reads` /
|
||||
`variable_writes` for such a name merge unrelated modules' local variables into one answer, and cannot
|
||||
tell them apart because they are genuinely the same node.
|
||||
**This is a schema decision, not a query fix**: giving placeholders a per-module identity (e.g. keying
|
||||
on the owning module, as `POSITIONAL_NODE_TYPES` already does with `startLine` for `DB_ACCESS` /
|
||||
`CONTROL_FLOW`) changes node identity, hence `DELETE_STALE_FILE_NODES` (which explicitly skips
|
||||
`sourceFile=""` because they "are shared across files"), the item-58 sweep, and every `CONTAINS`
|
||||
consumer. Deliberately **not** folded into the item-74 fix.
|
||||
**2026-07-18 — now the #1 finalize-cost driver.** Once the item-75 `CONTAINS*` explosions were removed
|
||||
(dynamic-`CALLNAT` + `link-args`), a full deep `upms` finalize (~28 min) is dominated **entirely** by
|
||||
step 19 (`resolve-field-placeholder WRITES`, ~25 min). Root cause is exactly this sharing: the step
|
||||
joins `(ph)-[:CONTAINS]->(phv)` × `(src)-[:WRITES]->(phv)`, and because `phv` is one shared node with
|
||||
many qualifier-parents and ~170k writers, the join blows up to ~28.7M rows. Three query
|
||||
restructurings (owner-scoped, includes-driven, sourceFile-indexed) each stayed > 2.5 min read-only —
|
||||
confirming this needs the per-module-identity change above, not a query rewrite. A `PROPOSAL` for that
|
||||
change is drafted (scope: field placeholders only — `MODULE`/`DB_TABLE` stay shared; new `ownerModule`
|
||||
merge-key term; ~+45 % placeholder nodes; re-validate steps 18–26, item-77, `DELETE_STALE_FILE_NODES`),
|
||||
awaiting sign-off before implementation. Note the suspicious 53-`READS`-vs-170k-`WRITES` asymmetry —
|
||||
worth confirming the `WRITES` placeholder edges are not themselves over-created at ingest before
|
||||
multiplying them per module.
|
||||
**2026-07-18 — fixed.** Field-level placeholders ({@code VARIABLE}/{@code CONSTANT}, `sourceFile=""`)
|
||||
now get a per-module identity: `GraphRepository.placeholderOwner` stamps an `ownerModule` (the
|
||||
referencing file) that is added to both the in-memory dedup key (`mergeKey`) and the DB merge key
|
||||
(`MERGE_NODES`/`MERGE_POSITIONAL_NODES`). `MODULE`/`DB_TABLE`/`DATA_STRUCTURE` placeholders keep
|
||||
`ownerModule=""` and stay shared (so a `CALLNAT`/`USING` target still resolves once — the dual role of
|
||||
the `DATA_STRUCTURE` qualifier is thereby sidestepped entirely). The stale-node sweep gained a companion
|
||||
`DELETE_STALE_PLACEHOLDER_NODES` (keyed by `ownerModule`) so a re-ingested file's obsolete placeholders
|
||||
are reaped instead of orphaning. Corpus result on `upms` v69: step 19 **55 min → 55 s** (~60×), whole
|
||||
deep finalize **~59 min → ~6 min**; item-77 misattribution stays **0**; the graph grew by only **+2,207**
|
||||
nodes total (the per-module placeholders resolve and are deleted in step 26, so only ~13.9k unresolved
|
||||
remain); `ARG_TO_PARAM` is well-formed (30,583 edges, 0 position mismatches). The suspected `WRITES`
|
||||
over-creation was a measurement artefact (READS were counted *after* step 18 had already resolved them).
|
||||
|
||||
- [ ] **75. `CONTAINS` is not acyclic — 22 self-loops and 162 two-cycles in `upms`**
|
||||
(found 2026-07-17 while root-causing item 74; **cause NOT established — do not treat the notes below as
|
||||
settled**). The containment hierarchy that dozens of queries traverse with `CONTAINS*` contains cycles:
|
||||
@@ -192,122 +133,23 @@ before. **No open items remain in this track**, with one optional idea parked be
|
||||
So the UI's Callees / module-overview / context panels spin on large modules. Same cure as the dynamic-
|
||||
`CALLNAT` fix (`src.sourceFile = m.sourceFile` hash-join, INCLUDES-scoped for variables). Affected read
|
||||
constants: `CALLEES`, `CALLERS`, `DIGEST`/`CONTEXT` query, `FUNCTION_CALLERS`, `DB_ACCESSES`,
|
||||
variable `READS`/`WRITES`, `*_FOR_MODULES`. Deferred by user decision on 2026-07-18 — record only, no fix
|
||||
this session.
|
||||
|
||||
- [ ] **74. A resolved field edge and a fresh placeholder edge for the same statement coexist**
|
||||
(observed 2026-07-17 while verifying item 72; **not fully root-caused — do not treat the cause below as
|
||||
settled**). After a *plain* (call-graph) refresh of a module that had previously been deep-ingested,
|
||||
`VMULTMN4` holds **two** `WRITES` edges for the same statement, differing only in their target:
|
||||
```
|
||||
line 223 MOVE YAPRFMA0.DES-AUTHPROF TO P-DESCRIPTION
|
||||
-> node 206827 P-DESCRIPTION sourceFile="" (fresh, unresolved)
|
||||
-> node 507598 P-DESCRIPTION sourceFile=.../old/VMULTMA1.pda (older, resolved)
|
||||
```
|
||||
A plain refresh re-parses and re-creates the placeholder-targeted edge but does not run field
|
||||
resolution, while the previously resolved edge survives — the item-58 sweep deletes stale *nodes* only,
|
||||
and both target nodes are live. The edge MERGE cannot dedupe them because the targets are genuinely
|
||||
different nodes.
|
||||
**Item 72 did not cause this; it exposed it.** Before the guard chain the two rows produced *identical*
|
||||
`dispatch-table` tuples and `DISTINCT` collapsed them. With `guards` present (set on the fresh edge,
|
||||
absent on the old one) they no longer collapse: `VMULTMN4`'s table went from 69 to 82 rows, the extra
|
||||
13 being phantom duplicates with an empty chain. `variables/{name}/reads|writes` still collapses them
|
||||
(no distinguishing column), so it under-reports the same condition rather than double-counting.
|
||||
**Open questions:** a subsequent deep ingest produced 69 resolved+chained edges but left the 13 old ones
|
||||
in place, which the MERGE key should have prevented if the targets were the same node — so *why* the
|
||||
resolution picks a different target on different passes is unexplained.
|
||||
**Do not "fix" this by filtering chain-less rows out of the API** — that hides a graph-state defect
|
||||
behind a query.
|
||||
**Root-cause attempt 2026-07-17 — the `old/` vs `new/` guess above is dropped, it was never more than a
|
||||
filename coincidence.** The investigation instead turned up items **75**, **76** and **77**; item 77
|
||||
(fixed) removed *one* concrete mechanism for "different pass, different target": the scoped and unscoped
|
||||
bare-field resolvers walked the include chain with different bounds (`*1..10` vs unbounded), so a name
|
||||
matching at two depths was one match to one pass and two (ambiguous, skip) to the other. They are now in
|
||||
lockstep. **Whether that was the mechanism behind these 13 edges is unverified** — plausible, not shown.
|
||||
What is certain is that item 77 does *not* address the coexistence itself: a plain refresh still
|
||||
re-creates the placeholder edge without running field resolution, and nothing deletes the previously
|
||||
resolved one. That deletion is the actual fix and is still open.
|
||||
**2026-07-18 — fresh reproduction in a fully deep-refreshed `upms` (a `WGEAGB0S` audit), so it is
|
||||
*not* only a plain-refresh artefact.** The write `BXFRABA4.#L-FORWARD-VIA-PRI := TRUE` (WGEAGB0S:335)
|
||||
carries **three** `WRITES` edges: the correct one to `BXFRABA4.pda` **plus** two misattributed to
|
||||
same-named local fields in `JX0124N6.nat` and `JX0129N0.nat` — modules WGEAGB0S neither includes nor
|
||||
calls. Same for lines 442/443/559 (`#L-ADJUST-NO-STOP-CHAR`, `#FORWARD`); uniquely-named fields
|
||||
(`TOTAL-ACTION-LINES`, only in BXFRABA4) resolve cleanly to one edge. Mechanism: the by-name field
|
||||
resolver (`buildResolvePlaceholderFieldTargetByNameQueries`) matches a placeholder field to **any**
|
||||
same-named real field project-wide, unscoped to what the accessing module includes, and `MERGE`s an
|
||||
edge to every match. `field-flow` cannot see this defect (WGEAGB0S never calls JX*, so no
|
||||
producer→consumer path) and `variables/{name}/reads|writes` collapses same-named nodes — a faithful
|
||||
failing test must assert on the raw `WRITES` target file at the `ac-neo4j-store` graph level, not the
|
||||
REST surface.
|
||||
**2026-07-18 — root-caused: these are STALE resolved edges, not a single-pass resolver bug.** Two
|
||||
clean single-file fixtures (a qualified write onto an unincluded area; a qualified write coexisting
|
||||
with same-named locals in unrelated modules) both deep-ingested **without any spill** — the current
|
||||
field resolvers (steps 18–25) are correct in one pass: 18/19 are qualifier-scoped, 20/21 match on the
|
||||
placeholder *area* name (so they cannot reach `JX0124N6`'s `#L-FORWARD-VIA-PRI`, which lives under a
|
||||
local group `INPUT-VALUES`, not an area named `BXFRABA4`), 23/24 are include-scoped. A targeted
|
||||
`refresh/WGEAGB0S?scope=neighborhood&deep=true` against the live graph left every misattributed
|
||||
`JX0124N6`/`JX0129N0` edge **in place** and *added* two fresh unresolved (`sourceFile=""`) placeholder
|
||||
writes beside them — the exact coexistence this item describes. So the `JX*` edges are cruft from an
|
||||
older resolver generation that no reconcile step deletes. **Consequence for the test:** a faithful
|
||||
failing test is **two-phase** (deep-ingest → re-ingest/plain-refresh → assert the stale/duplicate edge
|
||||
is gone), not the single-pass graph test guessed above. The actual fix is a reconcile step that deletes
|
||||
a module's prior resolved field edges before (or when) its placeholders are re-created. **A full clean
|
||||
`recreate` (CLEAR_ALL) removes the existing cruft; an incremental `refresh` does not.**
|
||||
**2026-07-18 — TRUE root cause found and fixed (the stale-edge theory above was WRONG).** A clean
|
||||
`recreate` (CLEAR_ALL, no stale state possible) still produced the `WGEAGB0S → JX0124N6/JX0129N0`
|
||||
misattribution, so it is **created fresh in a single pass**, not surviving cruft. Cause: there are
|
||||
**three** `DATA_STRUCTURE`s named `BXFRABA4` — the real `BXFRABA4.pda` **and** a `1 BXFRABA4` group each
|
||||
of `JX0124N6.nat`/`JX0129N0.nat` declares *inline* (module-owned) — and the placeholder resolver
|
||||
(`buildResolvePlaceholderTargetQueries`, plus the two by-name field resolvers) matched `USING BXFRABA4`
|
||||
to a real `DATA_STRUCTURE` by **name alone**, so `WGEAGB0S` got `INCLUDES` edges to all three and the
|
||||
field resolver linked `#L-FORWARD-VIA-PRI` into all three. **Fix (the semantic key, per the observation
|
||||
that a Natural `USING` always names a data area — PDA/LDA/GDA — never a subprogram):** a
|
||||
`DATA_STRUCTURE` placeholder now resolves only to a real area that is **not owned by a `MODULE`**
|
||||
(`NOT EXISTS { (:MODULE)-[:CONTAINS]->(real) }`) — a top-level member of a data-area file, never a
|
||||
program-internal group. Applied in all three name-matching resolvers. Data-area files produce no
|
||||
`MODULE` node, so real PDAs (`BXFRABA4.pda`, `CDPDA-M.pda`) are kept; the `.nat` inline groups are
|
||||
dropped. **Test (GREEN, single-pass):**
|
||||
`UsingResolvesToDataAreaNotLocalGroupIT.usingWriteResolvesOnlyIntoTheDataAreaFileNotASameNamedLocalGroup`.
|
||||
**Verified live** (deploy v73 + clean `recreate`, 2026-07-18): `WGEAGB0S`'s `#L-FORWARD-VIA-PRI` now
|
||||
targets only `BXFRABA4.pda` (the `JX0124N6`/`JX0129N0` edges are gone), and its whole variable-access
|
||||
set has **zero** `FOREIGN` (module-file) resolutions where before there were 9+9. `##MSG-NR`/`##MSG-PGM`
|
||||
(items 78/79) resolve to `CDPDA-M.pda`; callees/functions/data-structures/db-accesses unchanged.
|
||||
**Secondary safeguard (still kept):** `mergeResults` also runs `DELETE_STALE_RESOLVED_FIELD_EDGES` in
|
||||
the `reconcile` branch (deep re-ingest only) — deletes a re-parsed file's prior cross-file
|
||||
`READS`/`WRITES` to a `VARIABLE`/`CONSTANT`, so finalize rebuilds them from the fresh placeholders.
|
||||
This does *not* fix the misattribution (that is the resolver fix above); it addresses a genuine but
|
||||
separate re-ingest scenario — a module that changes which area it includes must not keep the old
|
||||
resolved edge. Scoped to `VARIABLE`/`CONSTANT` (not `DB_TABLE`) and cross-file targets; gated on
|
||||
`reconcile`. **Test (GREEN, two-phase):**
|
||||
`QualifiedWriteReconcileIT.reIngestDeletesTheStaleResolvedFieldEdge`.
|
||||
|
||||
- [x] **79. A qualified field read inside an expression is dropped** (found 2026-07-18, `WGEAGB0S`
|
||||
audit; **fixed 2026-07-18**). *(Originally filed as "a qualifier does not disambiguate a shared field
|
||||
name" — that framing was wrong: a controlled fixture proved the qualifier disambiguates correctly on
|
||||
the write side (`QCPDA.QC-DESC` resolves to `QCPDA` even when `QC-DESC` also exists in `QCOTHER`). The
|
||||
live `WGEAGB0S` "unresolved `BGEAGBA0.#P-DESC-NAME`" observation was stale-state (item 74), not a
|
||||
resolver bug.)* The real defect: the read side of an assignment/expression/`IF` tokenised on
|
||||
`IDENTIFIER_TOKEN` (`[#A-Za-z][#A-Za-z0-9\-]*`, no dot), so a qualified read such as
|
||||
`#C := QCPDA.QC-DESC` or `IF MSG-INFO.##MSG-NR EQ …` split into two tokens and neither resolved — the
|
||||
read was lost, and `IF` conditions were not read-scanned at all. **Fix:** new `OPERAND_TOKEN` keeps a
|
||||
dotted operand as one token; `addExpressionReads` resolves a *qualified* token with
|
||||
`allowIncludePlaceholder` (a placeholder for an explicit field access is legitimate) but a *bare* token
|
||||
without (no placeholder for the multi-token "expression soup", so item 74's by-name path is not fed);
|
||||
the assign-RHS loop and the `IF`-condition handler now both call it. **Test (GREEN):**
|
||||
`QualifiedFieldResolveIT.qualifiedFieldReadInsideAnExpressionIsCaptured`.
|
||||
|
||||
- [x] **78. A field qualified by its group name (not the `USING` name) is dropped entirely** (found
|
||||
2026-07-18, `WGEAGB0S` audit; **fixed 2026-07-18**). `WGEAGB0S` does `PARAMETER USING CDPDA-M`;
|
||||
`CDPDA-M`'s root group is `MSG-INFO`, and the program writes its fields qualified by the **group** name
|
||||
— `MSG-INFO.##MSG-PGM := *PROGRAM` (line 1378). `NaturalParser.lookupVariable` only recognised a
|
||||
qualifier that matches a `USING`/PDA name; a group-name qualifier fell through to a local-only lookup
|
||||
and, the field not being local, produced **zero** edges (`CDPDA-M.pda` parses fine and holds
|
||||
`MSG-INFO`/`##MSG-NR`/`##MSG-PGM`). **Fix:** when the qualifier is not a known include and the field is
|
||||
not local, `lookupVariable` now mints a bare included-field placeholder (gated exactly like the bare
|
||||
path: `allowIncludePlaceholder && hasIncludes && identifier-shaped`), which `resolveBareIncludedFieldTargets`
|
||||
redirects to the real PDA field. **Test (GREEN):**
|
||||
`QualifiedFieldResolveIT.groupQualifiedFieldAccessIsCaptured`. *(The read side of the same construct —
|
||||
`IF MSG-INFO.##MSG-NR` — is captured by the item-79 fix.)*
|
||||
variable `READS`/`WRITES`, `*_FOR_MODULES`.
|
||||
**2026-07-19 — fixed.** Rather than the `sourceFile` hash-join, the read queries use a tighter, provably
|
||||
equivalent bound: every {@code CALLS}/{@code READS}/{@code WRITES}/{@code DB_ACCESS}-parent edge source
|
||||
is a {@code MODULE} (depth 0) or a {@code FUNCTION} that is a *direct* {@code CONTAINS} child of the
|
||||
module (depth 1) — verified corpus-wide (0 sources deeper, 0 non-direct-child edge-source functions, and
|
||||
{@code DB_ACCESS} parents are only {@code FUNCTION}/{@code MODULE}, never {@code CONTROL_FLOW}). So
|
||||
`(m)-[:CONTAINS*0..]->(src)` becomes `(m)-[:CONTAINS*0..1]->(src)`, which returns the identical set but
|
||||
cannot walk the cyclic copycode region. 20 read-side traversals updated (`callees`, `MODULE_HOP_OUT`(+
|
||||
wiring), `DISPATCH_TABLE`, `EGO_NEIGHBORS_*`, `VARIABLE_ACCESSES`, `DB_ACCESSES`(+`FOR_MODULES`),
|
||||
`SQL_STATEMENTS`(+`FOR_MODULES`), `FUNCTION_CALLERS`, `SEARCH_BY_VALUE`(+`_CONTAINS`), `fieldFlow`,
|
||||
`BUILD_CALLS_MODULE`, `FLOW_FRONTIER_SOURCE_FILES`). Finalize/resolve queries left as-is (they completed).
|
||||
Guarded by `ReadPathBoundedTraversalIT` (EXPLAIN plan asserts no unbounded CONTAINS expand in `callees`);
|
||||
full IT suite green (193/0/0). No `recreate` needed — a query-only change against the existing graph.
|
||||
**Verified live (v76):** `ACCNPE01 callees` **>2 min → 0.16 s**, `digest` **>10 s → 3.3 s**,
|
||||
`context` **>10 s → 3.1 s`; `WGEAGB0S callees` unchanged (7). The underlying `CONTAINS` cycles
|
||||
(parser artefacts) still exist, but both the finalize and the read consumers are now bounded — item 75
|
||||
no longer has a practical impact.
|
||||
|
||||
- [ ] **73. A `NONE`/`ANY` branch is reported under its enclosing guard alone — the condition is a
|
||||
negation no guard chain can express** (found 2026-07-17 while fixing item 72; **item 72 does not fix
|
||||
|
||||
Reference in New Issue
Block a user