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.
- package/AGENTS.md +22 -13
- package/CHANGELOG.md +24 -9
- package/README.md +61 -19
- package/SECURITY-HARDENING.md +258 -0
- package/SECURITY.md +67 -3
- package/SUMMARY.md +55 -59
- package/adr/002-observer-semantics.md +1 -1
- package/adr/003-deep-mutation-semantics.md +1 -1
- package/adr/004-array-mutation-semantics.md +1 -1
- package/adr/010-logic-phase-0.md +42 -0
- package/adr/README.md +16 -11
- package/bin/cli.js +82 -60
- package/examples/acquired-knowledge.ts +174 -0
- package/examples/agent-memory-demo.ts +140 -0
- package/examples/sqlite-batched-writes.ts +60 -60
- package/examples/sync.ts +90 -90
- package/examples/useObserver.tsx +2 -2
- package/global.cjs +2026 -115
- package/global.js +2021 -116
- package/index.cjs +2026 -115
- package/index.d.ts +1 -0
- package/index.js +2021 -116
- package/llms.txt +135 -4
- package/markdown/EXTENSION_VSCODE_DESIGN.md +410 -0
- package/markdown/LOGIC.md +100 -0
- package/markdown/MEMORY-ATTACHMENT.md +17 -10
- package/markdown/MEMORY.md +404 -14
- package/markdown/MEM_FORMAT.md +313 -0
- package/markdown/SQLITE.md +2 -2
- package/markdown/STATE.md +27 -3
- package/markdown/SYNC.md +19 -15
- package/markdown/TEMPORAL.md +297 -0
- package/markdown/USEOBSERVER.md +7 -4
- package/modules/redux.cjs +1188 -38
- package/modules/redux.cjs.map +1 -1
- package/modules/redux.js +1187 -38
- package/modules/redux.js.map +1 -1
- package/package.json +14 -5
- package/types/exports.d.ts +47 -3
- package/types/logic.d.ts +79 -0
- package/types/memorio.d.ts +60 -16
- package/types/memory.d.ts +118 -0
- package/types/session.d.ts +1 -4
- package/types/state.d.ts +19 -5
- package/types/store.d.ts +1 -4
- package/types/temporal.d.ts +95 -0
- package/types/useObserver.d.ts +6 -10
- 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
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
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
|
-
|
|
8
|
-
|
|
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
|
|
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`
|
|
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
|
|
44
|
+
// 4.x - globals installed on import
|
|
30
45
|
import 'memorio'
|
|
31
46
|
|
|
32
|
-
// 5.x
|
|
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
|
|
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
|
[](https://www.npmjs.com/package/memorio)
|
|
6
6
|
[](./LICENSE)
|
|
7
|
-
[](https://bundlephobia.com/package/memorio)
|
|
8
7
|
[](https://socket.dev/npm/package/memorio)
|
|
9
8
|
[](https://snyk.io/test/npm/memorio)
|
|
10
9
|
[](#security)
|
|
@@ -13,14 +12,10 @@
|
|
|
13
12
|

|
|
14
13
|

|
|
15
14
|

|
|
16
|
-

|
|
17
15
|
[](#license)
|
|
18
16
|
|
|
19
|
-
<!--
|
|
20
|
-
[](https://github.com/GITHUB_ORG/GITHUB_REPO/actions)
|
|
21
|
-
-->
|
|
22
17
|
|
|
23
|
-
**The memory layer for AI agents and apps
|
|
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"
|
|
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
|
|
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,
|
|
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
|
-
//
|
|
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
|
-
|
|
|
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
|
|
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
|
|
177
|
-
- **`memory.context()` is rule-based, not embedding-based.** It ranks structured entries by recency, confidence, and type
|
|
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
|
|