Skip to content

fix(core): unresolve inbound relations on entity delete - #1358

Merged
phernandez merged 1 commit into
mainfrom
fix/1344-unresolve-inbound-relations
Aug 28, 2026
Merged

fix(core): unresolve inbound relations on entity delete#1358
phernandez merged 1 commit into
mainfrom
fix/1344-unresolve-inbound-relations

Conversation

@phernandez

Copy link
Copy Markdown
Member

Supersedes #1352 by @sammywachtel — their commit is cherry-picked here unchanged, authorship and sign-off preserved, so the full CI matrix (Postgres included) runs; fork PRs only get DCO/CLA/CodeQL. Will be rebase-merged so the commit lands on main as-is.

Fixes #1344.

Summary

Deleting an entity took every inbound relation row with it — relation.to_id carried ON DELETE CASCADE and Entity.incoming_relations a matching ORM delete-orphan, one per delete route (bulk-SQL runners vs. session.delete). The source notes still contained the [[wikilink]], so a broken-link report came back clean over a vault full of them, and nothing re-created the rows short of re-indexing the source.

to_id becomes ON DELETE SET NULL and incoming_relations uses passive_deletes=True; from_id keeps CASCADE. Inbound rows land in the unresolved state the indexer already produces (to_id NULL, to_name preserved), and forward-reference resolution re-links them if the target is recreated — matching docs/DOMAIN_MODEL.md's "resolution enriches the statement; it does not replace it". Migration bcdbd5a942ca (SQLite batch mode with a naming convention for the anonymous initial-schema FK; Postgres drop/recreate relation_to_id_fkey), with migration tests that run the real Alembic chain.

Verification (local, this branch, SQLite)

  • ruff check / ruff format --check / ty check src tests test-int (all extras) — clean
  • PR's own tests (API delete, directory delete, watcher delete, NULL-uniqueness, 5 migration tests) — 56 passed
  • tests/api tests/index tests/indexing tests/repository tests/services tests/db — 2186 passed
  • test-int (not slow/benchmark) — 482 passed
  • Codex on fix(core): unresolve inbound relations on entity delete #1352: no major issues

The contributor also verified the migration against real Postgres (pg_constraint.confdeltype = n for to_id, c for from_id) and ran just test across both backends; see #1352 for the red/green matrix showing the DB and ORM halves are independently load-bearing.

Out of scope, tracked separately: the stale search_index row on the API delete path (#1351).

🤖 Generated with Claude Code

https://claude.ai/code/session_017STCpbNsYjZgUdftxgEAZ4

Deleting a note erased every link that pointed at it. The notes doing the
pointing were never touched -- their markdown still said `[[That Note]]` --
but as far as the database was concerned no such link had ever been written.
Ask for the links that lead nowhere and you got a clean report over a vault
full of them. The row came back only if something re-indexed the source note,
which nothing schedules.

`relation.to_id` carried ON DELETE CASCADE, and `Entity.incoming_relations`
carried a matching ORM delete-orphan cascade -- two mechanisms doing the same
damage on two different routes. The watcher and directory runners take a bulk
SQL DELETE, so the constraint ate the rows; the API single-entity delete takes
`session.delete(entity)`, so the ORM did.

to_id becomes ON DELETE SET NULL and incoming_relations trades delete-orphan
for passive_deletes=True, handing the job to the database. from_id keeps
CASCADE: an entity does own the relations it declares. Deleting a target now
leaves its inbound rows in the unresolved state the indexer already produces
for a link to a note that does not exist yet -- to_id null, to_name holding
the source's link text -- so forward-reference resolution re-links them for
free if the target is recreated. Nothing downstream needed teaching.

Migration bcdbd5a942ca covers both backends. SQLite goes through
batch_alter_table with a naming convention, because the initial-schema
foreign key is unnamed and SQLite cannot address an anonymous constraint;
Postgres drops and recreates relation_to_id_fkey. Rows already lost to the
old behavior stay lost -- re-index the affected notes to recover them as
unresolved.

Tests cover all three delete paths plus the migration itself, which the rest
of the SQLite suite cannot: it builds its schema from SQLAlchemy metadata, so
a correct model with a missing migration would pass everywhere while every
database on disk kept the old CASCADE.

Two existing assertions in the watcher-parity and project-index tests expected
`outgoing_relations == []` after a target delete. Both tests are about
search-row repair; that line rode along as scenery and now asserts the
surviving unresolved row instead.

Fixes #1344

Signed-off-by: samwachtel <sammy@wachtel.us>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 28, 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-08-28T23:01:56.385453Z 320294d Manual request
ℹ️ 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.

@phernandez

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: 320294d2ee

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@phernandez
phernandez merged commit bdc3187 into main Aug 28, 2026
24 checks passed
@phernandez
phernandez deleted the fix/1344-unresolve-inbound-relations branch August 28, 2026 23:23
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.

[BUG] Deleting an entity removes inbound relations instead of leaving them unresolved

2 participants