Skip to content

fix(recall): recall_hierarchical reads the domain remember writes (#721, slice 3) - #914

Merged
github-actions[bot] merged 1 commit into
mainfrom
fix/721-hierarchical-domain-scope
Nov 6, 2026
Merged

github-actions[bot] merged 1 commit into
mainfrom
fix/721-hierarchical-domain-scope

Conversation

@cdeust

@cdeust cdeust commented Nov 6, 2026 •

Copy link
Copy Markdown
Owner

Summary

remember (#877) and recall (#901) resolve an explicit domain with resolve_explicit_domain (an exact repository name or ADR-0647 family member maps to its canonical, any other name is kept, lowercased). recall_hierarchical still passed the raw argument to get_memories_for_domain, so recall_hierarchical(domain="cortex-cowork") built its hierarchy over a scope no write ever used (remember stores under cortex), and domain="SciFact" missed scifact. The public handler now wraps _scoped_by_explicit_domain, which resolves the domain and calls the unchanged _handler_impl (same pattern as recall). A blank domain resolves to "", which the bounded-scope contract (ADR-0045 R3) treats as no domain, so it raises the existing ValidationError. The caller's dict is copied, never changed; the small-set fallback to flat recall receives the already-resolved domain (resolving again is idempotent).

Refs #721 (slice 3; does not close the issue).

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Test plan

  • New tests_py/handlers/test_recall_hierarchical_domain_scope.py (8 tests, real repository registry under tmp_path, fake store recording the domain read): 7 fail without the fix, 8 pass with it. They catch a mutation that drops the resolution, passes the raw name, or reuses the substring-guessing resolve_domain.
  • pytest tests_py/handlers/test_recall_hierarchical_domain_scope.py tests_py/handlers/test_recall_hierarchical_bounded.py tests_py/handlers/test_recall_domain_scope.py tests_py/handlers/test_tool_schema_parity.py tests_py/handlers/test_tool_description_drift_guard.py tests_py/shared/test_explicit_domain_identity.py → 51 passed (PostgreSQL-gated tests skip).
  • ruff check / ruff format --check on the changed files: clean. scripts/check_craftsmanship.py: OK. scripts/check_doc_claims.py: exit 0.
  • Not verified: full suite, PostgreSQL tests (no PostgreSQL in this session), the ADR-1122 mutation gate and benchmarks. The environment was synced with --no-install-package torch because download-r2.pytorch.org is refused by the session proxy.

Audit notes

  • No separate review lens ran in this session; CI's review job will.
  • Remaining slices of fix: avoid silently remapping explicit domain identities through substring collisions #721: (1) directory > domain priority in remember while the schema calls domain an override: owner decision; (2) other readers taking a domain (e.g. unified_search, drill_down, narrative) do not resolve it yet, each needs checking whether its domain is a memory scope or a profile domain; (3) document aliases and the migration of rows already written under a collided domain.

Coding-standards compliance

  • Layer direction preserved (handler imports shared).
  • Each constant-free change; new function 4 lines of body.

Breaking changes

None for named domains that already matched a stored scope. A whitespace-only domain now raises ValidationError (no scope) instead of reading the scope " ".

Reviewer checklist

  • CHANGELOG.md updated under Fixed.
  • No secrets / credentials / PII in the diff.
  • CI passes on the latest commit (pending).

https://claude.ai/code/session_018kyZYrLVRR1neS4ZBBFr1D


Generated by Claude Code

…, slice 3)

recall_hierarchical passed an explicit domain unresolved to
get_memories_for_domain, while remember and recall resolve it with
resolve_explicit_domain. recall_hierarchical(domain="cortex-cowork") read a
scope no write used (remember stores it under "cortex"). The public handler
now resolves the domain in _scoped_by_explicit_domain before _handler_impl.

Refs #721

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYrLVRR1neS4ZBBFr1D
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-06T16:31:49.301834Z b803e88 PR opened
🔒 Security Review ✅ Completed 2026-11-06T16:34:57.435575Z b803e88 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@claude

claude Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Claude finished @cdeust's task in 1m 31s —— View job


PR #914 review (1 of 3)

None of the five reviews found a Blocker. The review comment lists three notes that need no action before merge: one weak test assertion, three paths with no test, and a CHANGELOG wording that is only true when remember's directory doesn't resolve to a known repo.

Not run: I couldn't run pytest or scripts/check_craftsmanship.py here, because running Python needed approval this session didn't have. The review comes from reading the code. The craftsmanship gate passes because the file's existing size violation is already in the baseline.

Verdict: approve

@claude

claude Bot commented Nov 6, 2026

Copy link
Copy Markdown

Review 1/3 — PR #914 (recall_hierarchical domain scope, #721 slice 3)

I ran five reviews: code quality, performance, tests, documentation accuracy and security. None found a Blocker. I traced the following by hand:

  • _scoped_by_explicit_domain (mcp_server/handlers/recall_hierarchical.py:318-335) copies the arguments and resolves domain with resolve_explicit_domain. Only then does get_memories_for_domain run. This matches recall.py:693-711.
  • A blank domain resolves to "". With no memory_ids, that raises the existing ADR-0045 R3 ValidationError, so the call never falls through to an unbounded query.
  • The small-set fallback passes the resolved domain to flat_recall, which resolves it again. That is harmless because resolution is idempotent: a canonical name maps to itself.
  • Layering is respected (handlers → shared). The file is over the 300-line cap, but it is already listed in .craftsmanship-baseline.json:3666, so the gate does not flag it.
  • Security: the lookup is an exact registry match, so it cannot widen read scope beyond what recall already allows. The domain reaches SQL as a bound parameter.
  • Not run: I could not execute pytest or the craftsmanship script in this session, so all of the above comes from reading the code.

Notes, no action before merge

  • test_substring_collision_is_not_remapped (tests_py/handlers/test_recall_hierarchical_domain_scope.py:72) only checks != "harness-comparison", so it would also pass on "" or on the raw input. An == check would make it a real test.
  • These paths have no test: the flat-recall fallback (recall_hierarchical.py:278) receiving the resolved domain, a blank domain sent with memory_ids (the call should go down the ids path and not raise), and a missing or None domain left as given.
  • In CHANGELOG.md:91, "remember stores it under cortex" is only true when directory doesn't resolve to a known repo, because remember.py:78-83 gives the cwd priority. That precedence is slice 1 of the issue.

Verdict: approve

cdeust commented Nov 6, 2026

Copy link
Copy Markdown
Owner Author

Réponse à la revue 1/3 (verdict : approve, aucun bloquant). Conformément à ADR-1119, qui réserve les pushs aux bloquants, je ne pousse rien pour ces trois notes. Elles sont exactes et partiront avec le prochain push de code sur cette branche, ou avec la tranche suivante de #721 :

  • test_substring_collision_is_not_remapped passera à une égalité : == "scifact-comparison".
  • Tests à ajouter : la bascule vers flat_recall reçoit le domaine résolu ; un domaine blanc accompagné de memory_ids passe par le chemin des ids sans lever d'erreur ; un domaine absent ou None est laissé tel quel.
  • L'entrée CHANGELOG sera précisée : « stocké sous cortex » n'est vrai que si directory ne désigne pas un dépôt connu, puisque directory est prioritaire (tranche 1 de fix: avoid silently remapping explicit domain identities through substring collisions #721, décision du propriétaire).

Generated by Claude Code

@github-actions
github-actions Bot merged commit ca94c81 into main Nov 6, 2026
18 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.

2 participants