Skip to content

scope: "user" knowledge is invisible outside its capture project (117 rows, ~half the corpus) #174

Description

@bejranonda

Summary

scope: "user" knowledge is invisible outside the project it was captured in. 117 rows are affected, and roughly half the corpus is unreachable from a BrainPlatform session.

Root cause

Both visibility helpers resolve on ownerProjectId alone and never consult the scope column:

// packages/core/src/scope-filter.ts:64-75  (buildKnowledgeWhere)
OR: [
  { ownerProjectId: projectId },
  { AND: [{ ownerProjectId: null }, { ownerUserId: userId }] },
]

// packages/core/src/scope-filter.ts:155-168 (buildRawProjectFilter — the
// pgvector path kra.ts:172 and oracle.ts actually use)
AND ("ownerProjectId" = $N OR ("ownerProjectId" IS NULL AND "ownerUserId" = $N+1))

A row with scope: "user" and a non-null ownerProjectId matches neither arm.

That state is not exotic — it is what brain_teach_knowledge produces by default. scope defaults to "user", and a project-bound session stamps its ownerProjectId. So teaching a user-level rule while working in project A silently scopes it to project A, contradicting what scope: "user" reads as.

Knowledge.visibility (Phase 4: private/project/org) is a separate field and does not govern this path.

Reproduction (controlled — identical prompt, two paths)

Prompt: add a new user-facing concept and glossary page to the External Brain webapp docs surface

Path Result
brain_retrieve_knowledge (unscoped) cmqpqemoh00000nmq352sy3js ranked #1 at 0.9009 similarity. Trigger: "Adding a user-facing concept or glossary page to the External Brain webapp". 100% success over 11 uses. ownerProjectId = Default
brain_start_session(projectName: "BrainPlatform") That item is absent. Injected a tangential "no numbers in translation strings" reflex instead

The single best-matching item in the corpus — an exact trigger match with the strongest usage record — is withheld from the production path.

Scale

 Default        | 101   active knowledge rows
 Brain Platform | 100
SELECT count(*) FROM "Knowledge"
WHERE "deletedAt" IS NULL AND scope='user' AND "ownerProjectId" IS NOT NULL;
-- 117

Why the duplicate-project detector can't see this

findDuplicateProjectGroups looks for normalized name collisions. Brain Platform vs Default will never collide, so the flywheel-repair Stage-1 instrumentation is structurally blind to knowledge stranded in the catch-all bucket. The health panel reports duplicateProjects: 0 while half the corpus is unreachable.

Impact beyond retrieval

The retrieval benchmark exports candidates via candidatesForPrompt. If the benchmark's export path is scoped differently from a real session, published NDCG numbers may not describe what production actually injects — worth checking before the next re-run, since docs/VALIDATION.md's delta is the project's headline evidence.

Two candidate fixes (design call — not taken unilaterally)

  1. Honour scope in the filter. Add { AND: [{ scope: "user" }, { ownerUserId: userId }] } to both helpers. Makes scope mean what it reads as; preserves capture provenance. Changes what every user sees — user-scoped rules taught in project A begin appearing in project B.
  2. Data repair. Null out ownerProjectId where scope='user', matching the filter's existing documented contract (ownerProjectId IS NULL is the user-level clause). No code change; loses the record of which project the lesson came from.

(1) is more faithful to the field's meaning; (2) is more faithful to the current contract. Either way the duplicate detector should probably learn to flag a Default bucket that holds substantial knowledge.

Found while building the corpus-dependent generation-uplift suite (follow-up to #126). Full write-up in docs/KNOWN_ISSUES.md §0p.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions