eklavya 1.18.2 → 1.19.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/dist/assets/dashboard.html +559 -14
- package/dist/assets/tutor/references/focus-and-level.md +29 -0
- package/dist/cli.js +648 -20
- package/dist/cli.js.map +1 -1
- package/dist/config-path.js +156 -0
- package/dist/config-path.js.map +1 -0
- package/dist/config.js +231 -2
- package/dist/config.js.map +1 -1
- package/dist/dashboard.js +355 -0
- package/dist/dashboard.js.map +1 -1
- package/dist/eval/retrieval-score.js +77 -0
- package/dist/eval/retrieval-score.js.map +1 -0
- package/dist/hooks/capture-tool.js +107 -0
- package/dist/hooks/capture-tool.js.map +1 -0
- package/dist/hooks/checkpoint-quiz.js +5 -6
- package/dist/hooks/checkpoint-quiz.js.map +1 -1
- package/dist/hooks/lib.js +7 -33
- package/dist/hooks/lib.js.map +1 -1
- package/dist/hooks/memory-lib.js +181 -0
- package/dist/hooks/memory-lib.js.map +1 -0
- package/dist/hooks/prompt-submit-nudge.js +56 -20
- package/dist/hooks/prompt-submit-nudge.js.map +1 -1
- package/dist/hooks/session-start.js +73 -74
- package/dist/hooks/session-start.js.map +1 -1
- package/dist/hooks/stop-quiz-check.js +44 -7
- package/dist/hooks/stop-quiz-check.js.map +1 -1
- package/dist/install.js +37 -0
- package/dist/install.js.map +1 -1
- package/dist/memory/capture.js +123 -0
- package/dist/memory/capture.js.map +1 -0
- package/dist/memory/code.js +186 -0
- package/dist/memory/code.js.map +1 -0
- package/dist/memory/collections.js +87 -0
- package/dist/memory/collections.js.map +1 -0
- package/dist/memory/embed.js +81 -0
- package/dist/memory/embed.js.map +1 -0
- package/dist/memory/hosts.js +71 -0
- package/dist/memory/hosts.js.map +1 -0
- package/dist/memory/identity.js +74 -0
- package/dist/memory/identity.js.map +1 -0
- package/dist/memory/import.js +916 -0
- package/dist/memory/import.js.map +1 -0
- package/dist/memory/learning.js +148 -0
- package/dist/memory/learning.js.map +1 -0
- package/dist/memory/notify.js +220 -0
- package/dist/memory/notify.js.map +1 -0
- package/dist/memory/privacy.js +117 -0
- package/dist/memory/privacy.js.map +1 -0
- package/dist/memory/provider.js +155 -0
- package/dist/memory/provider.js.map +1 -0
- package/dist/memory/recall.js +327 -0
- package/dist/memory/recall.js.map +1 -0
- package/dist/memory/replay.js +174 -0
- package/dist/memory/replay.js.map +1 -0
- package/dist/memory/search.js +143 -0
- package/dist/memory/search.js.map +1 -0
- package/dist/memory/spool.js +93 -0
- package/dist/memory/spool.js.map +1 -0
- package/dist/memory/store.js +399 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/memory/summarize.js +178 -0
- package/dist/memory/summarize.js.map +1 -0
- package/dist/memory/sync.js +595 -0
- package/dist/memory/sync.js.map +1 -0
- package/dist/memory/tokens.js +55 -0
- package/dist/memory/tokens.js.map +1 -0
- package/dist/memory/worker.js +207 -0
- package/dist/memory/worker.js.map +1 -0
- package/dist/migrations/009_memory.sql +226 -0
- package/dist/migrations/010_import.sql +26 -0
- package/dist/migrations/011_sync.sql +76 -0
- package/dist/migrations/012_job_backoff.sql +13 -0
- package/dist/migrations/013_batch_provenance.sql +18 -0
- package/dist/migrations/014_batch_events_index.sql +18 -0
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/agents/tutor.md +3 -1
- package/dist/plugin/hooks/CLAUDE.md +30 -6
- package/dist/plugin/hooks/hooks.json +10 -0
- package/dist/plugin/skills/CLAUDE.md +17 -7
- package/dist/plugin/skills/memory/SKILL.md +88 -0
- package/dist/plugin/skills/setup/SKILL.md +16 -2
- package/dist/plugin/skills/tutor/references/focus-and-level.md +29 -0
- package/dist/time.js +41 -0
- package/dist/time.js.map +1 -0
- package/dist/tools/code_tools.js +73 -0
- package/dist/tools/code_tools.js.map +1 -0
- package/dist/tools/collection_tools.js +92 -0
- package/dist/tools/collection_tools.js.map +1 -0
- package/dist/tools/config_tools.js +104 -1
- package/dist/tools/config_tools.js.map +1 -1
- package/dist/tools/index.js +15 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/memory_read_tools.js +277 -0
- package/dist/tools/memory_read_tools.js.map +1 -0
- package/dist/tools/memory_write_tools.js +115 -0
- package/dist/tools/memory_write_tools.js.map +1 -0
- package/dist/user-skill/eklavya/SKILL.md +64 -8
- package/package.json +2 -1
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { nowIso } from '../time.js';
|
|
5
|
+
import { deleteEntry, indexVector } from './store.js';
|
|
6
|
+
/**
|
|
7
|
+
* Multi-device sync (ADR-09): a shared **directory**, not a server.
|
|
8
|
+
*
|
|
9
|
+
* The whole protocol is files on disk. Each device appends numbered revision
|
|
10
|
+
* records under its own folder and reads what the others wrote; Dropbox, iCloud
|
|
11
|
+
* Drive, Syncthing, a mounted share or a git repository all work because none of
|
|
12
|
+
* them is a dependency -- the target is a path. The hosted, authenticated team
|
|
13
|
+
* server is deliberately not built, and ADR-09 is where that is recorded.
|
|
14
|
+
*
|
|
15
|
+
* <target>/devices/<device_id>/000000000001.json
|
|
16
|
+
*
|
|
17
|
+
* One file per record, named by revision, zero-padded so lexical order is
|
|
18
|
+
* numeric order. That shape buys three properties nothing else here has to work
|
|
19
|
+
* for. A pull is incremental, because the reader skips filenames at or below the
|
|
20
|
+
* high-water mark without opening them. A push resumes, because a record's path
|
|
21
|
+
* is a function of (device, revision) and a file already there is already sent.
|
|
22
|
+
* And a reader never sees half a record, because every write is temp-then-rename
|
|
23
|
+
* -- with a payload hash as the second line of defence for the cases rename does
|
|
24
|
+
* not cover, such as a cloud client materialising a partial file under the real
|
|
25
|
+
* name.
|
|
26
|
+
*
|
|
27
|
+
* **What crosses, and what must not.** Memory entries, their tags and their
|
|
28
|
+
* tombstones. Not attempts, not mastery, not gates, not receipts, not raw
|
|
29
|
+
* `evidence_events` (PRD SEC-02). A team that points every member at one shared
|
|
30
|
+
* folder gets shared project memory; it does not thereby get everyone's quiz
|
|
31
|
+
* history, and "the schema happens not to mention it" is not an enforcement.
|
|
32
|
+
* `ENTRY_COLUMNS` is, and it is the only list any payload is built from or
|
|
33
|
+
* applied through.
|
|
34
|
+
*/
|
|
35
|
+
/** The wire format. Bump when the record shape changes. */
|
|
36
|
+
export const SYNC_FORMAT_VERSION = 1;
|
|
37
|
+
/**
|
|
38
|
+
* The only columns that ever leave this machine.
|
|
39
|
+
*
|
|
40
|
+
* An allowlist rather than `SELECT *`, so a later migration adding a column to
|
|
41
|
+
* `memory_entries` does not silently start publishing it. `session_id`,
|
|
42
|
+
* `batch_id` and `superseded_by` are left out for a different reason: they are
|
|
43
|
+
* local row ids and mean nothing on the other device.
|
|
44
|
+
*/
|
|
45
|
+
const ENTRY_COLUMNS = [
|
|
46
|
+
'project',
|
|
47
|
+
'kind',
|
|
48
|
+
'type',
|
|
49
|
+
'title',
|
|
50
|
+
'narrative',
|
|
51
|
+
'facts',
|
|
52
|
+
'files',
|
|
53
|
+
'generator',
|
|
54
|
+
'confidence',
|
|
55
|
+
'occurred_at',
|
|
56
|
+
'created_at',
|
|
57
|
+
'import_source',
|
|
58
|
+
];
|
|
59
|
+
const DEVICE_ID_KEY = 'sync_device_id';
|
|
60
|
+
/**
|
|
61
|
+
* This install's identity, generated once.
|
|
62
|
+
*
|
|
63
|
+
* Kept in `meta` rather than in the config file, and the difference matters.
|
|
64
|
+
* Config travels: `.eklavya.json` is committed, and `~/.eklavya/config.json` is
|
|
65
|
+
* what a dotfile manager copies to the second machine. Two devices sharing an id
|
|
66
|
+
* would interleave one revision stream and each would read the other's writes as
|
|
67
|
+
* its own already-applied history -- a silent, unrecoverable merge. `meta` lives
|
|
68
|
+
* in `knowledge.db`, which is per-install, and which the importer is already
|
|
69
|
+
* forbidden to copy foreign device identity into (PRD MIG-01).
|
|
70
|
+
*
|
|
71
|
+
* `sync.device_id` in the config overrides it when someone means to pin it.
|
|
72
|
+
*/
|
|
73
|
+
/**
|
|
74
|
+
* A device id becomes a directory name under the sync target, so it has to be
|
|
75
|
+
* one path segment and nothing else. Without this, a pinned
|
|
76
|
+
* `device_id: "../../../../tmp/evil"` writes outside the target — `path.join`
|
|
77
|
+
* resolves it happily and `mkdirSync(..., {recursive: true})` creates whatever
|
|
78
|
+
* it names.
|
|
79
|
+
*/
|
|
80
|
+
const DEVICE_ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
81
|
+
export function deviceId(db, config) {
|
|
82
|
+
const pinned = config.sync.device_id?.trim();
|
|
83
|
+
if (pinned) {
|
|
84
|
+
if (!DEVICE_ID.test(pinned)) {
|
|
85
|
+
throw new Error(`sync.device_id must be 1-64 characters of letters, digits, "-" or "_" (got ${JSON.stringify(pinned)}). It names a directory under the sync target.`);
|
|
86
|
+
}
|
|
87
|
+
return pinned;
|
|
88
|
+
}
|
|
89
|
+
const row = db.prepare('SELECT value FROM meta WHERE key = ?').get(DEVICE_ID_KEY);
|
|
90
|
+
if (row?.value)
|
|
91
|
+
return row.value;
|
|
92
|
+
const minted = crypto.randomUUID().replace(/-/g, '').slice(0, 16);
|
|
93
|
+
db.prepare('INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)').run(DEVICE_ID_KEY, minted);
|
|
94
|
+
return (db.prepare('SELECT value FROM meta WHERE key = ?').get(DEVICE_ID_KEY).value);
|
|
95
|
+
}
|
|
96
|
+
/** Resolves the target, honouring an explicit `--target` over the config. */
|
|
97
|
+
function resolveTarget(config, override) {
|
|
98
|
+
const chosen = override?.trim() || config.sync.target?.trim();
|
|
99
|
+
return chosen ? path.resolve(chosen) : null;
|
|
100
|
+
}
|
|
101
|
+
function guard(db, config, override) {
|
|
102
|
+
if (!config.sync.enabled)
|
|
103
|
+
return { ok: false, reason: 'disabled' };
|
|
104
|
+
const target = resolveTarget(config, override);
|
|
105
|
+
if (!target)
|
|
106
|
+
return { ok: false, reason: 'no-target' };
|
|
107
|
+
return { ok: true, device: deviceId(db, config), target };
|
|
108
|
+
}
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// Hashing and the version vector
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
/** Content identity of a version. Equal hashes mean nothing has to be sent. */
|
|
113
|
+
function payloadHash(op, entry, tags) {
|
|
114
|
+
const h = crypto.createHash('sha256');
|
|
115
|
+
// Canonical by construction: ENTRY_COLUMNS is ordered and tags are sorted, so
|
|
116
|
+
// two devices holding the same content always agree on the hash.
|
|
117
|
+
h.update(JSON.stringify([op, entry ? ENTRY_COLUMNS.map((c) => entry[c] ?? null) : null, [...tags].sort()]));
|
|
118
|
+
return h.digest('hex').slice(0, 32);
|
|
119
|
+
}
|
|
120
|
+
function ref(device, revision) {
|
|
121
|
+
return `${device}:${revision}`;
|
|
122
|
+
}
|
|
123
|
+
function localRecord(db, entryUid) {
|
|
124
|
+
return db.prepare('SELECT * FROM sync_records WHERE entry_uid = ?').get(entryUid);
|
|
125
|
+
}
|
|
126
|
+
function highWater(db, device) {
|
|
127
|
+
const row = db.prepare('SELECT last_revision FROM sync_state WHERE device_id = ?').get(device);
|
|
128
|
+
return row?.last_revision ?? 0;
|
|
129
|
+
}
|
|
130
|
+
function setHighWater(db, device, revision) {
|
|
131
|
+
db.prepare(`INSERT INTO sync_state (device_id, last_revision, last_sync_at) VALUES (?, ?, ?)
|
|
132
|
+
ON CONFLICT(device_id) DO UPDATE SET
|
|
133
|
+
last_revision = MAX(last_revision, excluded.last_revision),
|
|
134
|
+
last_sync_at = excluded.last_sync_at`).run(device, revision, nowIso());
|
|
135
|
+
}
|
|
136
|
+
function nextRevision(db, device) {
|
|
137
|
+
const next = highWater(db, device) + 1;
|
|
138
|
+
setHighWater(db, device, next);
|
|
139
|
+
return next;
|
|
140
|
+
}
|
|
141
|
+
function writeRecord(db, rec) {
|
|
142
|
+
db.prepare(`INSERT INTO sync_records (entry_uid, device_id, revision, base, op, hash, updated_at)
|
|
143
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
144
|
+
ON CONFLICT(entry_uid) DO UPDATE SET
|
|
145
|
+
device_id = excluded.device_id, revision = excluded.revision, base = excluded.base,
|
|
146
|
+
op = excluded.op, hash = excluded.hash, updated_at = excluded.updated_at`).run(rec.entry_uid, rec.device_id, rec.revision, rec.base, rec.op, rec.hash, nowIso());
|
|
147
|
+
}
|
|
148
|
+
function tombstone(entryUid) {
|
|
149
|
+
return { entry_uid: entryUid, op: 'delete', entry: null, tags: [], hash: payloadHash('delete', null, []) };
|
|
150
|
+
}
|
|
151
|
+
function tagsOf(db, entryUid) {
|
|
152
|
+
return db
|
|
153
|
+
.prepare('SELECT tag FROM memory_entry_tags t JOIN memory_entries e ON e.id = t.entry_id WHERE e.entry_uid = ? ORDER BY tag')
|
|
154
|
+
.all(entryUid).map((t) => t.tag);
|
|
155
|
+
}
|
|
156
|
+
function stage(db, row) {
|
|
157
|
+
if (row.deleted_at !== null)
|
|
158
|
+
return tombstone(row.entry_uid);
|
|
159
|
+
const entry = Object.fromEntries(ENTRY_COLUMNS.map((c) => [c, row[c] ?? null]));
|
|
160
|
+
const tags = tagsOf(db, row.entry_uid);
|
|
161
|
+
return { entry_uid: row.entry_uid, op: 'upsert', entry, tags, hash: payloadHash('upsert', entry, tags) };
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* What this device holds for one entry right now, whether or not it has been
|
|
165
|
+
* staged. Null when the entry is unknown here in every sense.
|
|
166
|
+
*/
|
|
167
|
+
function localContent(db, entryUid) {
|
|
168
|
+
const row = db
|
|
169
|
+
.prepare(`SELECT entry_uid, deleted_at, ${ENTRY_COLUMNS.join(', ')} FROM memory_entries WHERE entry_uid = ?`)
|
|
170
|
+
.get(entryUid);
|
|
171
|
+
if (row)
|
|
172
|
+
return stage(db, row);
|
|
173
|
+
// No row, but a record that outlived it: a hard delete. Still a tombstone.
|
|
174
|
+
return localRecord(db, entryUid) ? tombstone(entryUid) : null;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* What a push would send: every entry whose content no longer matches its
|
|
178
|
+
* record, plus tombstones.
|
|
179
|
+
*
|
|
180
|
+
* ponytail: a full scan of `memory_entries` per push rather than a write-time
|
|
181
|
+
* hook, because a hook means every call site in `memory/store.ts` remembering to
|
|
182
|
+
* call it and a corpus this size is a millisecond. Add the hook if a push ever
|
|
183
|
+
* shows up in a profile.
|
|
184
|
+
*/
|
|
185
|
+
function scanLocal(db) {
|
|
186
|
+
const rows = db
|
|
187
|
+
.prepare(`SELECT entry_uid, deleted_at, ${ENTRY_COLUMNS.join(', ')} FROM memory_entries`)
|
|
188
|
+
.all();
|
|
189
|
+
const out = rows
|
|
190
|
+
.map((row) => stage(db, row))
|
|
191
|
+
.filter((s) => localRecord(db, s.entry_uid)?.hash !== s.hash);
|
|
192
|
+
// A hard delete leaves no row at all, so the tombstone has to come from the
|
|
193
|
+
// record that outlived it. Without this the other device keeps its copy for
|
|
194
|
+
// ever and a later edit there resurrects it here.
|
|
195
|
+
const orphans = db
|
|
196
|
+
.prepare(`SELECT entry_uid FROM sync_records
|
|
197
|
+
WHERE op = 'upsert'
|
|
198
|
+
AND entry_uid NOT IN (SELECT entry_uid FROM memory_entries)`)
|
|
199
|
+
.all();
|
|
200
|
+
for (const o of orphans)
|
|
201
|
+
out.push(tombstone(o.entry_uid));
|
|
202
|
+
return out;
|
|
203
|
+
}
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
// The directory
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
function deviceDir(target, device) {
|
|
208
|
+
// Belt and braces with the check in `deviceId`: every path built here is one
|
|
209
|
+
// segment under `<target>/devices`, and a peer's directory name arrives from
|
|
210
|
+
// `readdirSync` rather than from a record, but a single assertion at the one
|
|
211
|
+
// place paths are built is cheaper than trusting both.
|
|
212
|
+
if (!DEVICE_ID.test(device))
|
|
213
|
+
throw new Error(`unsafe device id: ${JSON.stringify(device)}`);
|
|
214
|
+
return path.join(target, 'devices', device);
|
|
215
|
+
}
|
|
216
|
+
function recordPath(target, device, revision) {
|
|
217
|
+
return path.join(deviceDir(target, device), `${String(revision).padStart(12, '0')}.json`);
|
|
218
|
+
}
|
|
219
|
+
/** Temp name then rename: a reader must never see a half-written record. */
|
|
220
|
+
function writeAtomic(file, text) {
|
|
221
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
222
|
+
const tmp = `${file}.tmp-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
|
|
223
|
+
fs.writeFileSync(tmp, text, 'utf8');
|
|
224
|
+
fs.renameSync(tmp, file);
|
|
225
|
+
}
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
// Push
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
/**
|
|
230
|
+
* Stages local changes as new revisions and writes any local record the target
|
|
231
|
+
* has not got.
|
|
232
|
+
*
|
|
233
|
+
* Both halves are idempotent. Staging skips an entry whose hash already matches
|
|
234
|
+
* its record, and writing skips a path that already exists -- so a push killed
|
|
235
|
+
* halfway resumes by writing only the remainder, and a push with nothing to do
|
|
236
|
+
* writes nothing at all.
|
|
237
|
+
*/
|
|
238
|
+
export function push(db, config, opts = {}) {
|
|
239
|
+
const g = guard(db, config, opts.target);
|
|
240
|
+
if (!g.ok)
|
|
241
|
+
return { ok: false, reason: g.reason, staged: 0, written: 0, already: 0 };
|
|
242
|
+
const staged = scanLocal(db);
|
|
243
|
+
db.transaction(() => {
|
|
244
|
+
for (const change of staged) {
|
|
245
|
+
const previous = localRecord(db, change.entry_uid);
|
|
246
|
+
writeRecord(db, {
|
|
247
|
+
entry_uid: change.entry_uid,
|
|
248
|
+
device_id: g.device,
|
|
249
|
+
revision: nextRevision(db, g.device),
|
|
250
|
+
base: previous ? ref(previous.device_id, previous.revision) : null,
|
|
251
|
+
op: change.op,
|
|
252
|
+
hash: change.hash,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
})();
|
|
256
|
+
const payloads = new Map(staged.map((s) => [s.entry_uid, s]));
|
|
257
|
+
const mine = db
|
|
258
|
+
.prepare('SELECT * FROM sync_records WHERE device_id = ? ORDER BY revision')
|
|
259
|
+
.all(g.device);
|
|
260
|
+
let written = 0;
|
|
261
|
+
let already = 0;
|
|
262
|
+
for (const rec of mine) {
|
|
263
|
+
const file = recordPath(g.target, g.device, rec.revision);
|
|
264
|
+
if (fs.existsSync(file)) {
|
|
265
|
+
already += 1;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
const body = payloads.get(rec.entry_uid);
|
|
269
|
+
// A record whose payload this run did not rebuild predates the interruption
|
|
270
|
+
// that lost its file; it is rebuilt from the database below, and dropped if
|
|
271
|
+
// the entry is gone -- a missing revision file is a gap the peer's reader
|
|
272
|
+
// stops at, which is correct: it will be filled on the next push.
|
|
273
|
+
const rebuilt = body ?? rebuildPayload(db, rec);
|
|
274
|
+
if (!rebuilt)
|
|
275
|
+
continue;
|
|
276
|
+
const record = {
|
|
277
|
+
v: SYNC_FORMAT_VERSION,
|
|
278
|
+
device_id: g.device,
|
|
279
|
+
revision: rec.revision,
|
|
280
|
+
base: rec.base,
|
|
281
|
+
entry_uid: rec.entry_uid,
|
|
282
|
+
op: rec.op,
|
|
283
|
+
hash: rec.hash,
|
|
284
|
+
written_at: nowIso(),
|
|
285
|
+
entry: rebuilt.entry,
|
|
286
|
+
tags: rebuilt.tags,
|
|
287
|
+
};
|
|
288
|
+
writeAtomic(file, `${JSON.stringify(record)}\n`);
|
|
289
|
+
written += 1;
|
|
290
|
+
}
|
|
291
|
+
return { ok: true, device_id: g.device, target: g.target, staged: staged.length, written, already };
|
|
292
|
+
}
|
|
293
|
+
function rebuildPayload(db, rec) {
|
|
294
|
+
if (rec.op === 'delete')
|
|
295
|
+
return { entry: null, tags: [] };
|
|
296
|
+
const row = db
|
|
297
|
+
.prepare(`SELECT ${ENTRY_COLUMNS.join(', ')} FROM memory_entries WHERE entry_uid = ?`)
|
|
298
|
+
.get(rec.entry_uid);
|
|
299
|
+
if (!row)
|
|
300
|
+
return null;
|
|
301
|
+
const tags = db
|
|
302
|
+
.prepare('SELECT tag FROM memory_entry_tags t JOIN memory_entries e ON e.id = t.entry_id WHERE e.entry_uid = ? ORDER BY tag')
|
|
303
|
+
.all(rec.entry_uid).map((t) => t.tag);
|
|
304
|
+
const entry = Object.fromEntries(ENTRY_COLUMNS.map((c) => [c, row[c] ?? null]));
|
|
305
|
+
if (payloadHash('upsert', entry, tags) !== rec.hash)
|
|
306
|
+
return null;
|
|
307
|
+
return { entry, tags };
|
|
308
|
+
}
|
|
309
|
+
// ---------------------------------------------------------------------------
|
|
310
|
+
// Pull
|
|
311
|
+
// ---------------------------------------------------------------------------
|
|
312
|
+
/**
|
|
313
|
+
* Reads a record file, or returns null if it is not a whole, self-consistent
|
|
314
|
+
* one.
|
|
315
|
+
*
|
|
316
|
+
* The hash check is the part that matters. A truncated file usually fails
|
|
317
|
+
* `JSON.parse`, but a file a cloud client flushed on a record boundary can be
|
|
318
|
+
* valid JSON and still be the wrong record -- so the payload is re-hashed and
|
|
319
|
+
* compared with the hash the writer put in it.
|
|
320
|
+
*/
|
|
321
|
+
function readRecord(file, device, revision) {
|
|
322
|
+
let parsed;
|
|
323
|
+
try {
|
|
324
|
+
parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
if (!parsed || typeof parsed !== 'object')
|
|
330
|
+
return null;
|
|
331
|
+
const rec = parsed;
|
|
332
|
+
if (rec.v !== SYNC_FORMAT_VERSION)
|
|
333
|
+
return null;
|
|
334
|
+
if (rec.device_id !== device || rec.revision !== revision)
|
|
335
|
+
return null;
|
|
336
|
+
if (typeof rec.entry_uid !== 'string' || !rec.entry_uid)
|
|
337
|
+
return null;
|
|
338
|
+
if (rec.op !== 'upsert' && rec.op !== 'delete')
|
|
339
|
+
return null;
|
|
340
|
+
const tags = Array.isArray(rec.tags) ? rec.tags.filter((t) => typeof t === 'string') : [];
|
|
341
|
+
const entry = rec.op === 'delete' ? null : sanitizeEntry(rec.entry);
|
|
342
|
+
if (rec.op === 'upsert' && !entry)
|
|
343
|
+
return null;
|
|
344
|
+
if (payloadHash(rec.op, entry, tags) !== rec.hash)
|
|
345
|
+
return null;
|
|
346
|
+
return { ...rec, entry, tags, base: rec.base ?? null };
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* The inbound half of the allowlist. Anything a peer put in the payload that is
|
|
350
|
+
* not a synced column is dropped here rather than reaching a prepared statement
|
|
351
|
+
* -- a future version that starts shipping a field this one does not know about
|
|
352
|
+
* must not have it applied by accident.
|
|
353
|
+
*/
|
|
354
|
+
function sanitizeEntry(value) {
|
|
355
|
+
if (!value || typeof value !== 'object')
|
|
356
|
+
return null;
|
|
357
|
+
const v = value;
|
|
358
|
+
if (typeof v.title !== 'string' || typeof v.project !== 'string')
|
|
359
|
+
return null;
|
|
360
|
+
return Object.fromEntries(ENTRY_COLUMNS.map((c) => [c, v[c] ?? null]));
|
|
361
|
+
}
|
|
362
|
+
function peerDevices(target, local) {
|
|
363
|
+
const root = path.join(target, 'devices');
|
|
364
|
+
try {
|
|
365
|
+
return fs
|
|
366
|
+
.readdirSync(root, { withFileTypes: true })
|
|
367
|
+
.filter((d) => d.isDirectory() && d.name !== local)
|
|
368
|
+
.map((d) => d.name)
|
|
369
|
+
.sort();
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
return [];
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function pendingRecordFiles(target, device, after) {
|
|
376
|
+
let names;
|
|
377
|
+
try {
|
|
378
|
+
names = fs.readdirSync(deviceDir(target, device));
|
|
379
|
+
}
|
|
380
|
+
catch {
|
|
381
|
+
return [];
|
|
382
|
+
}
|
|
383
|
+
return names
|
|
384
|
+
.filter((n) => /^\d{12}\.json$/.test(n))
|
|
385
|
+
.map((n) => ({ revision: Number(n.slice(0, 12)), file: path.join(deviceDir(target, device), n) }))
|
|
386
|
+
.filter((r) => r.revision > after)
|
|
387
|
+
.sort((a, b) => a.revision - b.revision);
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Applies what the peers wrote, one record at a time, in revision order.
|
|
391
|
+
*
|
|
392
|
+
* Each record is applied in its own transaction together with the high-water
|
|
393
|
+
* mark it advances, so an interrupted pull leaves a consistent database and
|
|
394
|
+
* resumes exactly where it stopped. An unreadable record stops that device's
|
|
395
|
+
* stream rather than being skipped past: skipping would advance the mark over a
|
|
396
|
+
* file that is merely still being written, and the record would never be read
|
|
397
|
+
* again.
|
|
398
|
+
*/
|
|
399
|
+
export function pull(db, config, opts = {}) {
|
|
400
|
+
const g = guard(db, config, opts.target);
|
|
401
|
+
if (!g.ok) {
|
|
402
|
+
return { ok: false, reason: g.reason, applied: 0, tombstones: 0, skipped: 0, conflicts: 0, stalled: [] };
|
|
403
|
+
}
|
|
404
|
+
let applied = 0;
|
|
405
|
+
let tombstones = 0;
|
|
406
|
+
let skipped = 0;
|
|
407
|
+
let conflicts = 0;
|
|
408
|
+
const stalled = [];
|
|
409
|
+
for (const device of peerDevices(g.target, g.device)) {
|
|
410
|
+
for (const { revision, file } of pendingRecordFiles(g.target, device, highWater(db, device))) {
|
|
411
|
+
const rec = readRecord(file, device, revision);
|
|
412
|
+
if (!rec) {
|
|
413
|
+
stalled.push(device);
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
const outcome = db.transaction(() => {
|
|
417
|
+
const result = applyRecord(db, rec);
|
|
418
|
+
setHighWater(db, device, revision);
|
|
419
|
+
return result;
|
|
420
|
+
})();
|
|
421
|
+
if (outcome === 'applied')
|
|
422
|
+
applied += 1;
|
|
423
|
+
else if (outcome === 'tombstone') {
|
|
424
|
+
applied += 1;
|
|
425
|
+
tombstones += 1;
|
|
426
|
+
}
|
|
427
|
+
else if (outcome === 'conflict')
|
|
428
|
+
conflicts += 1;
|
|
429
|
+
else
|
|
430
|
+
skipped += 1;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return { ok: true, device_id: g.device, target: g.target, applied, tombstones, skipped, conflicts, stalled };
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* The merge rule, and the only place a remote version reaches local state.
|
|
437
|
+
*
|
|
438
|
+
* `held` is the version this device last agreed on; `local` is what it actually
|
|
439
|
+
* holds now. They come apart when somebody edited here and has not pushed yet,
|
|
440
|
+
* and that gap is the whole point: an unstaged local edit is still a local edit,
|
|
441
|
+
* so comparing the incoming record against `held` alone would let a pull
|
|
442
|
+
* silently overwrite work whose only crime was arriving between two syncs.
|
|
443
|
+
*
|
|
444
|
+
* So: identical content, nothing to do but agree on a name for it. Nothing here
|
|
445
|
+
* yet, or an incoming version that descends from exactly what is held and held
|
|
446
|
+
* is unmodified -- a fast-forward, applied. Anything else is two devices having
|
|
447
|
+
* edited the same entry, which no timestamp can adjudicate: the local version
|
|
448
|
+
* stays live and the incoming one is quarantined whole, for `repairConflict`.
|
|
449
|
+
*/
|
|
450
|
+
function applyRecord(db, rec) {
|
|
451
|
+
const held = localRecord(db, rec.entry_uid);
|
|
452
|
+
const local = localContent(db, rec.entry_uid);
|
|
453
|
+
if (local && local.hash === rec.hash) {
|
|
454
|
+
// The two devices reached the same content independently. Adopting the
|
|
455
|
+
// incoming version name costs nothing and lines up the next fast-forward.
|
|
456
|
+
settle(db, rec);
|
|
457
|
+
return 'skipped';
|
|
458
|
+
}
|
|
459
|
+
if (held) {
|
|
460
|
+
const dirty = Boolean(local) && local.hash !== held.hash;
|
|
461
|
+
const fastForward = !dirty &&
|
|
462
|
+
(rec.base === ref(held.device_id, held.revision) ||
|
|
463
|
+
(held.device_id === rec.device_id && rec.revision > held.revision));
|
|
464
|
+
if (!dirty && held.device_id === rec.device_id && rec.revision <= held.revision)
|
|
465
|
+
return 'skipped';
|
|
466
|
+
if (!fastForward) {
|
|
467
|
+
db.prepare(`INSERT INTO sync_conflicts (entry_uid, device_id, revision, base, local_ref, payload, detected_at)
|
|
468
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`).run(rec.entry_uid, rec.device_id, rec.revision, rec.base, ref(held.device_id, held.revision), JSON.stringify(rec), nowIso());
|
|
469
|
+
return 'conflict';
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
else if (local) {
|
|
473
|
+
// No agreed version, but content here already: two independent creations
|
|
474
|
+
// under one uid. Rare enough to be suspicious, so it is not merged either.
|
|
475
|
+
db.prepare(`INSERT INTO sync_conflicts (entry_uid, device_id, revision, base, local_ref, payload, detected_at)
|
|
476
|
+
VALUES (?, ?, ?, ?, 'local:unstaged', ?, ?)`).run(rec.entry_uid, rec.device_id, rec.revision, rec.base, JSON.stringify(rec), nowIso());
|
|
477
|
+
return 'conflict';
|
|
478
|
+
}
|
|
479
|
+
const outcome = materialize(db, rec);
|
|
480
|
+
settle(db, rec);
|
|
481
|
+
return outcome;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Records the version now agreed on, and retires the quarantine it settles --
|
|
485
|
+
* a repair chosen on one device resolves the mirror-image conflict on the other
|
|
486
|
+
* without anybody choosing twice.
|
|
487
|
+
*/
|
|
488
|
+
function settle(db, rec) {
|
|
489
|
+
writeRecord(db, {
|
|
490
|
+
entry_uid: rec.entry_uid,
|
|
491
|
+
device_id: rec.device_id,
|
|
492
|
+
revision: rec.revision,
|
|
493
|
+
base: rec.base,
|
|
494
|
+
op: rec.op,
|
|
495
|
+
hash: rec.hash,
|
|
496
|
+
});
|
|
497
|
+
db.prepare("UPDATE sync_conflicts SET resolved_at = ?, resolution = 'superseded' WHERE entry_uid = ? AND resolved_at IS NULL").run(nowIso(), rec.entry_uid);
|
|
498
|
+
}
|
|
499
|
+
/** Writes the entry itself. Memory tables only -- nothing here knows another. */
|
|
500
|
+
function materialize(db, rec) {
|
|
501
|
+
const existing = db.prepare('SELECT id FROM memory_entries WHERE entry_uid = ?').get(rec.entry_uid);
|
|
502
|
+
if (rec.op === 'delete') {
|
|
503
|
+
// No local row means the tombstone arrived before the entry ever did, or
|
|
504
|
+
// after it was hard-deleted. The record alone is enough: it is what stops a
|
|
505
|
+
// peer that still holds the entry from putting it back.
|
|
506
|
+
if (existing)
|
|
507
|
+
deleteEntry(db, existing.id);
|
|
508
|
+
return 'tombstone';
|
|
509
|
+
}
|
|
510
|
+
const entry = rec.entry;
|
|
511
|
+
const columns = ENTRY_COLUMNS.join(', ');
|
|
512
|
+
const placeholders = ENTRY_COLUMNS.map(() => '?').join(', ');
|
|
513
|
+
const updates = ENTRY_COLUMNS.map((c) => `${c} = excluded.${c}`).join(', ');
|
|
514
|
+
db.prepare(`INSERT INTO memory_entries (entry_uid, ${columns}, deleted_at) VALUES (?, ${placeholders}, NULL)
|
|
515
|
+
ON CONFLICT(entry_uid) DO UPDATE SET ${updates}, deleted_at = NULL`).run(rec.entry_uid, ...ENTRY_COLUMNS.map((c) => entry[c] ?? null));
|
|
516
|
+
const id = existing?.id ??
|
|
517
|
+
db.prepare('SELECT id FROM memory_entries WHERE entry_uid = ?').get(rec.entry_uid).id;
|
|
518
|
+
db.prepare('DELETE FROM memory_entry_tags WHERE entry_id = ?').run(id);
|
|
519
|
+
const tag = db.prepare('INSERT OR IGNORE INTO memory_entry_tags (entry_id, tag) VALUES (?, ?)');
|
|
520
|
+
for (const t of rec.tags)
|
|
521
|
+
tag.run(id, t.toLowerCase());
|
|
522
|
+
indexVector(db, id, [entry.title, entry.narrative ?? '', entry.facts ?? '', entry.files ?? ''].join('\n'));
|
|
523
|
+
return 'applied';
|
|
524
|
+
}
|
|
525
|
+
export function conflicts(db, includeResolved = false) {
|
|
526
|
+
const sql = includeResolved
|
|
527
|
+
? 'SELECT * FROM sync_conflicts ORDER BY id'
|
|
528
|
+
: 'SELECT * FROM sync_conflicts WHERE resolved_at IS NULL ORDER BY id';
|
|
529
|
+
return db.prepare(sql).all();
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Picks a side, and makes the choice travel.
|
|
533
|
+
*
|
|
534
|
+
* Either way the winner is restamped as a **new local revision whose `base` is
|
|
535
|
+
* the quarantined version**. That is what turns one person's decision into
|
|
536
|
+
* convergence: the peer holds exactly that version, so on its next pull the
|
|
537
|
+
* resolution is an ordinary fast-forward rather than a second conflict to
|
|
538
|
+
* adjudicate by hand.
|
|
539
|
+
*/
|
|
540
|
+
export function repairConflict(db, config, id, side) {
|
|
541
|
+
const row = db.prepare('SELECT * FROM sync_conflicts WHERE id = ?').get(id);
|
|
542
|
+
if (!row || row.resolved_at)
|
|
543
|
+
return false;
|
|
544
|
+
const device = deviceId(db, config);
|
|
545
|
+
const rec = JSON.parse(row.payload);
|
|
546
|
+
db.transaction(() => {
|
|
547
|
+
if (side === 'remote')
|
|
548
|
+
materialize(db, rec);
|
|
549
|
+
// Whatever is in the database once the choice is made is what the repair
|
|
550
|
+
// publishes -- read back rather than assumed, because the local side may
|
|
551
|
+
// have been edited again since the conflict was recorded.
|
|
552
|
+
const winner = localContent(db, row.entry_uid) ?? { hash: rec.hash, op: rec.op };
|
|
553
|
+
writeRecord(db, {
|
|
554
|
+
entry_uid: row.entry_uid,
|
|
555
|
+
device_id: device,
|
|
556
|
+
revision: nextRevision(db, device),
|
|
557
|
+
base: ref(rec.device_id, rec.revision),
|
|
558
|
+
op: winner.op,
|
|
559
|
+
hash: winner.hash,
|
|
560
|
+
});
|
|
561
|
+
db.prepare('UPDATE sync_conflicts SET resolved_at = ?, resolution = ? WHERE id = ?').run(nowIso(), side, id);
|
|
562
|
+
})();
|
|
563
|
+
return true;
|
|
564
|
+
}
|
|
565
|
+
// ---------------------------------------------------------------------------
|
|
566
|
+
// Status
|
|
567
|
+
// ---------------------------------------------------------------------------
|
|
568
|
+
export function syncStatus(db, config, opts = {}) {
|
|
569
|
+
const target = resolveTarget(config, opts.target);
|
|
570
|
+
const base = {
|
|
571
|
+
ok: config.sync.enabled && Boolean(target),
|
|
572
|
+
enabled: config.sync.enabled,
|
|
573
|
+
configured_target: config.sync.target,
|
|
574
|
+
target: target ?? undefined,
|
|
575
|
+
local_revision: 0,
|
|
576
|
+
pending: 0,
|
|
577
|
+
open_conflicts: 0,
|
|
578
|
+
peers: [],
|
|
579
|
+
};
|
|
580
|
+
if (!config.sync.enabled)
|
|
581
|
+
return { ...base, reason: 'disabled' };
|
|
582
|
+
if (!target)
|
|
583
|
+
return { ...base, reason: 'no-target' };
|
|
584
|
+
const device = deviceId(db, config);
|
|
585
|
+
const peers = db.prepare('SELECT * FROM sync_state ORDER BY device_id').all().filter((p) => p.device_id !== device);
|
|
586
|
+
return {
|
|
587
|
+
...base,
|
|
588
|
+
device_id: device,
|
|
589
|
+
local_revision: highWater(db, device),
|
|
590
|
+
pending: scanLocal(db).length,
|
|
591
|
+
open_conflicts: conflicts(db).length,
|
|
592
|
+
peers,
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
//# sourceMappingURL=sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/memory/sync.ts"],"names":[],"mappings":"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"}
|