@omnitype-code/journal 2.0.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 (69) hide show
  1. package/dist/blame/legacy.d.ts +24 -0
  2. package/dist/blame/legacy.d.ts.map +1 -0
  3. package/dist/blame/legacy.js +219 -0
  4. package/dist/blame/legacy.js.map +1 -0
  5. package/dist/blame/merge.d.ts +17 -0
  6. package/dist/blame/merge.d.ts.map +1 -0
  7. package/dist/blame/merge.js +32 -0
  8. package/dist/blame/merge.js.map +1 -0
  9. package/dist/cli.d.ts +7 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +638 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/cloud/anchor.d.ts +78 -0
  14. package/dist/cloud/anchor.d.ts.map +1 -0
  15. package/dist/cloud/anchor.js +220 -0
  16. package/dist/cloud/anchor.js.map +1 -0
  17. package/dist/cloud/pending.d.ts +29 -0
  18. package/dist/cloud/pending.d.ts.map +1 -0
  19. package/dist/cloud/pending.js +115 -0
  20. package/dist/cloud/pending.js.map +1 -0
  21. package/dist/cloud/shipper.d.ts +67 -0
  22. package/dist/cloud/shipper.d.ts.map +1 -0
  23. package/dist/cloud/shipper.js +177 -0
  24. package/dist/cloud/shipper.js.map +1 -0
  25. package/dist/crypto/chain.d.ts +19 -0
  26. package/dist/crypto/chain.d.ts.map +1 -0
  27. package/dist/crypto/chain.js +123 -0
  28. package/dist/crypto/chain.js.map +1 -0
  29. package/dist/daemon/journal.d.ts +92 -0
  30. package/dist/daemon/journal.d.ts.map +1 -0
  31. package/dist/daemon/journal.js +370 -0
  32. package/dist/daemon/journal.js.map +1 -0
  33. package/dist/daemon/server.d.ts +89 -0
  34. package/dist/daemon/server.d.ts.map +1 -0
  35. package/dist/daemon/server.js +323 -0
  36. package/dist/daemon/server.js.map +1 -0
  37. package/dist/index.d.ts +8 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +9 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/log/segment.d.ts +43 -0
  42. package/dist/log/segment.d.ts.map +1 -0
  43. package/dist/log/segment.js +180 -0
  44. package/dist/log/segment.js.map +1 -0
  45. package/dist/materializer/db.d.ts +47 -0
  46. package/dist/materializer/db.d.ts.map +1 -0
  47. package/dist/materializer/db.js +385 -0
  48. package/dist/materializer/db.js.map +1 -0
  49. package/dist/notes/git-notes.d.ts +50 -0
  50. package/dist/notes/git-notes.d.ts.map +1 -0
  51. package/dist/notes/git-notes.js +94 -0
  52. package/dist/notes/git-notes.js.map +1 -0
  53. package/dist/schema/events.d.ts +224 -0
  54. package/dist/schema/events.d.ts.map +1 -0
  55. package/dist/schema/events.js +10 -0
  56. package/dist/schema/events.js.map +1 -0
  57. package/dist/security/developer-identity.d.ts +35 -0
  58. package/dist/security/developer-identity.d.ts.map +1 -0
  59. package/dist/security/developer-identity.js +105 -0
  60. package/dist/security/developer-identity.js.map +1 -0
  61. package/dist/security/keychain.d.ts +20 -0
  62. package/dist/security/keychain.d.ts.map +1 -0
  63. package/dist/security/keychain.js +167 -0
  64. package/dist/security/keychain.js.map +1 -0
  65. package/dist/verify/chain-verify.d.ts +43 -0
  66. package/dist/verify/chain-verify.d.ts.map +1 -0
  67. package/dist/verify/chain-verify.js +119 -0
  68. package/dist/verify/chain-verify.js.map +1 -0
  69. package/package.json +47 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/materializer/db.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAGV,SAAS,EACT,YAAY,EACZ,MAAM,EACN,IAAI,EACJ,IAAI,EACL,MAAM,qBAAqB,CAAC;AA0F7B,qBAAa,YAAY;IACvB,OAAO,CAAC,EAAE,CAAe;gBAEb,MAAM,EAAE,MAAM;IAK1B,IAAI,cAAc,IAAI,MAAM,CAI3B;IAED,KAAK,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAqBhC,OAAO,CAAC,UAAU;IAmFlB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,WAAW;IAoEnB,OAAO,CAAC,iBAAiB;IAqEzB,OAAO,CAAC,WAAW;IAyDnB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,GAAG,IAAI,EAAE;IAuB9C,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAI5C,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAW3D;;;;;OAKG;IACH,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC;QAC9C,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,IAAI,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;KAC1G,CAAC;IA0BF;;;OAGG;IACH,uBAAuB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;QAC5C,IAAI,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;KACnF,CAAC;IAeF,KAAK,IAAI,IAAI;CAGd"}
