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
154 lines
4.9 KiB
Go
154 lines
4.9 KiB
Go
package analysis
|
|
|
|
import (
|
|
"math"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Content-addressed cache keying for seeded random walks.
|
|
//
|
|
// A Personalized-PageRank result over a seed set depends on the
|
|
// reachable subgraph. Recomputing it on every query is wasteful when
|
|
// the graph — or the part of it the walk touches — has not changed.
|
|
// These helpers derive a per-package Merkle root for the snapshot and a
|
|
// content-addressed cache key for a walk, so the MCP server's walk cache
|
|
// (see internal/mcp/ppr_cache.go) can serve an unchanged walk instantly
|
|
// and only recompute when a package the walk depends on actually
|
|
// changed — the incremental-RWR property a whole-graph version number
|
|
// (NodeCount/EdgeCount) cannot provide.
|
|
|
|
const (
|
|
fnvOffset64 uint64 = 14695981039346656037
|
|
fnvPrime64 uint64 = 1099511628211
|
|
)
|
|
|
|
// fnvStr folds a string into a running FNV-1a hash.
|
|
func fnvStr(h uint64, s string) uint64 {
|
|
for i := 0; i < len(s); i++ {
|
|
h ^= uint64(s[i])
|
|
h *= fnvPrime64
|
|
}
|
|
return h
|
|
}
|
|
|
|
// fnvU64 folds a uint64 into a running FNV-1a hash.
|
|
func fnvU64(h, v uint64) uint64 {
|
|
for i := 0; i < 8; i++ {
|
|
h ^= (v >> (8 * i)) & 0xff
|
|
h *= fnvPrime64
|
|
}
|
|
return h
|
|
}
|
|
|
|
// packageOfID returns the package directory for a node ID — the
|
|
// directory of the file-path portion (everything before "::"). For
|
|
// "gortex/internal/mcp/server.go::NewServer" that is
|
|
// "gortex/internal/mcp". A path with no slash maps to "" (repo root).
|
|
// This is the granularity at which the walk cache is invalidated.
|
|
func packageOfID(id string) string {
|
|
path := id
|
|
if i := strings.Index(path, "::"); i >= 0 {
|
|
path = path[:i]
|
|
}
|
|
if i := strings.LastIndexByte(path, '/'); i >= 0 {
|
|
return path[:i]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// computePackageRoots builds the per-package content roots from the CSR.
|
|
// For each node it folds the node's stable ID plus its out-edges
|
|
// (neighbour string IDs + weights) into the node's content hash, then
|
|
// sums those hashes per package. Summation is commutative, so the root
|
|
// is independent of node iteration order; it uses string IDs (not dense
|
|
// indices), so a package's root is invariant to index shifts caused by
|
|
// edits in OTHER packages — the property that makes the cache truly
|
|
// incremental.
|
|
func computePackageRoots(ids []string, offsets []int32, neighbors []int32, weights []float64) map[string]uint64 {
|
|
roots := make(map[string]uint64, len(ids)/8+1)
|
|
for i := 0; i < len(ids); i++ {
|
|
h := fnvStr(fnvOffset64, ids[i])
|
|
h ^= 0 // separator marker folded below
|
|
h = fnvU64(h, 0x1f)
|
|
start, end := offsets[i], offsets[i+1]
|
|
for k := start; k < end; k++ {
|
|
h = fnvStr(h, ids[neighbors[k]])
|
|
h = fnvU64(h, math.Float64bits(weights[k]))
|
|
}
|
|
roots[packageOfID(ids[i])] += h
|
|
}
|
|
return roots
|
|
}
|
|
|
|
// WalkCacheKey derives a content-addressed cache key for a seeded walk.
|
|
// The key folds: the resolved (in-snapshot) seed IDs, the restart
|
|
// probability, and the per-package roots of the packages the walk
|
|
// depends on — the seed packages plus their 1-hop out-neighbour
|
|
// packages. Two walks with the same seeds and restart over graph states
|
|
// whose relevant packages are byte-for-byte identical produce the same
|
|
// key (cache hit); a change to any depended-on package changes that
|
|
// package's root and therefore the key (cache miss → recompute).
|
|
//
|
|
// Returns "" when the snapshot has no package roots (cache disabled) or
|
|
// no seed resolves to a snapshot node — the caller then computes
|
|
// uncached. The resolved-seed set matches exactly what
|
|
// PersonalizedPageRank walks, so the key and the result stay coherent.
|
|
func (a *AdjacencySnapshot) WalkCacheKey(seeds []string, restart float64) string {
|
|
if a == nil || len(a.pkgRoots) == 0 || len(seeds) == 0 {
|
|
return ""
|
|
}
|
|
if restart <= 0 || restart >= 1 {
|
|
restart = pprDefaultRestart
|
|
}
|
|
|
|
resolved := make([]string, 0, len(seeds))
|
|
seen := make(map[int]bool, len(seeds))
|
|
pkgSet := make(map[string]struct{}, len(seeds)*4)
|
|
for _, s := range seeds {
|
|
i, ok := a.index[s]
|
|
if !ok || seen[i] {
|
|
continue
|
|
}
|
|
seen[i] = true
|
|
resolved = append(resolved, s)
|
|
pkgSet[packageOfID(s)] = struct{}{}
|
|
// 1-hop forward neighbours: their packages also influence the
|
|
// walk's first step, so fold them into the dependency set.
|
|
for k := a.offsets[i]; k < a.offsets[i+1]; k++ {
|
|
pkgSet[packageOfID(a.ids[a.neighbors[k]])] = struct{}{}
|
|
}
|
|
}
|
|
if len(resolved) == 0 {
|
|
return ""
|
|
}
|
|
sort.Strings(resolved)
|
|
pkgs := make([]string, 0, len(pkgSet))
|
|
for p := range pkgSet {
|
|
pkgs = append(pkgs, p)
|
|
}
|
|
sort.Strings(pkgs)
|
|
|
|
h := fnvStr(fnvOffset64, "ppr\x00")
|
|
for _, s := range resolved {
|
|
h = fnvStr(h, s)
|
|
h = fnvU64(h, 0x1f)
|
|
}
|
|
h = fnvU64(h, math.Float64bits(restart))
|
|
for _, p := range pkgs {
|
|
h = fnvStr(h, p)
|
|
h = fnvU64(h, a.pkgRoots[p])
|
|
}
|
|
return strconv.FormatUint(h, 16)
|
|
}
|
|
|
|
// PackageRootCount returns the number of distinct packages with a
|
|
// content root. Exposed for diagnostics / tests.
|
|
func (a *AdjacencySnapshot) PackageRootCount() int {
|
|
if a == nil {
|
|
return 0
|
|
}
|
|
return len(a.pkgRoots)
|
|
}
|