Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

202 lines
6.9 KiB
Go

package graph
import (
"sort"
"strings"
)
// EpistemicBoundary names one unresolved/dynamic-dispatch site that makes a
// traversal count a *floor* rather than an exact number. It is the honest
// answer to "could the real blast radius / reachable set be larger?" — yes,
// because the resolver could not bind this site, so an unknown number of
// callers/callees hide behind it.
//
// It is an attribute of a *traversal*, not a graph element: no new NodeKind or
// EdgeKind is introduced. Boundaries are recorded at exactly the sites a walk
// would otherwise silently drop (an out-edge to an `unresolved::*` target) or
// where dynamic dispatch is structurally possible (the seed implements an
// interface, so callers may invoke it through that interface).
type EpistemicBoundary struct {
SeedID string `json:"seed_id"`
SeedName string `json:"seed_name,omitempty"`
Target string `json:"target,omitempty"`
EdgeKind string `json:"edge_kind,omitempty"`
Reason BoundaryReason `json:"reason"`
Direction string `json:"direction"` // "callers" | "callees"
}
// DynamicBoundary enriches an EpistemicBoundary with the body-level detail an
// agent needs to cross a runtime-dispatch site without a read-spiral: the SITE
// (file:line), the dispatch FORM, the KEY selector, and a candidate-target
// shortlist. Like EpistemicBoundary it is an attribute of a query result, not a
// graph element — it is computed on demand by scanning the disconnected
// symbol's body, never persisted.
type DynamicBoundary struct {
Site string `json:"site"` // file:line of the dispatch
Form string `json:"form"` // reflection | computed_member | event_bus
Key string `json:"key"` // the selector expression / event name
Candidates []string `json:"candidates,omitempty"`
AgentNamed bool `json:"agent_named,omitempty"`
}
// BoundaryReason classifies why a boundary makes the count a floor. The
// vocabulary aligns with the resolution-outcomes taxonomy's dynamic-dispatch
// concept while staying graph-local (no name-resolution needed to compute it).
type BoundaryReason string
const (
// BoundaryDynamicDispatch: an out call/reference edge whose target the
// resolver left as `unresolved::*` — the callee set could be larger.
BoundaryDynamicDispatch BoundaryReason = "dynamic_dispatch"
// BoundaryInterfaceDispatch: the node implements/overrides an interface
// method, so callers may invoke it through the interface via dispatch that
// is not attributed to this node — the caller set could be larger.
BoundaryInterfaceDispatch BoundaryReason = "interface_dispatch"
// BoundaryExternal: an edge into the `external::` namespace — the chain
// leaves the indexed code. Listed for transparency; not floor-making.
BoundaryExternal BoundaryReason = "external_boundary"
// BoundaryStub: an edge into a stdlib/builtin/module stub. Listed; not
// floor-making (an external stdlib call adds no in-repo callers/callees).
BoundaryStub BoundaryReason = "stub"
)
// maxBoundaries caps the per-result boundary list so a pathological hub cannot
// bloat a response. Mirrors impact.go's maxPerTier.
const maxBoundaries = 50
// ClassifyDroppedTarget classifies an edge target a traversal could not follow.
// ok=false means it is an ordinary in-graph node (follow it normally).
func ClassifyDroppedTarget(targetID string, kind EdgeKind) (BoundaryReason, bool) {
if IsUnresolvedTarget(targetID) {
return BoundaryDynamicDispatch, true
}
if strings.HasPrefix(targetID, "external::") {
return BoundaryExternal, true
}
if IsStub(targetID) {
return BoundaryStub, true
}
return "", false
}
// CalleeBoundaries scans the out-edges of the given nodes for call/reference
// targets a forward (callee-direction) walk could not follow. Each such target
// means the reachable callee set could be larger than what was returned.
func CalleeBoundaries(g Store, nodeIDs []string, limit int) []EpistemicBoundary {
if g == nil {
return nil
}
if limit <= 0 {
limit = maxBoundaries
}
seen := map[string]bool{}
var out []EpistemicBoundary
for _, id := range nodeIDs {
for _, e := range g.GetOutEdges(id) {
if e.Kind != EdgeCalls && e.Kind != EdgeReferences {
continue
}
reason, ok := ClassifyDroppedTarget(e.To, e.Kind)
if !ok {
continue
}
key := id + "\x00" + e.To
if seen[key] {
continue
}
seen[key] = true
out = append(out, EpistemicBoundary{
SeedID: id,
SeedName: nameForID(g, id),
Target: boundaryTargetName(e.To),
EdgeKind: string(e.Kind),
Reason: reason,
Direction: "callees",
})
if len(out) >= limit {
return sortBoundaries(out)
}
}
}
return sortBoundaries(out)
}
// CallerBoundaries flags nodes whose *caller* count is a floor because dynamic
// dispatch into them is structurally possible: each node that implements or
// overrides an interface method may be reached through that interface by
// callers not attributed to it directly. It names the interface so an agent
// can run find_implementations / get_callers on it to widen the picture.
func CallerBoundaries(g Store, nodeIDs []string, limit int) []EpistemicBoundary {
if g == nil {
return nil
}
if limit <= 0 {
limit = maxBoundaries
}
seen := map[string]bool{}
var out []EpistemicBoundary
for _, id := range nodeIDs {
for _, e := range g.GetOutEdges(id) {
if e.Kind != EdgeImplements && e.Kind != EdgeOverrides {
continue
}
key := id + "\x00" + e.To
if seen[key] {
continue
}
seen[key] = true
out = append(out, EpistemicBoundary{
SeedID: id,
SeedName: nameForID(g, id),
Target: boundaryTargetName(e.To),
EdgeKind: string(e.Kind),
Reason: BoundaryInterfaceDispatch,
Direction: "callers",
})
if len(out) >= limit {
return sortBoundaries(out)
}
}
}
return sortBoundaries(out)
}
// LowerBoundCaveat reports whether the boundary set makes the count a genuine
// floor. Only dynamic-dispatch / interface-dispatch boundaries qualify: an
// external/stdlib stub edge is listed for transparency but adds no hidden
// in-repo callers/callees, so by itself it must not raise the flag (otherwise
// nearly every symbol with a stdlib call would be flagged — see the design's
// over-flagging guard).
func LowerBoundCaveat(boundaries []EpistemicBoundary) bool {
for _, b := range boundaries {
if b.Reason == BoundaryDynamicDispatch || b.Reason == BoundaryInterfaceDispatch {
return true
}
}
return false
}
func boundaryTargetName(id string) string {
if IsUnresolvedTarget(id) {
return UnresolvedName(id)
}
return id
}
func nameForID(g Store, id string) string {
if n := g.GetNode(id); n != nil {
return n.Name
}
return ""
}
func sortBoundaries(bs []EpistemicBoundary) []EpistemicBoundary {
sort.SliceStable(bs, func(i, j int) bool {
if bs[i].SeedID != bs[j].SeedID {
return bs[i].SeedID < bs[j].SeedID
}
return bs[i].Target < bs[j].Target
})
return bs
}