refactor(8.0): graph analytics contract — intent names, not algorithm names

The public brain.graph.* surface and the GraphAccelerationProvider seam must name
operations by INTENT, not by the algorithm that happens to implement them — so a
plugin can swap algorithms without the contract lying. The JS fallback computes
communities via connected-components and rank via PageRank, but a native provider
(cor) is free to use Louvain/Leiden for communities and personalized-PageRank /
eigenvector-centrality for rank: same intent, different algorithm, same name.

Renamed the (not-yet-implemented) Phase-C analytics methods + their option/result
types on GraphAccelerationProvider:
- pageRank -> rank            (PageRankOptions -> RankOptions; dropped the
                               pagerank-internal damping/maxIterations/tolerance
                               knobs from the contract — each impl tunes itself)
- connectedComponents -> communities   (ComponentsOptions -> CommunitiesOptions,
                               mode:'weak'|'strong' -> directed?:boolean;
                               GraphComponents -> GraphCommunities, componentIds/
                               Count -> communityIds/Count)
- shortestPath -> path        (ShortestPathOptions -> PathOptions; weight -> by:'hops'|'weight')
- neighborhoodSample -> sample (NeighborhoodSampleOptions -> SampleOptions)
- topByDegree -> mostConnected (TopByDegreeOptions -> MostConnectedOptions)

traverse / edgesForNode / graphCursorOpen/Next/Close + GraphScores/GraphPath/
OpaqueIdSet/Subgraph are UNCHANGED — everything cor has already built is untouched;
this is a pure pre-implementation rename (cor coordinated). JSDoc reworded to state
the algorithm is the provider's choice. index.ts exports + the native-seam test
mock updated. tsc/unit 1517/integration 599 green.
This commit is contained in:
David Snelling 2026-06-22 09:54:01 -07:00
parent 96d9c0b92c
commit f3e69110f0
3 changed files with 75 additions and 59 deletions

View file

@ -194,13 +194,13 @@ export type {
GraphCursorOptions, GraphCursorOptions,
GraphCursorChunk, GraphCursorChunk,
GraphScores, GraphScores,
GraphComponents, GraphCommunities,
GraphPath, GraphPath,
PageRankOptions, RankOptions,
ComponentsOptions, CommunitiesOptions,
ShortestPathOptions, PathOptions,
NeighborhoodSampleOptions, SampleOptions,
TopByDegreeOptions MostConnectedOptions
} from './plugin.js' } from './plugin.js'
// Export embedding functionality // Export embedding functionality

View file

