mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 19:59:26 +00:00
feat(contributor-scan): name the function a question cannot locate (M1b 12.3)
After per-family context, every remaining unresolved question reported context_scope_empty, all in three files. The cause is not a missing producer: app/routes/index.js requires seven handler modules and defines none of them, so a question naming handleContributionsUpdate there names a function that file genuinely does not hold, and the empty scope is the correct answer. Reporting that as "scope empty" hid which of two different things had happened. A file the frontend produced no methods for is one problem; a question naming a function defined in another module is another, and it is the common one. Taint and configuration now separate them, reporting context_function_unresolved with the name in the first case and context_scope_empty only when the file has no methods at all. The driver no longer stacks its generic gap on top of a query that already explained itself, and the new gap is question-owned so it cannot block another question's comparison. Completion is deliberately unchanged. The tempting fix is to widen such a question to its whole file, which would raise the number by answering a question nobody asked; a question whose function the graph does not hold stays unresolved, with the name now recoverable from the question that asked it. Group 12 is complete: admission no longer rejects genuine findings on a mis-extracted operation symbol, both families that could never complete their change context now can, and the remaining unresolved questions say precisely why. 1,406 tests passed, nine skipped. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
co-authored by
Claude Opus 5
parent
3e4ab8d219
commit
b95a371b7e
@@ -81,8 +81,14 @@
|
||||
}
|
||||
val settingIds = calls.map(_.method.id).toSet
|
||||
val located = scoped.map(m => location(m, if (settingIds.contains(m.id)) "setting" else "sibling"))
|
||||
// A question naming a function this file does not define is not the same as a file with no
|
||||
// methods at all, and saying so is the difference between a reader knowing what to look at
|
||||
// and reading "scope empty".
|
||||
val boundaries =
|
||||
if (scoped.isEmpty) List(ujson.Obj("kind" -> "context_boundary", "reason" -> "context_scope_empty:contributor-scan"))
|
||||
if (function.nonEmpty && scoped.isEmpty)
|
||||
List(ujson.Obj("kind" -> "context_boundary", "reason" -> "context_function_unresolved:contributor-scan", "function" -> function))
|
||||
else if (scoped.isEmpty)
|
||||
List(ujson.Obj("kind" -> "context_boundary", "reason" -> "context_scope_empty:contributor-scan"))
|
||||
else Nil
|
||||
ujson.Obj("kind" -> "context_summary") :: (located ++ boundaries)
|
||||
}
|
||||
|
||||
@@ -732,9 +732,19 @@
|
||||
destinations.isEmpty || destinations.exists(d => d.isExternal || !inScope(d))
|
||||
}
|
||||
})
|
||||
val boundaries = if (scoped.isEmpty) List(ujson.Obj("kind" -> "context_boundary",
|
||||
"reason" -> "context_scope_empty:contributor-scan")) else if (missing) List(ujson.Obj("kind" -> "context_boundary",
|
||||
"reason" -> "dynamic_external_or_depth_context_unresolved:contributor-scan")) else Nil
|
||||
// An empty scope has two different causes and they are not interchangeable. A file the
|
||||
// frontend produced no methods for is one thing; a question naming a function this file does
|
||||
// not define is another, and it is the common one: a route-registration module names handlers
|
||||
// that live in other files, so the graph rightly holds no such method here. Reporting both as
|
||||
// "scope empty" hides which is which, and only the second names something a reader can act on.
|
||||
val boundaries =
|
||||
if (function.nonEmpty && scoped.isEmpty)
|
||||
List(ujson.Obj("kind" -> "context_boundary", "reason" -> "context_function_unresolved:contributor-scan", "function" -> function))
|
||||
else if (scoped.isEmpty)
|
||||
List(ujson.Obj("kind" -> "context_boundary", "reason" -> "context_scope_empty:contributor-scan"))
|
||||
else if (missing)
|
||||
List(ujson.Obj("kind" -> "context_boundary", "reason" -> "dynamic_external_or_depth_context_unresolved:contributor-scan"))
|
||||
else Nil
|
||||
ujson.Obj("kind" -> "context_summary") :: (methods ++ fields ++ hooks ++ guards ++ boundaries)
|
||||
}
|
||||
return summary :: (perSink ++ contextRows)
|
||||
|
||||
@@ -226,7 +226,10 @@ def affected_context(
|
||||
local_gaps = []
|
||||
if not any(r.get("kind") == "context_summary" for r in rows):
|
||||
local_gaps.append("context_projection_unavailable:contributor-scan")
|
||||
elif not any(r.get("kind") == "context_method" for r in rows):
|
||||
elif not any(r.get("kind") == "context_method" for r in rows) and not any(r.get("kind") == "context_boundary" for r in rows):
|
||||
# Only when the query offered no account of its own. A query that already said why its
|
||||
# scope is empty -- a named function this file does not define, most often -- has given
|
||||
# the specific reason, and adding a generic one on top buries it.
|
||||
local_gaps.append("context_scope_empty:contributor-scan")
|
||||
recorded_only: list[str] = []
|
||||
for row in rows:
|
||||
|
||||
@@ -320,6 +320,7 @@ def _context_boundaries(scan: ModelScanResult, *questions: QuestionIdentity) ->
|
||||
owned_prefixes = (
|
||||
"context_projection_unavailable:contributor-scan:",
|
||||
"context_scope_empty:contributor-scan:",
|
||||
"context_function_unresolved:contributor-scan:",
|
||||
"dynamic_external_or_depth_context_unresolved:contributor-scan:",
|
||||
)
|
||||
retained = set(questions)
|
||||
|
||||
Reference in New Issue
Block a user