@oortmem/core 0.2.0 → 0.3.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.
@@ -0,0 +1,168 @@
1
+ /**
2
+ * The Oort export format (docs/export-format.md): JSON Lines, a manifest line,
3
+ * then one memory per line. Version 2 (written by default) adds `address`,
4
+ * `author` and `provenance` to every line and keeps `scope` on Planet
5
+ * memories; version 1 has `scope` only and can hold Planet memories only.
6
+ * Readers accept both, version 1 forever.
7
+ */
8
+ import { addressOf, authorOf, canonicalMemory, planetOf, resolveAddressing, scopeOf, } from './address.js';
9
+ /** The version `oort export` writes. */
10
+ export const EXPORT_FORMAT_VERSION = 2;
11
+ /** Raised for a memory a format version cannot hold, or a file that is not a readable export. */
12
+ export class ExportFormatError extends Error {
13
+ constructor(message) {
14
+ super(message);
15
+ this.name = 'ExportFormatError';
16
+ }
17
+ }
18
+ const base64 = (v) => Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString('base64');
19
+ /** True when version 1 can hold `m`: a Planet memory whose author is its scope's agent. */
20
+ export function fitsVersion1(m) {
21
+ const author = authorOf(m);
22
+ return (addressOf(m).level === 'user' &&
23
+ author.accountRef === undefined &&
24
+ author.agentId === m.scope.agentId &&
25
+ (m.provenance === undefined || m.provenance === null));
26
+ }
27
+ /**
28
+ * One memory as an export line of `version`: the embedding as base64 of its
29
+ * int8 bytes. Version 2 always writes `address`, `author` and `provenance`
30
+ * (null when none), and `scope` for Planet memories only.
31
+ */
32
+ export function exportLine(m, version) {
33
+ const { embedding, address: _a, author: _w, provenance, scope, id, ...rest } = m;
34
+ const tail = embedding === undefined ? {} : { embedding: base64(embedding) };
35
+ if (version === 1) {
36
+ if (!fitsVersion1(m)) {
37
+ throw new ExportFormatError(`memory ${id} is not a Planet memory; version 1 cannot hold it`);
38
+ }
39
+ return { id, scope, ...rest, ...tail };
40
+ }
41
+ const address = addressOf(m);
42
+ return {
43
+ id,
44
+ ...(address.level === 'user' && { scope }),
45
+ address,
46
+ author: authorOf(m),
47
+ provenance: provenance ?? null,
48
+ ...rest,
49
+ ...tail,
50
+ };
51
+ }
52
+ const keyOf = (x) => JSON.stringify(Object.entries(x).sort());
53
+ /** The manifest for `memories` written as `version`. */
54
+ export function exportManifest(memories, version, exportedAt = new Date()) {
55
+ const scopes = new Map();
56
+ const addresses = new Map();
57
+ for (const m of memories) {
58
+ const a = addressOf(m);
59
+ if (a.level === 'user')
60
+ scopes.set(keyOf(m.scope), m.scope);
61
+ addresses.set(keyOf(a), a);
62
+ }
63
+ const base = {
64
+ oort: 'export',
65
+ exportedAt: exportedAt.toISOString(),
66
+ memories: memories.length,
67
+ scopes: [...scopes.values()],
68
+ };
69
+ return version === 1
70
+ ? { ...base, version: 1 }
71
+ : { ...base, version: 2, addresses: [...addresses.values()] };
72
+ }
73
+ /** A whole export: the manifest line, then one line per memory, each ending in `\n`. */
74
+ export function formatExport(memories, version = EXPORT_FORMAT_VERSION, exportedAt = new Date()) {
75
+ return [
76
+ exportManifest(memories, version, exportedAt),
77
+ ...memories.map((m) => exportLine(m, version)),
78
+ ]
79
+ .map((x) => `${JSON.stringify(x)}\n`)
80
+ .join('');
81
+ }
82
+ /**
83
+ * Reads an export of version 1 or 2. A version 1 line's `scope` names the
84
+ * memory's Planet and its agent the author. Throws `ExportFormatError` for an
85
+ * unknown version, a count that does not match, or a line that is not a
86
+ * memory (with its line number, never its content).
87
+ */
88
+ export function parseExport(text, opts = {}) {
89
+ const lines = text.split('\n').filter((l) => l.trim() !== '');
90
+ const head = parseJson(lines[0], 1);
91
+ if (head?.oort !== 'export')
92
+ throw new ExportFormatError('line 1 is not an Oort export manifest');
93
+ if (head.version !== 1 && head.version !== 2) {
94
+ throw new ExportFormatError(`export version ${String(head.version)} is not supported`);
95
+ }
96
+ const version = head.version;
97
+ if (head.memories !== lines.length - 1) {
98
+ throw new ExportFormatError(`the manifest counts ${String(head.memories)} memories, the file has ${lines.length - 1}`);
99
+ }
100
+ const memories = lines.slice(1).map((l, i) => {
101
+ const n = i + 2;
102
+ const raw = parseJson(l, n);
103
+ if (typeof raw !== 'object' || raw === null || typeof raw['id'] !== 'string') {
104
+ throw new ExportFormatError(`line ${n} is not a memory`);
105
+ }
106
+ try {
107
+ return readLine(raw, version, opts);
108
+ }
109
+ catch (e) {
110
+ if (e instanceof RangeError || e instanceof TypeError) {
111
+ throw new ExportFormatError(`line ${n}: ${e.message}`);
112
+ }
113
+ throw e;
114
+ }
115
+ });
116
+ return { version, manifest: head, memories };
117
+ }
118
+ function parseJson(line, n) {
119
+ try {
120
+ return JSON.parse(line ?? '');
121
+ }
122
+ catch {
123
+ throw new ExportFormatError(`line ${n} is not JSON`);
124
+ }
125
+ }
126
+ function readLine(raw, version, opts) {
127
+ const { embedding, address: rawAddress, author: rawAuthor, provenance, scope: rawScope, supersedesHidden: _sh, supersededByHidden: _bh, ...rest } = raw;
128
+ let address;
129
+ let author;
130
+ if (version === 1) {
131
+ const scope = rawScope;
132
+ if (typeof scope !== 'object' || scope === null)
133
+ throw new RangeError('a version 1 line needs scope');
134
+ address = planetOf(scope);
135
+ author = { agentId: scope.agentId };
136
+ }
137
+ else {
138
+ ({ address, author } = checked(rawAddress, rawAuthor, rawScope));
139
+ }
140
+ if (opts.planet !== undefined && address.level === 'user') {
141
+ address = { ...address, orgId: opts.planet.orgId, customerId: opts.planet.customerId };
142
+ }
143
+ const m = {
144
+ ...rest,
145
+ scope: scopeOf(address, author),
146
+ address,
147
+ author,
148
+ ...(provenance !== undefined && provenance !== null && { provenance }),
149
+ ...(typeof embedding === 'string' && {
150
+ embedding: Int8Array.from(Buffer.from(embedding, 'base64')),
151
+ }),
152
+ };
153
+ return canonicalMemory(m);
154
+ }
155
+ /** A version 2 line's address and author, checked, with its scope (when present) matching them. */
156
+ function checked(rawAddress, rawAuthor, rawScope) {
157
+ if (rawAddress === undefined || rawAuthor === undefined) {
158
+ throw new RangeError('a version 2 line needs address and author');
159
+ }
160
+ const probe = { address: rawAddress, author: rawAuthor };
161
+ const want = scopeOf(probe.address, probe.author);
162
+ const { address, author } = resolveAddressing({
163
+ ...probe,
164
+ scope: rawScope === undefined ? want : rawScope,
165
+ });
166
+ return { address, author };
167
+ }
168
+ //# sourceMappingURL=export-format.js.map
package/dist/grant.d.ts CHANGED
@@ -1,6 +1,10 @@
1
+ import { type Address, type Author, type NodeRef } from './address.js';
1
2
  import type { ScopeFilter } from './adapters.js';
