Skip to content

Make every service function shard-aware, without cutting over shard assignment yet - #125

Merged
brylie merged 2 commits into
feat/workspace-catalog-113-phase-afrom
feat/shard-aware-service-resolution-120
Aug 30, 2026
Merged

Make every service function shard-aware, without cutting over shard assignment yet#125
brylie merged 2 commits into
feat/workspace-catalog-113-phase-afrom
feat/shard-aware-service-resolution-120

Conversation

@brylie

@brylie brylie commented Aug 30, 2026

Copy link
Copy Markdown
Owner

Summary

Stacks on #119 (Phase A catalog) — base branch is feat/workspace-catalog-113-phase-a, not main, so this diff only shows what's new here. Merge #119 first, then retarget/merge this one.

Every service function that used to call resolveWorkspaceContext() bare now resolves its actual target shard via new catalog primitives, falling back to the default context when untracked. createCollection still deliberately assigns shardId: 'default' — this PR proves the resolution mechanism correct for a genuinely separate shard (tests manually construct one, the same pattern as the holds eviction-wiring fix in #124) without changing where content actually lives yet. Production behavior is unchanged; no client or attach-ws.ts changes are needed in this slice, since the browser's live sync path is never affected.

  • catalog.ts: reserveRecordLocator/releaseRecordLocator (row-level — closes the gap where write_record/delete_record/hold_records only ever receive a bare recordId, no parent hint), resolveShardForParent/resolveShardForRecord.
  • permissions.ts: resolveParentWorkspaceContext/resolveRecordWorkspaceContext/groupRecordIdsByShard — shared resolution helpers. requireAccessibleRecord is now itself shard-aware, which every existing caller already goes through.
  • records.ts: createRecord reserves a row locator when its parent is a Collection; writeRecord/deleteRecord/getRecord resolve via the record's own locator.
  • collections.ts: queryCollection/updateCollectionTitle/deleteCollection resolve the collection's real shard.
  • holds.ts: hold_records/release_records group recordIds by resolved shard and operate against each shard's own Awareness (a cross-document agent batch is a stated acceptance criterion — see collaboration.md).
  • search.ts: Collections are enumerated via the catalog first (resolving each one's real shard, including its own meta entry — not just its rows), with a fallback pass over the default doc for uncataloged (direct-Yjs-written) Collections the catalog loop can't see.

No MCP tool schema changes — every tool already carries enough of an id for server-side shard resolution.

Test plan

  • npm run test — 661/661 passing (13 new)
  • npm run lint / npm run check — clean
  • New tests manually construct a Collection whose catalog row names a genuinely different shard (bypassing createCollection's always-'default' behavior) and assert queryCollection, createRecord, writeRecord, getRecord, deleteRecord, holdRecords/releaseRecords, and searchWorkspace all correctly reach into that shard rather than the default doc
  • No dev-server verification needed — production behavior is unchanged by design

Refs #120 (not closing — the shard-assignment cutover itself and Document sharding remain)

🤖 Generated with Claude Code

…hard assignment yet (#120)

Every service function that used to call resolveWorkspaceContext() bare now
resolves its actual target shard via new catalog primitives
(resolveShardForParent/resolveShardForRecord), falling back to the default
context when untracked. createCollection still assigns shardId: 'default'
deliberately — this proves the resolution mechanism correct for a genuinely
separate shard (tests manually construct one, same pattern as the holds
eviction-wiring fix) without changing where content actually lives yet, so
production behavior is unchanged and no client/attach-ws changes are needed
in this slice.

- catalog.ts: reserveRecordLocator/releaseRecordLocator (row-level, closes
  the gap where write_record/delete_record/hold_records only ever receive
  a bare recordId), resolveShardForParent/resolveShardForRecord.
- permissions.ts: resolveParentWorkspaceContext/resolveRecordWorkspaceContext/
  groupRecordIdsByShard — shared resolution helpers; requireAccessibleRecord
  is now itself shard-aware, which every existing caller already goes through.
- records.ts: createRecord reserves a row locator when its parent is a
  Collection; writeRecord/deleteRecord/getRecord resolve via the record's
  own locator.
- collections.ts: queryCollection/updateCollectionTitle/deleteCollection
  resolve the collection's real shard.
- holds.ts: hold_records/release_records group recordIds by resolved shard
  and operate against each shard's own Awareness (a cross-document agent
  batch is a stated acceptance criterion — see collaboration.md).
- search.ts: Collections are enumerated via the catalog first (resolving
  each one's real shard, including its own meta entry — not just its rows),
  with a fallback pass over the default doc for uncataloged (direct-Yjs-
  written) Collections the catalog loop can't see.

No MCP tool schema changes needed — every tool already carries enough of an
id for server-side shard resolution.

661/661 tests passing (13 new).

Refs #120. Branched off feat/workspace-catalog-113-phase-a (PR #119, not yet
merged) since this depends on its catalog.ts.
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b4bb5eaa-a8e9-420e-82c9-f7a8e35a363b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

brylie added a commit that referenced this pull request Aug 30, 2026
agentClocks/agentTtlTimers were keyed only by the synthetic clientId
(deterministic per access token, regardless of which shard's Awareness
it's operating against). A cross-shard agent hold batch under the same
token now legitimately holds on two different Awareness instances at
once (#125 made hold_records capable of this) — the second shard's
scheduleTtl() call was silently cancelling the first shard's timer via
the shared clientId key, so that hold never auto-expired.

Both maps are now Map<Awareness, Map<number, T>>, with get-or-create
helpers threaded through writeRemoteState/scheduleTtl/clearTtl.

635/635 tests passing (2 new, proving independent TTL expiry across
two Awareness instances under the same clientId).

Refs #120
…re-service-resolution-120

# Conflicts:
#	src/lib/server/catalog.test.ts
#	src/lib/server/db/schema.ts
@brylie
brylie merged commit 783fdd1 into feat/workspace-catalog-113-phase-a Aug 30, 2026
2 checks passed
@brylie
brylie deleted the feat/shard-aware-service-resolution-120 branch August 30, 2026 16:40
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.

1 participant