@ -400,69 +400,77 @@ export interface GraphCursorChunk {
done: boolean done: boolean
} }
/** Per-node scalar scores (pageRank, topByDegree); `scores[i]` belongs to `nodeInts[i]`. */ /** Per-node scores returned by `rank` / `mostConnected`; `scores[i]` belongs to `nodeInts[i]` (descending). */
export interface GraphScores { export interface GraphScores {
nodeInts: BigInt64Array nodeInts: BigInt64Array
scores: Float64Array scores: Float64Array
} }
/** Connected-component labelling; `componentIds[i]` is the component of `nodeInts[i]`. */ /** Community/cluster labelling; `communityIds[i]` is the group of `nodeInts[i]`. */
export interface GraphComponents { export interface GraphCommunities {
nodeInts: BigInt64Array nodeInts: BigInt64Array
componentIds: Uint32Array communityIds: Uint32Array
componentCount: number communityCount: number
} }
/** A shortest path: the node sequence + the edges between them (or `null` if unreachable). */ /** A path between two nodes — the node sequence + the edges between them (or `null` if unreachable). */
export interface GraphPath { export interface GraphPath {
nodeInts: BigInt64Array nodeInts: BigInt64Array
edgeVerbInts: BigInt64Array edgeVerbInts: BigInt64Array
/** Sum of edge weights for a weighted path; hop count for an unweighted one. */ /** Cost of the path: number of hops, or summed edge weight when `by: 'weight'`. */
cost: number cost: number
} }
export interface PageRankOptions { /**
/** Damping factor (default 0.85). */ * Options for `rank` (importance / influence ranking). INTENT-level only the
damping?: number * ranking ALGORITHM is the provider's choice (the JS fallback uses PageRank; a
/** Max power-iterations (default ~20). */ * native provider may use personalized PageRank, eigenvector centrality, etc.),
maxIterations?: number * so no algorithm-tuning knobs are exposed here.
/** Convergence tolerance. */ */
tolerance?: number export interface RankOptions {
excludeVisibility?: string[] /** Return only the top-K nodes by score (default: all). */
/** Return only the top-K by score (default: all). */
topK?: number topK?: number
} /** Visibility tiers to EXCLUDE from the ranked set (default `['internal','system']`). */
export interface ComponentsOptions {
/** `'weak'` ignores direction (default); `'strong'` honors it. */
mode?: 'weak' | 'strong'
excludeVisibility?: string[] excludeVisibility?: string[]
} }
export interface ShortestPathOptions { /** Options for `communities` (grouping related nodes). The grouping algorithm is the provider's choice. */
export interface CommunitiesOptions {
/** Treat the graph as directed when grouping (default `false` — direction-agnostic). */
directed?: boolean
excludeVisibility?: string[]
}
/** Options for `path` (best route between two nodes). */
export interface PathOptions {
direction?: GraphTraversalDirection direction?: GraphTraversalDirection
/** `'edge'` = weighted by edge weight; `'hops'` = unweighted BFS (default `'hops'`). */ /** Optimize for fewest `'hops'` (default) or least summed edge `'weight'`. */
weight?: 'edge' | 'hops' by?: 'hops' | 'weight'
/** Restrict the route to these verb-type indices (TypeIdx). */
verbTypes?: number[] verbTypes?: number[]
excludeVisibility?: string[] excludeVisibility?: string[]
/** Abandon the search past this many hops. */ /** Abandon the search past this many hops. */
maxDepth?: number maxDepth?: number
} }
export interface NeighborhoodSampleOptions { /** Options for `sample` (a representative neighborhood sample for dense-graph viz). */
export interface SampleOptions {
/** Max hop distance from any seed. */
depth: number depth: number
direction?: GraphTraversalDirection direction?: GraphTraversalDirection
/** Max neighbors to sample per node (random, seeded for reproducibility). */ /** Max neighbors kept per node (random, seeded for reproducibility). */
fanout: number fanout: number
/** RNG seed so a given (seeds, opts, seed) yields a stable sample for viz. */ /** RNG seed so a given (seeds, opts, seed) yields a stable sample. */
seed?: number seed?: number
excludeVisibility?: string[] excludeVisibility?: string[]
maxNodes?: number maxNodes?: number
} }
export interface TopByDegreeOptions { /** Options for `mostConnected` (the most-connected nodes). */
direction?: GraphTraversalDirection export interface MostConnectedOptions {
/** Return the top-K most-connected nodes. */
topK: number topK: number
direction?: GraphTraversalDirection
excludeVisibility?: string[] excludeVisibility?: string[]
} }
@ -538,45 +546,53 @@ export interface GraphAccelerationProvider {
graphCursorClose(handle: GraphCursorHandle): Promise<void> graphCursorClose(handle: GraphCursorHandle): Promise<void>
/** /**
* @description PageRank over the visible graph (hidden tiers excluded by default, * @description Rank nodes by influence/importance over the VISIBLE graph (hidden
* so importance reflects the public view). * tiers excluded by default, so the ranking reflects the public view). The
* @param options - Damping, iterations, tolerance, visibility, optional top-K. * ranking ALGORITHM is the provider's choice the JS fallback uses PageRank; a
* native provider may use personalized PageRank, eigenvector centrality, etc. The
* intent ("which nodes matter most") is the contract, not the algorithm.
* @param options - Top-K + visibility (intent-level; no algorithm tuning).
* @param generation - Optional as-of generation. * @param generation - Optional as-of generation.
* @returns Per-node scores. * @returns Per-node scores, descending.
*/ */
pageRank(options: PageRankOptions, generation?: bigint): Promise<GraphScores> rank(options: RankOptions, generation?: bigint): Promise<GraphScores>
/** /**
* @description Connected components over the visible graph. * @description Group related nodes into communities over the VISIBLE graph. The
* @param options - Weak/strong, visibility. * grouping ALGORITHM is the provider's choice the JS fallback uses connected
* components; a native provider may use community detection (e.g. Louvain/Leiden).
* @param options - Directed-grouping toggle + visibility.
* @param generation - Optional as-of generation. * @param generation - Optional as-of generation.
* @returns Per-node component labels + component count. * @returns Per-node community labels + community count.
*/ */
connectedComponents(options: ComponentsOptions, generation?: bigint): Promise<GraphComponents> communities(options: CommunitiesOptions, generation?: bigint): Promise<GraphCommunities>
/** /**
* @description Shortest path between two nodes (weighted by edge weight or hop count). * @description Find the best path between two nodes fewest hops by default, or
* least summed edge weight with `by: 'weight'`. The pathfinding ALGORITHM is the
* provider's choice (JS fallback: BFS / Dijkstra).
* @param fromInt - Start node int. * @param fromInt - Start node int.
* @param toInt - End node int. * @param toInt - End node int.
* @param options - Direction, weighting, filters, max depth. * @param options - Direction, cost basis (`by`), verb-type filter, max depth, visibility.
* @param generation - Optional as-of generation. * @param generation - Optional as-of generation.
* @returns The path, or `null` if `toInt` is unreachable from `fromInt`. * @returns The path, or `null` if `toInt` is unreachable from `fromInt`.
*/ */
shortestPath(fromInt: bigint, toInt: bigint, options: ShortestPathOptions, generation?: bigint): Promise<GraphPath | null> path(fromInt: bigint, toInt: bigint, options: PathOptions, generation?: bigint): Promise<GraphPath | null>
/** /**
* @description Bounded, randomly-sampled neighborhood expansion (capped fan-out per * @description A bounded, representative SAMPLE of the neighborhood around the seeds
* node) a representative subgraph for dense-graph viz. Seeded for reproducibility. * (capped fan-out per node) for dense-graph viz. Seeded for reproducibility.
* @param seeds - Start nodes (ints or an {@link OpaqueIdSet}). * @param seeds - Start nodes (ints or an {@link OpaqueIdSet}).
* @param options - Depth, fan-out, RNG seed, visibility, node cap. * @param options - Depth, fan-out, RNG seed, visibility, node cap.
* @param generation - Optional as-of generation. * @param generation - Optional as-of generation.
* @returns The sampled {@link Subgraph}. * @returns The sampled {@link Subgraph}.
*/ */
neighborhoodSample(seeds: bigint[] | OpaqueIdSet, options: NeighborhoodSampleOptions, generation?: bigint): Promise<Subgraph> sample(seeds: bigint[] | OpaqueIdSet, options: SampleOptions, generation?: bigint): Promise<Subgraph>
/** /**
* @description The top-K nodes by degree over the visible graph. * @description The top-K most-connected nodes over the VISIBLE graph. The
* @param options - Direction, K, visibility. * connectivity metric is the provider's choice (JS fallback: edge-count degree).
* @param options - Top-K, direction, visibility.
* @param generation - Optional as-of generation. * @param generation - Optional as-of generation.
* @returns Per-node degree scores (descending). * @returns Per-node connectivity scores, descending.
*/ */
topByDegree(options: TopByDegreeOptions, generation?: bigint): Promise<GraphScores> mostConnected(options: MostConnectedOptions, generation?: bigint): Promise<GraphScores>
} }
/** /**

View file

@ -45,11 +45,11 @@ function makeMockAccel() {
graphCursorClose: async () => { graphCursorClose: async () => {
calls.cursorClose++ calls.cursorClose++
}, },
pageRank: async () => empty, rank: async () => empty,
connectedComponents: async () => ({ nodeInts: new BigInt64Array(0), componentIds: new Uint32Array(0), componentCount: 0 }), communities: async () => ({ nodeInts: new BigInt64Array(0), communityIds: new Uint32Array(0), communityCount: 0 }),
shortestPath: async () => null, path: async () => null,
neighborhoodSample: async () => state.traverseResult, sample: async () => state.traverseResult,
topByDegree: async () => empty mostConnected: async () => empty
} }
return { provider, state } return { provider, state }
} }