2
- import type { Scope } from './model.js';
3
- /** Who a grant acts for: one (org, customer, agent). Grants never span principals. */
3
+ import type { Memory, Scope } from './model.js';
4
+ /**
5
+ * Who a grant acts for: an account (`orgId`, `customerId`) and the agent
6
+ * acting for it. Cursors belong to the principal.
7
+ */
4
8
  export interface Principal {
5
9
  orgId: string;
6
10
  customerId: string;
@@ -11,30 +15,54 @@ export interface Principal {
11
15
  * no project) or `'all'`, every project of the principal plus global.
12
16
  */
13
17
  export type GrantProjects = readonly (string | null)[] | 'all';
14
- /** One side (read or write) of a grant, as stores apply it. */
18
+ /**
19
+ * One side (read or write) of a pre-level grant: the principal's Planet,
20
+ * written by the principal's agent, in `projectIds`.
21
+ * @deprecated Stores read `Grant.readNodes` and `Grant.writeNodes`.
22
+ */
15
23
  export interface GrantFilter extends Readonly<Principal> {
16
24
  readonly projectIds: GrantProjects;
17
25
  }
26
+ /**
27
+ * One node a grant covers (CON-61): the node, the project bindings inside it,
28
+ * and optionally one author. Matching is structural and exact on every field
29
+ * (never a prefix). `authorAgentId` restricts the pattern to memories that
30
+ * agent wrote; the pre-level constructors (`localGrant`, `principalGrant`)
31
+ * set it to the principal's agent, so they see exactly what they saw before
32
+ * levels existed.
33
+ */
34
+ export interface NodePattern {
35
+ readonly node: NodeRef;
36
+ readonly projectIds: GrantProjects;
37
+ readonly authorAgentId?: string;
38
+ }
18
39
  /**
19
40
  * The permission every `MemoryStore` method takes. Opaque: built only by
20
- * `localGrant` or `principalGrant`, frozen, and nominal, so a raw
41
+ * `localGrant`, `principalGrant` or `nodeGrant`, frozen, and nominal, so a raw
21
42
  * `ScopeFilter` (or any look-alike object) is rejected by the type checker
22
43
  * and, for JavaScript callers, by `assertGrant` inside every store.
23
44
  *
24
- * `read` bounds what can be seen (`get`, `lexical`, `dense`, `list`,
25
- * `coldGet`, `recordAccess`); `write` bounds what can change (`upsert`,
26
- * `forget`, `setTier`, `expireMany`, `coldPut`). Cursors belong to the
27
- * principal. Anything outside a grant behaves exactly like something that
45
+ * `readNodes` bounds what can be seen (`get`, `lexical`, `dense`, `list`,
46
+ * `coldGet`, `recordAccess`); `writeNodes` bounds what can change (`upsert`,
47
+ * `forget`, `setTier`, `expireMany`, `coldPut`). A memory is covered when
48
+ * any pattern of the side covers its address and author. Cursors belong to
49
+ * the principal. Anything outside a grant behaves exactly like something that
28
50
  * does not exist.
29
51
  */
30
52
  export declare class Grant {
31
53
  #private;
32
54
  readonly principal: Readonly<Principal>;
55
+ /** @deprecated The pre-level view of `readNodes`; see `GrantFilter`. */
33
56
  readonly read: GrantFilter;
57
+ /** @deprecated The pre-level view of `writeNodes`; see `GrantFilter`. */
34
58
  readonly write: GrantFilter;
59
+ readonly readNodes: readonly NodePattern[];
60
+ readonly writeNodes: readonly NodePattern[];
35
61
  private constructor();
36
62
  /** @internal Use `localGrant` or `principalGrant`. */
37
63
  static issue(principal: Principal, read: GrantProjects, write: GrantProjects): Grant;
64
+ /** @internal Use `nodeGrant`. */
65
+ static issueNodes(principal: Principal, read: readonly NodePattern[], write: readonly NodePattern[]): Grant;
38
66
  /** True for a real grant (not a copy, not a look-alike). */
39
67
  static is(x: unknown): x is Grant;
40
68
  }
@@ -48,12 +76,44 @@ export declare function localGrant(read: ScopeFilter, opts?: {
48
76
  }): Grant;
49
77
  /** Every project of `p` (and its global memories), for read and write: maintenance such as export. */
50
78
  export declare function principalGrant(p: Principal): Grant;
79
+ /**
80
+ * A grant over explicit node patterns, possibly across owners and levels:
81
+ * `read` bounds what is seen, `write` (default: `read`) what may change.
82
+ * Trusted: whoever builds one has already decided the principal may use
83
+ * every node it names (local configuration, or membership checked by a
84
+ * control plane). Nothing in the engine builds one for its own callers yet:
85
+ * the CLI, hooks and MCP server keep the pre-level grants.
86
+ */
87
+ export declare function nodeGrant(opts: {
88
+ principal: Principal;
89
+ read: readonly NodePattern[];
90
+ write?: readonly NodePattern[];
91
+ }): Grant;
51
92
  /** Rejects anything that is not a real `Grant`; every store calls it first. */
52
93
  export declare function assertGrant(g: unknown): asserts g is Grant;
53
- /** True iff a memory with `scope` lies inside `f`. Extra scope fields do not widen or narrow it. */
94
+ /**
95
+ * True iff a memory with `scope` lies inside `f`. Extra scope fields do not widen or narrow it.
96
+ * @deprecated Use `grantReads` / `grantWrites`.
97
+ */
54
98
  export declare function filterCovers(f: GrantFilter, scope: Scope): boolean;
99
+ /** True iff `p` covers a memory at `address` written by `author`. */
100
+ export declare function patternCovers(p: NodePattern, address: Address, author: Author): boolean;
101
+ /** True iff `g` may read `m` (by its address and author). */
102
+ export declare const grantReads: (g: Grant, m: Pick<Memory, 'scope' | 'address' | 'author'>) => boolean;
103
+ /** True iff `g` may change `m` (by its address and author). */
104
+ export declare const grantWrites: (g: Grant, m: Pick<Memory, 'scope' | 'address' | 'author'>) => boolean;
105
+ /** True iff `g` may read a Planet memory with `scope` (`scope.agentId` as its author). */
55
106
  export declare const canRead: (g: Grant, scope: Scope) => boolean;
107
+ /** True iff `g` may change a Planet memory with `scope` (`scope.agentId` as its author). */
56
108
  export declare const canWrite: (g: Grant, scope: Scope) => boolean;
109
+ /**
110
+ * Two records occupy the same place: the same address, the same author and
111
+ * the same scope (extra scope fields included). A write that would change any
112
+ * of them for an existing id is refused.
113
+ */
114
+ export declare function sameRecordAddress(a: Pick<Memory, 'scope' | 'address' | 'author'>, b: Pick<Memory, 'scope' | 'address' | 'author'>): boolean;
115
+ /** The Planet node of a principal: what pre-level grants cover. */
116
+ export declare function planetNode(p: Pick<Principal, 'orgId' | 'customerId'>): NodeRef;
57
117
  /**
58
118
  * The key a store keeps a cursor under. The local principal (the CLI's own,
59
119
  * `local`/`self`/`claude-code`) keeps the bare key, so cursors written before
package/dist/grant.js CHANGED
@@ -1,33 +1,48 @@
1
+ import { addressOf, assertNode, authorOf, nodeKey, nodeOf, planetOf, sameAddress, sameAuthor, } from './address.js';
1
2
  /**
2
3
  * The permission every `MemoryStore` method takes. Opaque: built only by
3
- * `localGrant` or `principalGrant`, frozen, and nominal, so a raw
4
+ * `localGrant`, `principalGrant` or `nodeGrant`, frozen, and nominal, so a raw
4
5
  * `ScopeFilter` (or any look-alike object) is rejected by the type checker
5
6
  * and, for JavaScript callers, by `assertGrant` inside every store.
6
7
  *
7
- * `read` bounds what can be seen (`get`, `lexical`, `dense`, `list`,
8
- * `coldGet`, `recordAccess`); `write` bounds what can change (`upsert`,
9
- * `forget`, `setTier`, `expireMany`, `coldPut`). Cursors belong to the
10
- * principal. Anything outside a grant behaves exactly like something that
8
+ * `readNodes` bounds what can be seen (`get`, `lexical`, `dense`, `list`,
9
+ * `coldGet`, `recordAccess`); `writeNodes` bounds what can change (`upsert`,
10
+ * `forget`, `setTier`, `expireMany`, `coldPut`). A memory is covered when
11
+ * any pattern of the side covers its address and author. Cursors belong to
12
+ * the principal. Anything outside a grant behaves exactly like something that
11
13
  * does not exist.
12
14
  */
13
15
  export class Grant {
14
16
  #issued = true;
15
17
  principal;
18
+ /** @deprecated The pre-level view of `readNodes`; see `GrantFilter`. */
16
19
  read;
20
+ /** @deprecated The pre-level view of `writeNodes`; see `GrantFilter`. */
17
21
  write;
18
- constructor(principal, read, write) {
22
+ readNodes;
23
+ writeNodes;
24
+ constructor(principal, readNodes, writeNodes) {
19
25
  this.principal = Object.freeze({ ...principal });
20
- this.read = Object.freeze({ ...principal, projectIds: freezeProjects(read) });
21
- this.write = Object.freeze({ ...principal, projectIds: freezeProjects(write) });
26
+ this.readNodes = Object.freeze(readNodes.map(freezePattern));
27
+ this.writeNodes = Object.freeze(writeNodes.map(freezePattern));
28
+ this.read = Object.freeze({
29
+ ...principal,
30
+ projectIds: legacyProjects(principal, this.readNodes),
31
+ });
32
+ this.write = Object.freeze({
33
+ ...principal,
34
+ projectIds: legacyProjects(principal, this.writeNodes),
35
+ });
22
36
  Object.freeze(this);
23
37
  }
24
38
  /** @internal Use `localGrant` or `principalGrant`. */
25
39
  static issue(principal, read, write) {
26
- for (const k of ['orgId', 'customerId', 'agentId']) {
27
- if (typeof principal[k] !== 'string' || principal[k] === '') {
28
- throw new RangeError(`grant: ${k} must be a non-empty string`);
29
- }
30
- }
40
+ checkPrincipal(principal);
41
+ return new Grant(principal, [ownPlanet(principal, read)], [ownPlanet(principal, write)]);
42
+ }
43
+ /** @internal Use `nodeGrant`. */
44
+ static issueNodes(principal, read, write) {
45
+ checkPrincipal(principal);
31
46
  return new Grant(principal, read, write);
32
47
  }
33
48
  /** True for a real grant (not a copy, not a look-alike). */
@@ -35,6 +50,51 @@ export class Grant {
35
50
  return typeof x === 'object' && x !== null && #issued in x;
36
51
  }
37
52
  }
53
+ function checkPrincipal(principal) {
54
+ for (const k of ['orgId', 'customerId', 'agentId']) {
55
+ if (typeof principal[k] !== 'string' || principal[k] === '') {
56
+ throw new RangeError(`grant: ${k} must be a non-empty string`);
57
+ }
58
+ }
59
+ }
60
+ /** The pre-level grant side: the principal's own Planet, written by its own agent. */
61
+ function ownPlanet(p, projectIds) {
62
+ return {
63
+ node: { level: 'user', orgId: p.orgId, customerId: p.customerId },
64
+ projectIds,
65
+ authorAgentId: p.agentId,
66
+ };
67
+ }
68
+ function freezePattern(p) {
69
+ if (typeof p !== 'object' || p === null)
70
+ throw new TypeError('grant: a node pattern must be an object');
71
+ assertNode(p.node);
72
+ const { level: _l, ...rest } = p.node;
73
+ const node = Object.freeze({ level: p.node.level, ...rest });
74
+ if (p.authorAgentId !== undefined &&
75
+ (typeof p.authorAgentId !== 'string' || p.authorAgentId === '')) {
76
+ throw new RangeError('grant: authorAgentId must be a non-empty string');
77
+ }
78
+ return Object.freeze({
79
+ node,
80
+ projectIds: freezeProjects(p.projectIds),
81
+ ...(p.authorAgentId !== undefined && { authorAgentId: p.authorAgentId }),
82
+ });
83
+ }
84
+ /**
85
+ * The projects of `patterns` that a pre-level reader may assume: those of
86
+ * patterns naming exactly the principal's Planet, restricted to its agent.
87
+ * Never wider than the patterns themselves.
88
+ */
89
+ function legacyProjects(p, patterns) {
90
+ const own = nodeKey({ level: 'user', orgId: p.orgId, customerId: p.customerId });
91
+ const mine = patterns.filter((x) => x.authorAgentId === p.agentId && nodeKey(x.node) === own);
92
+ if (mine.some((x) => x.projectIds === 'all'))
93
+ return 'all';
94
+ return Object.freeze([
95
+ ...new Set(mine.flatMap((x) => x.projectIds)),
96
+ ]);
97
+ }
38
98
  function freezeProjects(p) {
39
99
  if (p === 'all')
40
100
  return 'all';
@@ -70,21 +130,70 @@ export function localGrant(read, opts = {}) {
70
130
  export function principalGrant(p) {
71
131
  return Grant.issue(p, 'all', 'all');
72
132
  }
133
+ /**
134
+ * A grant over explicit node patterns, possibly across owners and levels:
135
+ * `read` bounds what is seen, `write` (default: `read`) what may change.
136
+ * Trusted: whoever builds one has already decided the principal may use
137
+ * every node it names (local configuration, or membership checked by a
138
+ * control plane). Nothing in the engine builds one for its own callers yet:
139
+ * the CLI, hooks and MCP server keep the pre-level grants.
140
+ */
141
+ export function nodeGrant(opts) {
142
+ if (!Array.isArray(opts.read) || (opts.write !== undefined && !Array.isArray(opts.write))) {
143
+ throw new TypeError('nodeGrant: read and write must be arrays of node patterns');
144
+ }
145
+ return Grant.issueNodes(opts.principal, opts.read, opts.write ?? opts.read);
146
+ }
73
147
  /** Rejects anything that is not a real `Grant`; every store calls it first. */
74
148
  export function assertGrant(g) {
75
149
  if (!Grant.is(g)) {
76
- throw new TypeError('MemoryStore methods take a Grant (localGrant or principalGrant)');
150
+ throw new TypeError('MemoryStore methods take a Grant (localGrant, principalGrant or nodeGrant)');
77
151
  }
78
152
  }
79
- /** True iff a memory with `scope` lies inside `f`. Extra scope fields do not widen or narrow it. */
153
+ /**
154
+ * True iff a memory with `scope` lies inside `f`. Extra scope fields do not widen or narrow it.
155
+ * @deprecated Use `grantReads` / `grantWrites`.
156
+ */
80
157
  export function filterCovers(f, scope) {
81
158
  return (scope.orgId === f.orgId &&
82
159
  scope.customerId === f.customerId &&
83
160
  scope.agentId === f.agentId &&
84
161
  (f.projectIds === 'all' || f.projectIds.includes(scope.projectId ?? null)));
85
162
  }
86
- export const canRead = (g, scope) => filterCovers(g.read, scope);
87
- export const canWrite = (g, scope) => filterCovers(g.write, scope);
163
+ /** True iff `p` covers a memory at `address` written by `author`. */
164
+ export function patternCovers(p, address, author) {
165
+ return (nodeKey(nodeOf(address)) === nodeKey(p.node) &&
166
+ (p.projectIds === 'all' || p.projectIds.includes(address.projectId ?? null)) &&
167
+ (p.authorAgentId === undefined || p.authorAgentId === author.agentId));
168
+ }
169
+ function covers(patterns, m) {
170
+ const address = addressOf(m);
171
+ const author = authorOf(m);
172
+ return patterns.some((p) => patternCovers(p, address, author));
173
+ }
174
+ /** True iff `g` may read `m` (by its address and author). */
175
+ export const grantReads = (g, m) => covers(g.readNodes, m);
176
+ /** True iff `g` may change `m` (by its address and author). */
177
+ export const grantWrites = (g, m) => covers(g.writeNodes, m);
178
+ /** True iff `g` may read a Planet memory with `scope` (`scope.agentId` as its author). */
179
+ export const canRead = (g, scope) => grantReads(g, { scope });
180
+ /** True iff `g` may change a Planet memory with `scope` (`scope.agentId` as its author). */
181
+ export const canWrite = (g, scope) => grantWrites(g, { scope });
182
+ /**
183
+ * Two records occupy the same place: the same address, the same author and
184
+ * the same scope (extra scope fields included). A write that would change any
185
+ * of them for an existing id is refused.
186
+ */
187
+ export function sameRecordAddress(a, b) {
188
+ return (sameAddress(addressOf(a), addressOf(b)) &&
189
+ sameAuthor(authorOf(a), authorOf(b)) &&
190
+ sameScope(a.scope, b.scope));
191
+ }
192
+ /** The Planet node of a principal: what pre-level grants cover. */
193
+ export function planetNode(p) {
194
+ const { projectId: _p, ...node } = planetOf({ ...p, agentId: '-' });
195
+ return node;
196
+ }
88
197
  /**
89
198
  * The key a store keeps a cursor under. The local principal (the CLI's own,
90
199
  * `local`/`self`/`claude-code`) keeps the bare key, so cursors written before
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
- export declare const VERSION = "0.2.0";
1
+ export declare const VERSION = "0.3.0";
2
2
  export * from './model.js';
3
+ export * from './address.js';
4
+ export * from './export-format.js';
3
5
  export * from './scope.js';
4
6
  export * from './config.js';
5
7
  export * from './activation.js';
@@ -14,6 +16,7 @@ export * from './recall.js';
14
16
  export * from './relevance.js';
15
17
  export * from './observe.js';
16
18
  export * from './temporal.js';
19
+ export { chronoLoaded, mayHaveDateCue, preloadChrono } from './date-cue.js';
17
20
  export * from './volatile.js';
18
21
  export * from './scrub.js';
19
22
  export * from './cap.js';
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
- export const VERSION = '0.2.0';
1
+ export const VERSION = '0.3.0';
2
2
  export * from './model.js';
3
+ export * from './address.js';
4
+ export * from './export-format.js';
3
5
  export * from './scope.js';
4
6
  export * from './config.js';
5
7
  export * from './activation.js';
@@ -14,6 +16,7 @@ export * from './recall.js';
14
16
  export * from './relevance.js';
15
17
  export * from './observe.js';
16
18
  export * from './temporal.js';
19
+ export { chronoLoaded, mayHaveDateCue, preloadChrono } from './date-cue.js';
17
20
  export * from './volatile.js';
18
21
  export * from './scrub.js';
19
22
  export * from './cap.js';
@@ -4,6 +4,7 @@
4
4
  * can require an empty list and a test can prove a broken store is caught.
5
5
  * No vitest here; `store-conformance.ts` wires these into `it` blocks.
6
6
  */
7
+ import { type Address, type Author } from './address.js';
7
8
  import type { MemoryStore } from './adapters.js';
8
9
  import { type Grant } from './grant.js';
9
10
  import type { Memory, Scope } from './model.js';
@@ -11,6 +12,12 @@ import type { Memory, Scope } from './model.js';
11
12
  export declare function isoMemory(id: string, scope: Scope, marker: string, embedding?: Int8Array): Memory;
12
13
  /** The narrowest grant for one scope (its principal and its one project or global). */
13
14
  export declare function soleGrant(s: Scope): Grant;
15
+ /** The narrowest grant for one memory at any level: its node, its one project, its author. */
16
+ export declare function soleGrantFor(m: Pick<Memory, 'scope' | 'address' | 'author'>): Grant;
17
+ /** `m` moved to `scope`: its address and author dropped, so the scope's Planet is the address. */
18
+ export declare function movedTo(m: Memory, scope: Scope): Memory;
19
+ /** A memory at `address`, written by `author`, for the isolation checks. */
20
+ export declare function isoAt(id: string, address: Address, author: Author, marker: string, embedding?: Int8Array): Memory;
14
21
  /** The scopes the checks use: the caller, and victims that differ from it in one coordinate each. */
15
22
  export declare const ISO_SCOPES: {
16
23
  mine: {
@@ -49,6 +56,16 @@ export declare const ISO_SCOPES: {
49
56
  projectId: string;
50
57
  };
51
58
  };
59
+ /**
60
+ * Victims at the other levels, each next to the caller's own Planet: a Planet
61
+ * memory another of the caller's agents wrote, the caller's agent's own Star,
62
+ * an Orbit, a Flyby, a Constellation and a Galaxy root of an organisation, and
63
+ * one of the caller's own owner. A pre-level grant sees none of them.
64
+ */
65
+ export declare const ISO_LEVELS: Record<string, {
66
+ address: Address;
67
+ author: Author;
68
+ }>;
52
69
  /**
53
70
  * Every `MemoryStore` method, called with a grant for `ISO_SCOPES.mine`
54
71
  * against memories (warm and cold) in each other scope: nothing is seen,
@@ -73,5 +90,11 @@ export declare function seededRng(seed: number): {
73
90
  * order: SQLite FTS5's bm25 uses table-wide term statistics, so another
74
91
  * tenant's text shifts scores (a known side channel; see the PR notes).
75
92
  * Every other result is compared exactly, dense scores included.
93
+ *
94
+ * `multiNode`: A's grant spans its Planet, its agent's Star and a
95
+ * Constellation of another owner; B's spans its own Planet and a sibling
96
+ * Constellation. Same property.
76
97
  */
77
- export declare function nonInterference(make: () => Promise<MemoryStore>, seed: number, steps: number): Promise<string | null>;
98
+ export declare function nonInterference(make: () => Promise<MemoryStore>, seed: number, steps: number, opts?: {
99
+ multiNode?: boolean;
100
+ }): Promise<string | null>;