memorio 5.1.3 → 5.2.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.
Files changed (48) hide show
  1. package/AGENTS.md +22 -13
  2. package/CHANGELOG.md +24 -9
  3. package/README.md +61 -19
  4. package/SECURITY-HARDENING.md +258 -0
  5. package/SECURITY.md +67 -3
  6. package/SUMMARY.md +55 -59
  7. package/adr/002-observer-semantics.md +1 -1
  8. package/adr/003-deep-mutation-semantics.md +1 -1
  9. package/adr/004-array-mutation-semantics.md +1 -1
  10. package/adr/010-logic-phase-0.md +42 -0
  11. package/adr/README.md +16 -11
  12. package/bin/cli.js +82 -60
  13. package/examples/acquired-knowledge.ts +174 -0
  14. package/examples/agent-memory-demo.ts +140 -0
  15. package/examples/sqlite-batched-writes.ts +60 -60
  16. package/examples/sync.ts +90 -90
  17. package/examples/useObserver.tsx +2 -2
  18. package/global.cjs +2026 -115
  19. package/global.js +2021 -116
  20. package/index.cjs +2026 -115
  21. package/index.d.ts +1 -0
  22. package/index.js +2021 -116
  23. package/llms.txt +135 -4
  24. package/markdown/EXTENSION_VSCODE_DESIGN.md +410 -0
  25. package/markdown/LOGIC.md +100 -0
  26. package/markdown/MEMORY-ATTACHMENT.md +17 -10
  27. package/markdown/MEMORY.md +404 -14
  28. package/markdown/MEM_FORMAT.md +313 -0
  29. package/markdown/SQLITE.md +2 -2
  30. package/markdown/STATE.md +27 -3
  31. package/markdown/SYNC.md +19 -15
  32. package/markdown/TEMPORAL.md +297 -0
  33. package/markdown/USEOBSERVER.md +7 -4
  34. package/modules/redux.cjs +1188 -38
  35. package/modules/redux.cjs.map +1 -1
  36. package/modules/redux.js +1187 -38
  37. package/modules/redux.js.map +1 -1
  38. package/package.json +14 -5
  39. package/types/exports.d.ts +47 -3
  40. package/types/logic.d.ts +79 -0
  41. package/types/memorio.d.ts +60 -16
  42. package/types/memory.d.ts +118 -0
  43. package/types/session.d.ts +1 -4
  44. package/types/state.d.ts +19 -5
  45. package/types/store.d.ts +1 -4
  46. package/types/temporal.d.ts +95 -0
  47. package/types/useObserver.d.ts +6 -10
  48. package/vsix/memorio.vsix +0 -0
package/AGENTS.md CHANGED
@@ -6,18 +6,20 @@ when, and the specific footguns this library's own docs flag.
6
6
 
7
7
  ## Read this before generating any `memorio` code
8
8
 
9
- `memorio`'s own `llms.txt` contains an explicit unresolved discrepancy:
9
+ `memorio`'s `llms.txt` previously flagged an unresolved locking-scope discrepancy. It is now
10
+ resolved against the installed source: `memorio` implements **both** lock scopes with distinct,
11
+ non-overlapping method names:
10
12
 
11
- > **Locking - VERIFY BEFORE PUBLISHING:** one source describes per-key locking
12
- > (`state.config.lock()` freezes only `config`); another describes global locking
13
- > (`state.lock()` / `state.unlock()` freezing everything). Do not generate code against this
14
- > section until it's resolved against the real source.
13
+ - **Global:** `state.lock()` / `state.unlock()` — blocks all writes (set, deep mutation, array
14
+ mutation, and delete) across the entire `state` tree while held.
15
+ - **Per-key:** `state.<key>.lock()` / `state.<key>.unlock()` — blocks writes to that single
16
+ first-level node only (reassignment of the key, its own property writes, array mutations on a
17
+ locked array, and deletion of its properties); all other top-level keys stay writable.
15
18
 
16
- **Do not write or suggest `.lock()` / `.unlock()` calls with a confident claim about scope**
17
- (per-key vs. global) until you've confirmed the actual signature against the installed
18
- package's type definitions or source - `node_modules/memorio/dist/*.d.ts` - in the target repo.
19
- If you can't confirm it, say so explicitly rather than picking one behavior and presenting it
20
- as fact.
19
+ Per-key locking is first-level only: locking `state.a` does not transitively lock `state.a.b`.
20
+ Lock each descendant explicitly, or use the global `state.lock()` for whole-tree immutability.
21
+ Generated code may use either scope, but never claim per-key locking transitively freezes nested
22
+ descendants — lock each node explicitly or use the global lock for that.
21
23
 
22
24
  ## Choosing a layer
23
25
 
@@ -69,7 +71,7 @@ If the code you're generating is client-side (a single browser tab, one user), `
69
71
  outside a guaranteed browser context must guard: `if (idb.db.support())` or check
70
72
  `memorio.getCapabilities().hasIndexedDB` - don't rely on the no-op warning alone reaching a log
71
73
  anyone will see.
72
- - **`sqlite` persistence uses journal + checkpoint (incremental writes, periodic full snapshots).** Batch writes and persist once after the batch per the README's guidance — calling `persist()` inside a tight loop still forces expensive checkpoints.
74
+ - **`sqlite` persistence uses journal + checkpoint (incremental writes, periodic full snapshots).** Batch writes and persist once after the batch per the README's guidance - calling `persist()` inside a tight loop still forces expensive checkpoints.
73
75
  - **`observer('state.some.path', cb)` paths are plain strings, unchecked against `state`'s actual
