@contextableai/openclaw-memory-rebac 0.3.1 → 0.3.3

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/README.md CHANGED
@@ -168,7 +168,10 @@ When enabled (default: `true`), the plugin captures the last N messages from eac
168
168
  The SpiceDB schema defines four object types:
169
169
 
170
170
  ```
171
- definition person {}
171
+ definition person {
172
+ relation agent: agent
173
+ permission represents = agent
174
+ }
172
175
 
173
176
  definition agent {
174
177
  relation owner: person
@@ -186,7 +189,8 @@ definition memory_fragment {
186
189
  relation involves: person | agent
187
190
  relation shared_by: person | agent
188
191
 
189
- permission view = involves + shared_by + source_group->access
192
+ // involves->represents: if a person is involved, their agent can also view
193
+ permission view = involves + shared_by + source_group->access + involves->represents
190
194
  permission delete = shared_by
191
195
  }
192
196
  ```
package/dist/cli.js CHANGED
@@ -70,31 +70,42 @@ export function registerCommands(cmd, ctx) {
70
70
  limit: parseInt(opts.limit),
71
71
  })
72
72
  : [];
73
- // Owner-aware recall: if agent, resolve owner and find involves fragments
74
- let ownerResults = [];
75
- if (subject.type === "agent" && backend.getFragmentsByIds) {
73
+ // Fragment-based recall via involves/view permission
74
+ let fragmentResults = [];
75
+ if (backend.getFragmentsByIds) {
76
76
  try {
77
- const ownerId = await lookupAgentOwner(spicedb, subject.id, token);
78
- if (ownerId) {
79
- const ownerSubject = { type: "person", id: ownerId };
80
- const viewableIds = await lookupViewableFragments(spicedb, ownerSubject, token);
77
+ // Determine the person to search as
78
+ let personSubject;
79
+ if (subject.type === "person") {
80
+ // Direct person search look up fragments they can view
81
+ personSubject = subject;
82
+ }
83
+ else if (subject.type === "agent") {
84
+ // Agent search — resolve owner identity first
85
+ const ownerId = await lookupAgentOwner(spicedb, subject.id, token);
86
+ if (ownerId) {
87
+ personSubject = { type: "person", id: ownerId };
88
+ }
89
+ }
90
+ if (personSubject) {
91
+ const viewableIds = await lookupViewableFragments(spicedb, personSubject, token);
81
92
  if (viewableIds.length > 0) {
82
93
  const groupResultIds = new Set(groupResults.map((r) => r.uuid));
83
94
  const newIds = viewableIds.filter((id) => !groupResultIds.has(id));
84
95
  if (newIds.length > 0) {
85
- ownerResults = await backend.getFragmentsByIds(newIds);
96
+ fragmentResults = await backend.getFragmentsByIds(newIds);
86
97
  }
87
98
  }
88
- if (ownerResults.length > 0) {
89
- console.log(` + ${ownerResults.length} result(s) via owner identity (person:${ownerId})`);
99
+ if (fragmentResults.length > 0) {
100
+ console.log(` + ${fragmentResults.length} result(s) via involves (${personSubject.type}:${personSubject.id})`);
90
101
  }
91
102
  }
92
103
  }
93
104
  catch {
94
- // Owner lookup failed — proceed with group results only
105
+ // Fragment lookup failed — proceed with group results only
95
106
  }
96
107
  }
97
- const allResults = [...groupResults, ...ownerResults];
108
+ const allResults = [...groupResults, ...fragmentResults];
98
109
  if (allResults.length === 0) {
99
110
  console.log("No results found.");
100
111
  return;
@@ -552,14 +563,23 @@ export function registerCommands(cmd, ctx) {
552
563
  .argument("<person-id>", "Owner person ID")
553
564
  .action(async (agentId, personId) => {
554
565
  try {
555
- await spicedb.writeRelationships([{
566
+ await spicedb.writeRelationships([
567
+ {
556
568
  resourceType: "agent",
557
569
  resourceId: agentId,
558
570
  relation: "owner",
559
571
  subjectType: "person",
560
572
  subjectId: personId,
561
- }]);
562
- console.log(`Linked agent:${agentId} → person:${personId}`);
573
+ },
574
+ {
575
+ resourceType: "person",
576
+ resourceId: personId,
577
+ relation: "agent",
578
+ subjectType: "agent",
579
+ subjectId: agentId,
580
+ },
581
+ ]);
582
+ console.log(`Linked agent:${agentId} ↔ person:${personId}`);
563
583
  }
564
584
  catch (err) {
565
585
  console.error(`Failed to write identity link: ${err instanceof Error ? err.message : String(err)}`);
@@ -579,14 +599,23 @@ export function registerCommands(cmd, ctx) {
579
599
  console.log(`No owner link found for agent:${agentId}`);
580
600
  return;
581
601
  }
582
- await spicedb.deleteRelationships([{
602
+ await spicedb.deleteRelationships([
603
+ {
583
604
  resourceType: "agent",
584
605
  resourceId: agentId,
585
606
  relation: "owner",
586
607
  subjectType: "person",
587
608
  subjectId: ownerId,
588
- }]);
589
- console.log(`Unlinked agent:${agentId} (was → person:${ownerId})`);
609
+ },
610
+ {
611
+ resourceType: "person",
612
+ resourceId: ownerId,
613
+ relation: "agent",
614
+ subjectType: "agent",
615
+ subjectId: agentId,
616
+ },
617
+ ]);
618
+ console.log(`Unlinked agent:${agentId} (was ↔ person:${ownerId})`);
590
619
  }
591
620
  catch (err) {
592
621
  console.error(`Failed to remove identity link: ${err instanceof Error ? err.message : String(err)}`);
package/dist/index.js CHANGED
@@ -686,16 +686,25 @@ const rebacMemoryPlugin = {
686
686
  // Write agent → owner relationships from identities config
687
687
  for (const [agentId, personId] of Object.entries(cfg.identities)) {
688
688
  try {
689
- const token = await spicedb.writeRelationships([{
689
+ const token = await spicedb.writeRelationships([
690
+ {
690
691
  resourceType: "agent",
691
692
  resourceId: agentId,
692
693
  relation: "owner",
693
694
  subjectType: "person",
694
695
  subjectId: personId,
695
- }]);
696
+ },
697
+ {
698
+ resourceType: "person",
699
+ resourceId: personId,
700
+ relation: "agent",
701
+ subjectType: "agent",
702
+ subjectId: agentId,
703
+ },
704
+ ]);
696
705
  if (token)
697
706
  defaultState.lastWriteToken = token;
698
- api.logger.info(`openclaw-memory-rebac: linked agent:${agentId} person:${personId}`);
707
+ api.logger.info(`openclaw-memory-rebac: linked agent:${agentId} person:${personId}`);
699
708
  }
700
709
  catch (err) {
701
710
  api.logger.warn(`openclaw-memory-rebac: failed to write owner for agent:${agentId}: ${err}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contextableai/openclaw-memory-rebac",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "OpenClaw two-layer memory plugin: SpiceDB ReBAC authorization + Graphiti knowledge graph",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/schema.zed CHANGED
@@ -1,4 +1,7 @@
1
- definition person {}
1
+ definition person {
2
+ relation agent: agent
3
+ permission represents = agent
4
+ }
2
5
 
3
6
  definition agent {
4
7
  relation owner: person
@@ -16,8 +19,9 @@ definition memory_fragment {
16
19
  relation involves: person | agent
17
20
  relation shared_by: person | agent
18
21
 
19
- // Can view if: directly involved, shared it, or have access to the source group
20
- permission view = involves + shared_by + source_group->access
22
+ // Can view if: directly involved, shared it, have access to the source group,
23
+ // or are an agent whose owner is involved (involves->agent traversal)
24
+ permission view = involves + shared_by + source_group->access + involves->represents
21
25
  // Can delete if: you shared it (owner-level control)
22
26
  permission delete = shared_by
23
27
  }