chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user