mem-os-python 1.6.27__py3-none-any.whl
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.
- mem_os_python-1.6.27.dist-info/METADATA +1032 -0
- mem_os_python-1.6.27.dist-info/RECORD +253 -0
- mem_os_python-1.6.27.dist-info/WHEEL +4 -0
- mem_os_python-1.6.27.dist-info/entry_points.txt +2 -0
- mem_os_python-1.6.27.dist-info/licenses/LICENSE +21 -0
- memos/__init__.py +16 -0
- memos/_js/adapters/anthropic.d.ts +279 -0
- memos/_js/adapters/anthropic.d.ts.map +1 -0
- memos/_js/adapters/anthropic.js +348 -0
- memos/_js/adapters/anthropic.js.map +1 -0
- memos/_js/adapters/base.d.ts +10 -0
- memos/_js/adapters/base.d.ts.map +1 -0
- memos/_js/adapters/base.js +2 -0
- memos/_js/adapters/base.js.map +1 -0
- memos/_js/adapters/openai.d.ts +264 -0
- memos/_js/adapters/openai.d.ts.map +1 -0
- memos/_js/adapters/openai.js +342 -0
- memos/_js/adapters/openai.js.map +1 -0
- memos/_js/apps/explorer.d.ts +244 -0
- memos/_js/apps/explorer.d.ts.map +1 -0
- memos/_js/apps/explorer.js +738 -0
- memos/_js/apps/explorer.js.map +1 -0
- memos/_js/citations.d.ts +65 -0
- memos/_js/citations.d.ts.map +1 -0
- memos/_js/citations.js +143 -0
- memos/_js/citations.js.map +1 -0
- memos/_js/cli-browse.d.ts +3 -0
- memos/_js/cli-browse.d.ts.map +1 -0
- memos/_js/cli-browse.js +129 -0
- memos/_js/cli-browse.js.map +1 -0
- memos/_js/cli-completion.d.ts +31 -0
- memos/_js/cli-completion.d.ts.map +1 -0
- memos/_js/cli-completion.js +340 -0
- memos/_js/cli-completion.js.map +1 -0
- memos/_js/cli-help.d.ts +18 -0
- memos/_js/cli-help.d.ts.map +1 -0
- memos/_js/cli-help.js +357 -0
- memos/_js/cli-help.js.map +1 -0
- memos/_js/cli.d.ts +28 -0
- memos/_js/cli.d.ts.map +1 -0
- memos/_js/cli.js +2880 -0
- memos/_js/cli.js.map +1 -0
- memos/_js/compression.d.ts +25 -0
- memos/_js/compression.d.ts.map +1 -0
- memos/_js/compression.js +319 -0
- memos/_js/compression.js.map +1 -0
- memos/_js/confidence-machine.d.ts +99 -0
- memos/_js/confidence-machine.d.ts.map +1 -0
- memos/_js/confidence-machine.js +222 -0
- memos/_js/confidence-machine.js.map +1 -0
- memos/_js/context-pack.d.ts +506 -0
- memos/_js/context-pack.d.ts.map +1 -0
- memos/_js/context-pack.js +1435 -0
- memos/_js/context-pack.js.map +1 -0
- memos/_js/contradictions.d.ts +160 -0
- memos/_js/contradictions.d.ts.map +1 -0
- memos/_js/contradictions.js +255 -0
- memos/_js/contradictions.js.map +1 -0
- memos/_js/dashboard.d.ts +30 -0
- memos/_js/dashboard.d.ts.map +1 -0
- memos/_js/dashboard.js +302 -0
- memos/_js/dashboard.js.map +1 -0
- memos/_js/ebbinghaus.d.ts +29 -0
- memos/_js/ebbinghaus.d.ts.map +1 -0
- memos/_js/ebbinghaus.js +32 -0
- memos/_js/ebbinghaus.js.map +1 -0
- memos/_js/embedding-queue.d.ts +190 -0
- memos/_js/embedding-queue.d.ts.map +1 -0
- memos/_js/embedding-queue.js +316 -0
- memos/_js/embedding-queue.js.map +1 -0
- memos/_js/embeddings.d.ts +187 -0
- memos/_js/embeddings.d.ts.map +1 -0
- memos/_js/embeddings.js +633 -0
- memos/_js/embeddings.js.map +1 -0
- memos/_js/entity-extraction.d.ts +54 -0
- memos/_js/entity-extraction.d.ts.map +1 -0
- memos/_js/entity-extraction.js +325 -0
- memos/_js/entity-extraction.js.map +1 -0
- memos/_js/entity-index.d.ts +93 -0
- memos/_js/entity-index.d.ts.map +1 -0
- memos/_js/entity-index.js +165 -0
- memos/_js/entity-index.js.map +1 -0
- memos/_js/event-memory.d.ts +147 -0
- memos/_js/event-memory.d.ts.map +1 -0
- memos/_js/event-memory.js +290 -0
- memos/_js/event-memory.js.map +1 -0
- memos/_js/external-import.d.ts +98 -0
- memos/_js/external-import.d.ts.map +1 -0
- memos/_js/external-import.js +669 -0
- memos/_js/external-import.js.map +1 -0
- memos/_js/fidelity.d.ts +367 -0
- memos/_js/fidelity.d.ts.map +1 -0
- memos/_js/fidelity.js +911 -0
- memos/_js/fidelity.js.map +1 -0
- memos/_js/graph-expansion.d.ts +119 -0
- memos/_js/graph-expansion.d.ts.map +1 -0
- memos/_js/graph-expansion.js +213 -0
- memos/_js/graph-expansion.js.map +1 -0
- memos/_js/graph-mermaid.d.ts +13 -0
- memos/_js/graph-mermaid.d.ts.map +1 -0
- memos/_js/graph-mermaid.js +32 -0
- memos/_js/graph-mermaid.js.map +1 -0
- memos/_js/graph.d.ts +170 -0
- memos/_js/graph.d.ts.map +1 -0
- memos/_js/graph.js +482 -0
- memos/_js/graph.js.map +1 -0
- memos/_js/harness.d.ts +63 -0
- memos/_js/harness.d.ts.map +1 -0
- memos/_js/harness.js +93 -0
- memos/_js/harness.js.map +1 -0
- memos/_js/importance.d.ts +118 -0
- memos/_js/importance.d.ts.map +1 -0
- memos/_js/importance.js +144 -0
- memos/_js/importance.js.map +1 -0
- memos/_js/index.d.ts +51 -0
- memos/_js/index.d.ts.map +1 -0
- memos/_js/index.js +53 -0
- memos/_js/index.js.map +1 -0
- memos/_js/mcp.d.ts +58 -0
- memos/_js/mcp.d.ts.map +1 -0
- memos/_js/mcp.js +1853 -0
- memos/_js/mcp.js.map +1 -0
- memos/_js/memory.d.ts +1762 -0
- memos/_js/memory.d.ts.map +1 -0
- memos/_js/memory.js +6283 -0
- memos/_js/memory.js.map +1 -0
- memos/_js/procedural.d.ts +74 -0
- memos/_js/procedural.d.ts.map +1 -0
- memos/_js/procedural.js +125 -0
- memos/_js/procedural.js.map +1 -0
- memos/_js/project-config.d.ts +68 -0
- memos/_js/project-config.d.ts.map +1 -0
- memos/_js/project-config.js +59 -0
- memos/_js/project-config.js.map +1 -0
- memos/_js/proof-of-forgetting.d.ts +57 -0
- memos/_js/proof-of-forgetting.d.ts.map +1 -0
- memos/_js/proof-of-forgetting.js +80 -0
- memos/_js/proof-of-forgetting.js.map +1 -0
- memos/_js/provenance.d.ts +122 -0
- memos/_js/provenance.d.ts.map +1 -0
- memos/_js/provenance.js +182 -0
- memos/_js/provenance.js.map +1 -0
- memos/_js/quarantine-commands.d.ts +23 -0
- memos/_js/quarantine-commands.d.ts.map +1 -0
- memos/_js/quarantine-commands.js +42 -0
- memos/_js/quarantine-commands.js.map +1 -0
- memos/_js/quarantine.d.ts +120 -0
- memos/_js/quarantine.d.ts.map +1 -0
- memos/_js/quarantine.js +873 -0
- memos/_js/quarantine.js.map +1 -0
- memos/_js/redact.d.ts +12 -0
- memos/_js/redact.d.ts.map +1 -0
- memos/_js/redact.js +26 -0
- memos/_js/redact.js.map +1 -0
- memos/_js/repl.d.ts +57 -0
- memos/_js/repl.d.ts.map +1 -0
- memos/_js/repl.js +295 -0
- memos/_js/repl.js.map +1 -0
- memos/_js/rerank-local.d.ts +61 -0
- memos/_js/rerank-local.d.ts.map +1 -0
- memos/_js/rerank-local.js +210 -0
- memos/_js/rerank-local.js.map +1 -0
- memos/_js/retain-filter.d.ts +98 -0
- memos/_js/retain-filter.d.ts.map +1 -0
- memos/_js/retain-filter.js +232 -0
- memos/_js/retain-filter.js.map +1 -0
- memos/_js/retrieval.d.ts +265 -0
- memos/_js/retrieval.d.ts.map +1 -0
- memos/_js/retrieval.js +1018 -0
- memos/_js/retrieval.js.map +1 -0
- memos/_js/revert.d.ts +77 -0
- memos/_js/revert.d.ts.map +1 -0
- memos/_js/revert.js +252 -0
- memos/_js/revert.js.map +1 -0
- memos/_js/scope.d.ts +30 -0
- memos/_js/scope.d.ts.map +1 -0
- memos/_js/scope.js +71 -0
- memos/_js/scope.js.map +1 -0
- memos/_js/semantic-screen.d.ts +152 -0
- memos/_js/semantic-screen.d.ts.map +1 -0
- memos/_js/semantic-screen.js +290 -0
- memos/_js/semantic-screen.js.map +1 -0
- memos/_js/session-hooks.d.ts +127 -0
- memos/_js/session-hooks.d.ts.map +1 -0
- memos/_js/session-hooks.js +482 -0
- memos/_js/session-hooks.js.map +1 -0
- memos/_js/storage/sqlite.d.ts +427 -0
- memos/_js/storage/sqlite.d.ts.map +1 -0
- memos/_js/storage/sqlite.js +2428 -0
- memos/_js/storage/sqlite.js.map +1 -0
- memos/_js/sync.d.ts +213 -0
- memos/_js/sync.d.ts.map +1 -0
- memos/_js/sync.js +389 -0
- memos/_js/sync.js.map +1 -0
- memos/_js/tamper-log.d.ts +98 -0
- memos/_js/tamper-log.d.ts.map +1 -0
- memos/_js/tamper-log.js +327 -0
- memos/_js/tamper-log.js.map +1 -0
- memos/_js/temporal.d.ts +92 -0
- memos/_js/temporal.d.ts.map +1 -0
- memos/_js/temporal.js +536 -0
- memos/_js/temporal.js.map +1 -0
- memos/_js/time-travel.d.ts +38 -0
- memos/_js/time-travel.d.ts.map +1 -0
- memos/_js/time-travel.js +46 -0
- memos/_js/time-travel.js.map +1 -0
- memos/_js/timestamp-format.d.ts +18 -0
- memos/_js/timestamp-format.d.ts.map +1 -0
- memos/_js/timestamp-format.js +52 -0
- memos/_js/timestamp-format.js.map +1 -0
- memos/_js/triple-extraction.d.ts +31 -0
- memos/_js/triple-extraction.d.ts.map +1 -0
- memos/_js/triple-extraction.js +116 -0
- memos/_js/triple-extraction.js.map +1 -0
- memos/_js/types.d.ts +2474 -0
- memos/_js/types.d.ts.map +1 -0
- memos/_js/types.js +20 -0
- memos/_js/types.js.map +1 -0
- memos/_js/updater.d.ts +95 -0
- memos/_js/updater.d.ts.map +1 -0
- memos/_js/updater.js +268 -0
- memos/_js/updater.js.map +1 -0
- memos/_js/version.d.ts +17 -0
- memos/_js/version.d.ts.map +1 -0
- memos/_js/version.js +34 -0
- memos/_js/version.js.map +1 -0
- memos/_js/vision/clip-query-cache.d.ts +58 -0
- memos/_js/vision/clip-query-cache.d.ts.map +1 -0
- memos/_js/vision/clip-query-cache.js +79 -0
- memos/_js/vision/clip-query-cache.js.map +1 -0
- memos/_js/vision/clip.d.ts +113 -0
- memos/_js/vision/clip.d.ts.map +1 -0
- memos/_js/vision/clip.js +224 -0
- memos/_js/vision/clip.js.map +1 -0
- memos/_js/vision/fake-clip.d.ts +55 -0
- memos/_js/vision/fake-clip.d.ts.map +1 -0
- memos/_js/vision/fake-clip.js +122 -0
- memos/_js/vision/fake-clip.js.map +1 -0
- memos/_js/vision/index.d.ts +15 -0
- memos/_js/vision/index.d.ts.map +1 -0
- memos/_js/vision/index.js +13 -0
- memos/_js/vision/index.js.map +1 -0
- memos/_js/vision/store.d.ts +137 -0
- memos/_js/vision/store.d.ts.map +1 -0
- memos/_js/vision/store.js +383 -0
- memos/_js/vision/store.js.map +1 -0
- memos/adapters/__init__.py +0 -0
- memos/adapters/crewai.py +385 -0
- memos/adapters/langchain.py +301 -0
- memos/adapters/ollama.py +396 -0
- memos/server/__init__.py +0 -0
- memos/server/main.py +547 -0
- memos/server/routes.py +360 -0
memos/_js/tamper-log.js
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tamper-evident mutation log.
|
|
3
|
+
*
|
|
4
|
+
* Every memory mutation (store / import / update / forget) appends one
|
|
5
|
+
* entry to an append-only, hash-chained log stored alongside the nodes
|
|
6
|
+
* in the same database. `verifyTamperLog()` recomputes the chain and —
|
|
7
|
+
* with the deep check — cross-checks each node's current full state
|
|
8
|
+
* against the latest log entry, so it detects:
|
|
9
|
+
*
|
|
10
|
+
* - edited, deleted, or reordered log rows (chain linkage breaks),
|
|
11
|
+
* - nodes edited/deleted directly in the DB without a log entry
|
|
12
|
+
* (state-hash mismatch / missing-entry mismatch),
|
|
13
|
+
* - nodes resurrected after a `forget` entry.
|
|
14
|
+
*
|
|
15
|
+
* Honest threat model: the log is tamper-*evident*, not tamper-*proof*.
|
|
16
|
+
* An attacker with full write access to the database file can rewrite
|
|
17
|
+
* the entire chain from genesis and the log alone cannot tell. What
|
|
18
|
+
* stops that is an externally anchored checkpoint: `memos log
|
|
19
|
+
* checkpoint` prints the tip hash; write it down (paper, another
|
|
20
|
+
* machine, a signed commit) and `memos log verify --expect <hash>`
|
|
21
|
+
* later proves the chain still ends where you left it. Without an
|
|
22
|
+
* external anchor, a full-chain rewrite is undetectable — by anyone,
|
|
23
|
+
* with any local-only scheme.
|
|
24
|
+
*/
|
|
25
|
+
import { createHash } from "node:crypto";
|
|
26
|
+
/** Fixed `prevHash` for the first entry — a domain-separated constant. */
|
|
27
|
+
export const GENESIS_PREV = "memos-tamper-log-genesis-v1:" +
|
|
28
|
+
createHash("sha256").update("memos-tamper-log-genesis-v1").digest("hex");
|
|
29
|
+
export function sha256hex(s) {
|
|
30
|
+
return createHash("sha256").update(s, "utf8").digest("hex");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Canonical JSON: object keys sorted recursively, arrays in order.
|
|
34
|
+
* Two equal states always serialize to the same string regardless of
|
|
35
|
+
* insertion order — required for a stable hash.
|
|
36
|
+
*/
|
|
37
|
+
export function canonicalJson(value) {
|
|
38
|
+
if (value === null || value === undefined)
|
|
39
|
+
return "null";
|
|
40
|
+
if (Array.isArray(value))
|
|
41
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
42
|
+
if (typeof value === "object") {
|
|
43
|
+
const keys = Object.keys(value).sort();
|
|
44
|
+
return `{${keys
|
|
45
|
+
.map((k) => `${JSON.stringify(k)}:${canonicalJson(value[k])}`)
|
|
46
|
+
.join(",")}}`;
|
|
47
|
+
}
|
|
48
|
+
return JSON.stringify(value) ?? "null";
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Hash of a node's full mutable state at mutation time. Covers content,
|
|
52
|
+
* summary, type, tags (sorted), importance, namespace, metadata,
|
|
53
|
+
* provenance/source, validity window, TTL, and creation/update
|
|
54
|
+
* timestamps — everything a mutation could change. Deliberately excludes
|
|
55
|
+
* `accessCount`/`lastAccessed`: reads bump those without a log entry,
|
|
56
|
+
* so hashing them would false-positive on every retrieval.
|
|
57
|
+
*/
|
|
58
|
+
export function nodeHash(node) {
|
|
59
|
+
// The SQLite adapter folds top-level `confidence`/`evidenceCount`
|
|
60
|
+
// into the metadata JSON blob on write (and reads them back out of
|
|
61
|
+
// it), so hash the folded form: that way the in-memory node passed
|
|
62
|
+
// to the logger and the node read back from the DB hash identically.
|
|
63
|
+
const metadata = { ...(node.metadata ?? {}) };
|
|
64
|
+
if (node.confidence !== undefined && metadata.confidence === undefined) {
|
|
65
|
+
metadata.confidence = node.confidence;
|
|
66
|
+
}
|
|
67
|
+
if (node.evidenceCount !== undefined &&
|
|
68
|
+
metadata.evidenceCount === undefined) {
|
|
69
|
+
metadata.evidenceCount = node.evidenceCount;
|
|
70
|
+
}
|
|
71
|
+
const state = [
|
|
72
|
+
node.id,
|
|
73
|
+
node.content,
|
|
74
|
+
node.summary,
|
|
75
|
+
node.type,
|
|
76
|
+
[...(node.tags ?? [])].sort().join("\u0000"),
|
|
77
|
+
String(node.importance),
|
|
78
|
+
node.namespace ?? "",
|
|
79
|
+
canonicalJson(metadata),
|
|
80
|
+
String(node.source ?? ""),
|
|
81
|
+
canonicalJson(node.provenance ?? null),
|
|
82
|
+
String(node.validFrom ?? ""),
|
|
83
|
+
String(node.validTo ?? ""),
|
|
84
|
+
String(node.expiresAt ?? ""),
|
|
85
|
+
String(node.createdAt),
|
|
86
|
+
String(node.updatedAt),
|
|
87
|
+
].join("\u0001");
|
|
88
|
+
return sha256hex("memos-node-state-v1:" + state);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* First-day entry hash (v1): content-only, seq not bound. Kept solely
|
|
92
|
+
* to verify rows written before the full-state format; see the
|
|
93
|
+
* migration in SQLiteStorage init.
|
|
94
|
+
*/
|
|
95
|
+
function legacyEntryHash(e) {
|
|
96
|
+
return sha256hex([
|
|
97
|
+
"memos-tamper-entry-v1",
|
|
98
|
+
e.ts,
|
|
99
|
+
e.op,
|
|
100
|
+
e.nodeId,
|
|
101
|
+
e.contentHash,
|
|
102
|
+
e.prevHash,
|
|
103
|
+
].join("|"));
|
|
104
|
+
}
|
|
105
|
+
/** First-day content hash, for the legacy cross-check. */
|
|
106
|
+
function legacyContentHash(content) {
|
|
107
|
+
return sha256hex("memos-node-content-v1:" + content);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Hash of one log entry. The chain position `seq` is bound into the
|
|
111
|
+
* hash, so an entry cannot be replayed at a different position without
|
|
112
|
+
* invalidating it. Canonical field order and a versioned prefix make
|
|
113
|
+
* the serialization unambiguous — no JSON key-ordering games.
|
|
114
|
+
*/
|
|
115
|
+
export function entryHash(e) {
|
|
116
|
+
return sha256hex([
|
|
117
|
+
"memos-tamper-entry-v2",
|
|
118
|
+
String(e.seq),
|
|
119
|
+
String(e.ts),
|
|
120
|
+
e.op,
|
|
121
|
+
e.nodeId,
|
|
122
|
+
e.nodeHash,
|
|
123
|
+
e.prevHash,
|
|
124
|
+
].join("|"));
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Append one mutation to the log. Fail-open: when the storage adapter
|
|
128
|
+
* doesn't implement the log, or the write fails, the mutation itself
|
|
129
|
+
* is never blocked — the log is evidence, not a gate. The adapter
|
|
130
|
+
* assigns the chain position atomically (tip lookup, seq allocation,
|
|
131
|
+
* hash, insert in one transaction).
|
|
132
|
+
*/
|
|
133
|
+
export async function appendTamperEntry(storage, op, node, ts = Date.now()) {
|
|
134
|
+
if (!storage.appendTamperEntry || !storage.readTamperLog)
|
|
135
|
+
return;
|
|
136
|
+
try {
|
|
137
|
+
await storage.appendTamperEntry({
|
|
138
|
+
ts,
|
|
139
|
+
op,
|
|
140
|
+
nodeId: node.id,
|
|
141
|
+
nodeHash: nodeHash(node),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// Fail-open: a logging failure must never break the write path.
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Verify the log: chain integrity (seq continuity, prev-hash linkage,
|
|
150
|
+
* entry-hash recomputation) plus a deep cross-check of every node's
|
|
151
|
+
* current content against its latest log entry.
|
|
152
|
+
*
|
|
153
|
+
* `expectTip` anchors the chain to an externally recorded checkpoint:
|
|
154
|
+
* verification fails unless the current tip hash equals it.
|
|
155
|
+
*/
|
|
156
|
+
export async function verifyTamperLog(storage, opts = {}) {
|
|
157
|
+
const empty = {
|
|
158
|
+
ok: true,
|
|
159
|
+
supported: true,
|
|
160
|
+
entriesChecked: 0,
|
|
161
|
+
unloggedNodes: 0,
|
|
162
|
+
legacyEntries: 0,
|
|
163
|
+
issues: [],
|
|
164
|
+
tipHash: null,
|
|
165
|
+
};
|
|
166
|
+
if (!storage.readTamperLog) {
|
|
167
|
+
return { ...empty, supported: false };
|
|
168
|
+
}
|
|
169
|
+
let entries;
|
|
170
|
+
try {
|
|
171
|
+
entries = await storage.readTamperLog();
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return { ...empty, supported: false };
|
|
175
|
+
}
|
|
176
|
+
const result = {
|
|
177
|
+
...empty,
|
|
178
|
+
entriesChecked: entries.length,
|
|
179
|
+
tipHash: entries.length > 0 ? entries[entries.length - 1].entryHash : null,
|
|
180
|
+
};
|
|
181
|
+
const fail = (issue) => {
|
|
182
|
+
result.ok = false;
|
|
183
|
+
result.issues.push(issue);
|
|
184
|
+
};
|
|
185
|
+
// --- Chain integrity -------------------------------------------------
|
|
186
|
+
let prev = GENESIS_PREV;
|
|
187
|
+
for (let i = 0; i < entries.length; i++) {
|
|
188
|
+
const e = entries[i];
|
|
189
|
+
const expectedSeq = i + 1;
|
|
190
|
+
if (e.seq !== expectedSeq) {
|
|
191
|
+
fail({
|
|
192
|
+
kind: "broken_chain",
|
|
193
|
+
seq: e.seq,
|
|
194
|
+
detail: `sequence break at row ${i + 1}: expected seq ${expectedSeq}, found ${e.seq} (rows deleted or reordered)`,
|
|
195
|
+
});
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
if (e.prevHash !== prev) {
|
|
199
|
+
fail({
|
|
200
|
+
kind: "broken_chain",
|
|
201
|
+
seq: e.seq,
|
|
202
|
+
detail: `prevHash mismatch at seq ${e.seq}: chain was edited or reordered`,
|
|
203
|
+
});
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
const recomputed = entryHash({
|
|
207
|
+
seq: e.seq,
|
|
208
|
+
ts: e.ts,
|
|
209
|
+
op: e.op,
|
|
210
|
+
nodeId: e.nodeId,
|
|
211
|
+
nodeHash: e.nodeHash,
|
|
212
|
+
prevHash: e.prevHash,
|
|
213
|
+
});
|
|
214
|
+
let legacy = false;
|
|
215
|
+
if (recomputed !== e.entryHash) {
|
|
216
|
+
// Fall back to the first-day v1 format (content-only, seq
|
|
217
|
+
// unbound). A v2 row can never verify as v1 — the domain
|
|
218
|
+
// prefixes differ — so this only ever matches genuine old rows.
|
|
219
|
+
const legacyRecomputed = legacyEntryHash({
|
|
220
|
+
ts: e.ts,
|
|
221
|
+
op: e.op,
|
|
222
|
+
nodeId: e.nodeId,
|
|
223
|
+
contentHash: e.nodeHash,
|
|
224
|
+
prevHash: e.prevHash,
|
|
225
|
+
});
|
|
226
|
+
if (legacyRecomputed === e.entryHash) {
|
|
227
|
+
legacy = true;
|
|
228
|
+
result.legacyEntries += 1;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (!legacy && recomputed !== e.entryHash) {
|
|
232
|
+
fail({
|
|
233
|
+
kind: "broken_chain",
|
|
234
|
+
seq: e.seq,
|
|
235
|
+
nodeId: e.nodeId,
|
|
236
|
+
detail: `entry hash mismatch at seq ${e.seq}: row was edited in place`,
|
|
237
|
+
});
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
prev = e.entryHash;
|
|
241
|
+
}
|
|
242
|
+
// --- Deep cross-check: DB state vs latest log entry per node ---------
|
|
243
|
+
if (result.ok && typeof storage.queryNodes === "function") {
|
|
244
|
+
const latest = new Map();
|
|
245
|
+
const legacyNodes = new Set();
|
|
246
|
+
// Re-derive legacy status per entry (same v1 fallback as above).
|
|
247
|
+
for (const e of entries) {
|
|
248
|
+
latest.set(e.nodeId, e);
|
|
249
|
+
const v2 = entryHash({
|
|
250
|
+
seq: e.seq,
|
|
251
|
+
ts: e.ts,
|
|
252
|
+
op: e.op,
|
|
253
|
+
nodeId: e.nodeId,
|
|
254
|
+
nodeHash: e.nodeHash,
|
|
255
|
+
prevHash: e.prevHash,
|
|
256
|
+
});
|
|
257
|
+
if (v2 !== e.entryHash &&
|
|
258
|
+
legacyEntryHash({
|
|
259
|
+
ts: e.ts,
|
|
260
|
+
op: e.op,
|
|
261
|
+
nodeId: e.nodeId,
|
|
262
|
+
contentHash: e.nodeHash,
|
|
263
|
+
prevHash: e.prevHash,
|
|
264
|
+
}) === e.entryHash) {
|
|
265
|
+
legacyNodes.add(e.nodeId);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
const rows = await storage.queryNodes({ limit: 100_000 });
|
|
270
|
+
const live = new Map();
|
|
271
|
+
for (const r of rows)
|
|
272
|
+
live.set(r.node.id, r.node);
|
|
273
|
+
for (const [nodeId, entry] of latest) {
|
|
274
|
+
const node = live.get(nodeId);
|
|
275
|
+
if (entry.op === "forget") {
|
|
276
|
+
if (node) {
|
|
277
|
+
fail({
|
|
278
|
+
kind: "resurrected",
|
|
279
|
+
seq: entry.seq,
|
|
280
|
+
nodeId,
|
|
281
|
+
detail: `node ${nodeId.slice(0, 8)} has a forget entry (seq ${entry.seq}) but still exists in the DB`,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (!node) {
|
|
287
|
+
fail({
|
|
288
|
+
kind: "deleted_without_log",
|
|
289
|
+
seq: entry.seq,
|
|
290
|
+
nodeId,
|
|
291
|
+
detail: `node ${nodeId.slice(0, 8)} is gone from the DB but its latest log entry (seq ${entry.seq}, op ${entry.op}) is not a forget`,
|
|
292
|
+
});
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
// Legacy v1 rows recorded a content-only hash: cross-check
|
|
296
|
+
// content, not full state.
|
|
297
|
+
const expected = legacyNodes.has(nodeId)
|
|
298
|
+
? legacyContentHash(node.content)
|
|
299
|
+
: nodeHash(node);
|
|
300
|
+
if (expected !== entry.nodeHash) {
|
|
301
|
+
fail({
|
|
302
|
+
kind: "content_mismatch",
|
|
303
|
+
seq: entry.seq,
|
|
304
|
+
nodeId,
|
|
305
|
+
detail: `node ${nodeId.slice(0, 8)} state does not match its latest log entry (seq ${entry.seq}, op ${entry.op}): edited without logging`,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
for (const nodeId of live.keys()) {
|
|
310
|
+
if (!latest.has(nodeId))
|
|
311
|
+
result.unloggedNodes += 1;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
// Deep check is best-effort; chain verification above stands alone.
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
// --- External checkpoint anchor --------------------------------------
|
|
319
|
+
if (opts.expectTip && result.tipHash !== opts.expectTip) {
|
|
320
|
+
fail({
|
|
321
|
+
kind: "tip_mismatch",
|
|
322
|
+
detail: `tip hash ${result.tipHash ?? "(empty log)"} does not match the expected checkpoint ${opts.expectTip}: the chain was rewritten after the checkpoint`,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
return result;
|
|
326
|
+
}
|
|
327
|
+
//# sourceMappingURL=tamper-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tamper-log.js","sourceRoot":"","sources":["../src/tamper-log.ts"],"names":[],"mappings":"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"}
|
memos/_js/temporal.d.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic temporal parsing for MemOS — no LLM on any path.
|
|
3
|
+
*
|
|
4
|
+
* Why hand-rolled instead of `chrono`:
|
|
5
|
+
* - `npm install chrono` resolves to a *timezone-offset* library, not a
|
|
6
|
+
* date parser (the parser lives in `chrono-node`).
|
|
7
|
+
* - `chrono-node` (v2.10.1, evaluated 2026-09-19) is zero-dependency and
|
|
8
|
+
* dual CJS/ESM with types, but ships ~7MB of locale data and its
|
|
9
|
+
* weekday/time-of-day defaults ("at 3pm" at 18:00 silently rolls to
|
|
10
|
+
* tomorrow, multi-expression sentences need ad-hoc disambiguation)
|
|
11
|
+
* would still need a deterministic wrapper for MemOS's exact phrase
|
|
12
|
+
* set. A minimal hand-rolled parser is smaller, has zero dependencies,
|
|
13
|
+
* and every rule below is explicit and testable. Revisit if the phrase
|
|
14
|
+
* set grows beyond the documented grammar.
|
|
15
|
+
*
|
|
16
|
+
* Grammar (all case-insensitive, evaluated against an explicit `now`):
|
|
17
|
+
* - Relative: "in 2 hours", "in 3 days", "3 days later"
|
|
18
|
+
* - Day words: "today", "tonight" (= today 21:00), "tomorrow"
|
|
19
|
+
* - Weekdays: "Monday", "on Monday", "this Thursday", "next Friday",
|
|
20
|
+
* "last Friday"
|
|
21
|
+
* - Month/day: "Sept 24", "September 24th" (year-less → yearCertain=false)
|
|
22
|
+
* - ISO: "2026-09-24"
|
|
23
|
+
* - Times: "at 3pm", "at 2:30pm", "at 15:00", "noon", "midnight"
|
|
24
|
+
* (a bare time attaches to today, rolling to tomorrow when past)
|
|
25
|
+
* - Date+time adjacency merges: "tomorrow at 2pm", "at 3pm tomorrow"
|
|
26
|
+
*
|
|
27
|
+
* Deterministic rules (documented, no guessing):
|
|
28
|
+
* 1. When several temporal expressions occur, the LAST one wins — this
|
|
29
|
+
* mirrors how speakers refine ("tomorrow, on Monday" → Monday).
|
|
30
|
+
* 2. Adjacent date+date ("tomorrow on Monday") refines to the second date.
|
|
31
|
+
* 3. Adjacent date+time merges into one datetime.
|
|
32
|
+
* 4. Bare/"on" weekday = next occurrence strictly after today;
|
|
33
|
+
* "this <day>" = upcoming occurrence including today; "next <day>" =
|
|
34
|
+
* next occurrence strictly after today; "last <day>" = most recent
|
|
35
|
+
* past occurrence.
|
|
36
|
+
* 5. Day-grain results resolve to local start-of-day 00:00 (an all-day
|
|
37
|
+
* event starts when the day starts), except "tonight" → 21:00.
|
|
38
|
+
*
|
|
39
|
+
* @module @memos/temporal
|
|
40
|
+
*/
|
|
41
|
+
/** Temporal resolution of a parsed expression. */
|
|
42
|
+
export type TemporalGrain = "day" | "hour" | "minute";
|
|
43
|
+
export interface ParsedTemporal {
|
|
44
|
+
/** Resolved local datetime. */
|
|
45
|
+
at: Date;
|
|
46
|
+
/** Resolution: day (date only), hour, or minute. */
|
|
47
|
+
grain: TemporalGrain;
|
|
48
|
+
/** The exact source text that produced this parse. */
|
|
49
|
+
text: string;
|
|
50
|
+
/** False when the expression carried no year ("Sept 24", "June 1"). */
|
|
51
|
+
yearCertain: boolean;
|
|
52
|
+
}
|
|
53
|
+
/** A bare relative offset ("3 days later") awaiting an anchor. */
|
|
54
|
+
export interface TemporalDuration {
|
|
55
|
+
years: number;
|
|
56
|
+
months: number;
|
|
57
|
+
weeks: number;
|
|
58
|
+
days: number;
|
|
59
|
+
hours: number;
|
|
60
|
+
minutes: number;
|
|
61
|
+
seconds: number;
|
|
62
|
+
/** The exact source text that produced this duration. */
|
|
63
|
+
text: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Parse the temporal expression(s) in free text.
|
|
67
|
+
*
|
|
68
|
+
* Deterministic given `now`: no system clock reads, no randomness, no LLM.
|
|
69
|
+
* When several expressions occur, the LAST one wins (speaker-refinement
|
|
70
|
+
* rule); adjacent date+time pairs merge ("tomorrow at 2pm"), and adjacent
|
|
71
|
+
* date+date pairs refine to the second ("tomorrow on Monday" → Monday).
|
|
72
|
+
*
|
|
73
|
+
* @param text — Free text possibly containing a temporal expression.
|
|
74
|
+
* @param now — Reference time; ALL resolution is relative to this.
|
|
75
|
+
* @returns The parsed expression, or null when none is found.
|
|
76
|
+
*/
|
|
77
|
+
export declare function parseTemporal(text: string, now: Date): ParsedTemporal | null;
|
|
78
|
+
/**
|
|
79
|
+
* Parse a bare relative offset ("3 days later", "in 2 hours") as a
|
|
80
|
+
* DURATION — no anchor applied. Used for event updates ("moved 3 days
|
|
81
|
+
* later"), where the anchor is the event's current datetime, NOT now.
|
|
82
|
+
*
|
|
83
|
+
* @param text — Free text possibly containing a relative offset.
|
|
84
|
+
* @returns The duration, or null when no offset is found.
|
|
85
|
+
*/
|
|
86
|
+
export declare function parseDuration(text: string): TemporalDuration | null;
|
|
87
|
+
/**
|
|
88
|
+
* Apply a duration to an anchor date (calendar-aware for months/years).
|
|
89
|
+
* Deterministic: same anchor + same duration = same result.
|
|
90
|
+
*/
|
|
91
|
+
export declare function addDuration(anchor: Date, d: TemporalDuration): Date;
|
|
92
|
+
//# sourceMappingURL=temporal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"temporal.d.ts","sourceRoot":"","sources":["../src/temporal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,kDAAkD;AAClD,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEtD,MAAM,WAAW,cAAc;IAC7B,+BAA+B;IAC/B,EAAE,EAAE,IAAI,CAAC;IACT,oDAAoD;IACpD,KAAK,EAAE,aAAa,CAAC;IACrB,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,kEAAkE;AAClE,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;CACd;AAsWD;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,GAAG,cAAc,GAAG,IAAI,CAqG5E;AAKD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CA0CnE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAYnE"}
|