@evomap/evolver-core 2.0.0-beta.0 → 2.0.0-beta.2

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 (75) hide show
  1. package/assets/gep/genes.jsonl +5 -0
  2. package/dist/algo/candidateAssembly.js +17 -11
  3. package/dist/algo/capabilityCandidates.d.ts +2 -0
  4. package/dist/algo/capabilityCandidates.js +5 -0
  5. package/dist/algo/conversationSniffer.d.ts +18 -0
  6. package/dist/algo/conversationSniffer.js +132 -0
  7. package/dist/algo/cycleEngine.js +2 -0
  8. package/dist/algo/cycleFailureClassifier.js +13 -5
  9. package/dist/algo/geneIntake.d.ts +14 -4
  10. package/dist/algo/geneIntake.js +37 -9
  11. package/dist/algo/geneSelection.d.ts +6 -0
  12. package/dist/algo/geneSelection.js +19 -11
  13. package/dist/algo/index.d.ts +1 -0
  14. package/dist/algo/index.js +1 -0
  15. package/dist/assetstore/assetSyncLedger.d.ts +28 -0
  16. package/dist/assetstore/assetSyncLedger.js +86 -0
  17. package/dist/assetstore/index.d.ts +3 -1
  18. package/dist/assetstore/index.js +3 -1
  19. package/dist/assetstore/localJsonl.d.ts +9 -2
  20. package/dist/assetstore/localJsonl.js +75 -23
  21. package/dist/assetstore/pendingSignals.js +3 -1
  22. package/dist/assetstore/provenance.d.ts +7 -2
  23. package/dist/assetstore/provenance.js +85 -25
  24. package/dist/assetstore/reviewFilter.js +2 -1
  25. package/dist/assetstore/reviewLedger.d.ts +6 -0
  26. package/dist/assetstore/reviewLedger.js +9 -0
  27. package/dist/assetstore/seedGenes.d.ts +3 -0
  28. package/dist/assetstore/seedGenes.js +134 -0
  29. package/dist/benchmark/antiGeneBenchmark.d.ts +2 -0
  30. package/dist/benchmark/antiGeneBenchmark.js +11 -1
  31. package/dist/benchmark/antiGeneImpact.d.ts +16 -0
  32. package/dist/benchmark/antiGeneImpact.js +34 -0
  33. package/dist/benchmark/antiGeneRollout.d.ts +2 -0
  34. package/dist/benchmark/antiGeneRollout.js +13 -1
  35. package/dist/events/eventArchive.d.ts +65 -0
  36. package/dist/events/eventArchive.js +343 -0
  37. package/dist/events/eventStore.js +2 -12
  38. package/dist/events/public.d.ts +3 -1
  39. package/dist/events/public.js +2 -1
  40. package/dist/events/retention.d.ts +24 -1
  41. package/dist/events/retention.js +133 -37
  42. package/dist/exec/autoExec.js +3 -0
  43. package/dist/exec/claudeBridge.js +23 -0
  44. package/dist/exec/runnerRegistry.d.ts +8 -6
  45. package/dist/exec/runnerRegistry.js +71 -18
  46. package/dist/exec/selfPrObfuscation.d.ts +2 -1
  47. package/dist/exec/selfPrObfuscation.js +19 -6
  48. package/dist/hub/agentDirectory.d.ts +90 -0
  49. package/dist/hub/agentDirectory.js +104 -0
  50. package/dist/hub/bindings.js +13 -3
  51. package/dist/hub/capability.d.ts +14 -2
  52. package/dist/hub/fake.d.ts +4 -2
  53. package/dist/hub/fake.js +3 -2
  54. package/dist/hub/index.d.ts +1 -0
  55. package/dist/hub/index.js +1 -0
  56. package/dist/mailbox/catalog.js +3 -0
  57. package/dist/mailbox/ipcServer.js +2 -2
  58. package/dist/mailbox/store.d.ts +14 -0
  59. package/dist/mailbox/store.js +58 -6
  60. package/dist/material/consumer.js +9 -6
  61. package/dist/material/index.d.ts +1 -0
  62. package/dist/material/index.js +1 -0
  63. package/dist/material/materialArchive.d.ts +81 -0
  64. package/dist/material/materialArchive.js +466 -0
  65. package/dist/material/materialStore.d.ts +1 -0
  66. package/dist/material/materialStore.js +6 -13
  67. package/dist/schema/common.d.ts +1 -1
  68. package/dist/schema/common.js +1 -1
  69. package/dist/schema/material.d.ts +5 -5
  70. package/dist/trace/trajectoryExport.js +2 -2
  71. package/dist/wire/geneHints.d.ts +42 -1
  72. package/dist/wire/geneHints.js +52 -1
  73. package/dist/wire/index.d.ts +14 -2
  74. package/dist/wire/index.js +1 -1
  75. package/package.json +2 -1