@@ -0,0 +1,385 @@
1
+ /**
2
+ * SQLite WAL materializer (using node:sqlite — built into Node 22.5+).
3
+ *
4
+ * The spans table is the derived view of who wrote which bytes.
5
+ * Reconstructible by replaying the journal from scratch.
6
+ */
7
+ import { DatabaseSync } from 'node:sqlite';
8
+ const SCHEMA = `
9
+ PRAGMA journal_mode=WAL;
10
+ PRAGMA synchronous=NORMAL;
11
+ PRAGMA foreign_keys=ON;
12
+
13
+ CREATE TABLE IF NOT EXISTS meta (
14
+ key TEXT PRIMARY KEY,
15
+ value TEXT NOT NULL
16
+ );
17
+
18
+ CREATE TABLE IF NOT EXISTS files (
19
+ path TEXT PRIMARY KEY,
20
+ hash TEXT NOT NULL,
21
+ byte_count INTEGER NOT NULL DEFAULT 0,
22
+ last_seq INTEGER NOT NULL DEFAULT 0,
23
+ tracking_status TEXT NOT NULL DEFAULT 'unknown'
24
+ );
25
+
26
+ CREATE TABLE IF NOT EXISTS spans (
27
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
28
+ path TEXT NOT NULL,
29
+ byte_start INTEGER NOT NULL,
30
+ byte_end INTEGER NOT NULL,
31
+ origin TEXT NOT NULL,
32
+ actor_json TEXT NOT NULL,
33
+ seq_introduced INTEGER NOT NULL,
34
+ seq_last_touched INTEGER NOT NULL,
35
+ txn_id TEXT,
36
+ tier TEXT NOT NULL DEFAULT 'T3',
37
+ confidence REAL NOT NULL DEFAULT 0.5,
38
+ formatted INTEGER NOT NULL DEFAULT 0,
39
+ UNIQUE(path, byte_start)
40
+ );
41
+
42
+ CREATE INDEX IF NOT EXISTS spans_path ON spans(path, byte_start, byte_end);
43
+
44
+ -- Day 18 (§3.4): All attestations per byte range — highest-tier wins at query time.
45
+ -- Avoids "last writer wins" which lets a community T0 overwrite a verified T1.
46
+ CREATE TABLE IF NOT EXISTS attestations (
47
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
48
+ path TEXT NOT NULL,
49
+ byte_start INTEGER NOT NULL,
50
+ byte_end INTEGER NOT NULL,
51
+ origin TEXT NOT NULL,
52
+ actor_json TEXT NOT NULL,
53
+ seq INTEGER NOT NULL,
54
+ tier TEXT NOT NULL,
55
+ confidence REAL NOT NULL DEFAULT 0.5,
56
+ adapter_id TEXT NOT NULL,
57
+ basis TEXT NOT NULL DEFAULT ''
58
+ );
59
+
60
+ CREATE INDEX IF NOT EXISTS attestations_path ON attestations(path, byte_start, byte_end);
61
+ CREATE INDEX IF NOT EXISTS attestations_seq ON attestations(seq);
62
+
63
+ CREATE TABLE IF NOT EXISTS sessions (
64
+ session_id TEXT PRIMARY KEY,
65
+ tool TEXT NOT NULL,
66
+ model TEXT NOT NULL,
67
+ started_at INTEGER NOT NULL,
68
+ ended_at INTEGER,
69
+ end_reason TEXT,
70
+ prompt_hash TEXT,
71
+ prompt_bytes INTEGER DEFAULT 0,
72
+ prompt_deferred INTEGER NOT NULL DEFAULT 0
73
+ );
74
+
75
+ CREATE TABLE IF NOT EXISTS txns (
76
+ txn_id TEXT PRIMARY KEY,
77
+ session_id TEXT NOT NULL,
78
+ began_at_seq INTEGER NOT NULL,
79
+ committed_at INTEGER,
80
+ aborted_at INTEGER,
81
+ abort_reason TEXT,
82
+ expected_files TEXT NOT NULL DEFAULT '[]'
83
+ );
84
+
85
+ -- Day 15 (§4.1): Tracks pre_hash mismatches for retry-hint on optimistic concurrency.
86
+ CREATE TABLE IF NOT EXISTS concurrency_conflicts (
87
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
88
+ path TEXT NOT NULL,
89
+ conflicting_seq INTEGER NOT NULL,
90
+ expected_hash TEXT NOT NULL,
91
+ actual_hash TEXT NOT NULL,
92
+ detected_at INTEGER NOT NULL
93
+ );
94
+ `;
95
+ export class Materializer {
96
+ db;
97
+ constructor(dbPath) {
98
+ this.db = new DatabaseSync(dbPath);
99
+ this.db.exec(SCHEMA);
100
+ }
101
+ get lastAppliedSeq() {
102
+ const stmt = this.db.prepare('SELECT value FROM meta WHERE key = ?');
103
+ const row = stmt.get('last_applied_seq');
104
+ return row ? parseInt(row.value, 10) : -1;
105
+ }
106
+ apply(event) {
107
+ // Day 5 fix (§2.5): wrap applyEvent + meta update in a single SQLite transaction.
108
+ // If the daemon crashes between the two, replay will re-apply from last_applied_seq+1
109
+ // rather than re-applying a partially-committed event.
110
+ // Idempotency guard: skip events already applied (handles crash-replay double-apply).
111
+ const lastApplied = this.lastAppliedSeq;
112
+ if (event.seq <= lastApplied)
113
+ return; // already applied — idempotent
114
+ this.db.exec('BEGIN IMMEDIATE');
115
+ try {
116
+ this.applyEvent(event);
117
+ this.db
118
+ .prepare('INSERT OR REPLACE INTO meta(key,value) VALUES(?,?)')
119
+ .run('last_applied_seq', String(event.seq));
120
+ this.db.exec('COMMIT');
121
+ }
122
+ catch (err) {
123
+ try {
124
+ this.db.exec('ROLLBACK');
125
+ }
126
+ catch { /* ignore rollback error */ }
127
+ throw err;
128
+ }
129
+ }
130
+ applyEvent(event) {
131
+ switch (event.type) {
132
+ case 'FileOpened':
133
+ return this.applyFileOpened(event);
134
+ case 'EditOp':
135
+ return this.applyEditOp(event);
136
+ case 'UnattributedDelta':
137
+ return this.applyUnattributed(event);
138
+ case 'SessionStarted':
139
+ this.db
140
+ .prepare('INSERT OR REPLACE INTO sessions(session_id,tool,model,started_at,prompt_hash,prompt_bytes) VALUES(?,?,?,?,?,?)')
141
+ .run(event.session_id, event.tool, event.model, event.ts, event.prompt_hash, event.prompt_bytes);
142
+ break;
143
+ case 'SessionEnded':
144
+ this.db
145
+ .prepare('UPDATE sessions SET ended_at=?, end_reason=? WHERE session_id=?')
146
+ .run(event.ts, event.reason, event.session_id);
147
+ break;
148
+ case 'TxnBegin':
149
+ this.db
150
+ .prepare('INSERT OR REPLACE INTO txns(txn_id,session_id,began_at_seq,expected_files) VALUES(?,?,?,?)')
151
+ .run(event.txn_id, event.session_id, event.seq, JSON.stringify(event.expected_files));
152
+ break;
153
+ case 'TxnCommit':
154
+ this.db
155
+ .prepare('UPDATE txns SET committed_at=? WHERE txn_id=?')
156
+ .run(event.seq, event.txn_id);
157
+ break;
158
+ case 'TxnAbort':
159
+ this.db
160
+ .prepare('UPDATE txns SET aborted_at=?, abort_reason=? WHERE txn_id=?')
161
+ .run(event.seq, event.reason, event.txn_id);
162
+ break;
163
+ case 'IntentOverride':
164
+ // Day 12 (§9.2): reclassify spans for the original seq from quarantine/unknown
165
+ // to formatter-touched, preserving prior attribution but marking the format intent.
166
+ this.db
167
+ .prepare('UPDATE spans SET formatted=1 WHERE path=? AND seq_introduced=?')
168
+ .run(event.path, event.original_seq);
169
+ // Clear quarantine if it was set by the formatter write
170
+ this.db
171
+ .prepare('UPDATE files SET tracking_status="tracked" WHERE path=? AND tracking_status="quarantined"')
172
+ .run(event.path);
173
+ break;
174
+ case 'TierShift':
175
+ // Record tier shifts for dashboard observability (§3.3, §8.2)
176
+ // Spans already written at the old tier are not retroactively changed —
177
+ // the shift only affects future events from this adapter.
178
+ break;
179
+ case 'AttestationClaim':
180
+ // Day 18: explicit attestation from an adapter — record for arbitration
181
+ this.db
182
+ .prepare(`INSERT INTO attestations(path,byte_start,byte_end,origin,actor_json,seq,tier,confidence,adapter_id,basis)
183
+ VALUES(?,?,?,?,?,?,?,?,?,?)`)
184
+ .run(event.path, event.byte_start, event.byte_end, event.origin, JSON.stringify(event.actor ?? {}), event.seq, event.tier, event.confidence, event.adapter_id, event.basis);
185
+ break;
186
+ default:
187
+ break;
188
+ }
189
+ }
190
+ applyFileOpened(event) {
191
+ this.db
192
+ .prepare('INSERT OR REPLACE INTO files(path,hash,byte_count,last_seq,tracking_status) VALUES(?,?,?,?,?)')
193
+ .run(event.path, event.blob_sha256, event.byte_count, event.seq, 'tracked');
194
+ }
195
+ applyEditOp(event) {
196
+ const stmt = this.db.prepare('SELECT hash, byte_count, tracking_status FROM files WHERE path=?');
197
+ const file = stmt.get(event.path);
198
+ if (file && file.hash !== event.pre_hash) {
199
+ // Day 15 fix (§4.1): record conflict for retry-hint instead of silently losing.
200
+ this.db
201
+ .prepare('INSERT INTO concurrency_conflicts(path,conflicting_seq,expected_hash,actual_hash,detected_at) VALUES(?,?,?,?,?)')
202
+ .run(event.path, event.seq, event.pre_hash, file.hash, Date.now());
203
+ this.db
204
+ .prepare('UPDATE files SET tracking_status=? WHERE path=?')
205
+ .run('quarantined', event.path);
206
+ return;
207
+ }
208
+ // Day 18 (§3.4): record attestation for each splice — all adapters' claims stored.
209
+ const adapterId = event.adapter_id ?? 'unknown';
210
+ // Track byte_count delta across this EditOp's splices.
211
+ let byteDelta = 0;
212
+ for (const splice of event.ops) {
213
+ byteDelta += splice.insert_text.length - splice.delete_len;
214
+ this.applySplice(event.path, splice.offset, splice.delete_len, splice.insert_text.length, event.origin, event.actor, event.seq, event.tier, 1.0, false);
215
+ // Record attestation for highest-tier arbitration
216
+ if (splice.insert_text.length > 0) {
217
+ this.db
218
+ .prepare(`INSERT INTO attestations(path,byte_start,byte_end,origin,actor_json,seq,tier,confidence,adapter_id,basis)
219
+ VALUES(?,?,?,?,?,?,?,?,?,?)`)
220
+ .run(event.path, splice.offset, splice.offset + splice.insert_text.length, event.origin, JSON.stringify(event.actor), event.seq, event.tier, 1.0, adapterId, `EditOp@seq:${event.seq}`);
221
+ }
222
+ }
223
+ const priorBytes = file?.byte_count ?? 0;
224
+ const newByteCount = Math.max(0, priorBytes + byteDelta);
225
+ this.db
226
+ .prepare('INSERT OR REPLACE INTO files(path,hash,byte_count,last_seq,tracking_status) VALUES(?,?,?,?,?)')
227
+ .run(event.path, event.post_hash, newByteCount, event.seq, 'tracked');
228
+ }
229
+ applyUnattributed(event) {
230
+ const best = (event.candidates ?? [])[0];
231
+ const actor = best?.actor ?? event.actor;
232
+ const ops = event.ops ?? [];
233
+ if (ops.length > 0) {
234
+ // We have positional info — splice spans precisely.
235
+ for (const splice of ops) {
236
+ this.applySplice(event.path, splice.offset, splice.delete_len, splice.insert_text.length, 'unknown', // never default to 'ai' for unattributed
237
+ actor, event.seq, 'T3', event.confidence, false);
238
+ }
239
+ }
240
+ else {
241
+ // No ops — we don't know WHICH bytes changed.
242
+ // Recording a synthetic [0, delta_bytes] span here would silently shift
243
+ // every existing span on every save and grow the spans table unboundedly.
244
+ // Instead, mark the file as 'reconciling' and emit a single attestation
245
+ // record so the conflict is visible without corrupting span layout.
246
+ this.db
247
+ .prepare('UPDATE files SET tracking_status=? WHERE path=?')
248
+ .run('reconciling', event.path);
249
+ const adapterId = event.adapter_id ?? 'unknown';
250
+ const fileRow = this.db
251
+ .prepare('SELECT byte_count FROM files WHERE path=?')
252
+ .get(event.path);
253
+ const end = fileRow?.byte_count ?? Math.max(0, event.delta_bytes ?? 0);
254
+ this.db
255
+ .prepare(`INSERT INTO attestations(path,byte_start,byte_end,origin,actor_json,seq,tier,confidence,adapter_id,basis)
256
+ VALUES(?,?,?,?,?,?,?,?,?,?)`)
257
+ .run(event.path, 0, end, 'unknown', JSON.stringify(actor), event.seq, 'T3', event.confidence, adapterId, 'UnattributedDelta:no-ops');
258
+ }
259
+ // Compute new byte_count from delta if available, otherwise leave untouched.
260
+ if (ops.length > 0) {
261
+ let delta = 0;
262
+ for (const s of ops)
263
+ delta += s.insert_text.length - s.delete_len;
264
+ this.db
265
+ .prepare('UPDATE files SET hash=?, last_seq=?, byte_count=MAX(0, byte_count+?), tracking_status=COALESCE(tracking_status,?) WHERE path=?')
266
+ .run(event.post_hash, event.seq, delta, 'tracked', event.path);
267
+ }
268
+ else {
269
+ this.db
270
+ .prepare('UPDATE files SET hash=?, last_seq=? WHERE path=?')
271
+ .run(event.post_hash, event.seq, event.path);
272
+ }
273
+ }
274
+ applySplice(path, offset, deleteLen, insertLen, origin, actor, seq, tier, confidence, formatted) {
275
+ if (deleteLen > 0) {
276
+ this.db
277
+ .prepare('DELETE FROM spans WHERE path=? AND byte_start >= ? AND byte_end <= ?')
278
+ .run(path, offset, offset + deleteLen);
279
+ this.db
280
+ .prepare('UPDATE spans SET byte_end = ? WHERE path=? AND byte_start < ? AND byte_end > ?')
281
+ .run(offset, path, offset, offset);
282
+ this.db
283
+ .prepare('UPDATE spans SET byte_start = byte_start - ?, byte_end = byte_end - ? WHERE path=? AND byte_start >= ?')
284
+ .run(deleteLen, deleteLen, path, offset + deleteLen);
285
+ }
286
+ if (insertLen > 0) {
287
+ this.db
288
+ .prepare('UPDATE spans SET byte_start = byte_start + ?, byte_end = byte_end + ? WHERE path=? AND byte_start >= ?')
289
+ .run(insertLen, insertLen, path, offset);
290
+ this.db
291
+ .prepare(`INSERT OR REPLACE INTO spans(path,byte_start,byte_end,origin,actor_json,seq_introduced,seq_last_touched,tier,confidence,formatted)
292
+ VALUES(?,?,?,?,?,?,?,?,?,?)`)
293
+ .run(path, offset, offset + insertLen, origin, JSON.stringify(actor), seq, seq, tier, confidence, formatted ? 1 : 0);
294
+ }
295
+ }
296
+ // ─── Queries ───────────────────────────────────────────────────────────────
297
+ getSpans(path, minTier) {
298
+ const tierOrder = { T0: 0, T1: 1, T2: 2, T3: 3 };
299
+ const rows = this.db
300
+ .prepare('SELECT * FROM spans WHERE path=? ORDER BY byte_start')
301
+ .all(path);
302
+ return rows
303
+ .map((r) => ({
304
+ path: r['path'],
305
+ byte_start: r['byte_start'],
306
+ byte_end: r['byte_end'],
307
+ origin: r['origin'],
308
+ actor: JSON.parse(r['actor_json']),
309
+ seq_introduced: r['seq_introduced'],
310
+ seq_last_touched: r['seq_last_touched'],
311
+ txn_id: r['txn_id'],
312
+ tier: r['tier'],
313
+ confidence: r['confidence'],
314
+ formatted: r['formatted'] === 1,
315
+ }))
316
+ .filter((s) => !minTier || tierOrder[s.tier] <= tierOrder[minTier]);
317
+ }
318
+ getFile(path) {
319
+ return this.db.prepare('SELECT * FROM files WHERE path=?').get(path);
320
+ }
321
+ getAttributionSummary(path) {
322
+ const rows = this.db
323
+ .prepare('SELECT origin, SUM(byte_end - byte_start) as bytes FROM spans WHERE path=? GROUP BY origin')
324
+ .all(path);
325
+ const summary = {};
326
+ for (const r of rows)
327
+ summary[r.origin] = r.bytes;
328
+ return summary;
329
+ }
330
+ /**
331
+ * Day 18 (§3.4): Get highest-tier attribution for a byte range.
332
+ * Competes_with arbitration: when multiple adapters attest to the same bytes,
333
+ * the attestation with the lowest tier number (T0 < T1 < T2 < T3) wins.
334
+ * Ties are broken by highest confidence, then earliest seq.
335
+ */
336
+ getHighestTierAttestations(path) {
337
+ const tierOrder = { T0: 0, T1: 1, T2: 2, T3: 3 };
338
+ const rows = this.db
339
+ .prepare('SELECT * FROM attestations WHERE path=? ORDER BY byte_start, tier, confidence DESC, seq')
340
+ .all(path);
341
+ const best = new Map();
342
+ for (const row of rows) {
343
+ const key = `${row['byte_start']}-${row['byte_end']}`;
344
+ const existing = best.get(key);
345
+ if (!existing) {
346
+ best.set(key, row);
347
+ continue;
348
+ }
349
+ const existingOrder = tierOrder[existing['tier']] ?? 3;
350
+ const rowOrder = tierOrder[row['tier']] ?? 3;
351
+ if (rowOrder < existingOrder)
352
+ best.set(key, row);
353
+ }
354
+ return [...best.values()].map((r) => ({
355
+ byte_start: r['byte_start'],
356
+ byte_end: r['byte_end'],
357
+ origin: r['origin'],
358
+ tier: r['tier'],
359
+ confidence: r['confidence'],
360
+ adapter_id: r['adapter_id'],
361
+ }));
362
+ }
363
+ /**
364
+ * Day 15 (§4.1): Get pending concurrency conflicts — files that need retry.
365
+ * Returns the current hash the retrying tool should use as pre_hash.
366
+ */
367
+ getConcurrencyConflicts(path) {
368
+ const query = path
369
+ ? 'SELECT * FROM concurrency_conflicts WHERE path=? ORDER BY detected_at DESC'
370
+ : 'SELECT * FROM concurrency_conflicts ORDER BY detected_at DESC';
371
+ const rows = path
372
+ ? this.db.prepare(query).all(path)
373
+ : this.db.prepare(query).all();
374
+ return rows.map((r) => ({
375
+ path: r['path'],
376
+ conflicting_seq: r['conflicting_seq'],
377
+ expected_hash: r['expected_hash'],
378
+ actual_hash: r['actual_hash'],
379
+ }));
380
+ }
381
+ close() {
382
+ this.db.close();
383
+ }
384
+ }
385
+ //# sourceMappingURL=db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/materializer/db.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Git notes v2 writer with separate Ed25519 signing key. (Day 13: §7.3)
3
+ *
4
+ * Uses refs/notes/ai-v2 (separate from any v1 notes ref).
5
+ * Signed with the notes Ed25519 key — NOT the HMAC install key.
6
+ * This separation means a compromise of the install key does not allow
7
+ * forging historical git notes.
8
+ *
9
+ * Note format (JSON):
10
+ * { v:2, install_id, head_seq, head_hash, head_ts, tier_summary, sig_b64 }
11
+ *
12
+ * sig_b64 is Ed25519 over SHA256(canonical JSON without sig_b64).
13
+ * The public key is stored in .omnitype/journal/keys/notes.pub.pem so
14
+ * verifiers can check without any network call.
15
+ *
16
+ * Post-commit hook: omnitype-daemon note-commit <commit-sha>
17
+ */
18
+ export declare const NOTES_REF = "refs/notes/ai-v2";
19
+ export interface NotePayload {
20
+ v: 2;
21
+ install_id: string;
22
+ head_seq: number;
23
+ head_hash: string;
24
+ head_ts: number;
25
+ tier_summary: Record<string, number>;
26
+ pub_key_pem: string;
27
+ sig_b64: string;
28
+ }
29
+ export declare class GitNotesWriter {
30
+ private noteKey;
31
+ private keysDir;
32
+ private installId;
33
+ constructor(keysDir: string, installId: string);
34
+ /**
35
+ * Write a signed note onto the given commit SHA.
36
+ * Must be called from inside the git repository (cwd = workspace).
37
+ */
38
+ writeNoteForCommit(commitSha: string, headSeq: number, headHash: string, headTs: number, tierSummary: Record<string, number>, cwd: string): void;
39
+ /** Return the public key PEM for this installation (used by verifiers). */
40
+ get publicKeyPem(): string;
41
+ private isInsideGitRepo;
42
+ }
43
+ /**
44
+ * Verify a note retrieved from refs/notes/ai-v2.
45
+ * Returns true if the Ed25519 signature matches the embedded public key.
46
+ * Note: this does NOT verify that the public key is trusted — callers must
47
+ * cross-check pub_key_pem against the install's notes.pub.pem file.
48
+ */
49
+ export declare function verifyNote(noteText: string): boolean;
50
+ //# sourceMappingURL=git-notes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-notes.d.ts","sourceRoot":"","sources":["../../src/notes/git-notes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAOH,eAAO,MAAM,SAAS,qBAAqB,CAAC;AAE5C,MAAM,WAAW,WAAW;IAC1B,CAAC,EAAE,CAAC,CAAC;IACL,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAgD;IAC/D,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAS;gBAEd,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAM9C;;;OAGG;IACH,kBAAkB,CAChB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACnC,GAAG,EAAE,MAAM,GACV,IAAI;IAkCP,2EAA2E;IAC3E,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,OAAO,CAAC,eAAe;CAQxB;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAgBpD"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Git notes v2 writer with separate Ed25519 signing key. (Day 13: §7.3)
3
+ *
4
+ * Uses refs/notes/ai-v2 (separate from any v1 notes ref).
5
+ * Signed with the notes Ed25519 key — NOT the HMAC install key.
6
+ * This separation means a compromise of the install key does not allow
7
+ * forging historical git notes.
8
+ *
9
+ * Note format (JSON):
10
+ * { v:2, install_id, head_seq, head_hash, head_ts, tier_summary, sig_b64 }
11
+ *
12
+ * sig_b64 is Ed25519 over SHA256(canonical JSON without sig_b64).
13
+ * The public key is stored in .omnitype/journal/keys/notes.pub.pem so
14
+ * verifiers can check without any network call.
15
+ *
16
+ * Post-commit hook: omnitype-daemon note-commit <commit-sha>
17
+ */
18
+ import { execFileSync, spawnSync } from 'node:child_process';
19
+ import { createHash } from 'node:crypto';
20
+ import { loadOrCreateNoteSigningKey, signNote } from '../crypto/chain.js';
21
+ export const NOTES_REF = 'refs/notes/ai-v2';
22
+ export class GitNotesWriter {
23
+ noteKey;
24
+ keysDir;
25
+ installId;
26
+ constructor(keysDir, installId) {
27
+ this.keysDir = keysDir;
28
+ this.installId = installId;
29
+ this.noteKey = loadOrCreateNoteSigningKey(keysDir);
30
+ }
31
+ /**
32
+ * Write a signed note onto the given commit SHA.
33
+ * Must be called from inside the git repository (cwd = workspace).
34
+ */
35
+ writeNoteForCommit(commitSha, headSeq, headHash, headTs, tierSummary, cwd) {
36
+ if (!this.isInsideGitRepo(cwd))
37
+ return;
38
+ // Build payload without sig first
39
+ const partial = {
40
+ v: 2,
41
+ install_id: this.installId,
42
+ head_seq: headSeq,
43
+ head_hash: headHash,
44
+ head_ts: headTs,
45
+ tier_summary: tierSummary,
46
+ pub_key_pem: this.noteKey.publicKeyPem,
47
+ };
48
+ const canonical = Buffer.from(JSON.stringify(Object.fromEntries(Object.entries(partial).sort(([a], [b]) => a.localeCompare(b)))), 'utf-8');
49
+ const digestBuf = createHash('sha256').update(canonical).digest();
50
+ const sigBuf = signNote(this.noteKey, digestBuf);
51
+ const payload = { ...partial, sig_b64: sigBuf.toString('base64') };
52
+ const noteText = JSON.stringify(payload, null, 2);
53
+ try {
54
+ // git notes --ref=refs/notes/ai-v2 add -f -m <note> <commit>
55
+ spawnSync('git', ['notes', `--ref=${NOTES_REF}`, 'add', '-f', '-m', noteText, commitSha], { cwd, stdio: 'pipe' });
56
+ }
57
+ catch { /* ignore — best effort */ }
58
+ }
59
+ /** Return the public key PEM for this installation (used by verifiers). */
60
+ get publicKeyPem() {
61
+ return this.noteKey.publicKeyPem;
62
+ }
63
+ isInsideGitRepo(cwd) {
64
+ try {
65
+ execFileSync('git', ['rev-parse', '--git-dir'], { cwd, stdio: 'pipe' });
66
+ return true;
67
+ }
68
+ catch {
69
+ return false;
70
+ }
71
+ }
72
+ }
73
+ /**
74
+ * Verify a note retrieved from refs/notes/ai-v2.
75
+ * Returns true if the Ed25519 signature matches the embedded public key.
76
+ * Note: this does NOT verify that the public key is trusted — callers must
77
+ * cross-check pub_key_pem against the install's notes.pub.pem file.
78
+ */
79
+ export function verifyNote(noteText) {
80
+ try {
81
+ const payload = JSON.parse(noteText);
82
+ const { sig_b64, ...rest } = payload;
83
+ const canonical = Buffer.from(JSON.stringify(Object.fromEntries(Object.entries(rest).sort(([a], [b]) => a.localeCompare(b)))), 'utf-8');
84
+ const digest = createHash('sha256').update(canonical).digest();
85
+ const { createVerify } = require('node:crypto');
86
+ const verifier = createVerify('SHA256');
87
+ verifier.update(digest);
88
+ return verifier.verify(payload.pub_key_pem, Buffer.from(sig_b64, 'base64'));
89
+ }
90
+ catch {
91
+ return false;
92
+ }
93
+ }
94
+ //# sourceMappingURL=git-notes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-notes.js","sourceRoot":"","sources":["../../src/notes/git-notes.ts"],"names":[],"mappings":"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"}