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
@@ -0,0 +1,313 @@
1
+ # Memorio `.mem` Format
2
+
3
+ This document describes the implemented MEM package and `memorio.knowledge` logical format. A `.mem`
4
+ is data. It is not a program, module, prompt, or trusted instruction source.
5
+
6
+ > **ZIP is packaging. JSON is inspection. MAP is meaning. MEM is experience.**
7
+
8
+ Memorio must not require trust in Memorio to inspect a Memorio file. Ordinary ZIP and JSON tools can
9
+ open and inspect the package; opening or extracting it does not execute its contents.
10
+
11
+ ## Package
12
+
13
+ Current Memorio writes `.mem` as a standard ZIP archive with four ordinary JSON parts:
14
+
15
+ ```text
16
+ project.mem
17
+ ├── manifest.json
18
+ ├── map.json
19
+ ├── memory/data.json
20
+ └── security/integrity.json
21
+ ```
22
+
23
+ No empty future directories are created. Protected unknown files under `extensions/` are accepted and
24
+ preserved without interpretation. All other unexpected or unprotected entries are rejected.
25
+
26
+ The archive can be listed or extracted with normal ZIP tools. `manifest.json` identifies
27
+ `memorio.package` version 1 and locates the other standard parts. `map.json` and `memory/data.json`
28
+ contain the same logical v2 map and data described below. ZIP is packaging; MEM semantics do not
29
+ depend on ZIP entry metadata.
30
+
31
+ ```json
32
+ {
33
+ "format": "memorio.package",
34
+ "version": 1,
35
+ "memory": { "format": "memorio.knowledge", "version": 2 },
36
+ "parts": {
37
+ "map": "map.json",
38
+ "data": "memory/data.json",
39
+ "integrity": "security/integrity.json"
40
+ }
41
+ }
42
+ ```
43
+
44
+ For a Memorio diagnostic view, run:
45
+
46
+ ```bash
47
+ npx memorio inspect project.mem
48
+ ```
49
+
50
+ ### Integrity
51
+
52
+ `security/integrity.json` uses SHA-256 and records a `sha256:<hex>` digest for `manifest.json`,
53
+ `map.json`, `memory/data.json`, and every preserved extension part. It does not hash itself, avoiding
54
+ a recursive self-hash. Every non-integrity archive entry must appear exactly once in the protected
55
+ files table, and every recorded digest must match.
56
+
57
+ `contentId` is the SHA-256 digest of the canonical, path-sorted protected-files digest table. It is a
58
+ deterministic content/cache identity, not the hash of the ZIP bytes. It does not identify an author,
59
+ prove trust, certify content, or constitute a digital signature.
60
+
61
+ ```json
62
+ {
63
+ "algorithm": "sha256",
64
+ "files": {
65
+ "manifest.json": "sha256:...",
66
+ "map.json": "sha256:...",
67
+ "memory/data.json": "sha256:..."
68
+ },
69
+ "contentId": "sha256:..."
70
+ }
71
+ ```
72
+
73
+ Administrators can extract the package, inspect its JSON, and verify each listed digest with standard
74
+ SHA-256 tooling. The exact command differs by operating system; hash the raw bytes of each extracted
75
+ part and compare the lowercase hexadecimal digest with its integrity entry.
76
+
77
+ Package generation sorts entry paths, recursively sorts JSON object keys, retains array order, uses
78
+ two-space JSON indentation with a final newline, fixes ZIP timestamps to 1980-01-02 UTC, and uses
79
+ DEFLATE level 6. Equivalent supported logical content therefore produces deterministic package bytes.
80
+
81
+ ### Defensive Limits
82
+
83
+ - Maximum package size: 10 MiB.
84
+ - Maximum uncompressed total: 25 MiB.
85
+ - Maximum individual entry: 8 MiB.
86
+ - Maximum entries: 128.
87
+ - Maximum declared expansion ratio per entry: 200:1.
88
+
89
+ Readers reject unsafe absolute/traversal paths, backslash paths, duplicate entries, directory entries,
90
+ unsupported compression, malformed ZIP/UTF-8/JSON, missing or unprotected parts, unsupported package
91
+ or integrity versions, and checksum mismatches. Archives are read in memory without filesystem
92
+ extraction. Nested archive data is never recursively opened.
93
+
94
+ Integrity proves only that protected bytes match the package's digest table. Because an attacker can
95
+ replace content and recompute unsigned hashes, integrity is not authenticity, signature, certification,
96
+ authorization, safety, or trust. Implemented optional signatures are separate security parts and do
97
+ not change this distinction.
98
+
99
+ ### Optional Ed25519 Signatures
100
+
101
+ Unsigned packages remain valid. An explicitly signed package adds one JSON document per signing key:
102
+
103
+ ```text
104
+ security/signatures/<64-character-public-key-fingerprint>.json
105
+ ```
106
+
107
+ The protected manifest also declares `"authenticity": { "signed": true }`. This makes simple
108
+ signature-file removal structurally invalid. Converting a signed package back to an unsigned package
109
+ requires rebuilding the protected manifest and therefore produces a different `contentId`.
110
+
111
+ ```json
112
+ {
113
+ "format": "memorio.signature",
114
+ "version": 1,
115
+ "algorithm": "Ed25519",
116
+ "contentId": "sha256:...",
117
+ "keyId": "sha256:...",
118
+ "publicKey": { "format": "raw", "encoding": "base64url", "value": "..." },
119
+ "signature": { "encoding": "base64url", "value": "..." }
120
+ }
121
+ ```
122
+
123
+ The signed bytes are the following domain-separated UTF-8 payload:
124
+
125
+ ```text
126
+ MEMORIO-PACKAGE-SIGNATURE-V1\n<contentId>\n
127
+ ```
128
+
129
+ Signing the domain-separated `contentId` binds the signature to the canonical protected-parts digest
130
+ table without binding it to incidental ZIP bytes. Signature documents are outside both the protected
131
+ digest table and `contentId`, preventing signature recursion. Protected extensions are already part of
132
+ that table and are therefore authenticated even when Memorio does not understand them.
133
+
134
+ Ed25519 was selected for compact keys and signatures, deterministic signatures, standard raw public
135
+ keys, and Web Crypto support in the supported modern Node/browser path. The signing API accepts
136
+ external Ed25519 `CryptoKey` objects. Public verification material is stored as a 32-byte raw key.
137
+ `keyId` is the SHA-256 fingerprint of those raw public-key bytes. Private keys remain external and are
138
+ never serialized into `.mem`.
139
+
140
+ Verification validates package structure and limits, verifies SHA-256 integrity and reconstructs
141
+ `contentId`, and only then validates and verifies each signature. Inspection reports authenticity
142
+ separately as `unsigned`, `valid`, `invalid`, or `unsupported`, with `trust: not_evaluated`. A valid
143
+ signature proves control of the corresponding private key over that content identity. It does not
144
+ establish a person, organization, certification, authorization, safety, claim correctness, or trust.
145
+
146
+ Ordinary memory updates produce a new `contentId` and omit all prior signatures. Memorio never
147
+ automatically re-signs. Explicit signing is available through:
148
+
149
+ ```ts
150
+ const signed = await signMemPackage(unsignedPackage, privateCryptoKey, publicCryptoKey)
151
+ ```
152
+
153
+ The same protected content and Ed25519 key produce deterministic signature metadata and package bytes.
154
+ Multiple signature entry paths are structurally supported, but signer policy, trust, revocation, and
155
+ certification are not implemented.
156
+
157
+ ## Logical v2 Envelope
158
+
159
+ Before packaging, the logical memory has this version 2 envelope:
160
+
161
+ ```json
162
+ {
163
+ "format": "memorio.knowledge",
164
+ "version": 2,
165
+ "map": {},
166
+ "data": []
167
+ }
168
+ ```
169
+
170
+ `format` identifies the file family. `version` selects its codec. `map` declares how positional
171
+ records are interpreted. `data` contains the source's knowledge version and claims.
172
+
173
+ Version 1 used a `knowledge` object shaped like the runtime `KnowledgeState`. Current Memorio detects
174
+ content rather than relying on the `.mem` extension: it reads v1 JSON, v2 JSON, and package v1, while
175
+ new repository writes use package v1 containing logical v2. Reading historical JSON does not rewrite
176
+ it; a later explicit memory update writes the current package representation. Unsupported or corrupt
177
+ input is rejected with a diagnostic status.
178
+
179
+ ## MAP
180
+
181
+ A v2 map declares semantic identity separately from encoding position:
182
+
183
+ ```json
184
+ {
185
+ "state": ["knowledge-version", "claims"],
186
+ "claim": ["id", "knowledge", "applicability", "evidence", "epistemic-type", "provenance", "acquired-at", "revision"],
187
+ "evidence": ["id", "source", "observed-at", "supports"],
188
+ "provenance": ["source", "detail"],
189
+ "revision": ["kind", "claim-ids", "reason"],
190
+ "epistemicType": ["observed", "inferred", "human-stated", "verified"],
191
+ "revisionKind": ["refines", "supersedes", "retracts"]
192
+ }
193
+ ```
194
+
195
+ For example, a claim value at position `0` means `id` only when `map.claim[0]` is `id`. Readers look
196
+ up positions through the source-local map. Field and vocabulary order is not permanent.
197
+
198
+ The map is an interpretation contract and a compact vocabulary declaration. It is not executable,
199
+ an ontology, a synonym table, or authority to reinterpret historical meanings.
200
+
201
+ ## DATA
202
+
203
+ `data` is a positional state record interpreted through `map.state`. Its `claims` value contains
204
+ positional claim records interpreted through `map.claim`. Evidence, provenance, and revision records
205
+ use their corresponding maps. Optional trailing `null` positions may be omitted.
206
+
207
+ Vocabulary values are zero-based positions in the source-local `epistemicType` and `revisionKind`
208
+ arrays. For example, with the map above, epistemic value `3` means `verified`.
209
+
210
+ The current `memory/data.json` is therefore inspectable but deliberately compact: positional arrays
211
+ and numeric enum indexes are not maximally human-readable in isolation. Meaningful manual inspection
212
+ requires cross-referencing `map.json`; `memory/data.json` alone is not self-explanatory.
213
+
214
+ ```json
215
+ {
216
+ "format": "memorio.knowledge",
217
+ "version": 2,
218
+ "map": {
219
+ "state": ["knowledge-version", "claims"],
220
+ "claim": ["id", "knowledge", "applicability", "evidence", "epistemic-type", "provenance", "acquired-at", "revision"],
221
+ "evidence": ["id", "source", "observed-at", "supports"],
222
+ "provenance": ["source", "detail"],
223
+ "revision": ["kind", "claim-ids", "reason"],
224
+ "epistemicType": ["observed", "inferred", "human-stated", "verified"],
225
+ "revisionKind": ["refines", "supersedes", "retracts"]
226
+ },
227
+ "data": [1, [["layout.rule", "Use layout tokens.", {"area": "layout"}, [["review-1"]], 3]]]
228
+ }
229
+ ```
230
+
231
+ ## Implemented Semantics
232
+
233
+ - State: knowledge version and ordered append-only claims.
234
+ - Claim: identity, JSON knowledge, applicability, evidence, epistemic type, provenance, acquisition
235
+ time, and an optional revision relationship.
236
+ - Evidence: identity, source, observation time, and bounded `supports` text.
237
+ - Epistemic types: `observed`, `inferred`, `human-stated`, and `verified`.
238
+ - Revision kinds: `refines`, `supersedes`, and `retracts`.
239
+
240
+ Claim identity and source-file provenance are distinct. Source provenance is established by the file
241
+ that contributes a claim; claim provenance is claim metadata.
242
+
243
+ ## Known, Unknown, and Invalid
244
+
245
+ Known semantics are decoded into `KnowledgeState`, validated, and available to selection.
246
+
247
+ Unknown but valid mapped fields and vocabulary values remain opaque JSON data. They are retained with
248
+ their source-local map and positions, excluded from `KnowledgeState`, and not used for selection or
249
+ reasoning. When the same source is rewritten, the current writer overlays known fields and writes the
250
+ unknown data back unchanged. New claims leave unknown positions empty.
251
+
252
+ Invalid data is rejected. This includes malformed envelopes, missing required known meanings,
253
+ duplicate map identities, invalid positional records, non-JSON values, invalid known claims, and
254
+ unsupported format versions. Preservation does not turn invalid data into an unknown extension.
255
+
256
+ ## Compatibility and Evolution
257
+
258
+ Each `.mem` source carries its own version and interpretation map and is decoded independently.
259
+ Multiple sources need not have identical field order, vocabulary order, creation time, or producer.
260
+ Historical files therefore retain the information needed to interpret their original representation.
261
+
262
+ The source-local map is not declared to be the final or only interpretation layer. A future system
263
+ may build a separate dynamic map or index across many memories. Local maps remain valuable in that
264
+ architecture: individual sources stay independently interpretable and can contribute the declarations
265
+ needed to reconstruct a damaged or missing aggregate map. No global-map filename, schema, authority,
266
+ or runtime behavior is defined by v2.
267
+
268
+ A new representation does not necessarily invalidate or supersede an old representation. Different
269
+ labels may remain useful for the same underlying concept in different contexts. The format does not
270
+ perform synonym resolution, ontology translation, or automatic supersedence.
271
+
272
+ The opaque-preservation rule permits an older runtime or future independent editor to retain valid
273
+ extensions it does not understand. Actual composition, relationship editing, and plugin lifecycle
274
+ behavior are not part of this format version.
275
+
276
+ ## Composition and Reuse Readiness
277
+
278
+ A memory may eventually be used as a reusable, data-only memory plugin or as a component of a larger
279
+ memory. Version 2 does not define mounting, discovery, certification, permissions, dependencies, or
280
+ plugin execution.
281
+
282
+ Future composition is not assumed to copy complete input files into an output file. A derived memory
283
+ may instead record dependencies or lineage to independently retained sources, allowing a changed input
284
+ to trigger reevaluation rather than automatic copying or replacement. The identity, integrity, trust,
285
+ conflict, and lineage model required for this behavior remains deliberately unspecified.
286
+
287
+ The format likewise does not declare differently named concepts equivalent. An AI or another future
288
+ tool may propose relationships using the memories and current context, but the codec performs no
289
+ semantic discovery. New vocabulary does not automatically supersede old vocabulary, and a complete
290
+ memory may later be treated as one component of a larger memory.
291
+
292
+ These constraints keep the representation independent from any future editor technology. An editor,
293
+ CLI, library, AI, or server tool should operate on the same data and interpretation contracts.
294
+
295
+ ## Security
296
+
297
+ All `.mem` content is untrusted data. A reader must not evaluate values, construct functions from
298
+ them, execute expressions or commands, import modules named by them, or treat stored knowledge as
299
+ trusted instructions. Unknown preservation carries JSON data forward; it grants no execution or
300
+ authority.
301
+
302
+ ## Verified production inspection
303
+
304
+ Before the 5.2.0 release checkpoint, a representative package generated through the production
305
+ acquisition path was inspected with ordinary ZIP/JSON tooling and `inspectMem()`. It was 1,712 bytes
306
+ with SHA-256 `568cd4a309f753bb8179d756849a2bcb403ced2eb62fcf8df8ee6448b9c83335` and contained the four
307
+ standard unsigned entries shown above. Inspection reported package v1, memory v2, valid integrity,
308
+ no unknown semantics, and `unsigned` / `not_evaluated` authenticity. A field-by-field semantic
309
+ round-trip preserved both synthetic representative claims, including evidence, provenance,
310
+ applicability, epistemic type, timestamps, and a `refines` relationship.
311
+
312
+ That synthetic artifact is verification evidence, not normative production data or a fixture that
313
+ applications must reproduce byte-for-byte.
@@ -153,8 +153,8 @@ await sqlite.db.journal.checkpoint('app'); // manual checkpoint
153
153
 
