fix(semantic): key memory_suppressions by (content_hash, scope) (#106) - #144
Merged
Merged
Conversation
…, scope) Baseline for the autoresearch loop: 2 failing tests proving a tenant's forget must not suppress re-extraction for other tenants, a tenant's remember must not lift another tenant's suppression, and rows for the same hash in two scopes must coexist. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZykd17PEpTdhxGXqLVNKw
Root cause: memory_suppressions had content_hash as its sole primary key and every reader matched on the hash alone, so one tenant's forget overwrote and silenced every tenant's re-extraction of the same sentence, and any tenant's remember lifted it. - schema: suppressions_scope_v1 rebuilds the table with PRIMARY KEY (content_hash, scope), keeping rows (NULL scope -> global); fresh DBs get the composite key directly. SCHEMA_VERSION 5 -> 6, additive (older builds still run). - forget.ts: filterSuppressedFacts(db, facts, scope) matches scope IN (?, 'global'); clearSuppression(db, content, scope) and restoreMemory delete only their scope's row. - daemon.ts dropSuppressed passes getConversationScope; remember.ts passes the write scope. - tests: red tests go green; rebuild path covered; update rollback test derives the newest checkpoint from SCHEMA_MIGRATIONS instead of hardcoding forget_v1/4->5. - docs: db README/SPEC, semantic SPEC, CHANGELOG. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZykd17PEpTdhxGXqLVNKw
…ets in CHANGELOG Conflict was only CHANGELOG.md (Unreleased/Fixed: #87, #109, #108 on main, #106 here). All four kept. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xo3z3uv6caDhR8zjrv5QF
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.
Fixes #106.
Summary
memory_suppressionswas keyed oncontent_hashalone and every reader matched on the hash alone, so one tenant'sforgetsuppressed re-extraction of the same sentence for every tenant, and any tenant'srememberlifted every tenant's suppression.src/_core/db/schema.ts): new checkpointsuppressions_scope_v1rebuildsmemory_suppressionswithPRIMARY KEY (content_hash, scope)in one transaction, keeping every row (NULLscope →global). Idempotent viaPRAGMA table_info. Fresh databases get the composite key directly frommigrateForget.SCHEMA_VERSION5 → 6; additive (an older build'sINSERT OR REPLACE/WHERE content_hash = ?still run), so not inBREAKING_MIGRATIONS.src/semantic/forget.ts):filterSuppressedFacts(db, facts, scope)matchesscope IN (?, 'global')— a global suppression is the only one that crosses tenants.clearSuppression(db, content, scope)and therestoreMemorydelete matchscope = ?only.dropSuppressedinsrc/dream/daemon.tspassesgetConversationScope(db, conversationId); bothclearSuppressioncalls insrc/interfaces/shared/remember.tspass the write scope.src/_core/db/README.md/SPEC.mdREQ-6,src/semantic/SPEC.mdREQ-10,CHANGELOG.md.Tests
tests/semantic/forget.test.ts: tenant A's forget suppresses only A; B's forget of the same sentence coexists (2 rows); B's remember lifts only B; a global forget reaches every tenant; restore removes only its own scope's row.tests/migration/forget-schema.test.ts: composite-key rows coexist; a 0.4.0-shapedcontent_hash-only table is rebuilt with rows kept,NULL→global, idempotent re-run, checkpoint recorded.tests/interfaces/cli/update.test.ts: rollback test now derives the newest checkpoint fromSCHEMA_MIGRATIONSinstead of hardcodingforget_v1/4 -> 5.npm run lintclean;npm run test:run1691 passed, 0 failed.🤖 Generated with Claude Code
https://claude.ai/code/session_01UZykd17PEpTdhxGXqLVNKw