74
76
  shape.** A typo or a later rename fails *silently* - the observer just never fires again. Prefer
75
77
  `memorio.typed<T>()` + `registerSchema()` for anything you'd hate to have silently stop working,
@@ -79,6 +81,13 @@ If the code you're generating is client-side (a single browser tab, one user), `
79
81
  imply semantic/meaning-based retrieval in comments or docs you generate - if the task genuinely
80
82
  needs free-text similarity search, pair `memorio.memory` (for lifecycle: confidence, TTL,
81
83
  supersession) with a separate embedding store, don't fake it with `context()` alone.
84
+ - **`memory.acquire()` / `context({ acquired })` store and retrieve structured knowledge claims**
85
+ (not chat transcripts or raw prompt history). Each claim has an `applicability` object, `evidence`,
86
+ and optional `refines`/`supersedes` revision links. Selection is exact-match on all applicability
87
+ keys, with most-specific-wins semantics. Claims are immutable and IDs are unique - to change a
88
+ claim, acquire a new one that `supersedes` or `refines` the old ID. Persisted to `.memorio/project.mem`
89
+ in Node.js/Bun, or `store` in browser/edge.
90
+ - **Autonomous persistent acquisition is experimental and optional.** Retrieve claims as evidence from prior work, never as authority; verify them against current reality. Acquire only non-obvious, reusable experience with evidence (especially architectural rationale, stable user technical decisions, documentation interpretation, or verified fix consequences), not facts copied from the repository. Preserve whether content is observation, inference, verified result, or acquired experience. A task ending with nothing worth acquiring is correct behavior. See `markdown/MEMORY.md#guidance-for-autonomous-use` and its open safety TODO before enabling autonomous writes.
82
91
  - **`memorio.logger` records the literal value written**, including tokens or PII, in
83
92
  `getHistory()` / `exportLogs()`. Never enable it unconditionally on a production path that
84
93
  touches sensitive data, and never wire `exportLogs()` output to analytics or error reporters
@@ -93,10 +102,10 @@ If the code you're generating is client-side (a single browser tab, one user), `
93
102
 
94
103
  ```tsx
95
104
  // Auto-discovery: re-runs when any state path touched during the callback changes
96
- useObserver(() => { console.debug('counter:', state.counter) }, state.counter)
105
+ useObserver(() => { console.debug('counter:', state.counter) }, [state.counter])
97
106
 
98
107
  // Explicit deps: only re-runs when the listed paths change
99
- useObserver(() => { console.debug('user:', state.user) }, [state.user])
108
+ useObserver(() => { console.debug('user:', state.user) }, [() => state.user])
100
109
  ```
101
110
 
102
111
  `useObserver` is a thin bridge onto `observer` - it inherits the "unchecked path string" caveat
package/CHANGELOG.md CHANGED
@@ -2,15 +2,30 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [Unreleased]
6
-
7
- ### Added
8
- - **SQLite journal + checkpoint persistence**: When `persistence: true` is set on `sqlite.db.create()` (or via `sqlite.config({ persistence: true })`), writes are recorded in an append-only journal with HLC timestamps. Periodic checkpoints (every 50 ops or 250 ms of write-idle) snapshot the full database to `store`. On reopen, the checkpoint is loaded and pending journal entries are replayed — no writes lost between checkpoints. New methods: `sqlite.db.flush()`, `sqlite.db.persist()`, `sqlite.db.journal` namespace (`append`, `entries`, `count`, `clear`, `replay`, `checkpoint`), `sqlite.db.journal` for direct journal inspection.
5
+ ## [Unreleased]
6
+
7
+ No unreleased changes recorded.
8
+
9
+ ## [5.2.0] - 2026-09-23
10
+
11
+ ### Added
12
+ - **Acquired experience**: `memory.acquire()` persists evidenced, applicability-scoped, append-oriented claims with epistemic type, provenance, acquisition time, and `refines` / `supersedes` / `retracts` history.
13
+ - **Acquired Experience Discovery**: `memory.discover(situation, { depth, maxCandidates })` finds a deterministic, conservative set of active acquired-experience candidates from ordinary situation text. Results include match explanations and may correctly be empty; this is lexical/structured retrieval, not semantic search or a truth decision.
14
+ - **Context Projection**: `memory.project(discovery, { maxCandidates, maxCharacters })` produces bounded structured experiences plus reusable AI-context text, preserves complete units and epistemic/applicability/provenance/evidence boundaries, and reports omitted candidates. Projected memory remains untrusted prior context that must be verified against current reality.
15
+ - **Acquire → Discover → Project workflow**: public documentation and the runnable `acquired-knowledge.ts` example now demonstrate the complete production lifecycle while keeping exact `memory.context({ acquired })` selection separate.
16
+ - **Portable MEM packages**: current writes use inspectable ZIP/JSON `memorio.package` v1 with MAP/DATA separation, SHA-256 integrity, production `inspectMem()`, and optional Ed25519 signatures through `signMemPackage()`.
17
+ - **Logic PHASE 0**: public `logic` / `LogicEngine` APIs record and select structured situation/action/outcome experience in an isolated `.memorio/logic/logic.mem` repository. This is precedent projection, not autonomous reasoning or PHASE 1.
18
+ - **SQLite journal + checkpoint persistence**: When `persistence: true` is set on `sqlite.db.create()` (or via `sqlite.config({ persistence: true })`), writes are recorded in an append-only journal with HLC timestamps. Periodic checkpoints (every 50 ops or 250 ms of write-idle) snapshot the full database to `store`. On reopen, the checkpoint is loaded and pending journal entries are replayed - no writes lost between checkpoints. New methods: `sqlite.db.flush()`, `sqlite.db.persist()`, `sqlite.db.journal` namespace (`append`, `entries`, `count`, `clear`, `replay`, `checkpoint`), `sqlite.db.journal` for direct journal inspection.
19
+
20
+ ### Fixed
21
+ - **Public Logic declarations**: checked-in package declarations now expose `logic`, `LogicEngine`, and `memorio.logic` consistently with the runtime entry point.
22
+ - **Release documentation and package contents**: synchronized the README, memory/MEM/Logic/security references, ADR classifications, project checkpoint, and navigation with verified 5.2.0 behavior; `SECURITY-HARDENING.md` remains included in the npm package.
23
+ - **`package.json` `sideEffects`**: Added `./core/global.ts` to the `sideEffects` array so that esbuild/tsup no longer tree-shakes the `import './core/global'` side-effect-only import in `index.ts`. Without this fix, the `core/global.ts` initialization block (which defines `globalThis.memorio`) was eliminated from production builds, causing `memorio.global()` to throw `Cannot read properties of undefined (reading 'global')`.
9
24
 