154
154
  | Method | Parameters | Returns | Description |
155
155
  |--------|------------|---------|-------------|
156
- | `sqlite.config(opts)` | `opts: { loader?, locateFile?, wasmUrl?, persistence?, namespace? }` | `sqlite` | Configure the engine. `loader` fully replaces the initializer (default = CDN `<script>`); `locateFile` customizes wasm resolution; `wasmUrl` sets the wasm base directory; `persistence` toggles automatic db snapshots; `namespace` partitions persisted snapshots. Chainable. |
157
- | `sqlite.db.create(name, opts)` | `name: string, opts?: { data?, persistence? }` | `Database` | Opens/creates a named in-memory db. With `persistence: true` (or global config), a snapshot is restored if present and writes are auto-saved. |
156
+ | `sqlite.config(opts)` | `opts: { loader?, locateFile?, wasmUrl?, persistence?, namespace? }` | `sqlite` | Configure the engine. `loader` fully replaces the initializer (default = CDN `<script>`); `locateFile` customizes wasm resolution; `wasmUrl` sets the wasm base directory; `persistence` toggles journal + checkpoint persistence; `namespace` partitions persisted data. Chainable. |
157
+ | `sqlite.db.create(name, opts)` | `name: string, opts?: { data?, persistence? }` | `Database` | Opens/creates a named in-memory db. With `persistence: true` (or global config), the latest checkpoint is restored and pending journal entries are replayed for full crash recovery. |
158
158
  | `sqlite.db.persist(name)` | `name: string` | `Promise<boolean>` | Force an immediate checkpoint (full snapshot + journal clear) of a persisted database to `store`. Alias of `flush`. |
