Skip to content

fix(witan): delete one edge exactly and batch per-slug reads with GQ 2.1 - #488

Merged
blarghmatey merged 3 commits into
mainfrom
omnigraph-013-gq21-compound-where
Oct 15, 2026
Merged

blarghmatey merged 3 commits into
mainfrom
omnigraph-013-gq21-compound-where

Conversation

@blarghmatey

Copy link
Copy Markdown
Member

What are the relevant tickets?

Witan task tk-use-gq-2-1-compound-where-or-and-in-to-remove-ed-5f0b32, part of the omnigraph 0.13 feature adoption. Items 1 (edge unlink) and 3 (per-id reads) of that task. The rest is recorded on the task (see the end of this description).

Description (What does it do?)

Before omnigraph 0.12 a mutation where took one predicate, so _unlink_edge deleted every edge on one endpoint and then re-inserted the others. A concurrent reader could see those edges missing, and a failure between the delete and the re-inserts lost them. GQ 2.1 takes a compound where, so each edge kind now has one exact delete, delete Blocks where @src = $from and @dst = $to. The ten one-endpoint unlink_*_by_{to,from} mutations and the five inbound *_from_slugs listings go. _unlink_edge is now one existence read (so removed is still reported) and one delete.

$x.slug in $slugs replaces per-slug reads:

  • _task_statuses reads the status of a set of tasks in one query. It replaces one get_task per blocker in task_create, task_ready (blockers outside the rows it already scanned), task_unlink and the unblock pass after task_close.
  • workflow_project_get_blockers reads all blockers at once and keeps the blocked_by order.
  • recall's contradiction pass reads the Contradicts edges among the returned memories in one query (contradicts_among) instead of contradicts_out per memory. Pairs are now ordered by the returned memories' order, not by edge creation time.
  • The topic backfill (witan migrate topics) reads existing Tagged edges in chunks of 200 memories instead of one read per memory, because the list is an inline parameter.

Not in this PR. or cannot join two search() predicates on 0.13.0 (lint refuses it with T38), so the search title twins stay. The per-filter-combination queries look collapsible with ($repo is null or $m.repo = $repo), which lints, but I have not checked how it behaves. Dropping the bridge's repo_file key is a schema change on every deployed code-bridge graph.

How can this be tested?

Both .gq files lint clean with omnigraph lint on 0.13.0 (the two WorkflowTrace warnings in mutations.gq were already there). I ran the witan-council suite against the 0.13.0 binary: 1564 passed, 4 skipped. The one failure, test_pre_upgrade_candidates_exclude_the_current_binary, comes from my harness putting two omnigraph binaries on PATH and is unrelated. The test_unlink_* tests in test_tasks.py exercise the new delete, including a case where both endpoints have other edges that must survive. A new test covers the unblock pass with a remaining blocker in another repo, and test_get_project_blockers_keeps_blocked_by_order covers the order guarantee, including a repeated entry. An independent review probed in [], duplicate and missing slugs, and the exact delete on a scratch 0.13.0 store, and found nothing blocking. Its two low findings (a stale comment in read.gq and the missing order test) are fixed in the second commit.

https://claude.ai/code/session_01QFdqvZcHWSE4dtiyD8NbRQ

Before omnigraph 0.12 a mutation `where` took one predicate, so
`_unlink_edge` deleted every edge on one endpoint and re-inserted the
others. A concurrent reader could see them missing, and a failure
between the delete and the re-inserts lost them. GQ 2.1 takes a compound
`where`, so each edge kind now has one delete on `@src` and `@dst`, and
the ten one-endpoint deletes and five inbound listings go.

`in` on a list parameter also replaces the per-slug reads in the blocker
checks (task_create, task_ready, task_unlink, the post-close unblock
pass), workflow_project_get_blockers, recall's contradiction pass and
the topic backfill. Each now issues one read, or one per bounded chunk
for the backfill.

Claude-Session: https://claude.ai/code/session_01QFdqvZcHWSE4dtiyD8NbRQ
The blockers are now read in one `in` query, whose row order the engine
does not promise, so the order comes from `blocked_by`. Nothing checked
that: the existing test compares a set. The new test feeds an order no
insert sequence produces, with a repeat, and asserts it comes back as is.

Also corrects the comment on get_workflow_project_by_slug, which still
described the per-blocker loop this branch removed.

Claude-Session: https://claude.ai/code/session_01QFdqvZcHWSE4dtiyD8NbRQ
Copilot AI balanced review requested due to automatic review settings October 15, 2026 18:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unbounded task-status batches can exceed local process argument limits, failing after task closure commits but before dependents reopen.

1 open finding
What changed in this PR

Uses GQ 2.1 to make Witan edge removal precise and reduce per-slug reads.

Changes:

  • Replaces endpoint-wide deletion and reinsertion with exact edge deletes.
  • Batches blocker, contradiction, and topic lookups.
  • Adds regression coverage and a changelog fragment.
File Description
mcp/​servers/​witan/​witan/​server.py Uses exact deletes and batched reads.
mcp/​servers/​witan/​tests/​test_workflow.py Tests blocker ordering and duplicates.
mcp/​servers/​witan/​tests/​test_tasks.py Tests cross-repo blockers and edge preservation.
mcp/​servers/​witan/​queries/​read.gq Adds batch queries and removes unused inbound listings.
mcp/​servers/​witan/​queries/​mutations.gq Deletes edges using both endpoints.
mcp/​servers/​witan/​changelog.d/​20261015_181500_blarghmatey_omnigraph_013_gq21_compound_where.md Documents deletion and batching changes.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread mcp/servers/witan/witan/server.py
On the local transport a query's params go to the CLI as one --params
argument, and Linux refuses a single argument of 128 KiB or more with
E2BIG. _task_statuses sent every external blocker in a repo at once, so a
large enough set would fail task_close after the close had committed and
before dependents were reopened. recall's contradiction read had the same
exposure through its uncapped limit.

Both now go through _read_by_slugs, which the topic backfill already did
inline, at 200 slugs per read. recall's query matches only the source
end and filters the target locally, so a pair split across chunks is
still found.

Claude-Session: https://claude.ai/code/session_01QFdqvZcHWSE4dtiyD8NbRQ
@blarghmatey
blarghmatey merged commit d9a8a68 into main Oct 15, 2026
19 checks passed
@blarghmatey
blarghmatey deleted the omnigraph-013-gq21-compound-where branch October 15, 2026 20:09
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