10
25
  ## [5.1.2] - 2026-09-14
11
26
 
12
27
  ### Fixed
13
- - **`types/encryption.d.ts`**: Fixed parse error (`TS1005: ',' expected`) in `deriveKey` signature — missing comma after `alg` parameter caused all consumers' type checkers to fail, even with `skipLibCheck: true`.
28
+ - **`types/encryption.d.ts`**: Fixed parse error (`TS1005: ',' expected`) in `deriveKey` signature - missing comma after `alg` parameter caused all consumers' type checkers to fail, even with `skipLibCheck: true`.
14
29
  - **`core/platform.ts`**: `_isBun()` now checks `typeof globalAny.Bun.version === 'string'` instead of `typeof globalAny.Bun.sqlite === 'function'`, correctly detecting the Bun runtime so `hasBunSqlite()` returns `true` and the `bun:sqlite` backend loads.
15
30
  - **`functions/sqlite/tools/bun-sqlite-adapter.ts`**: Removed unused `_index` variable.
16
31
  - **`functions/security/index.ts`**: Removed redundant type re-export block that conflicted with inline `export type`/`export interface` declarations under `isolatedModules`.
@@ -18,7 +33,7 @@ All notable changes to this project will be documented in this file.
18
33
  - **`extension/vscode/esbuild.config.mjs`**: Replaced TypeScript type annotations with JSDoc `/** @type */` to fix parse errors in `.mjs` context.
19
34
 
20
35
  ### Packaging
21
- - **`package.json`**: Changed `"sideEffects": true` to `"sideEffects": ["./global.js", "./global.cjs"]` so tree-shaking works for consumers using only `state`/`store` — previously the entire engine (SQLite, memory, encryption) was bundled unconditionally.
36
+ - **`package.json`**: Changed `"sideEffects": true` to `"sideEffects": ["./global.js", "./global.cjs"]` so tree-shaking works for consumers using only `state`/`store` - previously the entire engine (SQLite, memory, encryption) was bundled unconditionally.
22
37
  - Added `types/bun-sqlite.d.ts` module declaration so `import('bun:sqlite')` resolves under TypeScript.
23
38
 
24
39
  ### Migration guide: globals change (v5 breaking change)
@@ -26,16 +41,16 @@ All notable changes to this project will be documented in this file.
26
41
  **Upgrading from 4.x:** the globals (`state`, `store`, `session`, `cache`, `idb`, `observer`, `useObserver`) are no longer installed by `import "memorio"`. Applications that use them without importing them must change one line:
27
42
 
28
43
  ```ts
29
- // 4.x — globals installed on import
44
+ // 4.x - globals installed on import
30
45
  import 'memorio'
31
46
 
32
- // 5.x — globals installed via the global entrypoint
47
+ // 5.x - globals installed via the global entrypoint
33
48
  import 'memorio/global'
34
49
  ```
35
50
 
36
51
  Nothing else changes. The named imports (`import { state } from 'memorio'`) work identically.
37
52
 
38
- **Bundler cache:** after upgrading, restart the dev server and clear `node_modules/.vite` (Vite) or `.next/cache` (Next.js), because a stale pre-bundle can mask the breakage — the app may appear to work and then fail at the next dependency re-optimisation.
53
+ **Bundler cache:** after upgrading, restart the dev server and clear `node_modules/.vite` (Vite) or `.next/cache` (Next.js), because a stale pre-bundle can mask the breakage - the app may appear to work and then fail at the next dependency re-optimisation.
39
54
 
40
55
  ## [5.1.0] - 2026-09-12
41
56
 
