Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .changeset/shared-scope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
'@tanstack/ai': minor
---

**Add a shared `Scope` identity type to `@tanstack/ai`.**

`Scope` is the single identity/isolation vocabulary for the subsystems that
persist or recall per-conversation data β€” `@tanstack/ai-persistence` and
`@tanstack/ai-memory`. Rather than each subsystem inventing its own notion of
"whose data is this?", both import one type:

```ts
interface Scope {
threadId: string // required β€” the single conversation key (same as ctx.threadId)
userId?: string // durable end-user identity; required in practice for multi-user apps
tenantId?: string // multi-tenant boundary
namespace?: string // reserved logical partition; no subsystem keys on it yet
}
```

`threadId` is the one conversation key across the codebase (matching
`ChatMiddlewareContext.threadId`, with `conversationId` already deprecated in
favor of it) β€” subsystems must not introduce a second name (`sessionId`, …) for
the same concept. Every field is an isolation boundary and must be derived
server-side from trusted session state, never from client input.

This is additive: nothing consumes `Scope` yet. It lands ahead of the
persistence and memory PRs so both build on one settled, unambiguous identity
contract instead of diverging.
3 changes: 3 additions & 0 deletions packages/ai/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ export type {
// All types
export * from './types'

// Shared identity/isolation scope for the persistence + memory subsystems
export type { Scope } from './scope'

export {
firstSentence,
renderLazyCatalogEntry,
Expand Down
47 changes: 47 additions & 0 deletions packages/ai/src/scope.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Shared identity/isolation scope for the TanStack AI subsystems that persist
* or recall per-conversation data β€” `@tanstack/ai-persistence` (keyed CRUD over
* threads, runs, interrupts) and `@tanstack/ai-memory` (ranked recall/save).
*
* Both subsystems answer the same underlying question β€” "whose data is this?" β€”
* so they share ONE identity vocabulary defined here rather than each inventing
* its own. `threadId` is the single conversation key across the codebase (it is
* `ChatMiddlewareContext.threadId`, and `conversationId` is deprecated in favor
* of it); a subsystem must never introduce a second name (`sessionId`,
* `conversationId`, …) for the same concept.
*
* ## Security
*
* A `Scope` is an isolation boundary. Derive every field server-side from
* trusted, validated session state β€” **never** from client input. `threadId`
* in particular resolves from a client-supplied value on the request, so a
* subsystem that reads/writes user-owned data (memory, per-user metadata) must
* pair it with a server-trusted `userId`/`tenantId` and must not treat a bare
* client `threadId` as sufficient isolation: thread ids are guessable, so on
* their own they let one caller read another's data.
*/
export interface Scope {
/**
* The conversation this data belongs to. Required β€” the minimal isolation
* key both subsystems already center on. Same concept as
* `ChatMiddlewareContext.threadId`.
*/
threadId: string
/**
* Durable end-user identity, for cross-thread recall and per-user isolation.
* Optional, but required in practice for any multi-user deployment β€” a
* `threadId` alone is not an authorization boundary (see Security above).
*/
userId?: string
/**
* Tenant/organization boundary for multi-tenant deployments. When present,
* every read and write must be confined to it.
*/
tenantId?: string
/**
* Logical partition within a tenant/user (e.g. separating distinct memory
* banks or persistence namespaces). Reserved β€” no subsystem keys on it yet;
* adapters that don't understand it must ignore it rather than error.
*/
namespace?: string
Comment on lines +41 to +46

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.

πŸ”’ Security & Privacy | 🟠 Major | ⚑ Quick win

Do not silently ignore an unsupported isolation boundary.

If namespace separates memory or persistence partitions, an adapter that ignores it can return data from another namespace. Require adapters to reject unsupported non-empty namespaces, or explicitly state that namespace has no isolation semantics until supported.

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai/src/scope.ts` around lines 41 - 46, The Scope namespace contract
currently permits adapters to silently ignore a potentially isolating boundary.
Update the namespace documentation and related Scope handling so unsupported
non-empty namespace values are rejected by adapters, or explicitly define
namespace as having no isolation semantics until implementation support exists;
preserve behavior for omitted or empty namespaces.

}
Loading