@pyxmate/memory 0.15.2 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts
CHANGED
|
@@ -337,6 +337,15 @@ interface MemoryEntry {
|
|
|
337
337
|
userId?: string;
|
|
338
338
|
/** Team/group ID within the tenant. */
|
|
339
339
|
teamId?: string;
|
|
340
|
+
/**
|
|
341
|
+
* Namespace ID — the primary protected resource for ReBAC authorization.
|
|
342
|
+
* `undefined` (NULL on the row) means the entry lives in the legacy
|
|
343
|
+
* "tenant-root" bucket and is visible to anyone with tenant access (the
|
|
344
|
+
* pre-ReBAC posture). Setting `namespaceId` opts the entry into AuthzPlan-
|
|
345
|
+
* based filtering, where visibility is computed from `authz_tuples` for
|
|
346
|
+
* the calling principal.
|
|
347
|
+
*/
|
|
348
|
+
namespaceId?: string;
|
|
340
349
|
/**
|
|
341
350
|
* When true, consolidation leaves this entry alone — it is never archived by
|
|
342
351
|
* decay and never merged (nor merged-into) by semantic deduplication. Use for
|
|
@@ -365,6 +374,22 @@ interface MemorySearchParams {
|
|
|
365
374
|
userId?: string;
|
|
366
375
|
/** Team/group ID within the tenant. */
|
|
367
376
|
teamId?: string;
|
|
377
|
+
/**
|
|
378
|
+
* AuthzPlan-derived visibility list. Populated internally by `Memory.search`
|
|
379
|
+
* after computing the plan from the calling principal — callers should
|
|
380
|
+
* NOT set this directly. Empty array means "no granted namespaces" (only
|
|
381
|
+
* legacy NULL-namespace entries are visible). `undefined` skips the
|
|
382
|
+
* filter (single-tenant / pre-ReBAC compat).
|
|
383
|
+
*
|
|
384
|
+
* "Search within this specific folder" UX is intentionally NOT supported
|
|
385
|
+
* via a singular `namespaceId` field for v1 — adding it would force
|
|
386
|
+
* intersection logic with the visibility list (and confused-deputy risk
|
|
387
|
+
* if the singular value isn't validated against the plan). Callers that
|
|
388
|
+
* need it can compute the singleton intersection client-side and pass
|
|
389
|
+
* `namespaceIds: [chosenNamespace]` after verifying access via the
|
|
390
|
+
* admin API.
|
|
391
|
+
*/
|
|
392
|
+
namespaceIds?: string[];
|
|
368
393
|
}
|
|
369
394
|
interface MemorySearchResult {
|
|
370
395
|
entries: MemoryEntry[];
|
|
@@ -474,6 +499,8 @@ interface MemoryIngestRequest {
|
|
|
474
499
|
userId?: string;
|
|
475
500
|
/** Team/group ID within the tenant. */
|
|
476
501
|
teamId?: string;
|
|
502
|
+
/** Namespace ID — see `MemoryEntry.namespaceId`. */
|
|
503
|
+
namespaceId?: string;
|
|
477
504
|
/**
|
|
478
505
|
* Exclude this entry from consolidation (decay-archive and semantic dedup).
|
|
479
506
|
* See `MemoryEntry.pinned` for full semantics.
|
|
@@ -629,4 +656,51 @@ interface IngestErrorEvent {
|
|
|
629
656
|
}
|
|
630
657
|
type IngestEvent = IngestProgressEvent | IngestHeartbeatEvent | IngestResultEvent | IngestErrorEvent;
|
|
631
658
|
|
|
632
|
-
|
|
659
|
+
/**
|
|
660
|
+
* Caller identity for ReBAC authorization.
|
|
661
|
+
*
|
|
662
|
+
* The canonical "subject" in Zanzibar terms — passed alongside requests so the
|
|
663
|
+
* memory layer can compute an AuthzPlan (visible namespaces + entry overrides)
|
|
664
|
+
* before any retrieval source fans out.
|
|
665
|
+
*
|
|
666
|
+
* Identity comes from authenticated request context (X-Tenant-Id / auth token
|
|
667
|
+
* claims), never from request bodies or multipart form fields. The legacy
|
|
668
|
+
* `userId` / `teamId` / `agentId` columns on MemoryEntry remain for audit and
|
|
669
|
+
* legacy filters but MUST NOT drive authorization decisions — they are
|
|
670
|
+
* collision-prone aliases (a service principal sharing an ID with a human
|
|
671
|
+
* user has no protection against confused-deputy attacks).
|
|
672
|
+
*
|
|
673
|
+
* Sensitivity / clearance is intentionally NOT on this object. It is a
|
|
674
|
+
* MAC-style classification, orthogonal to RBAC, and continues to be carried
|
|
675
|
+
* via the `X-Caller-Access-Level` header → `MemorySearchParams.maxSensitivity`.
|
|
676
|
+
* Conflating the two couples future changes (e.g. per-namespace classification
|
|
677
|
+
* rules) to identity propagation.
|
|
678
|
+
*/
|
|
679
|
+
interface PrincipalContext {
|
|
680
|
+
/**
|
|
681
|
+
* Hard isolation boundary. Required even in single-tenant deployments —
|
|
682
|
+
* single-mode passes a stable sentinel (`SINGLE_TENANT_ID`) so authz code
|
|
683
|
+
* paths look identical regardless of mode.
|
|
684
|
+
*/
|
|
685
|
+
tenantId: string;
|
|
686
|
+
/**
|
|
687
|
+
* Stable subject ID (within the tenant). For humans this is the userId;
|
|
688
|
+
* for AI runtimes it is the agentId; for system actors it is a service
|
|
689
|
+
* identifier. Combined with `kind`, forms the Zanzibar subject coordinate
|
|
690
|
+
* `<kind>:<principalId>`.
|
|
691
|
+
*/
|
|
692
|
+
principalId: string;
|
|
693
|
+
/**
|
|
694
|
+
* Subject namespace. Distinguishes humans from AI agents from internal
|
|
695
|
+
* services so a userId/agentId/serviceId collision cannot grant
|
|
696
|
+
* unintended access.
|
|
697
|
+
* - `user`: human end user
|
|
698
|
+
* - `agent`: AI runtime acting on a user's behalf or autonomously
|
|
699
|
+
* - `service`: non-AI internal system (cron, ETL, admin tooling)
|
|
700
|
+
*/
|
|
701
|
+
kind: 'user' | 'agent' | 'service';
|
|
702
|
+
}
|
|
703
|
+
/** Sentinel tenant ID used in single-tenant deployments. */
|
|
704
|
+
declare const SINGLE_TENANT_ID = "_single";
|
|
705
|
+
|
|
706
|
+
export { type AgentId, type ApiResponse, type ConsolidationRunResult, DEFAULTS, EmbeddingProviderName, type EnrichmentCallbacks, type ExtendedMemoryInterface, type GraphFailureMode, type GraphNode, type GraphRelationship, type GraphTraversalResult, type IngestEntity, type IngestErrorEvent, type IngestEvent, type IngestFileOptions, type IngestHeartbeatEvent, type IngestProgressEvent, type IngestRelationship, type IngestResultEvent, type IngestStage, type IngestionResult, MemoryClient, type MemoryClientOptions, type MemoryEntry, type MemoryIngestRequest, type MemoryInterface, type MemoryListParams, type MemoryListResult, type MemorySearchParams, type MemorySearchResult, MemoryServerError, type MemoryStats, MemoryType, type PrincipalContext, RAGStrategy, SINGLE_TENANT_ID, SensitivityLevel, type StoreInput, StoreTarget, type TemporalQueryFilters, type TenantScopeOptions, type Timestamp, VectorProvider };
|
package/dist/index.mjs
CHANGED
|
@@ -45,6 +45,9 @@ var StoreTarget = {
|
|
|
45
45
|
VECTOR: "vector",
|
|
46
46
|
GRAPH: "graph"
|
|
47
47
|
};
|
|
48
|
+
|
|
49
|
+
// ../shared/src/types/principal.ts
|
|
50
|
+
var SINGLE_TENANT_ID = "_single";
|
|
48
51
|
export {
|
|
49
52
|
DEFAULTS,
|
|
50
53
|
EmbeddingProviderName,
|
|
@@ -52,6 +55,7 @@ export {
|
|
|
52
55
|
MemoryServerError,
|
|
53
56
|
MemoryType,
|
|
54
57
|
RAGStrategy,
|
|
58
|
+
SINGLE_TENANT_ID,
|
|
55
59
|
SensitivityLevel,
|
|
56
60
|
StoreTarget,
|
|
57
61
|
VectorProvider
|
package/package.json
CHANGED
|
@@ -7,14 +7,17 @@ description: >
|
|
|
7
7
|
files or images, or search for what was discussed before. Also use when
|
|
8
8
|
integrating the pyx-memory SDK into code, working with MemoryClient, or using
|
|
9
9
|
the HTTP API. Covers multi-tenant isolation (tenantId, TENANT_MODE),
|
|
10
|
+
fine-grained ReBAC (namespaces, principals, authz tuples, AuthzPlan),
|
|
10
11
|
sensitivity classification, encryption at rest, and confidence/abstention.
|
|
11
12
|
Triggers on: 'remember', 'recall', 'store memory', 'search memory',
|
|
12
13
|
'ingest file', 'store this image', 'remember this document',
|
|
13
14
|
'what did we decide', 'pyx-memory', 'memory', 'MemoryClient',
|
|
14
15
|
'integrate memory', 'memory consolidation', 'multi-tenant',
|
|
15
16
|
'tenant isolation', 'sensitivity', 'encryption', 'confidence',
|
|
16
|
-
'abstention', 'access control', 'RBAC', '
|
|
17
|
-
'
|
|
17
|
+
'abstention', 'access control', 'RBAC', 'ReBAC', 'Zanzibar',
|
|
18
|
+
'namespace', 'principal', 'authz tuple', 'permission', 'permissions',
|
|
19
|
+
'role', 'role-based access', 'read-only', 'agent isolation',
|
|
20
|
+
'per-agent memory'.
|
|
18
21
|
allowed-tools: Read, Grep, Glob, Edit, Write, Bash(curl *)
|
|
19
22
|
argument-hint: "[store|search|ingest] <content or query>"
|
|
20
23
|
---
|
|
@@ -126,7 +129,7 @@ For integrating pyx-memory into TypeScript/Bun projects, see the reference docs:
|
|
|
126
129
|
|---|---|
|
|
127
130
|
| Wire pyx-memory into a consumer project | [patterns/consumer.md](patterns/consumer.md) |
|
|
128
131
|
| Set up embedded memory (full features) | [patterns/embedded.md](patterns/embedded.md) |
|
|
129
|
-
| Multi-tenant,
|
|
132
|
+
| Multi-tenant, ReBAC (namespaces + tuples), sensitivity, encryption | [patterns/access-control.md](patterns/access-control.md) |
|
|
130
133
|
| SDK quick start, package map, DO/DON'T | [reference/sdk-guide.md](reference/sdk-guide.md) |
|
|
131
134
|
| HTTP API endpoints | [reference/http-api.md](reference/http-api.md) |
|
|
132
135
|
| Type signatures and interfaces | [reference/types.md](reference/types.md) |
|
|
@@ -9,7 +9,7 @@ Production patterns for restricting who can read and write which memories.
|
|
|
9
9
|
- [Pattern 12: Sensitivity-Based Read Restriction](#pattern-12-sensitivity-based-read-restriction)
|
|
10
10
|
- [Pattern 13: Read-Only vs Read-Write Access](#pattern-13-read-only-vs-read-write-access)
|
|
11
11
|
- [Pattern 14: Full Production Stack](#pattern-14-full-production-stack)
|
|
12
|
-
- [
|
|
12
|
+
- [Pattern 15: Built-in ReBAC](#pattern-15-built-in-rebac-namespaces--tuples--authzplan) ← **start here for fine-grained per-user / per-team access**
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
@@ -22,6 +22,9 @@ Need to isolate agents from each other?
|
|
|
22
22
|
Need to isolate organizations/customers?
|
|
23
23
|
→ Use TENANT_MODE=multi + X-Tenant-Id (Pattern 11)
|
|
24
24
|
|
|
25
|
+
Need fine-grained per-user / per-team / per-folder access INSIDE a tenant?
|
|
26
|
+
→ Use built-in ReBAC: namespaces + principals + authz_tuples (Pattern 15)
|
|
27
|
+
|
|
25
28
|
Need to hide sensitive data from some users?
|
|
26
29
|
→ Use sensitivity classification + X-Caller-Access-Level (Pattern 12)
|
|
27
30
|
|
|
@@ -29,7 +32,7 @@ Need read-only vs read-write roles?
|
|
|
29
32
|
→ Use API_KEY + ADMIN_API_KEY + an API gateway (Pattern 13)
|
|
30
33
|
|
|
31
34
|
Need all of the above?
|
|
32
|
-
→ Pattern 14 (full production stack)
|
|
35
|
+
→ Pattern 14 (full production stack) + Pattern 15 (ReBAC)
|
|
33
36
|
```
|
|
34
37
|
|
|
35
38
|
---
|
|
@@ -256,7 +259,7 @@ function memoryAccessMiddleware(req: Request, userRole: string): boolean {
|
|
|
256
259
|
|
|
257
260
|
## Pattern 14: Full Production Stack
|
|
258
261
|
|
|
259
|
-
Combines all patterns for a production multi-tenant deployment with sensitivity, encryption, and
|
|
262
|
+
Combines all patterns for a production multi-tenant deployment with sensitivity, encryption, and the built-in two-tier API key model (`API_KEY` for read+write, `ADMIN_API_KEY` for destructive ops). For fine-grained per-user/per-namespace access inside a tenant, layer Pattern 15 (ReBAC) on top of this base.
|
|
260
263
|
|
|
261
264
|
### Server config
|
|
262
265
|
|
|
@@ -331,51 +334,136 @@ function createDashboardClient(tenantId: string, userId: string) {
|
|
|
331
334
|
|
|
332
335
|
---
|
|
333
336
|
|
|
334
|
-
##
|
|
337
|
+
## Pattern 15: Built-in ReBAC (namespaces + tuples + AuthzPlan)
|
|
338
|
+
|
|
339
|
+
For fine-grained access control inside a tenant — "alice can read project-X but not project-Y", "team-eng has editor on engineering/*", "revoke without entry rewrite" — pyx-memory ships first-class ReBAC primitives. No external policy service required for v1; the model is Zanzibar-aligned so you can later export to OpenFGA / SpiceDB without changing application code.
|
|
340
|
+
|
|
341
|
+
### The four primitives
|
|
335
342
|
|
|
336
|
-
|
|
343
|
+
| Resource | Role |
|
|
344
|
+
|----------|------|
|
|
345
|
+
| `namespace` | The unit you grant access to (folders, projects, channels). Hierarchical via `parentId`. Granting on a parent transitively covers descendants. |
|
|
346
|
+
| `principal` | A subject — `user`, `team`, `group`, `agent`, `service`, or per-tenant `everyone`. Identified by `(tenantId, kind, externalId)`. |
|
|
347
|
+
| `principal_member` | Membership edge `member ∈ group`. Group-of-groups supported (cycles rejected, max-depth-8 namespaces). |
|
|
348
|
+
| `authz_tuple` | The grant: `(subject, relation, object)` — e.g. `(user:alice, viewer, namespace:proj-acme)`. Built-in rewrite: `owner ⊇ editor ⊇ viewer`. |
|
|
337
349
|
|
|
350
|
+
### Server config
|
|
351
|
+
|
|
352
|
+
```yaml
|
|
353
|
+
# docker-compose.yaml
|
|
354
|
+
environment:
|
|
355
|
+
- TENANT_MODE=multi
|
|
356
|
+
- API_KEY=${MEMORY_API_KEY}
|
|
357
|
+
- ADMIN_API_KEY=${MEMORY_ADMIN_KEY} # required to manage namespaces / tuples
|
|
338
358
|
```
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
359
|
+
|
|
360
|
+
### Manage namespaces, principals, and tuples (admin API)
|
|
361
|
+
|
|
362
|
+
All `/api/admin/*` routes require `ADMIN_API_KEY` and `X-Tenant-Id`.
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
# 1. Create a namespace (the resource you'll grant access to)
|
|
366
|
+
curl -X POST http://memory:7822/api/admin/namespaces \
|
|
367
|
+
-H "Authorization: Bearer $ADMIN_KEY" \
|
|
368
|
+
-H "X-Tenant-Id: tenant-acme" \
|
|
369
|
+
-H "Content-Type: application/json" \
|
|
370
|
+
-d '{"name":"engineering"}'
|
|
371
|
+
# → {"id":"<NS_ID>", ...}
|
|
372
|
+
|
|
373
|
+
# 2. Register a principal (idempotent on tenant + kind + externalId)
|
|
374
|
+
curl -X POST http://memory:7822/api/admin/principals \
|
|
375
|
+
-H "Authorization: Bearer $ADMIN_KEY" \
|
|
376
|
+
-H "X-Tenant-Id: tenant-acme" \
|
|
377
|
+
-H "Content-Type: application/json" \
|
|
378
|
+
-d '{"kind":"user","externalId":"alice","displayName":"Alice"}'
|
|
379
|
+
# → {"id":"<ALICE_ID>", ...}
|
|
380
|
+
|
|
381
|
+
# 3. Grant alice viewer on engineering
|
|
382
|
+
curl -X POST http://memory:7822/api/admin/authz-tuples \
|
|
383
|
+
-H "Authorization: Bearer $ADMIN_KEY" \
|
|
384
|
+
-H "X-Tenant-Id: tenant-acme" \
|
|
385
|
+
-H "Content-Type: application/json" \
|
|
386
|
+
-d '{
|
|
387
|
+
"subjectKind":"user","subjectId":"<ALICE_ID>",
|
|
388
|
+
"relation":"viewer",
|
|
389
|
+
"objectKind":"namespace","objectId":"<NS_ID>"
|
|
390
|
+
}'
|
|
391
|
+
|
|
392
|
+
# 4. Revoke (one row delete — no entry rewrite, takes effect immediately)
|
|
393
|
+
curl -X DELETE http://memory:7822/api/admin/authz-tuples \
|
|
394
|
+
-H "Authorization: Bearer $ADMIN_KEY" \
|
|
395
|
+
-H "X-Tenant-Id: tenant-acme" \
|
|
396
|
+
-H "Content-Type: application/json" \
|
|
397
|
+
-d '{ ...same body as the grant... }'
|
|
346
398
|
```
|
|
347
399
|
|
|
348
|
-
###
|
|
400
|
+
### Make something public to the whole tenant
|
|
349
401
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
402
|
+
```bash
|
|
403
|
+
# Resolve the per-tenant `everyone` principal (auto-created)
|
|
404
|
+
curl -X POST http://memory:7822/api/admin/principals \
|
|
405
|
+
-H "Authorization: Bearer $ADMIN_KEY" \
|
|
406
|
+
-H "X-Tenant-Id: tenant-acme" \
|
|
407
|
+
-H "Content-Type: application/json" \
|
|
408
|
+
-d '{"kind":"everyone","externalId":"_everyone"}'
|
|
353
409
|
|
|
354
|
-
|
|
410
|
+
# Grant `everyone` viewer on the announcements namespace
|
|
411
|
+
curl -X POST http://memory:7822/api/admin/authz-tuples \
|
|
412
|
+
-H "Authorization: Bearer $ADMIN_KEY" \
|
|
413
|
+
-H "X-Tenant-Id: tenant-acme" \
|
|
414
|
+
-H "Content-Type: application/json" \
|
|
415
|
+
-d '{
|
|
416
|
+
"subjectKind":"everyone","subjectId":"<EVERYONE_ID>",
|
|
417
|
+
"relation":"viewer",
|
|
418
|
+
"objectKind":"namespace","objectId":"<NS_ID>"
|
|
419
|
+
}'
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Reversing later is the same — delete the tuple, visibility flips back the next request.
|
|
423
|
+
|
|
424
|
+
### Search-time enforcement
|
|
425
|
+
|
|
426
|
+
Pass the calling principal on `Memory.search()`. The server computes an `AuthzPlan` (visible namespaces + cached revision) BEFORE any retrieval source fans out. SQLite/FTS, LanceDB vector search, and the graph engine all apply the plan as a native pre-filter — forbidden entries never enter RRF fusion or reranker scoring (which would skew normalization for everyone).
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
const result = await memory.search({
|
|
430
|
+
query: 'Q4 revenue',
|
|
431
|
+
strategy: 'hybrid',
|
|
432
|
+
principal: {
|
|
433
|
+
tenantId: 'tenant-acme',
|
|
434
|
+
principalId: 'alice',
|
|
435
|
+
kind: 'user',
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
// result.entries contains only namespaces alice can `view`,
|
|
439
|
+
// plus legacy NULL-namespace entries (tenant-root bucket).
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### Legacy compatibility
|
|
443
|
+
|
|
444
|
+
Entries with `namespace_id IS NULL` (the default before ReBAC) remain visible to anyone with tenant access. You opt entries into ReBAC by setting `namespaceId` at ingest:
|
|
355
445
|
|
|
356
446
|
```typescript
|
|
357
|
-
// Define scopes as metadata on memory entries
|
|
358
447
|
await memory.store({
|
|
359
448
|
content: 'Q4 revenue projections',
|
|
360
449
|
type: 'long-term',
|
|
361
|
-
metadata: {
|
|
362
|
-
|
|
363
|
-
allowedTeams: ['finance', 'exec'], // who can read
|
|
364
|
-
allowedRoles: ['analyst', 'admin'], // which roles
|
|
365
|
-
},
|
|
450
|
+
metadata: {},
|
|
451
|
+
namespaceId: '<NS_ID>',
|
|
366
452
|
});
|
|
367
|
-
|
|
368
|
-
// In your API gateway, filter results based on caller identity
|
|
369
|
-
function filterByAccess(entries: MemoryEntry[], caller: CallerIdentity): MemoryEntry[] {
|
|
370
|
-
return entries.filter(entry => {
|
|
371
|
-
const meta = entry.metadata;
|
|
372
|
-
if (!meta.allowedTeams) return true; // no restriction = public
|
|
373
|
-
return (
|
|
374
|
-
meta.allowedTeams.includes(caller.teamId) ||
|
|
375
|
-
meta.allowedRoles.includes(caller.role)
|
|
376
|
-
);
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
453
|
```
|
|
380
454
|
|
|
381
|
-
|
|
455
|
+
No bulk migration is required — legacy data keeps working unchanged.
|
|
456
|
+
|
|
457
|
+
### Why not metadata + post-filter?
|
|
458
|
+
|
|
459
|
+
The pre-PR-D pattern was "tag entries with `metadata.allowedTeams`, filter results in your gateway". That breaks RAG retrieval:
|
|
460
|
+
|
|
461
|
+
1. **Ranking pollution** — RRF fusion and reranker interpolation normalize against the candidate set. If forbidden entries enter retrieval, they shift the scoring for the entries the caller IS allowed to see.
|
|
462
|
+
2. **Confidence corruption** — abstention scores depend on score variance over the candidate set. Hidden entries change the variance.
|
|
463
|
+
3. **Write amplification** — changing a role means rewriting metadata on every affected chunk.
|
|
464
|
+
|
|
465
|
+
ReBAC tuples solve all three: pre-filter at the SQL/vector layer, single-row mutations, no entry rewrites.
|
|
466
|
+
|
|
467
|
+
### Next-scale: external PDP
|
|
468
|
+
|
|
469
|
+
When you outgrow the in-process tuple store (~10M tuples, or you need cross-tenant sharing, or distributed enforcement), the tuple format is Zanzibar-compatible — export to OpenFGA or SpiceDB and swap the `AuthzPlan` compute path to call their `ListObjects` API. The retrieval-engine code does not change.
|
|
@@ -60,6 +60,28 @@ const client = new MemoryClient('http://localhost:7822', process.env.MEMORY_API_
|
|
|
60
60
|
| GET | `/api/memory/query-as-of?asOf=...` | Bi-temporal point-in-time query (asOf, type, agentId, source, limit) |
|
|
61
61
|
| GET | `/api/memory/query-by-event-time?startTime=...&endTime=...` | Bi-temporal event time range query (startTime, endTime, type, agentId, source, limit) |
|
|
62
62
|
|
|
63
|
+
## ReBAC Admin (11 endpoints)
|
|
64
|
+
|
|
65
|
+
Fine-grained access control inside a tenant — namespaces, principals, group membership, and authz tuples. All routes require `ADMIN_API_KEY` and an `X-Tenant-Id` header. See [`patterns/access-control.md`](../patterns/access-control.md) Pattern 15 for the full guide.
|
|
66
|
+
|
|
67
|
+
| Method | Endpoint | Description |
|
|
68
|
+
|--------|----------|-------------|
|
|
69
|
+
| POST | `/api/admin/namespaces` | Create namespace (JSON: `{ name, parentId?, metadata?, createdBy? }`) |
|
|
70
|
+
| GET | `/api/admin/namespaces` | List namespaces in this tenant |
|
|
71
|
+
| DELETE | `/api/admin/namespaces/:id` | Delete namespace + tuples referencing it (RESTRICT on children) |
|
|
72
|
+
| POST | `/api/admin/principals` | Upsert principal (JSON: `{ kind, externalId?, displayName?, metadata? }`) |
|
|
73
|
+
| GET | `/api/admin/principals` | List principals in this tenant |
|
|
74
|
+
| DELETE | `/api/admin/principals/:id` | Delete principal + cascade memberships |
|
|
75
|
+
| POST | `/api/admin/principal-members` | Add member to group (JSON: `{ groupId, memberId }`) — cycle-checked |
|
|
76
|
+
| DELETE | `/api/admin/principal-members/:groupId/:memberId` | Remove membership |
|
|
77
|
+
| POST | `/api/admin/authz-tuples` | Write tuple (JSON: `{ subjectKind, subjectId, subjectRelation?, relation, objectKind, objectId, createdBy? }`) |
|
|
78
|
+
| GET | `/api/admin/authz-tuples?objectId=&subjectId=` | List tuples (filterable) |
|
|
79
|
+
| DELETE | `/api/admin/authz-tuples` | Delete tuple (JSON: same body shape as POST) |
|
|
80
|
+
|
|
81
|
+
`kind`: `'user' | 'team' | 'group' | 'agent' | 'service' | 'everyone'`. `objectKind` is `'namespace'` only in v1. `relation` is freeform (built-in rewrite: `owner ⊇ editor ⊇ viewer`).
|
|
82
|
+
|
|
83
|
+
Cross-tenant safety: every route verifies the referenced resource belongs to the caller's tenant; mismatches return `404` (not `403`) to avoid existence disclosure.
|
|
84
|
+
|
|
63
85
|
## File Ingestion (Images + Documents)
|
|
64
86
|
|
|
65
87
|
`POST /api/memory/ingest/file` accepts multipart/form-data with:
|
|
@@ -185,9 +185,10 @@ interface MemoryEntry {
|
|
|
185
185
|
source?: string; // filename, URL, session ID
|
|
186
186
|
eventTime?: string; // when event happened (bi-temporal)
|
|
187
187
|
ingestTime?: string; // when stored (bi-temporal)
|
|
188
|
-
tenantId?: string; // multi-tenant isolation
|
|
189
|
-
userId?: string; // user within tenant
|
|
190
|
-
teamId?: string; // team/group within tenant
|
|
188
|
+
tenantId?: string; // multi-tenant isolation (hard boundary)
|
|
189
|
+
userId?: string; // user within tenant (legacy soft-scope)
|
|
190
|
+
teamId?: string; // team/group within tenant (legacy soft-scope)
|
|
191
|
+
namespaceId?: string; // ReBAC resource coordinate; NULL = legacy tenant-root bucket
|
|
191
192
|
sensitivity?: SensitivityLevel; // auto-classified: 'public' | 'internal' | 'secret'
|
|
192
193
|
encrypted?: boolean; // true when content is encrypted at rest
|
|
193
194
|
pinned?: boolean; // exempt from consolidation (decay-archive + dedup-merge); use for stable anchors referenced by external systems
|
|
@@ -209,6 +210,11 @@ interface MemorySearchParams {
|
|
|
209
210
|
tenantId?: string; // multi-tenant scoping
|
|
210
211
|
userId?: string; // user-level scoping
|
|
211
212
|
teamId?: string; // team-level scoping
|
|
213
|
+
/** Calling principal — drives ReBAC AuthzPlan. Search applies it as a
|
|
214
|
+
* native pre-filter on SQLite/FTS, vector, and graph layers; forbidden
|
|
215
|
+
* entries never enter RRF / reranker / abstention. Optional. */
|
|
216
|
+
principal?: PrincipalContext;
|
|
217
|
+
namespaceIds?: string[]; // populated internally from AuthzPlan — do not set directly
|
|
212
218
|
maxSensitivity?: SensitivityLevel; // filter by max sensitivity level
|
|
213
219
|
abstentionThreshold?: number; // 0-1, below this confidence → shouldAbstain=true (default: 0.3)
|
|
214
220
|
}
|
|
@@ -223,6 +229,66 @@ interface SearchFilters {
|
|
|
223
229
|
}
|
|
224
230
|
```
|
|
225
231
|
|
|
232
|
+
## PrincipalContext + ReBAC Types (v0.16.0+)
|
|
233
|
+
|
|
234
|
+
The calling identity used to compute search-time AuthzPlan. Comes from
|
|
235
|
+
authenticated request context (`X-Tenant-Id` + `X-User-Id` / `X-Agent-Id`),
|
|
236
|
+
NEVER from request bodies.
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
interface PrincipalContext {
|
|
240
|
+
tenantId: string; // hard boundary; SINGLE_TENANT_ID sentinel in single mode
|
|
241
|
+
principalId: string; // userId | agentId | service identifier
|
|
242
|
+
kind: 'user' | 'agent' | 'service';
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
interface Namespace {
|
|
246
|
+
id: string;
|
|
247
|
+
tenantId: string;
|
|
248
|
+
name: string;
|
|
249
|
+
parentId?: string; // adjacency tree, max-depth 8
|
|
250
|
+
createdAt: string;
|
|
251
|
+
createdBy?: string;
|
|
252
|
+
metadata: Record<string, unknown>;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
interface Principal {
|
|
256
|
+
id: string;
|
|
257
|
+
tenantId: string;
|
|
258
|
+
kind: 'user' | 'team' | 'group' | 'agent' | 'service' | 'everyone';
|
|
259
|
+
externalId?: string; // maps to userId / agentId / teamId on entries
|
|
260
|
+
displayName?: string;
|
|
261
|
+
createdAt: string;
|
|
262
|
+
metadata: Record<string, unknown>;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
interface AuthzTuple {
|
|
266
|
+
tenantId: string;
|
|
267
|
+
subjectKind: PrincipalKind;
|
|
268
|
+
subjectId: string;
|
|
269
|
+
subjectRelation?: string; // for usersets like "members of team-eng"
|
|
270
|
+
relation: 'viewer' | 'editor' | 'owner' | string;
|
|
271
|
+
objectKind: 'namespace'; // v1 only supports namespace-level grants
|
|
272
|
+
objectId: string;
|
|
273
|
+
createdAt: string;
|
|
274
|
+
createdBy?: string;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
interface AuthzPlan {
|
|
278
|
+
tenantId: string;
|
|
279
|
+
visibleNamespaceIds: string[]; // resolved + transitively expanded
|
|
280
|
+
visibleEntryOverrides: string[]; // reserved for v1.1
|
|
281
|
+
revision: string; // bumps on every tuple/principal/membership mutation
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Built-in relation rewrite: `owner ⊇ editor ⊇ viewer`. Adding new relations
|
|
286
|
+
is a code change, not a schema migration.
|
|
287
|
+
|
|
288
|
+
In-process API: `Memory.authz` exposes `AuthzStore` with `createNamespace`,
|
|
289
|
+
`upsertPrincipal`, `addMember`, `writeTuple`, `deleteTuple`, `buildAuthzPlan`
|
|
290
|
+
etc. Sidecar / HTTP API: see [`http-api.md`](./http-api.md) §ReBAC Admin.
|
|
291
|
+
|
|
226
292
|
## MemoryOptions Reference
|
|
227
293
|
|
|
228
294
|
> **Embedding is internal** — pyx-memory uses `LocalEmbeddingProvider` with BGE-M3 (1024d) automatically. You do NOT pass an `embedder` option.
|