Skip to content

fix(store): honor recordIds filter in sqlite queryL1Records - #1173

Open
dvd233 wants to merge 3 commits into
TencentCloud:feat/server_teamfrom
dvd233:fix/sqlite-query-l1-record-ids
Open

dvd233 wants to merge 3 commits into
TencentCloud:feat/server_teamfrom
dvd233:fix/sqlite-query-l1-record-ids

Conversation

@dvd233

@dvd233 dvd233 commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

Description | 描述

Fixes #1027. queryL1Records({ recordIds }) must return the requested records rather than an unrelated first row from the table.

Use one prepared primary-key lookup per distinct requested ID with the same complete result columns. Preserve session-ID precedence, strict updated-time filtering, chronological ordering, and team/user/agent/task filters. An empty ID list retains the existing scan behavior. This also avoids scanning the table merely to filter a small set of IDs afterwards.

Verification | 验证

  • Node 22.22.3 and Node 24.20.0: all ten focused tests passed on each version against real SQLite stores. Tests verify lookup count/no table scan, de-duplication, ordering/time/session semantics, each isolation dimension, and empty-ID fallback.
  • The plugin bundle and migration/export/read script builds succeeded. Full npm run build is not passing: the existing build:seed-v2 script references a missing scripts/seed-v2/tsconfig.json. The package scripts and missing path are unchanged; the same complete-build failure was reproduced at the original fix(core): keep Node's global proxy dispatcher when loading undici@8 #1171 head on the shared baseline.
  • The staged and committed follow-up diff matches the reviewed patch byte for byte.

DCO | 签署

The author confirmed the DCO submission rights and authorized correcting the two existing commits. Their code trees, author/committer metadata, and upstream parent are unchanged; only their own DCO trailers and the necessarily regenerated merge signature changed. The follow-up commit also carries the author's sign-off. Recovery refs retain the original commits.

Prepared and verified with Codex assistance. Automated verification does not replace maintainer review or establish official CI success.

The sqlite backend's queryL1Records() ignored filter.recordIds, so the
v3 /atomic/update handler — which loads the target note via
queryL1Records({ recordIds: [id] }) — received rows from the whole
table and could read another agent's note, failing the ownership check
with a spurious 403 "belongs to a different agent".

Apply the recordIds primary-key filter in memory alongside the existing
isolation-dimension filters, matching the tcvdb backend's documentIds
path. Verified that handleAtomicDelete is unaffected (it deletes via
deleteL1's primary-key path, not through queryL1Records).

Fixes TencentCloud#1027

Signed-off-by: dvd233 <[email protected]>
@Maxwell-Code07

Copy link
Copy Markdown
Collaborator

Thank you so much for your attention and contribution! We will arrange an internal review for this PR shortly, and all feedback will be shared right here in the discussion.

@kvnloo

kvnloo commented Sep 27, 2026

Copy link
Copy Markdown

The recordIds correctness fix looks right. One follow-up: could the SQLite implementation preserve the primary-key access path instead of materializing the normal query result and filtering IDs in memory?

For the important caller, queryL1Records({ recordIds: [id] }), with no session/time predicate, the current path executes stmtQueryAll.all() and only then applies the Set(recordIds) filter. The same store already has a prepared WHERE record_id = ? statement, and the public recordIds contract is bounded to at most 20 IDs.

I repeated a small node:sqlite microbenchmark on Node 22.16.0 with the same TEXT PRIMARY KEY shape:

  • 1k rows: full materialize+filter p50 1.85 ms; PK lookup p50 0.0020 ms
  • 10k rows: 13.58 ms; PK 0.0020 ms
  • 50k rows: 47.30 ms; PK 0.0018 ms

Those numbers are not production latency; the stronger invariant is the work count: an atomic update currently reads O(table size) rows to return one primary-key record.

Would an ID-driven fast path—bounded PK reads followed by the remaining isolation/time predicates—fit here, with the existing query path retained for non-ID scans?

I would not block the correctness fix on this, but it seems like a useful small follow-up before large standalone stores make the point lookup unexpectedly expensive.

AI-assisted source review + isolated local SQLite microbenchmark; no full MemoryCore benchmark was run.

@YiyuZh

YiyuZh commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

Independent verification on head 797cbc2 and after a local merge onto current feat/server_team (6a4bb55):

  • Merge completed without conflicts.
  • src/core/store/sqlite.test.ts: 10/10 passed on both the PR head and the current-base merge.
  • npm run build:plugin passed on the merged tree.
  • I traced the recordIds path through isolation, session/time precedence, deduplication, and empty-list fallback and did not find a blocking correctness issue.

This directly addresses #1027 and looks ready for maintainer merge. The only execution gap is that I did not run an external production database corpus; all verification used real temporary SQLite databases.

This branch has not been deployed

No deployments
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.

sqlite store: queryL1Records ignores filter.recordIds → /v3/atomic/update returns 403 'belongs to a different agent'

4 participants