159
159
  | `sqlite.db.flush(name)` | `name: string` | `Promise<boolean>` | Force an immediate checkpoint (alias of `persist`). |
160
160
  | `sqlite.db.download(name, filename?)` | `name: string, filename?: string` | `Promise<boolean>` | Dev-only: trigger a browser download of the db as a `.sqlite` file. |
package/markdown/STATE.md CHANGED
@@ -100,15 +100,39 @@ console.debug(protect); // Array of protected keys
100
100
 
101
101
  ### Lock
102
102
 
103
+ `state` exposes two lock scopes with distinct method names.
104
+
105
+ **Global lock** - freeze the entire `state` tree at once:
106
+
107
+ ```javascript
108
+ state.lock();
109
+ state.user = { name: 'Sara' }; // Error: state is locked
110
+ state.user.name = 'Luigi'; // Error: state is locked
111
+ state.lock(); // (safe to call again)
112
+ state.unlock(); // resume all writes
113
+ ```
114
+
115
+ **Per-key lock** - freeze a single first-level node:
116
+
103
117
  ```javascript
104
- // Lock an object or array
105
118
  state.myArray = [1, 2, 3];
106
119
  state.myArray.lock();
107
120
 
108
- // Now any modification will fail
109
- state.myArray.push(4); // Error: state 'myArray' is locked
121
+ // While locked, reassigning the key, mutating the node, array methods,
122
+ // and deleting its properties all fail:
123
+ state.myArray.push(4); // Error: state 'myArray' is locked
124
+ state.myArray[0] = 9; // Error: state 'myArray' is locked
125
+ delete state.myArray[0]; // Error: state 'myArray' is locked
126
+ state.myArray = []; // Error: state 'myArray' is locked
127
+
128
+ state.myArray.unlock(); // Resume writes to myArray only
110
129
  ```
