Skip to content

fix(brainbar): resolve brain_entity case-insensitively before substring fallback (Fixes #663) - #1632

Merged
EtanHey merged 1 commit into
mainfrom
claude/cloud-worker-auth-setup-wf11kp
Nov 5, 2026
Merged

EtanHey merged 1 commit into
mainfrom
claude/cloud-worker-auth-setup-wf11kp

Conversation

@EtanHey

@EtanHey EtanHey commented Nov 4, 2026 •

Copy link
Copy Markdown
Owner

TASK MARKER: cloud-credit-20261104-brainlayer-more-issue-663

Fixes #663

Change and evidence

  • Source branch / start SHA: origin/main @ 2f87dc7e9646db5fea46235720efb5bacc2bb50a (fetched and checked inside the cloud container before any edits)
  • Branch: claude/cloud-worker-auth-setup-wf11kp
  • Actual HEAD: a0727854cda412786384297d044b0b70832f76e4
  • Scoped files (2): brain-bar/Sources/BrainBar/BrainDatabase.swift (+32/−58), brain-bar/Tests/BrainBarTests/DatabaseTests.swift (+60)

Root cause

brain_entity is served by BrainBar: MCPRouter.handleBrainEntity calls BrainDatabase.lookupEntity. On main, that function runs two tiers:

  1. WHERE name = ?, which is case-sensitive.
  2. WHERE name LIKE '%q%' LIMIT 1, with no ORDER BY.

A query in a different case (nimbus for Nimbus) misses tier 1. Tier 2 then returns whichever row the table scan reaches first that contains the query, for example an older Old Nimbus Notes row with no relations. That is the "empty card" in the issue. The Python library path (kg_repo.resolve_entity) already matches names case-insensitively, but agents can't reach it because BrainBar doesn't serve it.

Fix (smallest root correction)

Adds a middle tier between the two: WHERE name = ? COLLATE NOCASE ... ORDER BY importance DESC, name COLLATE BINARY ASC, id ASC LIMIT 1. The three tiers now share one prepare/bind/step loop instead of copy-pasted blocks.

  • An exact spelling still wins first (tier 1 is unchanged).
  • The returned name is the stored canonical spelling, never the query text.
  • Ambiguous case variants (e.g. Nimbus and NIMBUS, which UNIQUE(entity_type, name) allows) resolve deterministically: highest importance, then binary name order, then id.
  • The entityType filter applies to the new tier.
  • The substring fallback is unchanged.
  • No writes: lookup never creates entities. No migration, no data change.

Tests added (Swift, synthetic names only)

  • testEntityLookupResolvesDifferentCaseToCanonicalEntity: Nimbus / nimbus / NIMBUS / nImBuS all resolve ent-nimbus with its canonical name and relation, even though a substring decoy was inserted first. The substring fallback still finds old nimbus.
  • testEntityLookupExactCasePrecedesAndCaseVariantsAreDeterministic: exact spelling beats higher importance. Without an exact spelling, importance decides; on a tie, binary name order decides, and the answer is stable across repeated calls. The type filter is respected.
  • testEntityLookupMissingDifferentCaseDoesNotCreateEntity: nimbuz / NIMBUZ return nil and the entity count stays at 1.

Exact commands and observed output

1. Before/after/mutant SQL replay (RUN, Linux, SQLite 3.53.1, in-memory synthetic fixture). A throwaway script (not committed) extracts the lookupEntity SQL from the Swift source text: git show origin/main:brain-bar/Sources/BrainBar/BrainDatabase.swift for before, the working tree for after. It runs the tier sequence exactly as lookupEntity binds it:

python3 -I replay_663.py before.swift brain-bar/Sources/BrainBar/BrainDatabase.swift; echo exit=$?
[BEFORE origin/main] 8/12 pass  FAIL: ["'nimbus'->Nimbus", "'NIMBUS'->Nimbus", "'nimbus' -> higher importance", 'tie -> binary name order']
    lookup('nimbus') on base fixture -> ('ent-notes', 'Old Nimbus Notes')
[AFTER fix] 12/12 pass
    lookup('nimbus') on base fixture -> ('ent-nimbus', 'Nimbus')
[MUTANT drop COLLATE NOCASE] 8/12 pass  FAIL: [same 4 as before]
[MUTANT drop ORDER BY] 10/12 pass  FAIL: ["'nimbus' -> higher importance", 'tie -> binary name order']
exit=0

This reproduces the bug: on main, nimbus returns the unrelated Old Nimbus Notes card. The fix passes every check, and both mutants are caught.

2. Swift XCTest: RUN in CI on macOS, not locally. This Linux container has no Swift toolchain, and BrainBar needs macOS 14 and AppKit. The job swift (macos-15) on head a0727854 succeeded. Its log shows:

Test Suite 'DatabaseTests' passed at 2026-11-04 16:45:08.159.
	 Executed 98 tests, with 0 failures (0 unexpected) in 11.604 (11.626) seconds
Test Suite 'All tests' passed at 2026-11-04 16:46:33.467.
	 Executed 1541 tests, with 23 tests skipped and 0 failures (0 unexpected) in 289.781 (290.098) seconds

DatabaseTests has 95 func test… on origin/main and 98 on this head (grep -c "^ func test"), so the 98 executed tests include the 3 new ones. The log lists per-suite counts, not test names. The 23 skipped are elsewhere: DatabaseTests reports 0 skipped.

3. Python tests that read the Swift sources: RUN, Linux.

python3 -m pytest tests/test_lexical_defense.py tests/test_atomic_brick_schema.py tests/test_chunk_write_contract.py -q -p no:cacheprovider
35 passed in 30.34s

4. CI on head a0727854: every check run completed green or skipped by path filter. That includes lint, test (3.11/3.12/3.13), swift (macos-15), installed wheel imports, crash sweeps, ratchet table and CodeQL Analyze (swift). DeepSource Swift reports issues outside this diff only, with no inline threads; its Security, Reliability, Complexity and Hygiene grades are all A.

Limitations / risks

  • COLLATE NOCASE folds ASCII only. That matches SQLite LIKE and the existing LOWER() in lookupEntityFacts, but É vs é is still case-sensitive. Hebrew has no case, so it is unaffected.
  • Behaviour change: a query that previously fell to the substring tier now matches a same-name-different-case entity first. This is the intended fix.
  • The substring tier still has no ORDER BY, so substring-only hits remain scan-order dependent. I left it alone to keep this diff scoped; it could be a follow-up.
  • The issue's second half (no HappyCampr entity: entity-creation lag) is not addressed. Per the dispatch, this PR does not create entities, does not fabricate profile or employer facts, and does not touch real data.
  • normalize_case_variants on the Python path was not changed.
  • UNRUN: a live check against a running BrainBar or a copy of the real DB. That needs a Mac. Because this change is read-only, AGENTS.md's pre-merge DB-copy rule for changes that touch stored data does not apply, but the lead may still want a smoke test on a DB copy.
  • The branch is behind main, with no conflict. I did not update it, to avoid re-running CI for nothing; the lead can update it before merge.
  • No production DB, BrainBar socket, account or secret was accessed. The proof uses only synthetic in-memory SQLite.

Cost / time

About 55 minutes wall clock, mostly waiting on CI. No cost meter is visible in this session. No paid API calls, subagents or LLM inference.

Crash sweep / feature batch

Swift-only change; no sweep-triggering paths are touched (crash sweeps passed).

Loop-record: implementer=anthropic/claude-code-cloud/high; reviewer=none; rounds=0; defects=0

@Orc handoff (for import into collab.md)

@brainlayer-lead — #663 draft PR ready for your review. I did not merge it or mark it ready.

  • Task: cloud-credit-20261104-brainlayer-more-issue-663. Start 2f87dc7e, HEAD a0727854. Branch claude/cloud-worker-auth-setup-wf11kp. PR fix(brainbar): resolve brain_entity case-insensitively before substring fallback (Fixes #663) #1632.
  • What: BrainBar lookupEntity gains an ASCII case-insensitive exact tier with deterministic ordering, between the exact and LIKE tiers. There are 3 new XCTests.
  • Proof RUN:
    • SQL replay extracted from the Swift source: before 8/12, after 12/12, both mutants caught.
    • macOS CI swift (macos-15): DatabaseTests 98/98 (95 existing + 3 new), all 1541 Swift tests with 0 failures.
    • Python Swift-source contract tests: 35 passed.
    • Full CI on this head: green.
  • Proof UNRUN: a live BrainBar or real-DB-copy smoke test, which needs a Mac.
  • Open item: the issue's HappyCampr entity-creation lag is out of scope. It needs a separate decision.
  • Asks: review the tie-break order (importance → binary name → id), and decide whether to order the LIKE tier in a follow-up.

🤖 Generated with Claude Code

https://claude.ai/code/session_01L3NoZEq6gr2o3cqCyd6ZTs

Note

Fix BrainDatabase.lookupEntity to resolve brain_entity case-insensitively

  • Replaces the separate exact-name and substring queries in BrainDatabase.swift with an ordered three-tier lookup: exact name, case-insensitive exact name, then substring match
  • Case-insensitive matches order by descending importance, then binary name, then ID; an exact spelling still takes precedence
  • Adds tests in DatabaseTests.swift covering case-variant resolution, deterministic tie-breaking, the entity-type filter, and that unmatched queries do not create entities
  • Behavioral Change: lookups that previously missed on capitalization now return a canonical entity instead of falling through to substring matching

Macroscope summarized 51c8bb3.

@coderabbitai

coderabbitai Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 26 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 6401b1c and 51c8bb3.

Files selected for processing: 2

  • brain-bar/Sources/BrainBar/BrainDatabase.swift
  • brain-bar/Tests/BrainBarTests/DatabaseTests.swift
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@deepsource-io

deepsource-io Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 6401b1c...51c8bb3 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

Important

Some issues found as part of this review are outside of the diff in this pull request and aren't shown in the inline review comments due to GitHub's API limitations. You can see those issues on the DeepSource dashboard.

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
Python Nov 5, 2026 7:03p.m. Review ↗
Swift Nov 5, 2026 7:03p.m. Review ↗
JavaScript Nov 5, 2026 7:03p.m. Review ↗
Shell Nov 5, 2026 7:03p.m. Review ↗
Secrets Nov 5, 2026 7:03p.m. Review ↗

Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

BrainLayer ratchet

Every Value below was measured by this run. A row this machine cannot measure says n/a — <reason> instead of a number; baselines in Notes name their own machine, method and date and were not measured here.

Hosted success gives no verdict on mapped bytes, search p50/p95, idle CPU, hotlane backlog CPU, or fallback replay debt.

Row Status Value (measured by this run) Method Notes
commit provenance 🟢 GREEN measured 51c8bb32690b == PR head · checkout 51c8bb32690b commit graph + live PR head · in-process · runner Which commit this whole table is about. On a pull_request event the checkout is GitHub's synthetic merge ref, whose sha is not on the PR — #759's table printed 13fa724278bf while that PR's head was 4632f979 — so this row names the PR-head parent instead, the sha a reviewer can actually see. The comparison sha is read live from repos/{owner}/{repo}/pulls/{n} when the table is collected, not taken from the event payload, because the payload cannot know the run has been overtaken. Residual window, stated rather than papered over: a push landing between that read and the comment being posted is not caught here — the run for that push refreshes the table.
baseline attestation 🟢 GREEN baseline f421d1a7c5e6 matches the main attestation (run 42089901905 · main 6401b1c6e4e4 · 2026-11-05T19:02:02Z) main attestation artifact via Actions API · in-process · runner What every comparison is measured AGAINST, and who says so. The baseline fields of tests/fixtures/sprint_gate/corpus.json (queries, latency_baseline_ms, thresholds) are compared to the ratchet-attestation artifact of the latest successful push or (no-input) workflow_dispatch run of ratchet-attest.yml on main, fetched through the Actions API — a PR run cannot write to another run's artifacts. A field that differs is RED unless that main run measured the new value. The calibrated socket collector can license p50/p95; every absent measured path stays locked, so missing collection never passes as permission for a hand edit. Boundary: the comparator is this PR's checkout of ci_ratchet_table.py, diff-reviewable, not tamper-proof.
provenance 🟢 GREEN stamped 51c8bb32690b == HEAD, tree clean wheel stamp · in-process · runner Sha half of #749 keg-mode provenance: a keg built from this wheel can answer __build_sha__. The helper-age and served-process predicates need a running BrainBar and are measured only by scripts/sprint_gate.py on an installed Mac. The sha here is the checkout's — the explicit PR head in ratchet.yml — because that is what publish.yml stamps at release time; the PR-head sha this table describes is the one in commit provenance above.
fallback replay debt ⚪ n/a n/a — no fallback queue on this machine: the pending memories live in ~/Gits/*/docs.local/decisions, and docs.local/ is gitignored, so a runner checkout has no copy of them to count docs.local walk · machine with the fallback queue local-only; not enforced by hosted CI. intended_brain_store: true with no chunk_id means a memory reached disk and never reached the DB, so it answers no brain_search. Budget: 0. Any pending or unparseable file is a finding, never a band -- 122 of these sat from 2026-06-28 to 2026-09-05 because nothing counted them where a reader would look. Measured by walking the tree, so it is only ever measured on a machine that HAS the tree.
pre-push: delete-only push runs no suite 🟢 GREEN 0 suite starts · exit 0 real checkout hook · delete-only stdin · sentinel suite runner Bug SHA: 44ecbf5ba6f1ada3b965797559eee90c94b45595 (RED replay, 2026-10-06). Fix: #1084; measured at 51c8bb32690b0643179b00265b5060a7034691c3. Ceiling: 0 suite starts; hook must exit 0 and announce the delete-only skip.
mapped bytes ⚪ n/a n/a — no BrainBar daemon at /tmp/brainbar.sock: this row needs the daemon, its hybrid helper and the indexed corpus running together, and no GitHub-hosted runner has them (macOS included) — only a self-hosted Darwin/arm64 runner on an installed Mac would socket · installed Mac local-only; not enforced by hosted CI. Baseline 26.2 GB — installed Mac, socket, 2026-09-03, after R2 drained 15,070 → 0. Up from 16.8 GB because the drain left more vectors mapped under the same cap: the change is the drain, not a leak. Not measured by this run.
search p50/p95 ⚪ n/a n/a — no BrainBar daemon at /tmp/brainbar.sock: this row needs the daemon, its hybrid helper and the indexed corpus running together, and no GitHub-hosted runner has them (macOS included) — only a self-hosted Darwin/arm64 runner on an installed Mac would socket · installed Mac local-only; not enforced by hosted CI. Margin p50: margin unmeasured — 0 of the 5 attested green main runs it needs; no verdict is rendered from fewer. Margin p95: margin unmeasured — 0 of the 5 attested green main runs it needs; no verdict is rendered from fewer. Calibrated on MacBook-Pro.local at 2026-09-01T08:42:22Z under active_sprint_load (tests/fixtures/sprint_gate/corpus.json). Not measured by this run.
idle CPU ⚪ n/a n/a — no BrainBar daemon at /tmp/brainbar.sock: this row needs the daemon, its hybrid helper and the indexed corpus running together, and no GitHub-hosted runner has them (macOS included) — only a self-hosted Darwin/arm64 runner on an installed Mac would ps sampling · installed Mac local-only; not enforced by hosted CI. Ceiling: average CPU < 30% over a 60 s window (resource_budget in scripts/sprint_gate.py), ratified and kept as a hard budget. Margin daemon: margin unmeasured — 0 of the 5 attested green main runs it needs; no verdict is rendered from fewer. Margin helper: margin unmeasured — 0 of the 5 attested green main runs it needs; no verdict is rendered from fewer. Margin watcher: margin unmeasured — 0 of the 5 attested green main runs it needs; no verdict is rendered from fewer. Needs the BrainBar daemon, helper and watcher actually running. Not measured by this run.
hotlane backlog CPU ⚪ n/a n/a — live hotlane CPU evidence requires an installed Mac CPU/wall for backlog-encoding cycles · rolling 600 s · installed Mac local-only; not enforced by hosted CI. Default budget 60% of one core; BRAINLAYER_HOTLANE_CPU_BUDGET_PERCENT overrides. At least 30s of active backlog cycles required; yielded time excluded. Measures installed code.
signature_valid ⚪ n/a n/a — the macOS signature-parity job is trigger-gated and did not run on this PR: it touches no release or signing path (pyproject.toml, scripts/release-*, scripts/brainlayer-version-check.sh, publish.yml, ratchet.yml) and carries no ratchet:signatures label — a GitHub macOS runner bills at ~10× Linux minutes and rebuilds the keg venv from source codesign · installed keg scripts/release-verify-signatures.sh <keg> codesign-verifies every *.so/*.dylib under libexec/venv. The macOS parity job installs the published tap formula (etanhey/layers/brainlayer), so this row measures the release path — formula, published sdist and Homebrew's relocation — and not this PR's tree. Release-time baseline for the same keg on a different machine: 442 valid / 0 invalid — installed Mac (M4 Max), brew --prefix brainlayer 1.5.11, 2026-09-03.
BrainBar renders no enrichment status 🟢 GREEN 19 actual-view captures; active pending-store/replay retained macOS DEBUG binary · AppKit actual views · Vision OCR + icon RGBA comparison · private synthetic state Missing render capability/evidence is RED. Historical data is retained; this is source-build proof, not installed-app proof.
Watcher heartbeat freshness and recovery ⚪ n/a n/a — macOS native probe trigger-gated: no affected path or ratchet:heartbeat label private Python producer -> compiled Swift consumer Preserves genuine silence; independent agent stores cannot revive the watcher. Not a load or installed-UI measurement.
maintenance quiesce: services stay down under a reviving watchdog ⚪ n/a n/a — macOS replay trigger-gated: no affected path or ratchet:quiesce label real launchd · GitHub macOS runner · real maintenance + fleet-watchdog Fix: #1076 (bc6d690f4331387485678d0247e19e14e6ef8d97). Ceiling: 0 revivals; supervisor held, both resumed, no hold marker.
no cloud model call reachable anywhere 🟢 GREEN 520 source files; 460 installed imports; 3 native tests; wheel 39e5a3663fc6; 0 candidate attempts immutable installed wheel (default + dev) + AST source + actual native BrainBar Synthetic private fixtures; SDK absence gated on merged R10b. Exact-head candidate proof, not deployment.

🟢 GREEN measured, within budget · 🔴 RED measured, out of budget — a finding to clear before merge · ⚪ n/a not measurable on this machine, never guessed.

No RED rows.

Measured on Linux/x86_64 · measured 51c8bb32690b · PR head 51c8bb32690b · checkout 51c8bb32690b · run · updated 2026-11-05 19:15:28 UTC

…ng fallback

lookupEntity tried an exact, case-sensitive name match and then an unordered
`LIKE '%q%' LIMIT 1`. A differently-cased query ("nimbus" for "Nimbus") skipped
the real entity and could return whichever row merely contained the query,
which showed up as an empty card.

Add a middle tier: exact name with COLLATE NOCASE, ordered by importance DESC,
binary name, id so ambiguous case variants resolve deterministically. Exact
spelling still wins, the stored canonical name is returned, the entity-type
filter applies, and the substring fallback is unchanged. Lookup never creates
entities.

Fixes #663

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L3NoZEq6gr2o3cqCyd6ZTs
@EtanHey
EtanHey force-pushed the claude/cloud-worker-auth-setup-wf11kp branch from b44d463 to 51c8bb3 Compare November 5, 2026 19:01
@EtanHey
EtanHey merged commit 0fbae9a into main Nov 5, 2026
34 of 39 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

brain_entity lookup is case-sensitive — 'domica' returns empty card while 'Domica' resolves

2 participants