Skip to content

refactor: lift DocAnchor to leaf module to break residual doc_linker ↔ store cycle (#501) - #502

Merged
robotrocketscience merged 2 commits into
mainfrom
fix/issue-501-doc-linker-cycle
May 8, 2026
Merged

refactor: lift DocAnchor to leaf module to break residual doc_linker ↔ store cycle (#501)#502
robotrocketscience merged 2 commits into
mainfrom
fix/issue-501-doc-linker-cycle

Conversation

@robotrocketscience

@robotrocketscience robotrocketscience commented May 8, 2026

Copy link
Copy Markdown
Owner

Phase 1 of #501 — close the residual doc_linker ↔ store cycle that #494's merge introduced and that PR #500's review flagged. Mirrors the leaf-module pattern PR #500 used for np_pattern / classification_core / db_paths.

Change

aelfrice.doc_linker_types is a new leaf module containing DocAnchor, ANCHOR_INGEST / ANCHOR_MANUAL / ANCHOR_DERIVED, and ANCHOR_TYPES. It imports nothing from the rest of aelfrice — keep it that way.

aelfrice.doc_linker re-exports the lifted symbols so external callers (from aelfrice.doc_linker import DocAnchor) keep working. The if TYPE_CHECKING: from aelfrice.store import MemoryStore block is gone — from __future__ import annotations already makes the "MemoryStore" forward-ref strings runtime no-ops.

aelfrice.store's three function-local imports in link_belief_to_document, get_doc_anchors, and get_doc_anchors_batch now read from doc_linker_types instead of doc_linker.

Static analysis

Cycle-finder from #499's body, run on the PR head:

  • No-TYPE_CHECKING-filter (strictest, what my AST walk does): 0 cycles ≤ length 4. Pre-PR (post-refactor: break 14 module-import cycles flagged by CodeQL (#499) #500): 1 cycle (doc_linker ↔ store).
  • TYPE_CHECKING-filter (industry-standard, what CodeQL does): 0 cycles ≤ length 4. Pre-PR: 0 (CodeQL never flagged this one — TYPE_CHECKING blocks aren't edges to it).

Both detectors agree at 0 now. Acceptance row for "cycle-finder reports 0 cycles ≤ length 4" met.

Test plan

  • Doc-linker tests: 18/18 pass (tests/test_doc_linker.py + tests/test_retrieve_doc_anchors.py).
  • Full suite: 2948 passed / 48 skipped — no regressions vs the post-refactor: break 14 module-import cycles flagged by CodeQL (#499) #500 baseline.
  • Discretion grep clean.
  • Both commits signed (G/G).
  • CodeQL note count for module-import-cycle — verify on the next PR run picks up the cleanup.

Refs

Closes #501.

Summary by Sourcery

Extract document linker anchor types into a leaf module to break the remaining import cycle between the doc linker and store while preserving the external API.

Enhancements:

  • Introduce a new doc_linker_types leaf module that holds DocAnchor and anchor-related constants without importing from the rest of the package.
  • Update doc_linker to re-export DocAnchor and anchor constants from the new leaf module and to declare an explicit public API.
  • Retarget store’s local imports for DocAnchor and anchor constants to the new doc_linker_types module to avoid a doc_linker ↔ store dependency cycle.

@sourcery-ai

sourcery-ai Bot commented May 8, 2026

Copy link
Copy Markdown

Reviewer's Guide

Refactors doc-linker types and constants into a new leaf module to break the residual import cycle between aelfrice.doc_linker and aelfrice.store, while preserving the public doc_linker API and updating store to depend only on the new leaf module.

Class diagram for DocAnchor and MemoryStore methods using it

classDiagram
    class DocAnchor {
        <<dataclass>>
        +belief_id: str
        +doc_uri: str
        +anchor_type: str
        +position_hint: str~None
        +created_at: float
    }

    class MemoryStore {
        +link_belief_to_document(belief_id: str, doc_uri: str, anchor_type: str, position_hint: str, created_at: float) DocAnchor
        +get_doc_anchors(belief_id: str) list~DocAnchor~
        +get_doc_anchors_batch(belief_ids: list~str~) dict~str, list~DocAnchor~~
    }

    MemoryStore ..> DocAnchor : returns
Loading

File-Level Changes

Change Details Files
Introduce a new leaf module for doc-linker types/constants and move DocAnchor plus anchor-type constants into it to eliminate the doc_linker ↔ store cycle.
  • Create aelfrice.doc_linker_types as a leaf module that imports only stdlib, defines ANCHOR_INGEST/ANCHOR_MANUAL/ANCHOR_DERIVED, aggregates them into ANCHOR_TYPES, and defines the frozen DocAnchor dataclass with its existing schema and docstring.
  • Ensure the new module does not import from other aelfrice modules and documents its role mirroring the existing leaf-module pattern used for np_pattern/classification_core/db_paths.
src/aelfrice/doc_linker_types.py
Update aelfrice.doc_linker to re-export the moved symbols and define a stable public surface while dropping the now-unnecessary TYPE_CHECKING import of MemoryStore.
  • Remove in-module definitions of DocAnchor and anchor-type constants, along with TYPE_CHECKING and the MemoryStore import block, relying on from future import annotations for forward references instead.
  • Import ANCHOR_INGEST/ANCHOR_MANUAL/ANCHOR_DERIVED/ANCHOR_TYPES and DocAnchor from aelfrice.doc_linker_types and expose them via all alongside existing public functions so existing imports from aelfrice.doc_linker continue to work.
src/aelfrice/doc_linker.py
Point store’s runtime imports at the new leaf module so it depends only on doc_linker_types instead of doc_linker.
  • Change the function-local imports in link_belief_to_document, get_doc_anchors, and get_doc_anchors_batch to import ANCHOR_TYPES and DocAnchor from aelfrice.doc_linker_types instead of aelfrice.doc_linker.
  • Preserve the existing call sites and type annotations, keeping imports local to avoid broader coupling while relying on the leaf module to prevent an import cycle.
src/aelfrice/store.py

Assessment against linked issues

Issue Objective Addressed Explanation
#501 Eliminate the import cycle between aelfrice.doc_linker and aelfrice.store so that the cycle-finder reports 0 cycles of length ≤ 4.
#501 Refactor DocAnchor and related anchor constants into a leaf module that imports nothing from the rest of aelfrice, with store importing from this leaf module and doc_linker remaining the logic host.
#501 Maintain existing behavior and public API (including from aelfrice.doc_linker import DocAnchor) with no regressions in the test suite.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented May 8, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@robotrocketscience has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 55 minutes and 35 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 75a1e9d8-bac3-48c5-9c28-59f1c3bf7660

📥 Commits

Reviewing files that changed from the base of the PR and between 6a1f04e and 414f56c.

📒 Files selected for processing (3)
  • src/aelfrice/doc_linker.py
  • src/aelfrice/doc_linker_types.py
  • src/aelfrice/store.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-501-doc-linker-cycle

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@robotrocketscience robotrocketscience added author-Toug PR coordination mutex attn:review Needs review (PR open, awaiting reviewer) labels May 8, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • Since doc_linker_types is a leaf with no dependencies, you can likely move the function-local imports in store to module scope (importing DocAnchor / ANCHOR_TYPES once) to avoid repeated imports and make the dependency structure clearer.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Since `doc_linker_types` is a leaf with no dependencies, you can likely move the function-local imports in `store` to module scope (importing `DocAnchor` / `ANCHOR_TYPES` once) to avoid repeated imports and make the dependency structure clearer.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@github-actions

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown

This PR is now behind main. Rebase locally so your commit signatures stay intact:

git fetch origin && git checkout 'fix/issue-501-doc-linker-cycle' && git rebase origin/main
# resolve conflicts if any, then
git push --force-with-lease

Auto-rebase was removed because the bot has no signing key; rebasing as the bot strips author signatures and the required_signatures rule on main then blocks the merge. See #341.

Move DocAnchor, ANCHOR_INGEST/MANUAL/DERIVED, ANCHOR_TYPES out of
doc_linker into the new leaf module aelfrice.doc_linker_types. Drop
the if-TYPE_CHECKING import of MemoryStore in doc_linker; the function
signatures keep the "MemoryStore" forward-ref string, which `from
__future__ import annotations` already makes a no-op at runtime.

doc_linker re-exports the lifted symbols so external callers
(`from aelfrice.doc_linker import DocAnchor`) keep working.

Mirrors the leaf-module pattern PR #500 used for np_pattern,
classification_core, and db_paths.

No behavior change. Sets up the store.py edge swap in the next commit.
The three function-local imports in MemoryStore.link_belief_to_document,
get_doc_anchors, and get_doc_anchors_batch now read from the leaf
module aelfrice.doc_linker_types instead of aelfrice.doc_linker.
Closes the residual edge that #494's merge introduced; the static
cycle-finder reports 0 cycles ≤ length 4 across src/aelfrice now,
matching the post-#500 invariant.

Drops the inline comment about TYPE_CHECKING-only avoidance — the leaf
module makes that workaround unnecessary; the comment's premise no
longer holds.

No behavior change. 2948 tests pass.
@robotrocketscience
robotrocketscience force-pushed the fix/issue-501-doc-linker-cycle branch from 448c9c7 to 414f56c Compare May 8, 2026 20:41
@robotrocketscience robotrocketscience removed the attn:merge-conflict PR branch needs rebase label May 8, 2026
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Gylf:2026-05-08T20:57:14Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Kulili:2026-05-08T20:57:26Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Kulili:2026-05-08T20:57:31Z]

@robotrocketscience
robotrocketscience merged commit 414f56c into main May 8, 2026
20 checks passed
@robotrocketscience
robotrocketscience deleted the fix/issue-501-doc-linker-cycle branch May 8, 2026 20:57
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Gylf:2026-05-08T20:57:57Z]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

attn:review Needs review (PR open, awaiting reviewer) author-Toug PR coordination mutex

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore: break residual doc_linker ↔ store cycle (post-#494 merge)

1 participant