fix(scan): symlink, unsupported-language and unparsed-file gaps are owned by what they name

This extends dbcb230 to the remaining graph-integrity gaps. In the independent
evaluation these gaps were repository-wide:
- alerta: one symlink, `alerta/app.wsgi`, which is not even source, made 261 of 261
  questions unresolved.
- wger and Signal K: a language no frontend reads did the same.
- YesWiki: unparsed files did the same.

Now:
- the partition build records the paths a symlink boundary skipped (a directory
  ends in `/`) and the languages no frontend reads;
- `files_unparsed` carries its full paths;
- the scan demotes only questions about an owned path or under an owned directory,
  and only questions in an owned language.
Every gap stays in the report as a boundary. A gap that names nothing keeps its
partition-wide or repository-wide reach. Tier-0 pruning stays off, as before.

The tests that pinned repository-wide reach now assert ownership. The one about a
vendor exclusion masking an integrity failure keeps its intent, using a census gap
that names no file.

Retune after protocol-v1; exploratory only.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
Marius Ciepluch
2026-09-25 14:30:36 +02:00
co-authored by Claude Opus 5.5
parent 949977c7a6
commit 65dcb46b5e
4 changed files with 96 additions and 31 deletions
+12
View File
@@ -51,6 +51,10 @@ class PartitionGraph:
self.path_languages: dict[str, str] = {}
self.partitions: tuple[PartitionCoverage, ...] = ()
self.boundaries: tuple[str, ...] = ()
# What each boundary is ABOUT, where that is known: the paths a symlink boundary skipped (a directory
# ends in `/`) and the languages no frontend reads. A boundary absent from both reaches everything.
self.boundary_paths: dict[str, tuple[str, ...]] = {}
self.boundary_languages: dict[str, tuple[str, ...]] = {}
self.unparsed: tuple[str, ...] = ()
self.cpg_path = Path("partitioned-cpg")
@@ -167,6 +171,8 @@ def build_partitions(
graph = PartitionGraph(budget)
paths: dict[str, list[Path]] = {}
boundaries: set[str] = set()
owned_paths: dict[str, set[str]] = {}
owned_languages: dict[str, set[str]] = {}
excluded: set[str] = set()
facts = layout_facts()
root_fd = os.open(root, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
@@ -192,6 +198,8 @@ def build_partitions(
if path.is_symlink() or is_vendored(relative + "/", facts):
excluded.add(relative + "/")
boundaries.add("symlink_context_unresolved" if path.is_symlink() else "vendor_semantics_unresolved")
if path.is_symlink():
owned_paths.setdefault("symlink_context_unresolved", set()).add(relative + "/")
dirs.remove(name)
for name in files:
if budget is not None and time.monotonic() >= budget.deadline_monotonic:
@@ -206,6 +214,7 @@ def build_partitions(
if path.is_symlink() or not path.is_file():
excluded.add(relative)
boundaries.add("symlink_context_unresolved")
owned_paths.setdefault("symlink_context_unresolved", set()).add(relative)
continue
language = LANGUAGE_BY_EXTENSION.get(path.suffix.lower(), "unknown")
if language == "unknown":
@@ -234,6 +243,7 @@ def build_partitions(
if frontend is None:
records.append(PartitionCoverage(language, None, relative_paths, 0, "unsupported", ("frontend_unsupported",)))
boundaries.add("frontend_unsupported")
owned_languages.setdefault("frontend_unsupported", set()).add(language)
continue
scratch = Path(tempfile.mkdtemp(prefix="ousast-input-"))
graph.roots[language] = scratch
@@ -283,6 +293,8 @@ def build_partitions(
for record in records
)
graph.boundaries = tuple(sorted(boundaries))
graph.boundary_paths = {reason: tuple(sorted(paths)) for reason, paths in owned_paths.items()}
graph.boundary_languages = {reason: tuple(sorted(languages)) for reason, languages in owned_languages.items()}
return graph
except BaseException:
graph.cleanup()