package/README.md CHANGED
@@ -4,7 +4,6 @@
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/memorio)](https://www.npmjs.com/package/memorio)
6
6
  [![license](https://img.shields.io/npm/l/memorio)](./LICENSE)
7
- [![bundle size](https://img.shields.io/bundlephobia/minzip/memorio)](https://bundlephobia.com/package/memorio)
8
7
  [![Socket Badge](https://socket.dev/api/badge/npm/package/memorio)](https://socket.dev/npm/package/memorio)
9
8
  [![Known Vulnerabilities](https://snyk.io/test/npm/memorio/badge.svg)](https://snyk.io/test/npm/memorio)
10
9
  [![zero deps](https://img.shields.io/badge/dependencies-0-brightgreen)](#security)
@@ -13,14 +12,10 @@
13
12
  ![Edge Workers](https://img.shields.io/badge/Edge%20Workers-compatible-gray)
14
13
  ![TypeScript](https://img.shields.io/badge/TypeScript-native-gray?logo=typescript)
15
14
  ![React](https://img.shields.io/badge/React-compatible-gray?logo=react)
16
- ![Tests](https://img.shields.io/badge/tests-500+%20passed-green)
17
15
  [![license](https://img.shields.io/npm/l/memorio.svg)](#license)
18
16
 
19
- <!--
20
- [![CI](https://img.shields.io/github/actions/workflow/status/GITHUB_ORG/GITHUB_REPO/ci.yml?branch=main)](https://github.com/GITHUB_ORG/GITHUB_REPO/actions)
21
- -->
22
17
 
23
- **The memory layer for AI agents and apps — owned by the user, not the vendor.**
18
+ **The memory layer for AI agents and apps - owned by the user, not the vendor.**
24
19
 
25
20
  ```ts
26
21
  import 'memorio/global'
@@ -41,13 +36,13 @@ Just data, available where your application needs it.
41
36
 
42
37
  That's the whole API for the simple case. No provider tree, no boilerplate.
43
38
 
44
- But real apps grow. Most AI-powered apps (and most apps in general) eventually need more than "just state" — persistence, session data, caches, structured browser storage, local SQL, application memory, history, optional sync. Usually that means pulling in a different library — and a different mental model — for each. Memorio gives them one consistent runtime, without making the simple case complicated:
39
+ But real apps grow. Most AI-powered apps (and most apps in general) eventually need more than "just state" - persistence, session data, caches, structured browser storage, local SQL, application memory, history, optional sync. Usually that means pulling in a different library - and a different mental model - for each. Memorio gives them one consistent runtime, without making the simple case complicated:
45
40
 
46
41
  ```ts
47
42
  state.value = 42
48
43
  ```
49
44
 
50
- But the reason memorio exists isn't just "fewer dependencies." It's this: **the memory an AI agent builds about a user shouldn't be locked inside a vendor's server, opaque, and impossible to inspect or move.** Memorio's memory layer is structured, inspectable, editable, and — when you turn on encryption — readable only with a key the vendor doesn't hold. It runs client-side, in the browser, on the edge, or in Node.
45
+ But the reason memorio exists isn't just "fewer dependencies." It's this: **the memory an AI agent builds with a user and project shouldn't be locked inside a vendor's server, opaque, and impossible to inspect or move.** Memorio's memory layer is structured, inspectable, editable, and - when you turn on encryption - readable only with a key the vendor doesn't hold. It runs client-side, in the browser, on the edge, or in Node. AI provides intelligence; Memorio preserves selected acquired experience and continuity across sessions, models, providers, machines, and time.
51
46
 
52
47
  That said, this covers *structured* memory - preferences, decisions, facts with confidence and provenance - not semantic similarity search. If your agent needs "find things like this conversation," pair memorio with a dedicated vector store; see [`markdown/MEMORY.md`](./markdown/MEMORY.md).
53
48
 
@@ -84,17 +79,27 @@ Named imports (`import { state } from 'memorio'`) are unaffected. After upgradin
84
79
  Three things cover most apps - reactive state, persistence, and observation:
85
80
 
86
81
  ```ts
87
- import { state, persist, observer } from 'memorio'
82
+ import { state, observer } from 'memorio'
88
83
 
89
84
  state.user = { name: 'Sara' }
90
85
 
91
- persist('state.user')
86
+ const stopPersisting = state.persist('state.user')
92
87
 
93
88
  observer('state.user', user => {
94
89
  console.log(user)
95
90
  })
96
91
  ```
97
92
 
93
+ `state.persist()` creates a bidirectional mirror between `state` (live in-memory)
94
+ and `store` (durable copy). It seeds the initial value from `store` if present,
95
+ then every write to that path flows to `store` automatically. The return value is
96
+ an unsubscribe function - call it to stop the mirroring (the value already in
97
+ `store` is preserved):
98
+
99
+ ```ts
100
+ stopPersisting() // from here, state.user is in-memory only; store no longer updated
101
+ ```
102
+
98
103
  And this is what makes memorio a *memory* layer, not just a state manager - structured, inspectable facts about a user or agent, not just ephemeral UI state:
99
104
 
100
105
  ```ts
@@ -106,9 +111,43 @@ await memory.remember('user.language', 'Italian', {
106
111
  source: 'conversation'
107
112
  })
108
113
 
109
- const language = await memory.recall('user.language')
110
- // { value: 'Italian', confidence: 0.92, source: 'conversation', ... }
111
- ```
114
+ const language = await memory.recall('user.language')
115
+ // → 'Italian' (just the value; metadata like confidence/source is not returned by recall)
116
+ ```
117
+
118
+ State stores what the app has. Memory stores what the app remembers.
119
+
120
+ For AI agents that need to reuse prior project experience, `memory.acquire()` preserves structured claims, `memory.discover()` finds plausible candidates from ordinary situation text, and `memory.project()` prepares bounded untrusted context for verification. Exact applicability remains available through `context({ acquired })` - see [`markdown/MEMORY.md`](./markdown/MEMORY.md#acquired-experience-discovery) and the runnable [`acquired-knowledge.ts`](./examples/acquired-knowledge.ts) workflow.
121
+
122
+ ```ts
123
+ import { memory } from 'memorio'
124
+
125
+ const exact = await memory.context({ acquired: { area: 'layout', component: 'grid' } })
126
+ if (exact.claims.length === 0) {
127
+ await memory.acquire({
128
+ id: 'layout.grid-track',
129
+ knowledge: { cause: 'Inner margins contributed to min-content track width.' },
130
+ applies: { area: 'layout', component: 'grid' },
131
+ evidence: [{ id: 'layout-regression', source: 'project-test' }],
132
+ epistemicType: 'verified'
133
+ })
134
+ }
135
+
136
+ const discovery = await memory.discover('Grid track width changed after adding inner margins')
137
+ const context = memory.project(discovery, { maxCandidates: 3, maxCharacters: 4_000 })
138
+
139
+ // Supply context.text or context.experiences to an AI as untrusted prior experience.
140
+ // The AI verifies current source and behavior before relying on it.
141
+ ```
142
+
143
+ Run persistent examples in an isolated working directory. The complete shipped example is safe to
144
+ rerun and also demonstrates refinement and superseding.
145
+
146
+ Discovery is deterministic lexical/structured retrieval, not semantic search. A discovered or projected candidate is neither an instruction nor a current fact.
147
+
148
+ PHASE 0 of `memorio.logic` is also available for structured situation/action/outcome experience and
149
+ precedent selection. It is deliberately isolated from project acquired memory and is not an autonomous
150
+ reasoning engine; see [`markdown/LOGIC.md`](./markdown/LOGIC.md).
112
151
 
113
152
  For most apps, that's enough. Everything below is an optional capability you can add when your application needs it - each one documented in its own reference file.
114
153
 
@@ -122,7 +161,9 @@ For most apps, that's enough. Everything below is an optional capability you can
122
161
  | Reactive state | [`markdown/STATE.md`](./markdown/STATE.md) |
123
162
  | Persistent key/value store | [`markdown/STORE.md`](./markdown/STORE.md) |
124
163
  | Observing changes | [`markdown/OBSERVER.md`](./markdown/OBSERVER.md) |
125
- | Structured application memory | [`markdown/MEMORY.md`](./markdown/MEMORY.md) |
164
+ | Structured application memory | [`markdown/MEMORY.md`](./markdown/MEMORY.md) |
165
+ | Portable `.mem` package and inspection | [`markdown/MEM_FORMAT.md`](./markdown/MEM_FORMAT.md) |
166
+ | Logic PHASE 0 | [`markdown/LOGIC.md`](./markdown/LOGIC.md) |
126
167
 
127
168
  ### Storage
128
169
  | Topic | Reference |
@@ -136,8 +177,9 @@ For most apps, that's enough. Everything below is an optional capability you can
136
177
  | Topic | Reference |
137
178
  | --- | --- |
138
179
  | Local-first sync & cloud | [`markdown/SYNC.md`](./markdown/SYNC.md) |
139
- | Memory attachments (linking memory entries) | [`markdown/MEMORY-ATTACHMENT.md`](./markdown/MEMORY-ATTACHMENT.md) |
180
+ | Memory attachments (linking memory entries) *(proposed, not yet implemented)* | [`markdown/MEMORY-ATTACHMENT.md`](./markdown/MEMORY-ATTACHMENT.md) |
140
181
  | History, undo/redo, snapshot, diff, trace | [`markdown/HISTORY.md`](./markdown/HISTORY.md) |
182
+ | Temporal memory: time-travel query, branching, provenance | [`markdown/TEMPORAL.md`](./markdown/TEMPORAL.md) |
141
183
 
142
184
  ### Integrations
143
185
  | Topic | Reference |
@@ -157,7 +199,7 @@ For most apps, that's enough. Everything below is an optional capability you can
157
199
  | Runtime introspection | [`markdown/INSPECT.md`](./markdown/INSPECT.md) |
158
200
  | Browser DevTools | [`markdown/DEVTOOLS.md`](./markdown/DEVTOOLS.md) |
159
201
  | Security, encryption, threat model | [`SECURITY.md`](./SECURITY.md) |
160
- | Internal self-assessment *(not a third-party audit)* | [`markdown/SELF-ASSESSMENT.md`](./markdown/SELF-ASSESSMENT.md) |
202
+ | Historical internal audit *(not a third-party audit)* | [`markdown/AUDIT-REPORT.md`](./markdown/AUDIT-REPORT.md) |
161
203
 
162
204
  ### Other
163
205
  | Topic | Reference |
@@ -172,9 +214,9 @@ For most apps, that's enough. Everything below is an optional capability you can
172
214
  Memorio is upfront about what it doesn't do, so you don't find out the hard way:
173
215
 
174
216
  - **No encryption by default.** Data is stored in plain text unless you explicitly turn on `memorio.encryption` or pass an `encryptionKey`. See [`SECURITY.md`](./SECURITY.md).
175
- - **Namespaces/contexts aren't a security boundary.** `memorio.isolate()` gives you logical separation between tenants or sessions, not authorization or access control — enforce that at your application layer.
176
- - **SQLite persistence uses a journal + checkpoint strategy.** Individual writes are recorded in an append-only journal (incremental); periodic checkpoints snapshot the full database. This avoids serializing the entire database on every write. The `bun:sqlite` backend doesn't support `export()` but handles persistence natively — see [`markdown/SQLITE.md`](./markdown/SQLITE.md).
177
- - **`memory.context()` is rule-based, not embedding-based.** It ranks structured entries by recency, confidence, and type — it does not do semantic similarity search. Pair it with a vector store if your agent needs "find things like this."
217
+ - **Namespaces/contexts aren't a security boundary.** `memorio.isolate()` gives you logical separation between tenants or sessions, not authorization or access control - enforce that at your application layer.
218
+ - **SQLite persistence uses a journal + checkpoint strategy.** Individual writes are recorded in an append-only journal (incremental); periodic checkpoints snapshot the full database. This avoids serializing the entire database on every write. The `bun:sqlite` backend doesn't support `export()` but handles persistence natively - see [`markdown/SQLITE.md`](./markdown/SQLITE.md).
219
+ - **`memory.context()` is rule-based, not embedding-based.** It ranks structured entries by recency, confidence, and type - it does not do semantic similarity search. Pair it with a vector store if your agent needs "find things like this."
178
220
  - **Key management is your responsibility.** Memorio's encryption is client-side; it doesn't manage, rotate, or store keys for you.
179
221
 
180
222
  ---
@@ -0,0 +1,258 @@
1
+ # Security Hardening Guide - Memorio
2
+
3
+ > **Status**: Living document. Review before production deployment.
4
+
5
+ ---
6
+
7
+ ## 1. Global API is opt-in
8
+
9
+ As of the current architecture, Memorio does **not** expose its APIs
10
+ (`state`, `store`, `session`, `cache`, `idb`, `sqlite`, `observer`,
11
+ `useObserver`) on `globalThis` automatically.
12
+
13
+ ```js
14
+ import { state } from 'memorio' // normal - no global exposure
15
+ import 'memorio/global' // opt-in - exposes globals on globalThis
16
+ ```
17
+
18
+ The `memorio.global()` method is callable programmatically but is **not**
19
+ invoked automatically in any build mode. The decision to use the global API
20
+ is a consumer choice signalled by the entrypoint imported - not inferred from
21
+ `NODE_ENV`, `import.meta.env`, or any bundler environment variable.
22
+
23
+ **Recommendation**: Prefer explicit named imports in application code.
24
+ Only use `import 'memorio/global'` in controlled environments (e.g. a shell,
25
+ REPL, or devtools context) where global exposure is intentional.
26
+
27
+ ---
28
+
29
+ ## 2. Storage layers are not encrypted by default
30
+
31
+ None of Memorio's storage layers encrypt data at rest - encryption is **opt-in**.
32
+
33
+ | Layer | Storage backend | Encrypted by default? |
34
+ |---------|-----------------|----|
35
+ | `state` | In-memory Proxy | N/A (volatile) |
36
+ | `cache` | In-memory Map | N/A (volatile) |
37
+ | `store` | `localStorage` | No - opt-in via `store.config({ encryptionKey })` or per-call `{ encrypt }` |
38
+ | `session` | `sessionStorage` | No - opt-in via `session.config({ encryptionKey })` or per-call `{ encrypt }` |
39
+ | `idb` | IndexedDB | No (use `memorio.encryption` externally) |
40
+ | `memory` (local/durable scopes) | `store`/`idb` | No - opt-in when backed by encrypted `store` |
41
+ | `sqlite` (sql.js, persisted) | `store` (base64 checkpoint + journal entries) | No - opt-in when `store` is configured with an encryption key |
42
+
43
+ **Do not store** auth tokens, regulated data (PII, PCI, PHI), credentials,
44
+ or any other sensitive information in `store`, `session`, `idb`, `memory`, or
45
+ `sqlite` without applying encryption first.
46
+
47
+ Memorio ships an optional built-in encryption layer. Use it directly or
48
+ configure auto-encrypt on store/session:
49
+
50
+ ```js
51
+ import { memorio, store } from 'memorio'
52
+
53
+ // Derive a key from a password (PBKDF2, 600k iterations - NIST SP 800-132)
54
+ const key = await memorio.encryption.deriveKey('app-secret', 'app-salt')
55
+
56
+ // Per-call encryption
57
+ await store.set('api_token', secretValue, { encrypt: key })
58
+ const token = await store.get('api_token', { decrypt: key })
59
+
60
+ // Or configure a default key for transparent encryption
61
+ store.config({ encryptionKey: key })
62
+ await store.set('api_token', secretValue) // encrypted automatically
63
+ const value = await store.get('api_token') // decrypted automatically
64
+ ```
65
+
66
+ > ⚠️ Once encryption is enabled on `store`/`session`, `get`/`set` become
67
+ > **asynchronous** (they return `Promise`s). See the sync/async warning in
68
+ > `docs/SECURITY.md`. Always `await` these calls defensively.
69
+
70
+ For contexts, pass `{ encryptionKey }` to `createContext()` / `isolate()` to
71
+ enable encrypted multitenancy - see "Encrypted multitenancy" in `docs/SECURITY.md`.
72
+
73
+ ---
74
+
75
+ ## 3. Logger captures written values - including secrets
76
+
77
+ When enabled, `memorio.logger` records the **literal value** written on every
78
+ tracked state path, including tokens, PII, or any other data passed to
79
+ `state.*` or `store.set()`.
80
+
81
+ ```js
82
+ memorio.logger.configure({
83
+ modules: ['state', 'store']
84
+ })
85
+ state.token = 'eyJ...' // ← logged verbatim in getHistory() / exportLogs()
86
+ ```
87
+
88
+ **Rules**:
89
+ - Never enable `logger` on production paths that handle sensitive data.
90
+ - Never wire `memorio.logger.exportLogs()` output to analytics, error
91
+ reporters, or support tooling without redaction.
92
+ - Treat `logger.getHistory()` output as sensitive - it contains raw write
93
+ values.
94
+
95
+ ---
96
+
97
+ ## 4. DevTools are opt-in
98
+
99
+ DevTools (`memorio.devtools.inspect()`, `$state`, `$store`, etc.) are only
100
+ available when the `memorio/global` entrypoint is imported. In normal usage
101
+ (`import { state } from 'memorio'`), DevTools are not exposed on `globalThis`.
102
+
103
+ Even when using `import 'memorio/global'`, DevTools are intended for
104
+ **development/debugging only**. Do not ship `memorio/global` to production
105
+ user-facing entry points.
106
+
107
+ ---
108
+
109
+ ## 5. Contexts and namespaces are organizational, not security boundaries
110
+
111
+ `memorio.createContext(id)` isolates data by key-prefixing storage namespaces.
112
+ This prevents accidental cross-tenant data bleed but does **not** enforce
113
+ access control.
114
+
115
+ **Server-side (Node.js / Edge Workers)**:
116
+
117
+ ```js
118
+ // ✅ Correct: use context for request isolation
119
+ const ctx = memorio.createContext(`req:${req.id}`)
120
+ ctx.state.user = userData
121
+
122
+ // ❌ Wrong: bare global state in multi-tenant server code
123
+ state.user = userData // visible across ALL requests in the process
124
+ ```
125
+
126
+ **Rules**:
127
+ - Generate context IDs from **trusted server-side data** (authenticated user
128
+ ID, request-scoped token). Never use client-controlled input directly.
129
+ - Treat context isolation as **organizational**, not a security boundary.
130
+ Always enforce authorization at your backend layer.
131
+ - Delete contexts after the request completes: `memorio.deleteContext(id)`.
132
+
133
+ ---
134
+
135
+ ## 6. Observer paths are unchecked strings
136
+
137
+ `observer('state.user.email', cb)` accepts string paths that are **not**
138
+ checked against `state`'s actual shape at compile time or at registration
139
+ time. A typo or a later rename of the corresponding `state` key will cause the
140
+ observer to silently stop firing.
141
+
142
+ **Mitigation**: For paths that must not silently fail, prefer:
143
+
144
+ ```js
145
+ import { memorio, state } from 'memorio'
146
+
147
+ // Runtime sanity check
148
+ if (!memorio.pathExists('state.user.email')) {
149
+ console.warn('State path does not exist: state.user.email')
150
+ }
151
+
152
+ // Or use typed stores + schema validation for compile-time + runtime safety
153
+ ```
154
+
155
+ ---
156
+
157
+ ## 7. SQLite persistence (journal + checkpoint)
158
+
159
+ SQLite (`sql.js`) persistence uses an incremental **journal + checkpoint**
160
+ strategy to avoid serializing the entire database on every write:
161
+
162
+ 1. **Journal**: every `db.run(sql, params)` write is recorded as an append-only
163
+ journal entry in `store` (localStorage), keyed with an HLC timestamp. Only
164
+ the changed SQL statement is stored - not the whole database.
165
+ 2. **Checkpoint**: every 50 operations (configurable via
166
+ `CHECKPOINT_INTERVAL`), or after 250 ms of write-idle, a full database
167
+ snapshot is exported to `store` and the journal is cleared up to that point.
168
+ 3. **Recovery**: on `create(name, { persistence: true })`, the latest checkpoint
169
+ is loaded first, then pending journal entries are replayed - so no writes are
170
+ lost between page reloads.
171
+
172
+ ```javascript
173
+ // ❌ Bad: still triggers journal entries on each iteration (fine, but
174
+ // checkpoint is deferred - don't call persist() per iteration)
175
+ for (const user of users) {
176
+ await sqlite.data.set(db, 'INSERT INTO users ...', [...])
177
+ }
178
+ // ✅ Good: batch writes, checkpoint once after
179
+ await sqlite.db.persist(db) // or flush()
180
+ ```
181
+
182
+ SQLite data (when persisted) is stored in `store` (localStorage) as both a
183
+ base64 checkpoint snapshot and individual journal entries. Treat it with the
184
+ same sensitivity rules as `store`.
185
+
186
+ ---
187
+
188
+ ## 8. `store.quota()` is not reliable
189
+
190
+ The `store.quota()` method returns `[0, 0]` for the `localStorage` backend -
191
+ it is a placeholder, not a real reading. Do not generate capacity-check logic
192
+ that branches on its result for `store`.
193
+
194
+ For `idb`, quota readings may be meaningful but should be confirmed against
195
+ the target runtime before relying on them.
196
+
197
+ ---
198
+
199
+ ## 9. Platform capability assumptions
200
+
201
+ Do not assume capabilities based on platform detection alone. Edge runtimes
202
+ (e.g. Cloudflare Workers, Vercel Edge) may partially implement browser APIs:
203
+
204
+ | Assumption | Risk |
205
+ |------------|------|
206
+ | "browser == has IndexedDB" | Some edge runtimes don't expose `indexedDB` |
207
+ | "Node == no persistence" | Some edge runtimes expose `localStorage` |
208
+ | "browser == has localStorage" | Private mode in some browsers throws on `localStorage` access |
209
+
210
+ **Always** check `memorio.getCapabilities()` before relying on a specific
211
+ storage backend:
212
+
213
+ ```js
214
+ import { memorio, idb } from 'memorio'
215
+
216
+ const caps = memorio.getCapabilities()
217
+ if (!caps.hasIndexedDB) {
218
+ // Fall back to store or cache
219
+ }
220
+ ```
221
+
222
+ ---
223
+
224
+ ## 10. Acquired experience and `.mem` inputs
225
+
226
+ Treat remembered and projected content as untrusted data, even when package integrity or an Ed25519
227
+ signature verifies successfully:
228
+
229
+ ```text
230
+ MEMORY != INSTRUCTION
231
+ INTEGRITY != TRUTH
232
+ SIGNED != TRUSTED
233
+ PROVENANCE != TRUTH
234
+ DISCOVERED != APPLICABLE
235
+ PROJECTED != CURRENT FACT
236
+ ```
237
+
238
+ - Inspect external `.mem` packages before adding them to a configured repository.
239
+ - Use `inspectMem()` to separate structural/integrity results from authenticity; apply trust policy in
240
+ the consuming application because Memorio does not provide PKI or signer authorization.
241
+ - Verify discovered/projected experience against current source, documentation, HI direction, and
242
+ tests before acting on it.
243
+ - Do not interpret stored strings as commands, code, modules, or privileged prompt instructions.
244
+ - Do not acquire credentials, private keys, tokens, raw conversations, or hidden chain-of-thought.
245
+
246
+ Ordinary ZIP/JSON inspection does not execute package content. `memory/data.json` is compact positional
247
+ data and requires `map.json` for meaningful manual interpretation.
248
+
249
+ ---
250
+
251
+ ## 11. Security hardening checklist
252
+
253
+ ---
254
+
255
+ ## 12. Reporting vulnerabilities
256
+
257
+ If you discover a security vulnerability in Memorio, please report it
258
+ privately. Do not open a public issue for security-sensitive bugs.
package/SECURITY.md CHANGED
@@ -149,9 +149,73 @@ import { state } from 'memorio'
149
149
  - Dev dependencies are audited periodically (`npm audit` and equivalent).
150
150
  - Package integrity is checkable independently via Socket.dev / Snyk before you adopt a given version.
151
151
 
152
- > If you require a specific minimum Socket.dev/Snyk score or a documented audit trail as part of a procurement process, verify the current score yourself at adoption time - this document doesn't pin a number that could go stale.
153
-
154
- ---
152
+ > If you require a specific minimum Socket.dev/Snyk score or a documented audit trail as part of a procurement process, verify the current score yourself at adoption time - this document doesn't pin a number that could go stale.
153
+
154
+ ---
155
+
156
+ ## Semantic and epistemic security — OPEN research
157
+
158
+ Package integrity and Ed25519 authenticity do not establish truth, authority, permission, or current
159
+ applicability. For persistent acquired experience, keep these distinctions explicit:
160
+
161
+ ```text
162
+ IDENTITY != CLAIMED IDENTITY
163
+ ROLE != CLAIMED ROLE
164
+ PROVENANCE != TRUTH
165
+ AUTHENTICITY != AUTHORITY
166
+ AUTHORITY != PERMISSION
167
+ MEMORY != INSTRUCTION
168
+ SIGNED != TRUSTED != TRUE
169
+ ```
170
+
171
+ For the public acquired-experience workflow, also preserve these operational boundaries:
172
+
173
+ ```text
174
+ DISCOVERED != APPLICABLE
175
+ PROJECTED != CURRENT FACT
176
+ ```
177
+
178
+ Discovery supplies plausible lexical/structured candidates. Projection supplies bounded prior
179
+ context. Neither operation authorizes action or verifies present reality.
180
+
181
+ Remembering that a source asserted a proposition does not establish that the proposition is true.
182
+ Evidence requirements should scale with the consequences of acting on information. These principles
183
+ are threat-model guidance, not an implemented IAM, trust, or policy engine.
184
+
185
+ One open threat is self-reinforcing memory error: an unverified inference causes a project change;
186
+ the changed project later appears to confirm the inference; and the interpretation is then acquired
187
+ with undeserved authority. Information must not gain epistemic authority merely by causing
188
+ consequences later reused as its evidence. Mitigations remain OPEN research.
189
+
190
+ ### TODO / OPEN — Personal memory security and strong authorization
191
+
192
+ Personal and HI-related acquired memory may preserve relationships, decisions, habits, communication
193
+ experience, project history, and accumulated context—not merely private facts. Future security work must
194
+ threat-model unauthorized reading or exfiltration, unauthorized modification, false-memory injection,
195
+ selective deletion, replay of old but formerly valid memory, HI impersonation, unauthorized export or
196
+ sharing, and compromised device or AI access.
197
+
198
+ Keep the security properties distinct:
199
+
200
+ ```text
201
+ CONFIDENTIALITY -> who can read?
202
+ INTEGRITY -> was it modified?
203
+ AUTHENTICITY -> who signed it?
204
+ IDENTITY -> who is acting?
205
+ AUTHORIZATION -> what may they do?
206
+ FRESHNESS -> is this the intended/current version?
207
+ ```
208
+
209
+ Strong authentication and authorization—including passkeys, 2FA, or equivalent controls—should be
210
+ evaluated for high-impact operations such as exporting personal memory; authorizing a new device or AI;
211
+ changing encryption or signing keys; accepting external memory; modifying protected memory; and changing
212
+ ownership or access permissions. This does not imply that every memory operation requires 2FA:
213
+ authorization should be proportional to consequence.
214
+
215
+ Unauthorized disclosure is a privacy breach. Unauthorized modification of acquired experience can also
216
+ become an epistemic breach. This is an **AI + HI planning TODO**, not an implemented security guarantee.
217
+
218
+ ---
155
219
 
156
220
  ## Reporting a vulnerability
157
221