111
130
 
131
+ Per-key locking is **first-level only**: `state.a.lock()` does not transitively lock
132
+ `state.a.b`. Lock each descendant explicitly (`state.a.b.lock()`), or use the global
133
+ `state.lock()` for whole-subtree immutability. While a key is per-key locked, every other
134
+ top-level key remains writable.
135
+
112
136
  ---
113
137
 
114
138
  ## How It Works
package/markdown/SYNC.md CHANGED
@@ -48,18 +48,23 @@ Instead:
48
48
  ```ts
49
49
  memorio.memory.remember('user.language', 'Italian')
50
50
  // and a single configuration point:
51
- memorio.memory.configure({ sync: { provider: myCloudProvider, namespace: '…' } })
51
+ memorio.memory.configure({ provider: myCloudProvider, namespace: '…' })
52
52
  ```
53
53
 
54
- ## 2. Scopes (isolation, not a security boundary)
54
+ ## 2. Namespace isolation (not a security boundary)
55
55
 
56
- | Scope | Lifetime | Syncs by default |
56
+ The journal is partitioned by a `namespace` string you pass to `configure()`.
57
+ All journal entries, pending operations, and replay calls for one namespace are
58
+ fully isolated from another - there is no API to enumerate or open a different
59
+ namespace's journal. You can use any naming convention, for example:
60
+
61
+ | Namespace pattern | Lifetime | Syncs by default |
57
62
  |---|---|---|
