Observed
For a caller whose request frame carries a visible-namespace set beyond local:
list(kind="edge") with the keyset cursor walks edges across all frame-visible namespaces (observed: 11,174 unique edges = 11,173 in local + 1 in the caller's own namespace).
stats() reports edges: 11173 — one namespace only.
stats().edges_by_relation sums to 11,173, so it shares the scalar's single-namespace scope (count_edges_by_relation in crates/khive-db/src/stores/graph.rs pins WHERE namespace = ?1).
So a full list walk and the stats totals cannot be reconciled by a caller with any non-trivial visibility: the two verbs compute over different namespace scopes.
Analysis
The store-layer functions are all namespace-pinned; the divergence is at the runtime layer: edge listing merges pages across the caller's visible namespaces, while the stats path queries a single namespace regardless of frame visibility.
Per the per-request identity model, read surfaces should honor the frame's visibility consistently. Either:
stats() aggregates over the caller's visible namespaces (consistent with list), or
- both accept an explicit
namespace= parameter and default identically.
Option 1 matches the direction the listing surface already took. Whichever is chosen, the scalar totals and edges_by_relation must share one scope, and the contract should be stated in the verb docs.
Acceptance
stats() totals == count of a full list keyset walk under the same caller identity, for entities, edges, and notes.
edges_by_relation sums to the edges scalar under all identities.
- Regression test with two namespaces and an identity-bearing frame.
Observed
For a caller whose request frame carries a visible-namespace set beyond
local:list(kind="edge")with the keyset cursor walks edges across all frame-visible namespaces (observed: 11,174 unique edges = 11,173 inlocal+ 1 in the caller's own namespace).stats()reportsedges: 11173— one namespace only.stats().edges_by_relationsums to 11,173, so it shares the scalar's single-namespace scope (count_edges_by_relationincrates/khive-db/src/stores/graph.rspinsWHERE namespace = ?1).So a full
listwalk and thestatstotals cannot be reconciled by a caller with any non-trivial visibility: the two verbs compute over different namespace scopes.Analysis
The store-layer functions are all namespace-pinned; the divergence is at the runtime layer: edge listing merges pages across the caller's visible namespaces, while the stats path queries a single namespace regardless of frame visibility.
Per the per-request identity model, read surfaces should honor the frame's visibility consistently. Either:
stats()aggregates over the caller's visible namespaces (consistent withlist), ornamespace=parameter and default identically.Option 1 matches the direction the listing surface already took. Whichever is chosen, the scalar totals and
edges_by_relationmust share one scope, and the contract should be stated in the verb docs.Acceptance
stats()totals == count of a fulllistkeyset walk under the same caller identity, for entities, edges, and notes.edges_by_relationsums to theedgesscalar under all identities.