@seanhogg/builderforce-memory 2026.9.34 → 2026.10.2

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.
Files changed (97) hide show
  1. package/dist/errors/SSMError.d.ts +1 -1
  2. package/dist/errors/SSMError.d.ts.map +1 -1
  3. package/dist/errors/SSMError.js.map +1 -1
  4. package/dist/handoff/index.d.ts +5 -5
  5. package/dist/handoff/index.js +5 -5
  6. package/dist/handoff/note.d.ts +1 -1
  7. package/dist/handoff/note.js +1 -1
  8. package/dist/handoff/pack.d.ts +13 -4
  9. package/dist/handoff/pack.d.ts.map +1 -1
  10. package/dist/handoff/pack.js +31 -10
  11. package/dist/handoff/pack.js.map +1 -1
  12. package/dist/memory/MemoryStore.d.ts +120 -181
  13. package/dist/memory/MemoryStore.d.ts.map +1 -1
  14. package/dist/memory/MemoryStore.js +201 -389
  15. package/dist/memory/MemoryStore.js.map +1 -1
  16. package/dist/memory/SemanticRecall.d.ts +64 -0
  17. package/dist/memory/SemanticRecall.d.ts.map +1 -0
  18. package/dist/memory/SemanticRecall.js +188 -0
  19. package/dist/memory/SemanticRecall.js.map +1 -0
  20. package/dist/memory/records.d.ts +26 -0
  21. package/dist/memory/records.d.ts.map +1 -0
  22. package/dist/memory/records.js +71 -0
  23. package/dist/memory/records.js.map +1 -0
  24. package/dist/memory/ttlSweeper.d.ts +12 -0
  25. package/dist/memory/ttlSweeper.d.ts.map +1 -0
  26. package/dist/memory/ttlSweeper.js +28 -0
  27. package/dist/memory/ttlSweeper.js.map +1 -0
  28. package/dist/memory/types.d.ts +142 -0
  29. package/dist/memory/types.d.ts.map +1 -0
  30. package/dist/memory/types.js +6 -0
  31. package/dist/memory/types.js.map +1 -0
  32. package/dist/storage/ScopedFactRepository.d.ts +156 -0
  33. package/dist/storage/ScopedFactRepository.d.ts.map +1 -0
  34. package/dist/storage/ScopedFactRepository.js +278 -0
  35. package/dist/storage/ScopedFactRepository.js.map +1 -0
  36. package/dist/storage/factImport.d.ts +21 -0
  37. package/dist/storage/factImport.d.ts.map +1 -0
  38. package/dist/storage/factImport.js +61 -0
  39. package/dist/storage/factImport.js.map +1 -0
  40. package/dist/storage/factMutations.d.ts +46 -0
  41. package/dist/storage/factMutations.d.ts.map +1 -0
  42. package/dist/storage/factMutations.js +73 -0
  43. package/dist/storage/factMutations.js.map +1 -0
  44. package/dist/storage/factQueries.d.ts +16 -0
  45. package/dist/storage/factQueries.d.ts.map +1 -0
  46. package/dist/storage/factQueries.js +36 -0
  47. package/dist/storage/factQueries.js.map +1 -0
  48. package/dist/storage/factRetention.d.ts +30 -0
  49. package/dist/storage/factRetention.d.ts.map +1 -0
  50. package/dist/storage/factRetention.js +65 -0
  51. package/dist/storage/factRetention.js.map +1 -0
  52. package/dist/storage/fileLock.d.ts +56 -0
  53. package/dist/storage/fileLock.d.ts.map +1 -0
  54. package/dist/storage/fileLock.js +138 -0
  55. package/dist/storage/fileLock.js.map +1 -0
  56. package/dist/storage/fileTransaction.d.ts +33 -0
  57. package/dist/storage/fileTransaction.d.ts.map +1 -0
  58. package/dist/storage/fileTransaction.js +108 -0
  59. package/dist/storage/fileTransaction.js.map +1 -0
  60. package/dist/storage/idbAdapter.d.ts +95 -0
  61. package/dist/storage/idbAdapter.d.ts.map +1 -0
  62. package/dist/storage/idbAdapter.js +311 -0
  63. package/dist/storage/idbAdapter.js.map +1 -0
  64. package/dist/storage/nodeFileAdapter.d.ts +127 -0
  65. package/dist/storage/nodeFileAdapter.d.ts.map +1 -0
  66. package/dist/storage/nodeFileAdapter.js +275 -0
  67. package/dist/storage/nodeFileAdapter.js.map +1 -0
  68. package/dist/storage/syncJournal.d.ts +19 -0
  69. package/dist/storage/syncJournal.d.ts.map +1 -0
  70. package/dist/storage/syncJournal.js +46 -0
  71. package/dist/storage/syncJournal.js.map +1 -0
  72. package/dist/storage/types.d.ts +207 -0
  73. package/dist/storage/types.d.ts.map +1 -0
  74. package/dist/storage/types.js +90 -0
  75. package/dist/storage/types.js.map +1 -0
  76. package/package.json +3 -3
  77. package/src/errors/SSMError.ts +1 -0
  78. package/src/handoff/handoff.test.ts +52 -2
  79. package/src/handoff/index.ts +5 -5
  80. package/src/handoff/note.ts +1 -1
  81. package/src/handoff/pack.ts +32 -9
  82. package/src/memory/MemoryStore.ts +245 -525
  83. package/src/memory/SemanticRecall.ts +204 -0
  84. package/src/memory/records.ts +79 -0
  85. package/src/memory/ttlSweeper.ts +29 -0
  86. package/src/memory/types.ts +149 -0
  87. package/src/storage/ScopedFactRepository.ts +373 -0
  88. package/src/storage/factImport.ts +73 -0
  89. package/src/storage/factMutations.ts +106 -0
  90. package/src/storage/factQueries.ts +41 -0
  91. package/src/storage/factRetention.ts +82 -0
  92. package/src/storage/fileLock.ts +162 -0
  93. package/src/storage/fileTransaction.ts +116 -0
  94. package/src/storage/idbAdapter.ts +373 -0
  95. package/src/storage/nodeFileAdapter.ts +331 -0
  96. package/src/storage/syncJournal.ts +47 -0
  97. package/src/storage/types.ts +262 -0