58
- | `'device'` | this browser/device only | no (sticky) |
59
- | `'user'` | follows the user across devices | yes (requires provider + namespace) |
60
- | `'shared'` | shared across users / tenant | yes (requires provider + namespace) |
63
+ | `device:<id>` | this browser/device only | no (sticky) |
64
+ | `user:<id>:device:<id>` | follows the user across devices | yes (requires provider) |
65
+ | `tenant:<id>:user:<id>` | shared across users / tenant | yes (requires provider) |
61
66
 
62
- > As with `memorio.createContext`, **scoping is a naming convention, not a
67
+ > As with `memorio.createContext`, **namespace scoping is a naming convention, not a
63
68
  > security boundary.** Enforce real isolation server-side.
64
69
 
65
70
  ## 3. SQLite as the local durable store
@@ -71,7 +76,7 @@ the durable journal/value store when you opt in:
71
76
  snapshot the database to `store` (localStorage) and restore it on reopen.
72
77
  - Writes are journaled incrementally (append-only, HLC-timestamped) and
73
78
  checkpointed periodically (every 50 ops or 250 ms of idle). On reopen, the
74
- last checkpoint is loaded and the journal is replayed — no writes are lost.
79
+ last checkpoint is loaded and the journal is replayed - no writes are lost.
75
80
  - `sqlite.db.persist(name)` / `sqlite.db.flush(name)` forces an immediate
