@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,156 @@
1
+ /**
2
+ * storage/ScopedFactRepository.ts – Scoped, journaled, revision-tracked fact
3
+ * repository over a {@link FactStoragePort}.
4
+ *
5
+ * This is the unit-of-work owner the offline-memory scope requires:
6
+ *
7
+ * - Scope partitions (M03): every operation takes an explicit
8
+ * {@link MemoryScope}; records are physically partitioned by scoped record
9
+ * id and logically filtered by partition, so no code path can recall across
10
+ * account/tenant/project/repository/chat boundaries. The unbound scope is
11
+ * an explicit partition for personal, never-connected memory.
12
+ * - Atomic commits (M02): fact mutation + provenance + tombstone + sync
13
+ * journal + durable counters commit in ONE storage transaction.
14
+ * - Durable identity (M04): every record carries a durable `revision` (the
15
+ * store generation that wrote it) and `opId`; deletes write tombstones;
16
+ * writes may be compare-and-set against a base revision, so concurrent
17
+ * evidence conflicts surface as explicit conflicts instead of unconditional
18
+ * last-write-wins.
19
+ * - Sync-aware retention (M06): facts with `pendingSync` are never evicted by
20
+ * caps and are never dropped by import; TTL expiry is an intentional
21
+ * deletion and writes a tombstone when sync is enabled.
22
+ *
23
+ * The shared write rules live in `factMutations.ts`, retention in
24
+ * `factRetention.ts`, import in `factImport.ts`, sync acknowledgement in
25
+ * `syncJournal.ts`.
26
+ */
27
+ import { type ImportResult } from './factImport.js';
28
+ import { type ConsistencyNote, type FactProvenance, type FactStoragePort, type JournalEntry, type MemoryScope, type StoredFactRecord, type TombstoneRecord } from './types.js';
29
+ export interface ScopedFactRepositoryOptions {
30
+ /**
31
+ * When true, mutations are journaled (unsynced) until {@link markSynced}
32
+ * acknowledges and prunes them, deletes write tombstones, and cap eviction
33
+ * skips unsynced facts. When false (default — no sync target configured)
34
+ * nothing is journaled and retention behaves exactly like the legacy store.
35
+ */
36
+ syncEnabled?: boolean;
37
+ /** Identity of this repository context for opIds and change notes. */
38
+ contextId?: string;
39
+ /** Injectable clock (ms). Default: Date.now. */
40
+ now?: () => number;
41
+ }
42
+ export interface RememberRecordInput {
43
+ content: string;
44
+ /** Explicit source time (import). Default: now. */
45
+ timestamp?: number;
46
+ /** Explicit source sequence (import). Default: next durable sequence. */
47
+ seq?: number;
48
+ ttlMs?: number;
49
+ type?: StoredFactRecord['type'];
50
+ tags?: string[];
51
+ importance?: number;
52
+ provenance?: FactProvenance;
53
+ /**
54
+ * Compare-and-set base: `null` asserts the record does not exist; a number
55
+ * asserts the current revision; undefined is a blind write (legacy).
56
+ */
57
+ expectedRevision?: number | null;
58
+ /** Explicit op identity (import/replay). Default: generated from the durable op counter. */
59
+ opId?: string;
60
+ }
61
+ export interface WriteOutcome {
62
+ record: StoredFactRecord;
63
+ /** True when an existing tombstone for this key was cleared by the write. */
64
+ resurrected: boolean;
65
+ }
66
+ export type { ImportResult };
67
+ export declare class ScopedFactRepository {
68
+ private readonly _storage;
69
+ private readonly _ctx;
70
+ constructor(storage: FactStoragePort, opts?: ScopedFactRepositoryOptions);
71
+ get storage(): FactStoragePort;
72
+ get contextId(): string;
73
+ get syncEnabled(): boolean;
74
+ /** Current durable store generation (0 on a fresh store). */
75
+ revision(): Promise<number>;
76
+ /**
77
+ * Fetches one record by (scope, key), or undefined. Tombstoned = absent.
78
+ * Uses the `byKey` index when the adapter has it; falls back to the primary
79
+ * key (itself an index on the scoped id) only when that index is missing.
80
+ */
81
+ get(scope: MemoryScope | undefined, key: string): Promise<StoredFactRecord | undefined>;
82
+ /**
83
+ * Facts in `scope` carrying `tag`. Uses the multi-entry `byTag` index when
84
+ * present. A full scan runs only when the index is missing (legacy schema).
85
+ */
86
+ getByTag(scope: MemoryScope | undefined, tag: string): Promise<StoredFactRecord[]>;
87
+ /**
88
+ * All records in exactly this scope partition — never a wider or narrower
89
+ * set. Expired records are included; TTL filtering is the caller's policy.
90
+ */
91
+ getAll(scope: MemoryScope | undefined): Promise<StoredFactRecord[]>;
92
+ /** Tombstones, optionally narrowed to one scope partition. */
93
+ tombstones(scope?: MemoryScope): Promise<TombstoneRecord[]>;
94
+ /** Journal entries not yet acknowledged by sync — the pending operation set. */
95
+ pendingOps(): Promise<JournalEntry[]>;
96
+ /**
97
+ * Stores or overwrites a fact in `scope`. With `expectedRevision` set this
98
+ * is a compare-and-set: a mismatch throws a STORAGE_CONFLICT error carrying
99
+ * the current record, so a concurrent writer's evidence is never silently
100
+ * overwritten (M04/M05).
101
+ */
102
+ remember(scope: MemoryScope | undefined, key: string, input: RememberRecordInput): Promise<WriteOutcome>;
103
+ /**
104
+ * Deletes a fact. With sync enabled the delete is a durable tombstone +
105
+ * journal entry committed in the same transaction, so the deletion
106
+ * propagates and a stale replica cannot resurrect the fact (M04). Without
107
+ * sync this is the legacy hard delete. Deleting an absent key is a no-op.
108
+ */
109
+ forget(scope: MemoryScope | undefined, key: string, opts?: {
110
+ expectedRevision?: number;
111
+ }): Promise<boolean>;
112
+ /** Removes every fact in the scope. Tombstones each deletion when sync is enabled. */
113
+ clear(scope: MemoryScope | undefined): Promise<number>;
114
+ /**
115
+ * Imports records INTO `scope`, preserving each record's source timestamp,
116
+ * sequence, tags, importance, TTL and provenance. `importedFrom` is stamped
117
+ * onto the provenance so the snapshot origin survives. 'merge' keeps the
118
+ * record with the newer source timestamp; 'overwrite' always writes.
119
+ * Imported facts are pendingSync when sync is enabled — they are new to
120
+ * THIS replica even if another store already had them.
121
+ */
122
+ importRecords(scope: MemoryScope | undefined, records: StoredFactRecord[], strategy: 'merge' | 'overwrite', importedFrom?: string): Promise<ImportResult>;
123
+ /** Exports every record in the scope, with scope/revision/provenance intact (M03). */
124
+ exportRecords(scope: MemoryScope | undefined): Promise<StoredFactRecord[]>;
125
+ /**
126
+ * Acknowledges synced operations: their journal entries are pruned and the
127
+ * facts/tombstones they produced clear `pendingSync`, making cap eviction
128
+ * eligible again. Only after this may retention touch those records.
129
+ */
130
+ markSynced(opIds: string[]): Promise<void>;
131
+ /**
132
+ * Local-only cap eviction: removes the lowest-value records past
133
+ * `maxEntries`, EXCLUDING records with unsynced operations — pending
134
+ * knowledge is never evicted to satisfy a cache cap. Eviction of synced
135
+ * records writes no tombstone and no journal entry by design: it is local
136
+ * cache management, not a deletion to propagate. Returns the evicted keys.
137
+ */
138
+ evictForCap(scope: MemoryScope | undefined, maxEntries: number, isExpired: (record: StoredFactRecord) => boolean): Promise<string[]>;
139
+ /**
140
+ * Intentional retention: removes expired records. With sync enabled each
141
+ * expiry is a scoped deletion — tombstone + journal in one transaction — so
142
+ * the expiry propagates instead of resurrecting from a replica. Returns the
143
+ * number of records purged.
144
+ */
145
+ purgeExpired(scope: MemoryScope | undefined, isExpired: (record: StoredFactRecord) => boolean): Promise<number>;
146
+ /**
147
+ * Subscribes to change notes committed by OTHER contexts sharing this
148
+ * storage (other tabs, extension windows, MCP processes). In-process
149
+ * serialization alone cannot be the distributed consistency boundary; this
150
+ * is the invalidation half of the seam, and compare-and-set is the
151
+ * write-conflict half.
152
+ */
153
+ subscribe(listener: (note: ConsistencyNote) => void): () => void;
154
+ private _publish;
155
+ }
156
+ //# sourceMappingURL=ScopedFactRepository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScopedFactRepository.d.ts","sourceRoot":"","sources":["../../src/storage/ScopedFactRepository.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAaH,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGhE,OAAO,EAMH,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACvB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,2BAA2B;IACxC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,4FAA4F;IAC5F,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IACzB,MAAM,EAAE,gBAAgB,CAAC;IACzB,6EAA6E;IAC7E,WAAW,EAAE,OAAO,CAAC;CACxB;AAED,YAAY,EAAE,YAAY,EAAE,CAAC;AAE7B,qBAAa,oBAAoB;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAkB;gBAE3B,OAAO,EAAE,eAAe,EAAE,IAAI,GAAE,2BAAgC;IAS5E,IAAI,OAAO,IAAI,eAAe,CAE7B;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,IAAI,WAAW,IAAI,OAAO,CAEzB;IAID,6DAA6D;IACvD,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAIjC;;;;OAIG;IACG,GAAG,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC;IAS7F;;;OAGG;IACG,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAWxF;;;OAGG;IACG,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAIzE,8DAA8D;IACxD,UAAU,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IASjE,gFAAgF;IAC1E,UAAU,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAM3C;;;;;OAKG;IACG,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,YAAY,CAAC;IAoD9G;;;;;OAKG;IACG,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE;QAAE,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAmBrH,sFAAsF;IAChF,KAAK,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;IAe5D;;;;;;;OAOG;IACG,aAAa,CACf,KAAK,EAAE,WAAW,GAAG,SAAS,EAC9B,OAAO,EAAE,gBAAgB,EAAE,EAC3B,QAAQ,EAAE,OAAO,GAAG,WAAW,EAC/B,YAAY,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,YAAY,CAAC;IAMxB,sFAAsF;IAChF,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAMhF;;;;OAIG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAMhD;;;;;;OAMG;IACG,WAAW,CACb,KAAK,EAAE,WAAW,GAAG,SAAS,EAC9B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,OAAO,GACjD,OAAO,CAAC,MAAM,EAAE,CAAC;IAIpB;;;;;OAKG;IACG,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,EAAE,SAAS,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAQrH;;;;;;OAMG;IACH,SAAS,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,GAAG,MAAM,IAAI;IAQhE,OAAO,CAAC,QAAQ;CAQnB"}
@@ -0,0 +1,278 @@
1
+ /**
2
+ * storage/ScopedFactRepository.ts – Scoped, journaled, revision-tracked fact
3
+ * repository over a {@link FactStoragePort}.
4
+ *
5
+ * This is the unit-of-work owner the offline-memory scope requires:
6
+ *
7
+ * - Scope partitions (M03): every operation takes an explicit
8
+ * {@link MemoryScope}; records are physically partitioned by scoped record
9
+ * id and logically filtered by partition, so no code path can recall across
10
+ * account/tenant/project/repository/chat boundaries. The unbound scope is
11
+ * an explicit partition for personal, never-connected memory.
12
+ * - Atomic commits (M02): fact mutation + provenance + tombstone + sync
13
+ * journal + durable counters commit in ONE storage transaction.
14
+ * - Durable identity (M04): every record carries a durable `revision` (the
15
+ * store generation that wrote it) and `opId`; deletes write tombstones;
16
+ * writes may be compare-and-set against a base revision, so concurrent
17
+ * evidence conflicts surface as explicit conflicts instead of unconditional
18
+ * last-write-wins.
19
+ * - Sync-aware retention (M06): facts with `pendingSync` are never evicted by
20
+ * caps and are never dropped by import; TTL expiry is an intentional
21
+ * deletion and writes a tombstone when sync is enabled.
22
+ *
23
+ * The shared write rules live in `factMutations.ts`, retention in
24
+ * `factRetention.ts`, import in `factImport.ts`, sync acknowledgement in
25
+ * `syncJournal.ts`.
26
+ */
27
+ import { factsInScope, indexedFacts } from './factQueries.js';
28
+ import { WRITE_STORES, bumpOp, clearTombstone, deleteWithTombstone, journal, readCounters, writeCounters, } from './factMutations.js';
29
+ import { importInto } from './factImport.js';
30
+ import * as retention from './factRetention.js';
31
+ import * as syncJournal from './syncJournal.js';
32
+ import { FACT_KEY_INDEX, FACT_TAG_INDEX, MemoryStorageError, scopedRecordId, scopePartitionKey, } from './types.js';
33
+ export class ScopedFactRepository {
34
+ _storage;
35
+ _ctx;
36
+ constructor(storage, opts = {}) {
37
+ this._storage = storage;
38
+ this._ctx = {
39
+ syncEnabled: opts.syncEnabled ?? false,
40
+ contextId: opts.contextId ?? 'repo',
41
+ now: opts.now ?? (() => Date.now()),
42
+ };
43
+ }
44
+ get storage() {
45
+ return this._storage;
46
+ }
47
+ get contextId() {
48
+ return this._ctx.contextId;
49
+ }
50
+ get syncEnabled() {
51
+ return this._ctx.syncEnabled;
52
+ }
53
+ // ── Reads ─────────────────────────────────────────────────────────────────
54
+ /** Current durable store generation (0 on a fresh store). */
55
+ async revision() {
56
+ return this._storage.transact(['meta'], 'readonly', async (tx) => (await readCounters(tx)).revision);
57
+ }
58
+ /**
59
+ * Fetches one record by (scope, key), or undefined. Tombstoned = absent.
60
+ * Uses the `byKey` index when the adapter has it; falls back to the primary
61
+ * key (itself an index on the scoped id) only when that index is missing.
62
+ */
63
+ async get(scope, key) {
64
+ const id = scopedRecordId(scope, key);
65
+ return this._storage.transact(['facts'], 'readonly', async (tx) => {
66
+ const indexed = await indexedFacts(tx, FACT_KEY_INDEX, key);
67
+ if (indexed !== undefined)
68
+ return indexed.find((r) => r.id === id);
69
+ return tx.get('facts', id);
70
+ });
71
+ }
72
+ /**
73
+ * Facts in `scope` carrying `tag`. Uses the multi-entry `byTag` index when
74
+ * present. A full scan runs only when the index is missing (legacy schema).
75
+ */
76
+ async getByTag(scope, tag) {
77
+ const partition = scopePartitionKey(scope);
78
+ return this._storage.transact(['facts'], 'readonly', async (tx) => {
79
+ const indexed = await indexedFacts(tx, FACT_TAG_INDEX, tag);
80
+ if (indexed !== undefined) {
81
+ return indexed.filter((r) => scopePartitionKey(r.scope) === partition);
82
+ }
83
+ return (await factsInScope(tx, scope)).filter((r) => r.tags?.includes(tag) ?? false);
84
+ });
85
+ }
86
+ /**
87
+ * All records in exactly this scope partition — never a wider or narrower
88
+ * set. Expired records are included; TTL filtering is the caller's policy.
89
+ */
90
+ async getAll(scope) {
91
+ return this._storage.transact(['facts'], 'readonly', (tx) => factsInScope(tx, scope));
92
+ }
93
+ /** Tombstones, optionally narrowed to one scope partition. */
94
+ async tombstones(scope) {
95
+ return this._storage.transact(['tombstones'], 'readonly', async (tx) => {
96
+ const all = await tx.getAll('tombstones');
97
+ if (scope === undefined)
98
+ return all;
99
+ const partition = scopePartitionKey(scope);
100
+ return all.filter((t) => scopePartitionKey(t.scope) === partition);
101
+ });
102
+ }
103
+ /** Journal entries not yet acknowledged by sync — the pending operation set. */
104
+ async pendingOps() {
105
+ return syncJournal.pendingOps(this._storage);
106
+ }
107
+ // ── Writes (unit of work: fact + journal + counters in one transaction) ───
108
+ /**
109
+ * Stores or overwrites a fact in `scope`. With `expectedRevision` set this
110
+ * is a compare-and-set: a mismatch throws a STORAGE_CONFLICT error carrying
111
+ * the current record, so a concurrent writer's evidence is never silently
112
+ * overwritten (M04/M05).
113
+ */
114
+ async remember(scope, key, input) {
115
+ const id = scopedRecordId(scope, key);
116
+ const outcome = await this._storage.transact(WRITE_STORES, 'readwrite', async (tx) => {
117
+ const counters = await readCounters(tx);
118
+ const existing = await tx.get('facts', id);
119
+ if (input.expectedRevision !== undefined) {
120
+ const current = existing?.revision ?? null;
121
+ if (current !== input.expectedRevision) {
122
+ throw new MemoryStorageError('STORAGE_CONFLICT', `Conflict writing "${key}": expected revision ${String(input.expectedRevision)}, found ${String(current)}. Re-read and reconcile instead of overwriting.`);
123
+ }
124
+ }
125
+ const bumped = bumpOp(counters, this._ctx);
126
+ const opId = input.opId ?? bumped.opId;
127
+ const next = { ...bumped.counters, seq: Math.max(counters.seq + 1, input.seq ?? 0) };
128
+ const record = {
129
+ id,
130
+ key,
131
+ scope: { ...(scope ?? {}) },
132
+ content: input.content,
133
+ timestamp: input.timestamp ?? this._ctx.now(),
134
+ seq: next.seq,
135
+ revision: next.revision,
136
+ opId,
137
+ ttlMs: input.ttlMs,
138
+ type: input.type,
139
+ tags: input.tags,
140
+ importance: input.importance,
141
+ provenance: input.provenance,
142
+ pendingSync: this._ctx.syncEnabled,
143
+ };
144
+ await tx.put('facts', record);
145
+ const resurrected = await clearTombstone(tx, id);
146
+ await journal(tx, this._ctx, {
147
+ opId,
148
+ kind: 'remember',
149
+ id,
150
+ key,
151
+ scope: record.scope,
152
+ revision: next.revision,
153
+ baseRevision: input.expectedRevision ?? undefined,
154
+ timestamp: record.timestamp,
155
+ });
156
+ await writeCounters(tx, next);
157
+ return { record, resurrected };
158
+ });
159
+ this._publish(scope, outcome.record.revision);
160
+ return outcome;
161
+ }
162
+ /**
163
+ * Deletes a fact. With sync enabled the delete is a durable tombstone +
164
+ * journal entry committed in the same transaction, so the deletion
165
+ * propagates and a stale replica cannot resurrect the fact (M04). Without
166
+ * sync this is the legacy hard delete. Deleting an absent key is a no-op.
167
+ */
168
+ async forget(scope, key, opts = {}) {
169
+ const id = scopedRecordId(scope, key);
170
+ const revision = await this._storage.transact(WRITE_STORES, 'readwrite', async (tx) => {
171
+ const existing = await tx.get('facts', id);
172
+ if (!existing)
173
+ return 0;
174
+ if (opts.expectedRevision !== undefined && existing.revision !== opts.expectedRevision) {
175
+ throw new MemoryStorageError('STORAGE_CONFLICT', `Conflict deleting "${key}": expected revision ${opts.expectedRevision}, found ${existing.revision}.`);
176
+ }
177
+ const next = await deleteWithTombstone(tx, this._ctx, existing, await readCounters(tx), opts.expectedRevision);
178
+ await writeCounters(tx, next);
179
+ return next.revision;
180
+ });
181
+ if (revision > 0)
182
+ this._publish(scope, revision);
183
+ return revision > 0;
184
+ }
185
+ /** Removes every fact in the scope. Tombstones each deletion when sync is enabled. */
186
+ async clear(scope) {
187
+ const { removed, revision } = await this._storage.transact(WRITE_STORES, 'readwrite', async (tx) => {
188
+ const mine = await factsInScope(tx, scope);
189
+ if (mine.length === 0)
190
+ return { removed: 0, revision: 0 };
191
+ let counters = await readCounters(tx);
192
+ for (const record of mine)
193
+ counters = await deleteWithTombstone(tx, this._ctx, record, counters);
194
+ await writeCounters(tx, counters);
195
+ return { removed: mine.length, revision: counters.revision };
196
+ });
197
+ if (removed > 0)
198
+ this._publish(scope, revision);
199
+ return removed;
200
+ }
201
+ // ── Import / export (M03/M06: scope + source time + provenance preserved) ──
202
+ /**
203
+ * Imports records INTO `scope`, preserving each record's source timestamp,
204
+ * sequence, tags, importance, TTL and provenance. `importedFrom` is stamped
205
+ * onto the provenance so the snapshot origin survives. 'merge' keeps the
206
+ * record with the newer source timestamp; 'overwrite' always writes.
207
+ * Imported facts are pendingSync when sync is enabled — they are new to
208
+ * THIS replica even if another store already had them.
209
+ */
210
+ async importRecords(scope, records, strategy, importedFrom) {
211
+ const { written, skipped, revision } = await importInto(this._storage, this._ctx, scope, records, strategy, importedFrom);
212
+ if (written > 0)
213
+ this._publish(scope, revision);
214
+ return { written, skipped };
215
+ }
216
+ /** Exports every record in the scope, with scope/revision/provenance intact (M03). */
217
+ async exportRecords(scope) {
218
+ return this.getAll(scope);
219
+ }
220
+ // ── Sync acknowledgement (M04/M06) ────────────────────────────────────────
221
+ /**
222
+ * Acknowledges synced operations: their journal entries are pruned and the
223
+ * facts/tombstones they produced clear `pendingSync`, making cap eviction
224
+ * eligible again. Only after this may retention touch those records.
225
+ */
226
+ async markSynced(opIds) {
227
+ await syncJournal.markSynced(this._storage, opIds);
228
+ }
229
+ // ── Retention (M06) ───────────────────────────────────────────────────────
230
+ /**
231
+ * Local-only cap eviction: removes the lowest-value records past
232
+ * `maxEntries`, EXCLUDING records with unsynced operations — pending
233
+ * knowledge is never evicted to satisfy a cache cap. Eviction of synced
234
+ * records writes no tombstone and no journal entry by design: it is local
235
+ * cache management, not a deletion to propagate. Returns the evicted keys.
236
+ */
237
+ async evictForCap(scope, maxEntries, isExpired) {
238
+ return retention.evictForCap(this._storage, scope, maxEntries, isExpired);
239
+ }
240
+ /**
241
+ * Intentional retention: removes expired records. With sync enabled each
242
+ * expiry is a scoped deletion — tombstone + journal in one transaction — so
243
+ * the expiry propagates instead of resurrecting from a replica. Returns the
244
+ * number of records purged.
245
+ */
246
+ async purgeExpired(scope, isExpired) {
247
+ const { purged, revision } = await retention.purgeExpired(this._storage, this._ctx, scope, isExpired);
248
+ if (purged > 0)
249
+ this._publish(scope, revision);
250
+ return purged;
251
+ }
252
+ // ── Cross-context consistency seam (M05) ──────────────────────────────────
253
+ /**
254
+ * Subscribes to change notes committed by OTHER contexts sharing this
255
+ * storage (other tabs, extension windows, MCP processes). In-process
256
+ * serialization alone cannot be the distributed consistency boundary; this
257
+ * is the invalidation half of the seam, and compare-and-set is the
258
+ * write-conflict half.
259
+ */
260
+ subscribe(listener) {
261
+ if (!this._storage.subscribe)
262
+ return () => undefined;
263
+ return this._storage.subscribe((note) => {
264
+ if (note.origin === this._ctx.contextId)
265
+ return;
266
+ listener(note);
267
+ });
268
+ }
269
+ _publish(scope, revision) {
270
+ this._storage.publish?.({
271
+ kind: 'facts-changed',
272
+ scope: scope ? { ...scope } : undefined,
273
+ revision,
274
+ origin: this._ctx.contextId,
275
+ });
276
+ }
277
+ }
278
+ //# sourceMappingURL=ScopedFactRepository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScopedFactRepository.js","sourceRoot":"","sources":["../../src/storage/ScopedFactRepository.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * storage/factImport.ts – Import INTO a scope (M03/M06): source timestamp,
3
+ * sequence, tags, importance, TTL and provenance survive; `importedFrom` is
4
+ * stamped onto the provenance; imported facts are `pendingSync` when sync is
5
+ * enabled (new to THIS replica even if another store already had them).
6
+ */
7
+ import { type MutationContext } from './factMutations.js';
8
+ import { type FactStoragePort, type MemoryScope, type StoredFactRecord } from './types.js';
9
+ export interface ImportResult {
10
+ written: number;
11
+ skipped: number;
12
+ }
13
+ /**
14
+ * Writes `records` into `scope` in one transaction. 'merge' keeps the record
15
+ * with the newer source timestamp; 'overwrite' always writes. Returns the
16
+ * counts plus the store revision after the import (0 when nothing was written).
17
+ */
18
+ export declare function importInto(storage: FactStoragePort, ctx: MutationContext, scope: MemoryScope | undefined, records: StoredFactRecord[], strategy: 'merge' | 'overwrite', importedFrom?: string): Promise<ImportResult & {
19
+ revision: number;
20
+ }>;
21
+ //# sourceMappingURL=factImport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factImport.d.ts","sourceRoot":"","sources":["../../src/storage/factImport.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAA8E,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACtI,OAAO,EAAkB,KAAK,eAAe,EAAE,KAAK,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE3G,MAAM,WAAW,YAAY;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAC5B,OAAO,EAAE,eAAe,EACxB,GAAG,EAAE,eAAe,EACpB,KAAK,EAAE,WAAW,GAAG,SAAS,EAC9B,OAAO,EAAE,gBAAgB,EAAE,EAC3B,QAAQ,EAAE,OAAO,GAAG,WAAW,EAC/B,YAAY,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,YAAY,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA6C9C"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * storage/factImport.ts – Import INTO a scope (M03/M06): source timestamp,
3
+ * sequence, tags, importance, TTL and provenance survive; `importedFrom` is
4
+ * stamped onto the provenance; imported facts are `pendingSync` when sync is
5
+ * enabled (new to THIS replica even if another store already had them).
6
+ */
7
+ import { WRITE_STORES, bumpOp, clearTombstone, journal, readCounters, writeCounters } from './factMutations.js';
8
+ import { scopedRecordId } from './types.js';
9
+ /**
10
+ * Writes `records` into `scope` in one transaction. 'merge' keeps the record
11
+ * with the newer source timestamp; 'overwrite' always writes. Returns the
12
+ * counts plus the store revision after the import (0 when nothing was written).
13
+ */
14
+ export async function importInto(storage, ctx, scope, records, strategy, importedFrom) {
15
+ return storage.transact(WRITE_STORES, 'readwrite', async (tx) => {
16
+ let counters = await readCounters(tx);
17
+ let written = 0;
18
+ let skipped = 0;
19
+ for (const incoming of records) {
20
+ const id = scopedRecordId(scope, incoming.key);
21
+ const existing = await tx.get('facts', id);
22
+ if (strategy === 'merge' && existing && existing.timestamp >= incoming.timestamp) {
23
+ skipped++;
24
+ continue;
25
+ }
26
+ const bumped = bumpOp(counters, ctx);
27
+ counters = { ...bumped.counters, seq: Math.max(counters.seq + 1, incoming.seq ?? 0) };
28
+ const record = {
29
+ ...incoming,
30
+ id,
31
+ key: incoming.key,
32
+ scope: { ...(scope ?? {}) },
33
+ seq: incoming.seq ?? counters.seq,
34
+ revision: counters.revision,
35
+ opId: bumped.opId,
36
+ provenance: {
37
+ ...incoming.provenance,
38
+ ...(importedFrom ? { importedFrom } : {}),
39
+ originalTimestamp: incoming.provenance?.originalTimestamp ?? incoming.timestamp,
40
+ },
41
+ pendingSync: ctx.syncEnabled,
42
+ };
43
+ await tx.put('facts', record);
44
+ await clearTombstone(tx, id);
45
+ await journal(tx, ctx, {
46
+ opId: bumped.opId,
47
+ kind: 'remember',
48
+ id,
49
+ key: incoming.key,
50
+ scope: record.scope,
51
+ revision: counters.revision,
52
+ timestamp: record.timestamp,
53
+ });
54
+ written++;
55
+ }
56
+ if (written > 0)
57
+ await writeCounters(tx, counters);
58
+ return { written, skipped, revision: written > 0 ? counters.revision : 0 };
59
+ });
60
+ }
61
+ //# sourceMappingURL=factImport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factImport.js","sourceRoot":"","sources":["../../src/storage/factImport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * storage/factMutations.ts – The write primitives every repository mutation
3
+ * shares, so the counter bump / tombstone / journal rules live in ONE place:
4
+ *
5
+ * - {@link bumpOp}: next durable revision + operation id.
6
+ * - {@link journal}: append a sync-journal entry — ONLY when sync is enabled.
7
+ * Without a sync target there is nothing to acknowledge, so writing rows
8
+ * already marked synced just grew the journal forever.
9
+ * - {@link deleteWithTombstone}: delete a fact, tombstone it when sync is on,
10
+ * journal the delete — one call for forget, clear and TTL purge.
11
+ * - {@link clearTombstone}: a write resurrects a key (remember, import).
12
+ */
13
+ import type { JournalEntry, MemoryScope, MetaCounters, StorageStoreName, StorageTransaction } from './types.js';
14
+ export declare const COUNTERS_ID = "counters";
15
+ /** The stores a mutating unit of work spans. */
16
+ export declare const WRITE_STORES: StorageStoreName[];
17
+ /** Who is writing, and under which retention rules. */
18
+ export interface MutationContext {
19
+ readonly syncEnabled: boolean;
20
+ readonly contextId: string;
21
+ readonly now: () => number;
22
+ }
23
+ type CounterReader = Pick<StorageTransaction, 'get'>;
24
+ export declare function readCounters(tx: CounterReader): Promise<MetaCounters>;
25
+ export declare function writeCounters(tx: StorageTransaction, counters: MetaCounters): Promise<void>;
26
+ /** The next revision + operation identity after `counters`. */
27
+ export declare function bumpOp(counters: MetaCounters, ctx: MutationContext): {
28
+ counters: MetaCounters;
29
+ opId: string;
30
+ };
31
+ /** Appends an unsynced journal entry when sync is enabled; a no-op otherwise. */
32
+ export declare function journal(tx: StorageTransaction, ctx: MutationContext, entry: Omit<JournalEntry, 'synced'>): Promise<void>;
33
+ /** Clears any tombstone for `id`; true when one existed (the write resurrected the key). */
34
+ export declare function clearTombstone(tx: StorageTransaction, id: string): Promise<boolean>;
35
+ /**
36
+ * Deletes one fact as a durable, propagating deletion: bump the counters,
37
+ * delete, tombstone (sync on), journal (sync on). Returns the bumped counters;
38
+ * the caller persists them once per transaction with {@link writeCounters}.
39
+ */
40
+ export declare function deleteWithTombstone(tx: StorageTransaction, ctx: MutationContext, record: {
41
+ id: string;
42
+ key: string;
43
+ scope: MemoryScope;
44
+ }, counters: MetaCounters, baseRevision?: number): Promise<MetaCounters>;
45
+ export {};
46
+ //# sourceMappingURL=factMutations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factMutations.d.ts","sourceRoot":"","sources":["../../src/storage/factMutations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACR,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAErB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,WAAW,aAAa,CAAC;AAEtC,gDAAgD;AAChD,eAAO,MAAM,YAAY,EAAE,gBAAgB,EAA+C,CAAC;AAE3F,uDAAuD;AACvD,MAAM,WAAW,eAAe;IAC5B,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;CAC9B;AAED,KAAK,aAAa,GAAG,IAAI,CAAC,kBAAkB,EAAE,KAAK,CAAC,CAAC;AAErD,wBAAsB,YAAY,CAAC,EAAE,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAG3E;AAED,wBAAsB,aAAa,CAAC,EAAE,EAAE,kBAAkB,EAAE,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAEjG;AAED,+DAA+D;AAC/D,wBAAgB,MAAM,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,EAAE,eAAe,GAAG;IAAE,QAAQ,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAG7G;AAED,iFAAiF;AACjF,wBAAsB,OAAO,CACzB,EAAE,EAAE,kBAAkB,EACtB,GAAG,EAAE,eAAe,EACpB,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,GACpC,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,4FAA4F;AAC5F,wBAAsB,cAAc,CAAC,EAAE,EAAE,kBAAkB,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAIzF;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACrC,EAAE,EAAE,kBAAkB,EACtB,GAAG,EAAE,eAAe,EACpB,MAAM,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,EACvD,QAAQ,EAAE,YAAY,EACtB,YAAY,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,YAAY,CAAC,CA0BvB"}