@@ -0,0 +1,466 @@
1
+ import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeSync, } from 'node:fs';
2
+ import { basename, dirname, extname, join } from 'node:path';
3
+ import { material } from '../schema/material.js';
4
+ import { acquireLock, releaseLock } from '../util/fileLock.js';
5
+ export const MATERIAL_ARCHIVE_DEFAULT_KEEP_RECORDS = 1_000;
6
+ const SEGMENT_PATTERN = /^material-(\d{16})-(\d{16})\.jsonl$/;
7
+ export class InvalidMaterialLogError extends Error {
8
+ line;
9
+ code = 'MATERIAL_ARCHIVE_INVALID_LOG';
10
+ constructor(line, reason) {
11
+ super(`material archive refused invalid active log at line ${line}: ${reason}`);
12
+ this.line = line;
13
+ this.name = 'InvalidMaterialLogError';
14
+ }
15
+ }
16
+ export class InvalidMaterialArchiveError extends Error {
17
+ segment;
18
+ line;
19
+ code = 'MATERIAL_ARCHIVE_INVALID_ARCHIVE';
20
+ constructor(segment, line, reason) {
21
+ super(`material archive segment ${segment} is invalid at line ${line}: ${reason}`);
22
+ this.segment = segment;
23
+ this.line = line;
24
+ this.name = 'InvalidMaterialArchiveError';
25
+ }
26
+ }
27
+ export class MaterialArchiveRangeError extends Error {
28
+ code = 'MATERIAL_ARCHIVE_RANGE_INVALID';
29
+ constructor(reason) {
30
+ super(`material archive range is invalid: ${reason}`);
31
+ this.name = 'MaterialArchiveRangeError';
32
+ }
33
+ }
34
+ export class MaterialArchiveSegmentConflictError extends Error {
35
+ archiveId;
36
+ code = 'MATERIAL_ARCHIVE_SEGMENT_CONFLICT';
37
+ constructor(archiveId) {
38
+ super(`material archive segment ${archiveId} already exists with different content`);
39
+ this.archiveId = archiveId;
40
+ this.name = 'MaterialArchiveSegmentConflictError';
41
+ }
42
+ }
43
+ export class InvalidMaterialArchiveCursorError extends Error {
44
+ code = 'MATERIAL_ARCHIVE_CURSOR_INVALID';
45
+ constructor(reason) {
46
+ super(`material archive cursor is invalid: ${reason}`);
47
+ this.name = 'InvalidMaterialArchiveCursorError';
48
+ }
49
+ }
50
+ export function materialArchiveDir(path) {
51
+ const ext = extname(path);
52
+ const stem = ext.length > 0 ? basename(path, ext) : basename(path);
53
+ return join(dirname(path), `${stem}.archive`);
54
+ }
55
+ export function materialArchiveSegmentName(start, end) {
56
+ return `material-${String(start).padStart(16, '0')}-${String(end).padStart(16, '0')}.jsonl`;
57
+ }
58
+ export function planMaterialArchive(opts) {
59
+ const keepRecords = normalizeKeepRecords(opts.keepRecords);
60
+ const state = readStrictHistory(opts.path);
61
+ const historyRecords = state.archive.length + state.logicalActive.length;
62
+ const cursors = readCursorsStrict(opts.cursorPaths ?? [], historyRecords);
63
+ const minCursor = minimumCursor(cursors, state.archive.length);
64
+ const wouldArchive = archiveableCount(state, minCursor, keepRecords);
65
+ const start = state.archive.length;
66
+ const end = start + wouldArchive - 1;
67
+ return {
68
+ mode: 'preview',
69
+ activeRecords: state.logicalActive.length,
70
+ physicalActiveRecords: state.active.length,
71
+ archiveRecords: state.archive.length,
72
+ historyRecords,
73
+ keepRecords,
74
+ minCursor,
75
+ cursors,
76
+ overlapRecords: state.overlap,
77
+ wouldArchive,
78
+ retainedRecords: state.logicalActive.length - wouldArchive,
79
+ archiveId: wouldArchive === 0 ? null : `${start}-${end}`,
80
+ };
81
+ }
82
+ export function archiveMaterialStore(opts) {
83
+ const keepRecords = normalizeKeepRecords(opts.keepRecords);
84
+ const lockPath = `${opts.path}.lock`;
85
+ mkdirSync(dirname(opts.path), { recursive: true });
86
+ acquireLock(lockPath);
87
+ try {
88
+ const state = readStrictHistory(opts.path);
89
+ const historyRecords = state.archive.length + state.logicalActive.length;
90
+ const cursors = readCursorsStrict(opts.cursorPaths ?? [], historyRecords);
91
+ const minCursor = minimumCursor(cursors, state.archive.length);
92
+ const archivedRecords = archiveableCount(state, minCursor, keepRecords);
93
+ const start = state.archive.length;
94
+ const end = start + archivedRecords - 1;
95
+ const archiveId = archivedRecords === 0 ? null : `${start}-${end}`;
96
+ if (archivedRecords > 0) {
97
+ const prefix = state.logicalActive.slice(0, archivedRecords);
98
+ const archiveDir = materialArchiveDir(opts.path);
99
+ mkdirSync(archiveDir, { recursive: true });
100
+ const segmentPath = join(archiveDir, materialArchiveSegmentName(start, end));
101
+ const contents = serializeLines(prefix);
102
+ if (existsSync(segmentPath)) {
103
+ if (readFileSync(segmentPath, 'utf8') !== contents) {
104
+ throw new MaterialArchiveSegmentConflictError(archiveId ?? `${start}-${end}`);
105
+ }
106
+ }
107
+ else {
108
+ durableReplace(`${segmentPath}.tmp`, segmentPath, contents);
109
+ fsyncDirectoryBestEffort(archiveDir);
110
+ }
111
+ }
112
+ const tail = state.logicalActive.slice(archivedRecords);
113
+ if (archivedRecords > 0 || state.overlap > 0) {
114
+ durableReplace(`${opts.path}.archive.tmp`, opts.path, serializeLines(tail));
115
+ fsyncDirectoryBestEffort(dirname(opts.path));
116
+ }
117
+ return {
118
+ mode: 'write',
119
+ activeRecordsBefore: state.logicalActive.length,
120
+ physicalActiveRecordsBefore: state.active.length,
121
+ keepRecords,
122
+ minCursor,
123
+ cursors,
124
+ archivedRecords,
125
+ retainedRecords: tail.length,
126
+ archiveRecords: state.archive.length + archivedRecords,
127
+ historyRecords,
128
+ recoveredOverlap: state.overlap,
129
+ archiveId,
130
+ };
131
+ }
132
+ finally {
133
+ releaseLock(lockPath);
134
+ }
135
+ }
136
+ /** Operational, fail-soft full history reader. Strict archive writes validate every line and range. */
137
+ export function readMaterialHistory(path) {
138
+ // Snapshot active first. Writers persist an archive segment before replacing active, so every interleaving
139
+ // now observes either the old active state or a recoverable archive/active overlap, never a new tail with an
140
+ // old archive base.
141
+ const active = readMaterialsLenient(path);
142
+ const ordered = [];
143
+ const positions = new Map();
144
+ const archiveDir = materialArchiveDir(path);
145
+ for (const descriptor of segmentDescriptors(archiveDir)) {
146
+ for (const record of readMaterialsLenient(join(archiveDir, descriptor.file))) {
147
+ if (positions.has(record.materialId))
148
+ continue;
149
+ positions.set(record.materialId, ordered.length);
150
+ ordered.push(record);
151
+ }
152
+ }
153
+ // The active log is the current writer source. In a crash overlap it owns the duplicate record while its
154
+ // original absolute position remains stable.
155
+ for (const record of active) {
156
+ const position = positions.get(record.materialId);
157
+ if (position === undefined) {
158
+ positions.set(record.materialId, ordered.length);
159
+ ordered.push(record);
160
+ }
161
+ else {
162
+ ordered[position] = record;
163
+ }
164
+ }
165
+ return ordered;
166
+ }
167
+ /** Absolute-offset range reader used by durable consumers after active records move into archive segments. */
168
+ export function readMaterialRange(path, start, count) {
169
+ const normalizedStart = normalizeRangeStart(start);
170
+ const normalizedCount = normalizeRangeCount(count);
171
+ if (normalizedCount === 0)
172
+ return [];
173
+ // Keep the same active-first snapshot order as readMaterialHistory; see its concurrency note.
174
+ const active = readMaterialsLenient(path);
175
+ const archiveDir = materialArchiveDir(path);
176
+ const descriptors = segmentDescriptors(archiveDir);
177
+ if (descriptors.length === 0) {
178
+ return active.slice(normalizedStart, normalizedStart + normalizedCount);
179
+ }
180
+ const archiveEnd = Math.max(...descriptors.map((descriptor) => descriptor.end));
181
+ const requestedEnd = Math.min(Number.MAX_SAFE_INTEGER, normalizedStart + normalizedCount - 1);
182
+ const byOffset = new Map();
183
+ for (const descriptor of descriptors) {
184
+ if (descriptor.end < normalizedStart || descriptor.start > requestedEnd)
185
+ continue;
186
+ const records = readMaterialsLenient(join(archiveDir, descriptor.file));
187
+ for (let index = 0; index < records.length; index += 1) {
188
+ const offset = descriptor.start + index;
189
+ if (offset >= normalizedStart && offset <= requestedEnd && !byOffset.has(offset)) {
190
+ byOffset.set(offset, records[index]);
191
+ }
192
+ }
193
+ }
194
+ if (requestedEnd > archiveEnd) {
195
+ const overlap = detectOperationalOverlap(path, descriptors, active);
196
+ const logicalActive = active.slice(overlap);
197
+ const activeStart = archiveEnd + 1;
198
+ for (let index = 0; index < logicalActive.length; index += 1) {
199
+ const offset = activeStart + index;
200
+ if (offset >= normalizedStart && offset <= requestedEnd)
201
+ byOffset.set(offset, logicalActive[index]);
202
+ }
203
+ }
204
+ return [...byOffset.entries()]
205
+ .sort(([left], [right]) => left - right)
206
+ .map(([, record]) => record);
207
+ }
208
+ export function inspectMaterialArchive(path) {
209
+ const archiveDir = materialArchiveDir(path);
210
+ const descriptors = segmentDescriptors(archiveDir);
211
+ let bytes = 0;
212
+ let records = 0;
213
+ let invalidLines = 0;
214
+ for (const descriptor of descriptors) {
215
+ const segmentPath = join(archiveDir, descriptor.file);
216
+ bytes += statSync(segmentPath).size;
217
+ for (const line of readFileSync(segmentPath, 'utf8').split('\n')) {
218
+ if (line.length === 0)
219
+ continue;
220
+ try {
221
+ material.parse(JSON.parse(line));
222
+ records += 1;
223
+ }
224
+ catch {
225
+ invalidLines += 1;
226
+ }
227
+ }
228
+ }
229
+ return {
230
+ exists: descriptors.length > 0,
231
+ segments: descriptors.length,
232
+ bytes,
233
+ records,
234
+ invalidLines,
235
+ firstOffset: descriptors[0]?.start ?? null,
236
+ lastOffset: descriptors[descriptors.length - 1]?.end ?? null,
237
+ };
238
+ }
239
+ function readStrictHistory(path) {
240
+ const archive = readArchiveStrict(materialArchiveDir(path));
241
+ const active = readActiveStrict(path);
242
+ const overlap = detectOverlap(archive, active);
243
+ const logicalActive = active.slice(overlap);
244
+ const seen = new Map();
245
+ for (const line of [...archive, ...logicalActive]) {
246
+ const canonical = JSON.stringify(line.record);
247
+ const existing = seen.get(line.record.materialId);
248
+ if (existing !== undefined) {
249
+ if (existing !== canonical)
250
+ throw new MaterialArchiveSegmentConflictError(line.record.materialId);
251
+ throw new MaterialArchiveRangeError(`duplicate material ${line.record.materialId}`);
252
+ }
253
+ seen.set(line.record.materialId, canonical);
254
+ }
255
+ return { archive, active, logicalActive, overlap };
256
+ }
257
+ function readArchiveStrict(archiveDir) {
258
+ const descriptors = segmentDescriptors(archiveDir, true);
259
+ const out = [];
260
+ let expectedStart = 0;
261
+ for (const descriptor of descriptors) {
262
+ if (descriptor.start !== expectedStart) {
263
+ throw new MaterialArchiveRangeError(`expected offset ${expectedStart} but found ${descriptor.start}`);
264
+ }
265
+ const lines = readStrictLines(join(archiveDir, descriptor.file), descriptor.file, true);
266
+ const expectedLength = descriptor.end - descriptor.start + 1;
267
+ if (lines.length !== expectedLength) {
268
+ throw new MaterialArchiveRangeError(`${descriptor.file} declares ${expectedLength} record(s) but contains ${lines.length}`);
269
+ }
270
+ out.push(...lines);
271
+ expectedStart = descriptor.end + 1;
272
+ }
273
+ return out;
274
+ }
275
+ function readActiveStrict(path) {
276
+ if (!existsSync(path))
277
+ return [];
278
+ const out = [];
279
+ const lines = readFileSync(path, 'utf8').split('\n');
280
+ for (let index = 0; index < lines.length; index += 1) {
281
+ const raw = lines[index];
282
+ if (raw.length === 0)
283
+ continue;
284
+ try {
285
+ out.push({ record: material.parse(JSON.parse(raw)), raw });
286
+ }
287
+ catch {
288
+ throw new InvalidMaterialLogError(index + 1, 'invalid json or material schema');
289
+ }
290
+ }
291
+ return out;
292
+ }
293
+ function readStrictLines(path, segment, archive) {
294
+ const out = [];
295
+ const lines = readFileSync(path, 'utf8').split('\n');
296
+ for (let index = 0; index < lines.length; index += 1) {
297
+ const raw = lines[index];
298
+ if (raw.length === 0)
299
+ continue;
300
+ try {
301
+ out.push({ record: material.parse(JSON.parse(raw)), raw });
302
+ }
303
+ catch {
304
+ if (archive)
305
+ throw new InvalidMaterialArchiveError(segment, index + 1, 'invalid json or material schema');
306
+ throw new InvalidMaterialLogError(index + 1, 'invalid json or material schema');
307
+ }
308
+ }
309
+ return out;
310
+ }
311
+ function readMaterialsLenient(path) {
312
+ if (!existsSync(path))
313
+ return [];
314
+ const out = [];
315
+ for (const line of readFileSync(path, 'utf8').split('\n')) {
316
+ if (line.length === 0)
317
+ continue;
318
+ try {
319
+ out.push(material.parse(JSON.parse(line)));
320
+ }
321
+ catch {
322
+ // Preserve MaterialStore's existing fail-soft reads. Archive writes use strict parsing.
323
+ }
324
+ }
325
+ return out;
326
+ }
327
+ function segmentDescriptors(archiveDir, strict = false) {
328
+ if (!existsSync(archiveDir))
329
+ return [];
330
+ const out = [];
331
+ for (const file of readdirSync(archiveDir).sort()) {
332
+ const match = SEGMENT_PATTERN.exec(file);
333
+ if (match === null)
334
+ continue;
335
+ const start = Number(match[1]);
336
+ const end = Number(match[2]);
337
+ if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end < start) {
338
+ if (strict)
339
+ throw new MaterialArchiveRangeError(`invalid segment filename ${file}`);
340
+ continue;
341
+ }
342
+ out.push({ file, start, end });
343
+ }
344
+ return out.sort((left, right) => left.start - right.start || left.end - right.end);
345
+ }
346
+ function detectOverlap(archive, active) {
347
+ const max = Math.min(archive.length, active.length);
348
+ for (let length = max; length > 0; length -= 1) {
349
+ const archiveStart = archive.length - length;
350
+ let matches = true;
351
+ for (let index = 0; index < length; index += 1) {
352
+ if (JSON.stringify(archive[archiveStart + index].record) !== JSON.stringify(active[index].record)) {
353
+ matches = false;
354
+ break;
355
+ }
356
+ }
357
+ if (matches)
358
+ return length;
359
+ }
360
+ return 0;
361
+ }
362
+ function detectOperationalOverlap(path, descriptors, active) {
363
+ const latest = descriptors[descriptors.length - 1];
364
+ if (latest === undefined || active.length === 0)
365
+ return 0;
366
+ const archived = readMaterialsLenient(join(materialArchiveDir(path), latest.file));
367
+ const max = Math.min(archived.length, active.length);
368
+ for (let length = max; length > 0; length -= 1) {
369
+ const archiveStart = archived.length - length;
370
+ let matches = true;
371
+ for (let index = 0; index < length; index += 1) {
372
+ if (JSON.stringify(archived[archiveStart + index]) !== JSON.stringify(active[index])) {
373
+ matches = false;
374
+ break;
375
+ }
376
+ }
377
+ if (matches)
378
+ return length;
379
+ }
380
+ return 0;
381
+ }
382
+ function readCursorsStrict(paths, historyRecords) {
383
+ const out = [];
384
+ for (const path of paths) {
385
+ if (!existsSync(path))
386
+ continue;
387
+ let parsed;
388
+ try {
389
+ parsed = JSON.parse(readFileSync(path, 'utf8'));
390
+ }
391
+ catch {
392
+ throw new InvalidMaterialArchiveCursorError('cursor file is not valid JSON');
393
+ }
394
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
395
+ throw new InvalidMaterialArchiveCursorError('cursor file must contain an object');
396
+ }
397
+ for (const [group, value] of Object.entries(parsed)) {
398
+ if (!Number.isSafeInteger(value) || value < 0 || value > historyRecords) {
399
+ throw new InvalidMaterialArchiveCursorError(`group ${group} is outside history bounds`);
400
+ }
401
+ out.push({ group, position: value });
402
+ }
403
+ }
404
+ return out;
405
+ }
406
+ function minimumCursor(cursors, archiveRecords) {
407
+ return cursors.length === 0 ? archiveRecords : Math.min(...cursors.map((cursor) => cursor.position));
408
+ }
409
+ function archiveableCount(state, minCursor, keepRecords) {
410
+ const committedActive = Math.max(0, minCursor - state.archive.length);
411
+ const tailBound = Math.max(0, state.logicalActive.length - keepRecords);
412
+ return Math.min(committedActive, tailBound);
413
+ }
414
+ function serializeLines(lines) {
415
+ return lines.length === 0 ? '' : `${lines.map((line) => line.raw).join('\n')}\n`;
416
+ }
417
+ function durableReplace(tmpPath, finalPath, contents) {
418
+ let fd = null;
419
+ try {
420
+ fd = openSync(tmpPath, 'w', 0o600);
421
+ writeSync(fd, contents, undefined, 'utf8');
422
+ fsyncSync(fd);
423
+ closeSync(fd);
424
+ fd = null;
425
+ renameSync(tmpPath, finalPath);
426
+ }
427
+ catch (error) {
428
+ if (fd !== null)
429
+ closeSync(fd);
430
+ rmSync(tmpPath, { force: true });
431
+ throw error;
432
+ }
433
+ }
434
+ function fsyncDirectoryBestEffort(path) {
435
+ let fd = null;
436
+ try {
437
+ fd = openSync(path, 'r');
438
+ fsyncSync(fd);
439
+ }
440
+ catch {
441
+ // Windows and some filesystems do not support fsync on directories.
442
+ }
443
+ finally {
444
+ if (fd !== null)
445
+ closeSync(fd);
446
+ }
447
+ }
448
+ function normalizeKeepRecords(value) {
449
+ if (value === undefined)
450
+ return MATERIAL_ARCHIVE_DEFAULT_KEEP_RECORDS;
451
+ if (!Number.isFinite(value) || value < 1)
452
+ throw new RangeError('keepRecords must be a positive integer');
453
+ return Math.floor(value);
454
+ }
455
+ function normalizeRangeStart(value) {
456
+ if (!Number.isFinite(value) || value < 0)
457
+ return 0;
458
+ return Math.floor(value);
459
+ }
460
+ function normalizeRangeCount(value) {
461
+ if (!Number.isFinite(value))
462
+ return Number.MAX_SAFE_INTEGER;
463
+ if (value <= 0)
464
+ return 0;
465
+ return Math.floor(value);
466
+ }
@@ -17,6 +17,7 @@ export declare class MaterialStore {
17
17
  private putLocked;
18
18
  get(materialId: string): Material | undefined;
19
19
  readAll(): Material[];
20
+ readRange(start: number, count: number): Material[];
20
21
  iterate(opts?: {
21
22
  consumerGroup?: string;
22
23
  since?: string;
@@ -1,8 +1,9 @@
1
- import { openSync, writeSync, fsyncSync, closeSync, existsSync, readFileSync, mkdirSync } from 'node:fs';
1
+ import { openSync, writeSync, fsyncSync, closeSync, mkdirSync } from 'node:fs';
2
2
  import { dirname } from 'node:path';
3
3
  import { material } from '../schema/material.js';
4
4
  import { acquireLock, releaseLock } from '../util/fileLock.js';
5
5
  import { assertMaterialSource } from './boundary.js';
6
+ import { readMaterialHistory, readMaterialRange } from './materialArchive.js';
6
7
  /** 原材料库 (append-only jsonl + single-writer + materialId 去重). */
7
8
  export class MaterialStore {
8
9
  path;
@@ -51,18 +52,10 @@ export class MaterialStore {
51
52
  return undefined;
52
53
  }
53
54
  readAll() {
54
- if (!existsSync(this.path))
55
- return [];
56
- const out = [];
57
- for (const l of readFileSync(this.path, 'utf8').split('\n')) {
58
- if (!l)
59
- continue;
60
- try {
61
- out.push(material.parse(JSON.parse(l)));
62
- }
63
- catch { /* skip */ }
64
- }
65
- return out;
55
+ return readMaterialHistory(this.path);
56
+ }
57
+ readRange(start, count) {
58
+ return readMaterialRange(this.path, start, count);
66
59
  }
67
60
  *iterate(opts = {}) {
68
61
  for (const m of this.readAll()) {
@@ -22,7 +22,7 @@ export declare const artifactRef: z.ZodObject<{
22
22
  }>;
23
23
  export type ArtifactRef = z.infer<typeof artifactRef>;
24
24
  /** runtime 来源 (D11). 'generic-chat' = 任何输出标准 OpenAI/Anthropic messages 的 AI(经 genericChatAdapter 接入). */
25
- export declare const sourceAgent: z.ZodEnum<["claude-code", "codex", "cursor", "gemini", "kimi", "kiro", "opencode", "generic-chat"]>;
25
+ export declare const sourceAgent: z.ZodEnum<["claude-code", "codex", "cursor", "gemini", "antigravity", "kimi", "kiro", "opencode", "generic-chat"]>;
26
26
  export type SourceAgent = z.infer<typeof sourceAgent>;
27
27
  export declare const blastRadius: z.ZodEnum<["file", "module", "package", "system"]>;
28
28
  export type BlastRadius = z.infer<typeof blastRadius>;
@@ -13,5 +13,5 @@ export const artifactRef = z.object({
13
13
  size: z.number().int().nonnegative(),
14
14
  });
15
15
  /** runtime 来源 (D11). 'generic-chat' = 任何输出标准 OpenAI/Anthropic messages 的 AI(经 genericChatAdapter 接入). */
16
- export const sourceAgent = z.enum(['claude-code', 'codex', 'cursor', 'gemini', 'kimi', 'kiro', 'opencode', 'generic-chat']);
16
+ export const sourceAgent = z.enum(['claude-code', 'codex', 'cursor', 'gemini', 'antigravity', 'kimi', 'kiro', 'opencode', 'generic-chat']);
17
17
  export const blastRadius = z.enum(['file', 'module', 'package', 'system']);
@@ -21,7 +21,7 @@ export type Watermark = z.infer<typeof watermark>;
21
21
  export declare const material: z.ZodEffects<z.ZodObject<{
22
22
  materialId: z.ZodString;
23
23
  /** Runtime agent that produced a session/tool source. Absent for agent-agnostic origins (proxy trace), #95. */
24
- sourceAgent: z.ZodOptional<z.ZodEnum<["claude-code", "codex", "cursor", "gemini", "kimi", "kiro", "opencode", "generic-chat"]>>;
24
+ sourceAgent: z.ZodOptional<z.ZodEnum<["claude-code", "codex", "cursor", "gemini", "antigravity", "kimi", "kiro", "opencode", "generic-chat"]>>;
25
25
  /** Origin class: a runtime agent session, or the proxy gateway's LLM trace (agent-agnostic), #95.
26
26
  * Defaults to runtime_session so pre-#95 records (which only ever held sessions) parse unchanged. */
27
27
  sourceKind: z.ZodDefault<z.ZodEnum<["runtime_session", "proxy_trace"]>>;
@@ -76,7 +76,7 @@ export declare const material: z.ZodEffects<z.ZodObject<{
76
76
  consumerGroup: string;
77
77
  feedsMaterial: boolean;
78
78
  extensions: Record<string, unknown>;
79
- sourceAgent?: "claude-code" | "codex" | "cursor" | "gemini" | "kimi" | "kiro" | "opencode" | "generic-chat" | undefined;
79
+ sourceAgent?: "claude-code" | "codex" | "cursor" | "gemini" | "antigravity" | "kimi" | "kiro" | "opencode" | "generic-chat" | undefined;
80
80
  payloadRef?: {
81
81
  path: string;
82
82
  sha256: string;
@@ -95,7 +95,7 @@ export declare const material: z.ZodEffects<z.ZodObject<{
95
95
  };
96
96
  capturedAt: string;
97
97
  consumerGroup: string;
98
- sourceAgent?: "claude-code" | "codex" | "cursor" | "gemini" | "kimi" | "kiro" | "opencode" | "generic-chat" | undefined;
98
+ sourceAgent?: "claude-code" | "codex" | "cursor" | "gemini" | "antigravity" | "kimi" | "kiro" | "opencode" | "generic-chat" | undefined;
99
99
  sourceKind?: "runtime_session" | "proxy_trace" | undefined;
100
100
  payloadRef?: {
101
101
  path: string;
@@ -120,7 +120,7 @@ export declare const material: z.ZodEffects<z.ZodObject<{
120
120
  consumerGroup: string;
121
121
  feedsMaterial: boolean;
122
122
  extensions: Record<string, unknown>;
123
- sourceAgent?: "claude-code" | "codex" | "cursor" | "gemini" | "kimi" | "kiro" | "opencode" | "generic-chat" | undefined;
123
+ sourceAgent?: "claude-code" | "codex" | "cursor" | "gemini" | "antigravity" | "kimi" | "kiro" | "opencode" | "generic-chat" | undefined;
124
124
  payloadRef?: {
125
125
  path: string;
126
126
  sha256: string;
@@ -139,7 +139,7 @@ export declare const material: z.ZodEffects<z.ZodObject<{
139
139
  };
140
140
  capturedAt: string;
141
141
  consumerGroup: string;
142
- sourceAgent?: "claude-code" | "codex" | "cursor" | "gemini" | "kimi" | "kiro" | "opencode" | "generic-chat" | undefined;
142
+ sourceAgent?: "claude-code" | "codex" | "cursor" | "gemini" | "antigravity" | "kimi" | "kiro" | "opencode" | "generic-chat" | undefined;
143
143
  sourceKind?: "runtime_session" | "proxy_trace" | undefined;
144
144
  payloadRef?: {
145
145
  path: string;
@@ -1558,7 +1558,7 @@ function runtimeSessionId(input) {
1558
1558
  return explicit;
1559
1559
  const sourcePath = stringValue(input.sourcePath);
1560
1560
  const base = sourcePath.split(/[\\/]/).filter(Boolean).pop() ?? '';
1561
- return base.replace(/\.jsonl?$/i, '') || 'unknown';
1561
+ return redactString(base.replace(/\.jsonl?$/i, '')) || 'unknown';
1562
1562
  }
1563
1563
  function nativeCallHasField(call, key) {
1564
1564
  return Object.prototype.hasOwnProperty.call(call, key);
@@ -1704,7 +1704,7 @@ export function buildCodingTrajectoryFromSessionLog(input) {
1704
1704
  session_id: sessionId,
1705
1705
  source_kind: 'runtime_session',
1706
1706
  source_agent: sourceAgent,
1707
- source_path: sourcePath,
1707
+ source_path: redactString(sourcePath),
1708
1708
  ...(sessionModel ? { session_model: sessionModel } : {}),
1709
1709
  ...(provider !== sourceAgent ? { session_provider: provider } : {}),
1710
1710
  ...(sessionTools !== undefined ? { session_tools: redactRuntimeStructuredValue(sessionTools) } : {}),
@@ -34,13 +34,45 @@ export interface GeneToolPolicy {
34
34
  deny?: string[];
35
35
  severity?: ToolPolicySeverity;
36
36
  }
37
+ /**
38
+ * Provenance classification (V1 #302 classifyProvenance, aligned with TaskGenome Bench §3.1): a Gene's value
39
+ * depends on WHERE it came from, not on being short. The three tiers:
40
+ * evolved -- distilled from a real solve -> fail -> mutate -> pass trajectory; beats Skills (+8.7..+15.5pp)
41
+ * distilled -- transcribed from reference/teacher text with no real failing trajectory; WORSE than Skills (-3.2..-11.2pp)
42
+ * manual -- pure human transcription, no execution evidence at all
43
+ * The high-value payload of an evolved Gene is the corrective_insight that flipped the outcome.
44
+ */
45
+ export declare const GENERATION_SOURCES: readonly ["evolved", "distilled", "manual"];
46
+ export type GenerationSource = (typeof GENERATION_SOURCES)[number];
47
+ export interface GeneGenerationHeuristics {
48
+ strategy_steps?: number;
49
+ avoid_count?: number;
50
+ validation_declared_count?: number;
51
+ validation_runnable_count?: number;
52
+ signals_extracted?: number;
53
+ preconditions_extracted?: number;
54
+ trajectory_depth?: number;
55
+ has_corrective_insight?: boolean;
56
+ }
57
+ /**
58
+ * The full provenance + quality metadata block (V1 `gene._source`). Lives on Gene as the v2-delta field
59
+ * `generation_meta`. `source` is the only required key; the rest is descriptive for reviewers / future
60
+ * governance. `quality_score` is a coarse [0,1] tier-anchored score (evolved 0.7 / distilled 0.4 / manual 0.3
61
+ * baseline + bonuses); `overcame_errors` is the mutation_log copy that the trajectory overcame.
62
+ */
63
+ export interface GenerationMeta {
64
+ source: GenerationSource;
65
+ quality_score?: number;
66
+ quality_heuristics?: GeneGenerationHeuristics;
67
+ overcame_errors?: string[];
68
+ }
37
69
  /**
38
70
  * The v2-delta hint field names, in one place — the intake gate strips these before the gep-sdk schema check.
39
71
  * IMPORTANT: when a gep-sdk gene-schema bump makes one of these first-class, REMOVE it from this list in the
40
72
  * same change. Otherwise intakeGene keeps stripping a now-validated field before validateWire (fail-open — the
41
73
  * field's new schema constraints go unchecked at intake) while asset_id still commits it.
42
74
  */
43
- export declare const GENE_HINT_FIELDS: readonly ["routing_hint", "tool_policy"];
75
+ export declare const GENE_HINT_FIELDS: readonly ["routing_hint", "tool_policy", "generation_meta"];
44
76
  /**
45
77
  * Normalize an arbitrary routing_hint fragment to a strict { tier?, reasoning_level? } object, or null.
46
78
  * Unknown enum values are dropped (a stray tier would fail the consumer's exhaustive match and route as if
@@ -60,5 +92,14 @@ export declare function normalizeRoutingHint(raw: unknown): GeneRoutingHint | nu
60
92
  * defaults to 'warn' when a list is present; the whole object collapses to null when neither list survives.
61
93
  */
62
94
  export declare function normalizeToolPolicy(raw: unknown): GeneToolPolicy | null;
95
+ /**
96
+ * Normalize an arbitrary generation_meta fragment to a strict { source, quality_score?, quality_heuristics?,
97
+ * overcame_errors? } object, or null. Lossy (mirrors normalizeRoutingHint / normalizeToolPolicy): unknown source
98
+ * values are dropped (the whole block collapses to null — a generation_meta with no recognized source carries no
99
+ * usable provenance signal); quality_score is clamped to [0,1]; heuristics keeps only its numeric/boolean fields;
100
+ * overcame_errors keeps only non-empty strings. A block with a valid source but all-else-empty still survives (source
101
+ * alone is a meaningful provenance tag); only a missing/unknown source yields null.
102
+ */
103
+ export declare function normalizeGenerationMeta(raw: unknown): GenerationMeta | null;
63
104
  /** Strip the v2-delta hint fields from a gene-shaped object (used to validate the gep-sdk-known core). */
64
105
  export declare function stripGeneHints(gene: Record<string, unknown>): Record<string, unknown>;