76
81
  checkpoint; `sqlite.db.close(name)` checkpoints + closes;
77
82
  `sqlite.db.download(name, file?)` triggers a browser `.sqlite` download
@@ -87,7 +92,7 @@ because pending operations must survive a refresh for offline-first to work.
87
92
 
88
93
  | Method | Returns | Notes |
89
94
  |---|---|---|
90
- | `memory.journal.append(entry, operation)` | `Promise<MemoryEntry>` | records `remember\|update\|forget\|expire\|confirm\|supersede` with `sync:'pending'` |
95
+ | `memory.journal.append(entry, operation)` | `Promise<MemoryEntry>` | records `remember\|update\|forget\|expire\|confirm\|supersede\|delete\|patch` with `sync:'pending'` |
91
96
  | `memory.journal.pending()` | `Promise<MemoryEntry[]>` | rows where `sync != 'synced'`, for the current namespace |
92
97
  | `memory.journal.markSynced(ids)` | `Promise<number>` | advances rows to `synced` (namespace-scoped) |
93
98
  | `memory.journal.get(id)` | `Promise<MemoryEntry \| null>` | single entry, namespace-scoped |
@@ -113,7 +118,8 @@ The cloud must not simply say "last write wins." Memorio tags every entry with:
113
118
  - `source` / `scope`
114
119
 
115
120
  Remote conflicts are surfaced as `sync:'conflict'` rows via
116
- `journal.pending()`; the provider's `resolve(op)` hint decides locally. Example:
121
+ `journal.pending()`; the provider's `resolveConflict(local, remote)` hint decides
122
+ which entry wins. Example:
117
123
 
118
124
  ```
119
125
  Laptop: language=Italian, confidence=0.92
@@ -132,7 +138,7 @@ memorio.memory.configure({
132
138
  provider: {
133
139
  push(ops) { return fetch('/api/sync', { method: 'POST', body: JSON.stringify(ops), headers: authHeaders }) }
134
140
  pull(since) { return fetch(`/api/sync?since=${since}`).then(r => r.json()) }
135
- resolve(op) { return op.confidence >= 0.8 ? 'local' : 'remote' }
141
+ resolveConflict(local, remote) { return (local.confidence ?? 0) >= 0.8 ? 'local' : 'remote' }
136
142
  },
137
143
  auto: true // auto-replay on focus/online (default true)
138
144
  })
@@ -142,12 +148,10 @@ memorio.memory.configure({
142
148
  interface SyncProvider {
143
149
  push(ops: MemoryEntry[]): Promise<{ synced: string[]; conflicts?: string[]; error?: string }>
144
150
  pull?(since?: number): Promise<MemoryEntry[]>
145
- resolve?(op: MemoryEntry): Promise<'local' | 'remote' | 'merge'>
151
+ resolveConflict?(local: MemoryEntry, remote: MemoryEntry): Promise<'local' | 'remote' | 'merge'>
146
152
  }
147
153
  ```
148
154
 
149
- `memorio.memory.ready` resolves once the local journal substrate is chosen.
150
-
151
155
  ## 7. Security (NIST / OWASP / NSA posture)
152
156
 
153
157
  - **Memorio never handles credentials.** No passwords, tokens, or API keys are
@@ -189,7 +193,7 @@ layered onto the existing journal **without** adopting a full CRDT framework
189
193
  (Yjs, Automerge, etc.).
190
194
 
191
195
  > **Scope note.** These strategies target `memorio.memory` first - it already
192
- > carries the metadata a journal needs (`confidence`, `source`, `tag`, `scope`,
196
+ > carries the metadata a journal needs (`confidence`, `source`, `tags`, `scope`,
193
197
  > `createdAt`, `lastConfirmedAt`). If synchronization is ever extended to
194
198
  > `state` or `store`, those layers must gain HLC timestamps and path-level
195
199
  > fields explicitly - they cannot inherit them from `memory`.