LearnNewsExamplesServices
Frontmatter
id11716
titleAudit kb-config daemon readability under GraphService RLS
stateClosed
labels
bugaiarchitecture
assigneesneo-opus-ada
createdAtMay 21, 2026, 1:01 PM
updatedAtMay 21, 2026, 2:01 PM
githubUrlhttps://github.com/neomjs/neo/issues/11716
authorneo-gpt
commentsCount0
parentIssue11628
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMay 21, 2026, 2:01 PM

Audit kb-config daemon readability under GraphService RLS

Closed v13.0.0/archive-v13-0-0-chunk-12 bugaiarchitecture
neo-gpt
neo-gpt commented on May 21, 2026, 1:01 PM

Context

Surfaced during PR #11714 Cycle-2 re-review (review PRR_kwDODSospM8AAAABAnZslw). RA-3 verified that GraphService RLS makes visibility:'team' load-bearing for kb-manifest:<tenantId> nodes read by the offline KB reconciliation daemon. That same verification exposed an adjacent risk for kb-config:<tenantId> nodes written by #11637 and consumed by #11640.

Duplicate sweep before filing: ask_knowledge_base('kb-config visibility team offline reconciliation daemon getTenantConfig RLS follow-up', type='ticket') surfaced historical RLS tickets (#10010/#10011 family) but no KB config daemon-readability ticket; exact rg over resources/content/issues / resources/content/discussions found RLS history but no kb-config follow-up.

The Problem

KnowledgeBaseIngestionService.setTenantConfig() writes kb-config:<tenantId> via GraphService.upsertNode() with tenant config properties and version, but it does not set visibility:'team'.

Verified code path:

  • GraphService.isRlsVisible() exposes a node when userId is absent, matches the requester, sharedEntity is true, or visibility === 'team' (ai/services/memory-core/GraphService.mjs:21-34).
  • GraphService.upsertNode() stamps properties.userId when a request-scoped identity exists and p.userId is absent (ai/services/memory-core/GraphService.mjs:199-237).
  • GraphService.getNodeRecord() resolves the active requester and returns null when isRlsVisible() rejects the node (ai/services/memory-core/GraphService.mjs:623-637).
  • setTenantConfig() writes kb-config:<tenantId> without visibility:'team' (ai/services/knowledge-base/KnowledgeBaseIngestionService.mjs:882-904).
  • KbReconciliationService.fetchTenantConfigVersion() reads that config offline through KnowledgeBaseIngestionService.getTenantConfig() and degrades to version 0 when no graph config is visible (ai/daemons/KbReconciliationService.mjs:220-226, 300-304).

If setTenantConfig() runs under request context, the config node can become owned by that request identity. The offline reconciliation daemon has no request identity, so getTenantConfig() may fail-soft to the default/yaml tier (version: 0). That would silently weaken or disable #11640 config-staleness detection for the affected tenant.

The Architectural Reality

This is not a #11714 merge blocker. PR #11714 correctly marked the new kb-manifest:<tenantId> node as visibility:'team' and documented why. This ticket audits the older sibling config node from #11637 under the same verified RLS model.

The owning substrate is the KB ingestion service / Native Edge Graph RLS boundary, not the reconciliation diff engine. The fix should preserve tenant write authorization (resolveTenantContext) while ensuring daemon-owned offline reads observe the same config state that request-authored writes create.

The Fix

Audit and, if verified, make kb-config:<tenantId> daemon-readable without weakening tenant write gates.

Likely implementation shape:

  1. Add visibility:'team' to the KnowledgeBaseTenantConfig node properties written by setTenantConfig(), mirroring the manifest node rationale from #11714.
  2. Update JSDoc to state the write gate remains resolveTenantContext, while visibility:'team' is the shared-read marker for offline daemons.
  3. Add focused unit coverage proving a request-authored tenant config remains readable by the offline getTenantConfig() / reconciliation path.
  4. If investigation finds setTenantConfig() cannot run under request context in production, document that evidence and close as no-code.

Contract Ledger

Target Surface Source of Authority Proposed Behavior Fallback / Edge Case Docs Evidence
KnowledgeBaseTenantConfig graph node (kb-config:<tenantId>) #11637 + GraphService RLS verified in PR #11714 Cycle-2 Request-authored tenant config nodes must remain readable to offline KB daemons If no request identity stamped the node, current ownerless visibility remains valid JSDoc on setTenantConfig() Unit test with request-context write + offline read
KbReconciliationService.fetchTenantConfigVersion() #11640 config-staleness daemon Offline daemon reads the real tenant config version, not fallback 0, when graph config exists Missing/invisible config still degrades fail-soft to 0 only when truly absent/unreadable Inline test name / PR body Focused daemon or service test proving version visibility

Acceptance Criteria

  • V-B-A confirms whether request-scoped setTenantConfig() currently creates kb-config nodes invisible to offline getTenantConfig().
  • If confirmed, setTenantConfig() marks the config node daemon-readable (likely visibility:'team') without bypassing resolveTenantContext tenant-write protection.
  • JSDoc explains the RLS/readability rationale and distinguishes write authorization from daemon read visibility.
  • Focused unit coverage proves request-authored config remains visible to the offline daemon path.
  • No change to KnowledgeBaseTenantConfig.version increment semantics.

Out of Scope

  • Changing the broader GraphService RLS policy.
  • Changing kb-manifest:<tenantId> behavior from #11714.
  • Reworking tenant config storage architecture from #11637.
  • Altering #11640 reconciliation diff semantics beyond correctly reading the existing config version.

Avoided Traps

Trap Why rejected
Treating this as a #11714 blocker #11714 fixed the manifest node it introduced; the config-node risk is adjacent and pre-existing.
Bypassing RLS in the daemon Would hide a policy mismatch and risk cross-tenant leakage. The safer shape is explicit shared-read metadata on the node that needs daemon visibility.
Removing resolveTenantContext write gate Tenant write authorization and offline read visibility are different concerns.
Broad #10011 RLS refactor The observed risk is narrow and can be tested on the KB config surface.

Related

  • PR #11714kb-manifest:<tenantId> freshness/RLS review that surfaced this audit.
  • #11711 — manifest-orphan reconciliation V1.x increment.
  • #11637 — tenant config storage implementation.
  • #11640 — reconciliation daemon that consumes tenant config version.
  • #11628 — Phase 4 operations/observability epic.
  • #10011 — Native Edge Graph RLS substrate.

Origin Session ID

d13c94dd-e721-4e28-ac9e-4d0b3c0f66de

Handoff Retrieval Hints

  • query_raw_memories({query: 'kb-config visibility team offline reconciliation daemon RLS'})
  • PR #11714 Cycle-2 review PRR_kwDODSospM8AAAABAnZslw
  • Author response IC_kwDODSospM8AAAABDKi56g
  • Code anchors: KnowledgeBaseIngestionService.setTenantConfig, GraphService.isRlsVisible, KbReconciliationService.fetchTenantConfigVersion
tobiu referenced in commit 5626d8e - "fix(ai): mark kb-config node visibility:team for offline daemon reads (#11716) (#11717) on May 21, 2026, 2:01 PM
tobiu closed this issue on May 21, 2026, 2:01 PM