fix(memory): gate person_pending facts out of memory_lookup FTS search (#53)#153
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
This PR addresses a high-severity kid-safety issue in dotty-pi-ext where the free-text memory lookup (FTS) could surface unreviewed person_pending:<id> facts (e.g., about minors) into live turns. It adds an explicit namespace gate to the FTS query and locks the behavior in with a hermetic regression test.
Changes:
- Exclude
person_pending:%rows from the FTS-backedsearchMemoriesquery inbrain_db.ts. - Mirror the same exclusion into the Python oracle (
tests/oracle.py) so parity/spec remains aligned. - Add a new hermetic regression test (
tests/memory_gate.test.ts) and wire it intonpm test.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| dotty-pi-ext/src/lib/brain_db.ts | Adds an explicit NOT LIKE 'person_pending:%' gate to FTS search results to prevent leaking unreviewed per-person facts. |
| dotty-pi-ext/tests/oracle.py | Updates the oracle/spec query to include the same kid-safety namespace exclusion as the TS implementation. |
| dotty-pi-ext/tests/memory_gate.test.ts | Adds a hermetic regression test that builds a real FTS5 schema and asserts pending facts are excluded from FTS search. |
| dotty-pi-ext/package.json | Wires the new test:memgate script into the main npm test chain. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
#53) Audit finding (HIGH, kid-safety, confirmed 3/3). fetchPersonMemories is namespace-scoped to approved `person:<id>` and deliberately never returns `person_pending:<id>` (unreviewed facts about a minor) — but searchMemories ran a bare `WHERE memories_fts MATCH ?` over ALL rows, so a pending fact ("kiddo is allergic to peanuts") was returned to a live turn whenever the query phrase- matched it. Add `AND m.namespace NOT LIKE 'person_pending:%'` to the FTS query in brain_db.ts, and mirror it into the test oracle (tests/oracle.py) — the original bridge.py search fn it was copied from was removed in #111, so this is now the standalone spec; the filter is the one intentional change, applied to both. Tests: new hermetic tests/memory_gate.test.ts (wired into `npm test`) — builds a throwaway brain.db with the real FTS5 schema, one approved + one pending row both matching the query, asserts only the approved row returns. No snapshot needed. Deploying needs a dotty-pi image rebuild (TS), like the pi-ext auth PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
35a1c1d to
127d7e2
Compare
This was referenced Jun 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Audit finding (HIGH, kid-safety, confirmed 3/3). Off
main.Bug
fetchPersonMemoriesis namespace-scoped to approvedperson:<id>and deliberately never returnsperson_pending:<id>(unreviewed facts about a minor) — butsearchMemoriesran a bareWHERE memories_fts MATCH ?over all rows, so a pending fact ("kiddo is allergic to peanuts") was returned to a live turn whenever the query phrase-matched it.Fix
Add
AND m.namespace NOT LIKE 'person_pending:%'to the FTS query inbrain_db.ts, and mirror it into the test oracle (tests/oracle.py). The originalbridge.pysearch fn the oracle was copied from was removed in #111, so the oracle is now the standalone spec; the kid-safety filter is the one intentional change, applied to both together.Tests
New hermetic
tests/memory_gate.test.ts(wired intonpm test): builds a throwawaybrain.dbwith the real FTS5 schema, one approved + one pending row that both phrase-match the query, asserts only the approved row returns. NoDOTTY_BRAIN_DB_SNAPSHOTneeded (unlike the existing parity test).🤖 Generated with Claude Code