@@ -0,0 +1,275 @@
1
+ /**
2
+ * storage/nodeFileAdapter.ts – Durable Node.js implementation of the storage
3
+ * port, backed by a single atomically-replaced JSON document.
4
+ *
5
+ * This is the VSIX extension-host / MCP adapter the offline-memory scope (M01)
6
+ * requires: real durable storage on Node WITHOUT an in-memory IndexedDB shim.
7
+ * fake-indexeddb evaporates with the process; this adapter fsyncs a temp file
8
+ * and atomically renames it over the database file, so a crash mid-write leaves
9
+ * either the old or the new document — never a torn one.
10
+ *
11
+ * Transaction semantics (M02/M05):
12
+ * - All transactions are serialized through one promise queue, so concurrent
13
+ * writers in this process interleave at transaction granularity.
14
+ * - A read-write transaction holds an exclusive `<file>.lock` (see
15
+ * `fileLock.ts`: atomic create, stale-lock reclamation by PID liveness,
16
+ * bounded wait) across refresh + mutate + persist, so two processes can
17
+ * never both build on the same stale document and lose a write.
18
+ * - Under that lock, the file is re-read when another process changed it
19
+ * (mtime/size freshness), so cross-process writers build on each other's
20
+ * commits instead of blindly overwriting.
21
+ * - If the persist fails, the in-memory document is rolled back to the
22
+ * pre-transaction snapshot and the transaction rejects — no partial commits.
23
+ *
24
+ * Cross-process consistency: a polling watcher (default 1s, injectable)
25
+ * republishes foreign changes to subscribers, the Node analogue of the IDB
26
+ * adapter's BroadcastChannel.
27
+ *
28
+ * Node builtins are loaded indirectly (via a non-static import) so a browser
29
+ * bundle of this package never statically pulls in `node:fs`.
30
+ */
31
+ import { FileLock } from './fileLock.js';
32
+ import { FileTransaction, emptyDocument } from './fileTransaction.js';
33
+ import { MemoryStorageError, } from './types.js';
34
+ /** Load a module without a static specifier, so bundlers skip Node builtins. */
35
+ function dynamicImport(m) {
36
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func
37
+ return new Function('m', 'return import(m)')(m);
38
+ }
39
+ export class NodeFileStorageAdapter {
40
+ _filePath;
41
+ kind = 'node-file';
42
+ _fs;
43
+ _path;
44
+ _contextId;
45
+ _doc;
46
+ _stamp = null;
47
+ /** Transaction queue tail — all transacts serialize through this. */
48
+ _queue = Promise.resolve();
49
+ _watchTimer = null;
50
+ _listeners = new Set();
51
+ _watchIntervalMs;
52
+ _lock;
53
+ constructor(_filePath, fs, path, opts) {
54
+ this._filePath = _filePath;
55
+ this._fs = fs;
56
+ this._path = path;
57
+ this._contextId = opts.contextId ?? `node-${Math.random().toString(36).slice(2)}-${Date.now().toString(36)}`;
58
+ this._watchIntervalMs = opts.watchIntervalMs ?? 1000;
59
+ this._lock = new FileLock(fs, `${_filePath}.lock`, opts.lock);
60
+ this._doc = this._loadFromDisk();
61
+ }
62
+ /**
63
+ * Opens (creating if necessary) the durable file at `filePath`. Async only
64
+ * because Node builtins load indirectly; the document itself is read
65
+ * synchronously before this resolves.
66
+ */
67
+ static async open(filePath, opts = {}) {
68
+ const fs = (opts.fs ?? (await dynamicImport('node:fs')));
69
+ const path = (opts.path ?? (await dynamicImport('node:path')));
70
+ return new NodeFileStorageAdapter(filePath, fs, path, opts);
71
+ }
72
+ /** Identity of this context, stamped onto change notes and opIds. */
73
+ get contextId() {
74
+ return this._contextId;
75
+ }
76
+ /** The durable file backing this adapter. */
77
+ get filePath() {
78
+ return this._filePath;
79
+ }
80
+ // ── Boot / crash recovery ─────────────────────────────────────────────────
81
+ /**
82
+ * Reads the document from disk. Recovery order on a corrupt main file
83
+ * (killed mid-rename is impossible, but a torn fsync on exotic filesystems
84
+ * is not): try `<file>.tmp` (the last fully-fsynced candidate), then give up
85
+ * with the corrupt file preserved at `<file>.corrupt` for forensics rather
86
+ * than silently starting empty.
87
+ */
88
+ _loadFromDisk() {
89
+ const fs = this._fs;
90
+ const dir = this._path.dirname(this._filePath);
91
+ if (dir && !fs.existsSync(dir))
92
+ fs.mkdirSync(dir, { recursive: true });
93
+ const parsed = this._readDocument(this._filePath);
94
+ if (parsed)
95
+ return parsed;
96
+ if (fs.existsSync(this._filePath)) {
97
+ const tmp = this._readDocument(`${this._filePath}.tmp`);
98
+ if (tmp)
99
+ return tmp;
100
+ try {
101
+ fs.copyFileSync(this._filePath, `${this._filePath}.corrupt`);
102
+ fs.unlinkSync(this._filePath);
103
+ }
104
+ catch { /* best-effort forensics */ }
105
+ }
106
+ return emptyDocument();
107
+ }
108
+ _readDocument(path) {
109
+ const fs = this._fs;
110
+ if (!fs.existsSync(path))
111
+ return null;
112
+ let raw;
113
+ try {
114
+ raw = JSON.parse(fs.readFileSync(path, 'utf8'));
115
+ }
116
+ catch {
117
+ return null;
118
+ }
119
+ if (!raw || typeof raw !== 'object')
120
+ return null;
121
+ const doc = raw;
122
+ if (doc.formatVersion !== 1 || !Array.isArray(doc.facts))
123
+ return null;
124
+ const full = {
125
+ formatVersion: 1,
126
+ facts: doc.facts,
127
+ tombstones: Array.isArray(doc.tombstones) ? doc.tombstones : [],
128
+ journal: Array.isArray(doc.journal) ? doc.journal : [],
129
+ meta: doc.meta?.key === 'counters'
130
+ ? doc.meta
131
+ : { key: 'counters', seq: 0, revision: 0, opSeq: 0 },
132
+ };
133
+ try {
134
+ const st = fs.statSync(path);
135
+ this._stamp = { mtimeMs: st.mtimeMs, size: st.size };
136
+ }
137
+ catch { /* stamp stays null; next write re-checks */ }
138
+ return full;
139
+ }
140
+ /** Re-reads the document when another process rewrote the file. */
141
+ _refreshIfChanged() {
142
+ const fs = this._fs;
143
+ if (!fs.existsSync(this._filePath)) {
144
+ // Deleted underneath us: an empty document is the truth now.
145
+ if (this._stamp) {
146
+ this._stamp = null;
147
+ this._doc = emptyDocument();
148
+ }
149
+ return;
150
+ }
151
+ let st;
152
+ try {
153
+ st = fs.statSync(this._filePath);
154
+ }
155
+ catch {
156
+ return;
157
+ }
158
+ if (this._stamp && st.mtimeMs === this._stamp.mtimeMs && st.size === this._stamp.size)
159
+ return;
160
+ const doc = this._readDocument(this._filePath);
161
+ if (doc)
162
+ this._doc = doc;
163
+ }
164
+ /**
165
+ * Atomic durable write: fsync the full document to `<file>.tmp`, then rename
166
+ * over the live file. A crash leaves the old file intact; the tmp file is
167
+ * the recovery source on next boot.
168
+ */
169
+ _persist() {
170
+ const fs = this._fs;
171
+ const text = JSON.stringify(this._doc);
172
+ const tmp = `${this._filePath}.tmp`;
173
+ const fd = fs.openSync(tmp, 'w');
174
+ try {
175
+ fs.writeFileSync(fd, text);
176
+ fs.fsyncSync(fd);
177
+ }
178
+ finally {
179
+ fs.closeSync(fd);
180
+ }
181
+ fs.renameSync(tmp, this._filePath);
182
+ try {
183
+ const st = fs.statSync(this._filePath);
184
+ this._stamp = { mtimeMs: st.mtimeMs, size: st.size };
185
+ }
186
+ catch { /* re-stamped on next change check */ }
187
+ }
188
+ // ── Port ──────────────────────────────────────────────────────────────────
189
+ /**
190
+ * Serialized unit of work. Read-write transactions take the cross-process
191
+ * lock, refresh from disk (so we build on other processes' commits), run
192
+ * `fn` against the live document, then persist atomically before
193
+ * releasing. Any failure rolls the in-memory document back to the
194
+ * pre-transaction snapshot — no partial commits, in memory or on disk.
195
+ */
196
+ transact(_stores, mode, fn) {
197
+ const run = this._queue.then(() => mode === 'readwrite'
198
+ ? this._lock.run(() => this._readWrite(fn))
199
+ : Promise.resolve(fn(new FileTransaction(this._doc))));
200
+ // Keep the queue alive across rejections.
201
+ this._queue = run.catch(() => undefined);
202
+ return run;
203
+ }
204
+ async _readWrite(fn) {
205
+ this._refreshIfChanged();
206
+ const snapshot = JSON.stringify(this._doc);
207
+ let result;
208
+ try {
209
+ result = await fn(new FileTransaction(this._doc));
210
+ }
211
+ catch (err) {
212
+ // A unit of work that threw leaves nothing behind, in memory either.
213
+ this._doc = JSON.parse(snapshot);
214
+ throw err;
215
+ }
216
+ try {
217
+ this._persist();
218
+ }
219
+ catch (err) {
220
+ // Roll back: the caller must not observe a committed-looking state
221
+ // that never reached disk.
222
+ this._doc = JSON.parse(snapshot);
223
+ throw new MemoryStorageError('STORAGE_ABORTED', `Failed to durably persist "${this._filePath}": ${err instanceof Error ? err.message : String(err)}`, err);
224
+ }
225
+ return result;
226
+ }
227
+ // ── Cross-process consistency seam (M05) ──────────────────────────────────
228
+ publish(note) {
229
+ // Co-process subscribers get the note synchronously (subscribers filter
230
+ // their own origin); foreign processes learn via the watcher.
231
+ for (const l of this._listeners)
232
+ l(note);
233
+ }
234
+ subscribe(listener) {
235
+ this._listeners.add(listener);
236
+ if (this._watchIntervalMs > 0 && !this._watchTimer) {
237
+ let lastRevision = this._doc.meta.revision;
238
+ const timer = setInterval(() => {
239
+ const before = this._doc.meta.revision;
240
+ this._refreshIfChanged();
241
+ if (this._doc.meta.revision !== before && this._doc.meta.revision !== lastRevision) {
242
+ lastRevision = this._doc.meta.revision;
243
+ const note = {
244
+ kind: 'facts-changed',
245
+ revision: this._doc.meta.revision,
246
+ origin: 'foreign',
247
+ };
248
+ for (const l of this._listeners)
249
+ l(note);
250
+ }
251
+ else {
252
+ lastRevision = this._doc.meta.revision;
253
+ }
254
+ }, this._watchIntervalMs);
255
+ const maybeUnref = timer;
256
+ if (typeof maybeUnref.unref === 'function')
257
+ maybeUnref.unref();
258
+ this._watchTimer = timer;
259
+ }
260
+ return () => {
261
+ this._listeners.delete(listener);
262
+ if (this._listeners.size === 0 && this._watchTimer) {
263
+ clearInterval(this._watchTimer);
264
+ this._watchTimer = null;
265
+ }
266
+ };
267
+ }
268
+ close() {
269
+ if (this._watchTimer) {
270
+ clearInterval(this._watchTimer);
271
+ this._watchTimer = null;
272
+ }
273
+ }
274
+ }
275
+ //# sourceMappingURL=nodeFileAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodeFileAdapter.js","sourceRoot":"","sources":["../../src/storage/nodeFileAdapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * storage/syncJournal.ts – The sync-acknowledgement half of the journal (M04/M06).
3
+ *
4
+ * The journal holds only operations still owed to a sync target: entries are
5
+ * written unsynced (and only when sync is enabled — see `factMutations.journal`)
6
+ * and are DELETED once acknowledged, so the journal stays bounded by the
7
+ * outstanding work instead of growing with every write ever made.
8
+ */
9
+ import type { FactStoragePort, JournalEntry } from './types.js';
10
+ /** Journal entries not yet acknowledged by sync — the pending operation set. */
11
+ export declare function pendingOps(storage: FactStoragePort): Promise<JournalEntry[]>;
12
+ /**
13
+ * Acknowledges synced operations: their journal entries are pruned, and the
14
+ * facts/tombstones they produced clear `pendingSync`, making cap eviction
15
+ * eligible again. Rows already marked synced (written by older versions,
16
+ * which journaled every write even without sync) are pruned too.
17
+ */
18
+ export declare function markSynced(storage: FactStoragePort, opIds: string[]): Promise<void>;
19
+ //# sourceMappingURL=syncJournal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"syncJournal.d.ts","sourceRoot":"","sources":["../../src/storage/syncJournal.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAqC,MAAM,YAAY,CAAC;AAEnG,gFAAgF;AAChF,wBAAsB,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAKlF;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBzF"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * storage/syncJournal.ts – The sync-acknowledgement half of the journal (M04/M06).
3
+ *
4
+ * The journal holds only operations still owed to a sync target: entries are
5
+ * written unsynced (and only when sync is enabled — see `factMutations.journal`)
6
+ * and are DELETED once acknowledged, so the journal stays bounded by the
7
+ * outstanding work instead of growing with every write ever made.
8
+ */
9
+ /** Journal entries not yet acknowledged by sync — the pending operation set. */
10
+ export async function pendingOps(storage) {
11
+ return storage.transact(['journal'], 'readonly', async (tx) => {
12
+ const all = await tx.getAll('journal');
13
+ return all.filter((j) => !j.synced);
14
+ });
15
+ }
16
+ /**
17
+ * Acknowledges synced operations: their journal entries are pruned, and the
18
+ * facts/tombstones they produced clear `pendingSync`, making cap eviction
19
+ * eligible again. Rows already marked synced (written by older versions,
20
+ * which journaled every write even without sync) are pruned too.
21
+ */
22
+ export async function markSynced(storage, opIds) {
23
+ if (opIds.length === 0)
24
+ return;
25
+ const acked = new Set(opIds);
26
+ await storage.transact(['journal', 'facts', 'tombstones'], 'readwrite', async (tx) => {
27
+ const journal = await tx.getAll('journal');
28
+ for (const entry of journal) {
29
+ if (entry.synced || acked.has(entry.opId))
30
+ await tx.delete('journal', entry.opId);
31
+ }
32
+ const facts = await tx.getAll('facts');
33
+ for (const record of facts) {
34
+ if (acked.has(record.opId) && record.pendingSync) {
35
+ await tx.put('facts', { ...record, pendingSync: false });
36
+ }
37
+ }
38
+ const tombstones = await tx.getAll('tombstones');
39
+ for (const tomb of tombstones) {
40
+ if (acked.has(tomb.opId) && tomb.pendingSync) {
41
+ await tx.put('tombstones', { ...tomb, pendingSync: false });
42
+ }
43
+ }
44
+ });
45
+ }
46
+ //# sourceMappingURL=syncJournal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"syncJournal.js","sourceRoot":"","sources":["../../src/storage/syncJournal.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,gFAAgF;AAChF,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAwB;IACrD,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;QAC1D,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,MAAM,CAAe,SAAS,CAAC,CAAC;QACrD,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAwB,EAAE,KAAe;IACtE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAC/B,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAC7B,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,YAAY,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;QACjF,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,MAAM,CAAe,SAAS,CAAC,CAAC;QACzD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC1B,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,MAAM,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACtF,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,MAAM,CAAmB,OAAO,CAAC,CAAC;QACzD,KAAK,MAAM,MAAM,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;gBAC/C,MAAM,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,GAAG,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;YAC7D,CAAC;QACL,CAAC;QACD,MAAM,UAAU,GAAG,MAAM,EAAE,CAAC,MAAM,CAAkB,YAAY,CAAC,CAAC;QAClE,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;YAC5B,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;gBAC3C,MAAM,EAAE,CAAC,GAAG,CAAC,YAAY,EAAE,EAAE,GAAG,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;YAChE,CAAC;QACL,CAAC;IACL,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,207 @@
1
+ /**
2
+ * storage/types.ts – Narrow storage/transaction ports for durable memory.
3
+ *
4
+ * This module is the seam the offline-memory scope (M01–M05) requires:
5
+ * - MemoryStore no longer owns IndexedDB directly; it speaks to a
6
+ * {@link FactStoragePort} with REAL transaction semantics (a write resolves
7
+ * only after the transaction commits, and rejects on abort — M02).
8
+ * - Facts carry explicit scope partitions (account/tenant/project/repository/
9
+ * chat — or the explicit unbound scope for personal, never-connected
10
+ * memory), durable revisions, operation identity and provenance (M03/M04).
11
+ * - Deletes write tombstones, and every mutation appends to a sync journal in
12
+ * the SAME transaction as the fact change, so a crash can never acknowledge
13
+ * a fact whose journal entry was lost (M02/M04).
14
+ *
15
+ * Two durable adapters implement the port:
16
+ * - `IdbStorageAdapter` (browser / VSIX webview — IndexedDB)
17
+ * - `NodeFileStorageAdapter` (VSIX extension host / MCP — atomic JSON file)
18
+ * An in-memory IndexedDB shim (fake-indexeddb) is a TEST double, never a
19
+ * durability claim.
20
+ */
21
+ import { SSMError } from '../errors/SSMError.js';
22
+ /** Explicit ownership partition for a fact. All fields optional; absent = unbound. */
23
+ export interface MemoryScope {
24
+ account?: string;
25
+ tenant?: string;
26
+ project?: string;
27
+ repository?: string;
28
+ chat?: string;
29
+ }
30
+ /** The explicit "no account, never connected" partition — NOT the absence of a scope. */
31
+ export declare const UNBOUND_SCOPE: MemoryScope;
32
+ /**
33
+ * Canonical partition string for a scope. The unbound scope partitions to the
34
+ * empty string, so legacy records (written before scopes existed) land in the
35
+ * unbound partition and stay readable. Two scopes that differ in any field are
36
+ * different partitions — callers can never widen a recall by omitting a field
37
+ * unless they genuinely target the unbound partition.
38
+ */
39
+ export declare function scopePartitionKey(scope: MemoryScope | undefined): string;
40
+ /**
41
+ * Physical record id for a (scope, key) pair. Unbound-scope ids are the bare
42
+ * key — byte-identical to pre-scope records, so existing databases migrate
43
+ * without rewriting keys.
44
+ */
45
+ export declare function scopedRecordId(scope: MemoryScope | undefined, key: string): string;
46
+ /** Where a fact came from. Survives export/import so source time is never rewritten. */
47
+ export interface FactProvenance {
48
+ /** Origin surface/agent, e.g. 'vscode', 'mcp', 'web', 'cognition'. */
49
+ source?: string;
50
+ /** Snapshot/store this record was imported from. */
51
+ importedFrom?: string;
52
+ /** The timestamp the source store recorded; preserved across import (M06). */
53
+ originalTimestamp?: number;
54
+ }
55
+ export type StorageStoreName = 'facts' | 'tombstones' | 'journal' | 'meta';
56
+ /**
57
+ * Secondary index on {@link StoredFactRecord.key}. Key lookup uses this (or the
58
+ * primary key) — never a full scan — when the adapter has built it.
59
+ */
60
+ export declare const FACT_KEY_INDEX = "byKey";
61
+ /**
62
+ * Secondary multi-entry index on {@link StoredFactRecord.tags}. Tag lookup uses
63
+ * this when present and falls back to a scan only when the index is missing.
64
+ */
65
+ export declare const FACT_TAG_INDEX = "byTag";
66
+ /**
67
+ * The durable fact record. Extends the legacy MemoryEntry shape with identity,
68
+ * revision and sync state. `pendingSync` is only ever true when the owning
69
+ * store was created with sync enabled — without a sync target there is nothing
70
+ * to be pending FOR, and eviction must behave exactly as before (M06).
71
+ */
72
+ export interface StoredFactRecord {
73
+ /** Physical primary key: {@link scopedRecordId}. */
74
+ id: string;
75
+ /** Caller-facing key within the scope. */
76
+ key: string;
77
+ /** Ownership partition. */
78
+ scope: MemoryScope;
79
+ content: string;
80
+ /** Wall-clock write time (source time; preserved by import). */
81
+ timestamp: number;
82
+ /** Durable monotonic tiebreaker for same-millisecond writes. */
83
+ seq: number;
84
+ /** Durable revision: the store generation that last wrote this record. */
85
+ revision: number;
86
+ /** Durable identity of the operation that produced this record. */
87
+ opId: string;
88
+ ttlMs?: number;
89
+ type?: 'text' | 'json' | 'number' | 'boolean';
90
+ tags?: string[];
91
+ importance?: number;
92
+ provenance?: FactProvenance;
93
+ /** True until a sync acknowledgement clears it; such facts are NEVER evicted by caps. */
94
+ pendingSync: boolean;
95
+ }
96
+ /** A durable deletion marker. Deletes propagate through sync; tombstones prevent resurrection. */
97
+ export interface TombstoneRecord {
98
+ id: string;
99
+ key: string;
100
+ scope: MemoryScope;
101
+ deletedAt: number;
102
+ /** Store generation of the delete; always greater than the fact's last revision. */
103
+ revision: number;
104
+ opId: string;
105
+ pendingSync: boolean;
106
+ }
107
+ export type JournalOpKind = 'remember' | 'forget';
108
+ /** One durable operation in the sync journal — written atomically with its fact change. */
109
+ export interface JournalEntry {
110
+ opId: string;
111
+ kind: JournalOpKind;
112
+ id: string;
113
+ key: string;
114
+ scope: MemoryScope;
115
+ /** Store generation after this op committed. */
116
+ revision: number;
117
+ /** The revision the writer based its write on (CAS); undefined = blind write. */
118
+ baseRevision?: number;
119
+ timestamp: number;
120
+ synced: boolean;
121
+ }
122
+ /** Counters kept durably in the meta store. */
123
+ export interface MetaCounters {
124
+ key: 'counters';
125
+ /** Durable write sequence (replaces the process-local `_writeSeq`). */
126
+ seq: number;
127
+ /** Durable store generation, bumped by every committed write transaction. */
128
+ revision: number;
129
+ /** Durable operation counter; opIds are `${contextId}:${opSeq}`. */
130
+ opSeq: number;
131
+ }
132
+ /**
133
+ * The minimal surface a transaction offers. Adapters MUST keep the underlying
134
+ * transaction alive across the awaited operations (request-chained awaits only)
135
+ * and MUST NOT resolve {@link FactStoragePort.transact} before commit.
136
+ */
137
+ export interface StorageTransaction {
138
+ get<T>(store: StorageStoreName, id: string): Promise<T | undefined>;
139
+ getAll<T>(store: StorageStoreName): Promise<T[]>;
140
+ put(store: StorageStoreName, value: unknown): Promise<void>;
141
+ delete(store: StorageStoreName, id: string): Promise<void>;
142
+ /**
143
+ * Lookup via a named index. `undefined` means this store has no such index
144
+ * (legacy schema, or an adapter that has not built it) — the caller MUST
145
+ * fall back to a primary-key get or a scan. An empty array means the index
146
+ * exists and matched nothing; do not scan in that case.
147
+ */
148
+ getByIndex?<T>(store: StorageStoreName, indexName: string, query: string): Promise<T[] | undefined>;
149
+ }
150
+ /** A change notice for cross-context invalidation (M05). */
151
+ export interface ConsistencyNote {
152
+ kind: 'facts-changed';
153
+ /** Scope partition that changed; undefined = whole database. */
154
+ scope?: MemoryScope;
155
+ /** Durable generation after the change. */
156
+ revision: number;
157
+ /** Identity of the committing context; a context ignores its own notes. */
158
+ origin: string;
159
+ }
160
+ /**
161
+ * The narrow durable-storage port. One transaction = one unit of work:
162
+ * fact mutation + provenance + tombstone/event + journal commit together or
163
+ * not at all.
164
+ */
165
+ export interface FactStoragePort {
166
+ /** Which durable backend this is (for honest capability reporting). */
167
+ readonly kind: 'indexedDB' | 'node-file';
168
+ /**
169
+ * Runs `fn` inside one real transaction over the named stores. Resolves with
170
+ * `fn`'s result ONLY after the transaction has committed durably; rejects if
171
+ * the transaction aborts, `fn` throws, or the persist fails. A rejected
172
+ * transact leaves NO partial writes behind.
173
+ */
174
+ transact<T>(stores: StorageStoreName[], mode: 'readonly' | 'readwrite', fn: (tx: StorageTransaction) => Promise<T> | T): Promise<T>;
175
+ /**
176
+ * Publishes a change notice to other contexts sharing this storage, when the
177
+ * adapter supports cross-context notification. Optional.
178
+ */
179
+ publish?(note: ConsistencyNote): void;
180
+ /**
181
+ * Subscribes to change notices from OTHER contexts (tabs, extension windows,
182
+ * MCP processes) sharing this storage. Returns an unsubscribe function.
183
+ * Optional: an adapter without it simply offers no cross-context seam.
184
+ */
185
+ subscribe?(listener: (note: ConsistencyNote) => void): () => void;
186
+ close?(): void | Promise<void>;
187
+ }
188
+ /** Detailed error codes surfaced by the storage layer (`MemoryStorageError.storageCode`). */
189
+ export type StorageErrorCode =
190
+ /** The durable backend is unavailable or faulted. */
191
+ 'STORAGE_UNAVAILABLE'
192
+ /** A compare-and-set write found a newer revision than its base. */
193
+ | 'STORAGE_CONFLICT'
194
+ /** A transaction aborted; no partial writes were committed. */
195
+ | 'STORAGE_ABORTED';
196
+ /**
197
+ * A storage-layer failure. It IS an {@link SSMError} whose `code` is the public
198
+ * contract (`MEMORY_UNAVAILABLE` / `MEMORY_CONFLICT`); `storageCode` keeps the
199
+ * detailed storage code for callers that need it.
200
+ */
201
+ export declare class MemoryStorageError extends SSMError {
202
+ readonly storageCode: StorageErrorCode;
203
+ constructor(storageCode: StorageErrorCode, message: string, cause?: unknown);
204
+ }
205
+ /** Type guard for conflict errors so callers can reconcile instead of blindly retrying. */
206
+ export declare function isStorageConflict(err: unknown): err is MemoryStorageError;
207
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"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"}