@evomap/evolver-core 2.0.0-beta.1 → 2.0.0-beta.3

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 (110) hide show
  1. package/assets/gep/genes.jsonl +5 -0
  2. package/dist/algo/candidateAssembly.js +23 -14
  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.d.ts +11 -0
  8. package/dist/algo/cycleEngine.js +14 -6
  9. package/dist/algo/cycleFailureClassifier.d.ts +1 -1
  10. package/dist/algo/cycleFailureClassifier.js +13 -5
  11. package/dist/algo/geneIntake.d.ts +14 -4
  12. package/dist/algo/geneIntake.js +37 -9
  13. package/dist/algo/geneSelection.d.ts +18 -1
  14. package/dist/algo/geneSelection.js +42 -18
  15. package/dist/algo/index.d.ts +2 -0
  16. package/dist/algo/index.js +2 -0
  17. package/dist/algo/memoryGraph.d.ts +62 -0
  18. package/dist/algo/memoryGraph.js +86 -0
  19. package/dist/algo/orchestrator.d.ts +3 -0
  20. package/dist/algo/orchestrator.js +14 -2
  21. package/dist/assetstore/assetSidecarRecords.d.ts +23 -0
  22. package/dist/assetstore/assetSidecarRecords.js +142 -0
  23. package/dist/assetstore/assetSidecarRecovery.d.ts +48 -0
  24. package/dist/assetstore/assetSidecarRecovery.js +288 -0
  25. package/dist/assetstore/assetStoreHealth.d.ts +75 -0
  26. package/dist/assetstore/assetStoreHealth.js +277 -0
  27. package/dist/assetstore/assetStoreLayout.d.ts +2 -0
  28. package/dist/assetstore/assetStoreLayout.js +6 -0
  29. package/dist/assetstore/assetStoreStorage.d.ts +42 -0
  30. package/dist/assetstore/assetStoreStorage.js +318 -0
  31. package/dist/assetstore/assetSyncLedger.d.ts +32 -0
  32. package/dist/assetstore/assetSyncLedger.js +66 -0
  33. package/dist/assetstore/index.d.ts +5 -1
  34. package/dist/assetstore/index.js +5 -1
  35. package/dist/assetstore/localJsonl.d.ts +10 -2
  36. package/dist/assetstore/localJsonl.js +93 -37
  37. package/dist/assetstore/pendingSignals.js +3 -1
  38. package/dist/assetstore/provenance.d.ts +20 -2
  39. package/dist/assetstore/provenance.js +92 -56
  40. package/dist/assetstore/provider.d.ts +2 -0
  41. package/dist/assetstore/reviewFilter.js +5 -2
  42. package/dist/assetstore/reviewLedger.d.ts +14 -2
  43. package/dist/assetstore/reviewLedger.js +78 -43
  44. package/dist/assetstore/seedGenes.d.ts +3 -0
  45. package/dist/assetstore/seedGenes.js +134 -0
  46. package/dist/benchmark/antiGeneBenchmark.d.ts +2 -0
  47. package/dist/benchmark/antiGeneBenchmark.js +11 -1
  48. package/dist/benchmark/antiGeneImpact.d.ts +16 -0
  49. package/dist/benchmark/antiGeneImpact.js +34 -0
  50. package/dist/benchmark/antiGeneRollout.d.ts +2 -0
  51. package/dist/benchmark/antiGeneRollout.js +13 -1
  52. package/dist/benchmark/index.d.ts +2 -1
  53. package/dist/benchmark/index.js +2 -1
  54. package/dist/benchmark/triggerShift.d.ts +62 -0
  55. package/dist/benchmark/triggerShift.js +106 -0
  56. package/dist/events/eventArchive.d.ts +65 -0
  57. package/dist/events/eventArchive.js +343 -0
  58. package/dist/events/eventStore.js +2 -12
  59. package/dist/events/ingest.d.ts +1 -1
  60. package/dist/events/ingest.js +2 -0
  61. package/dist/events/paths.d.ts +1 -1
  62. package/dist/events/paths.js +2 -2
  63. package/dist/events/public.d.ts +3 -1
  64. package/dist/events/public.js +2 -1
  65. package/dist/events/retention.d.ts +24 -1
  66. package/dist/events/retention.js +133 -37
  67. package/dist/exec/autoExec.d.ts +6 -1
  68. package/dist/exec/autoExec.js +34 -0
  69. package/dist/exec/autonomousCycle.d.ts +2 -0
  70. package/dist/exec/autonomousCycle.js +5 -0
  71. package/dist/exec/claudeBridge.d.ts +7 -2
  72. package/dist/exec/claudeBridge.js +115 -14
  73. package/dist/exec/prompt.js +9 -0
  74. package/dist/exec/runnerRegistry.d.ts +64 -18
  75. package/dist/exec/runnerRegistry.js +341 -38
  76. package/dist/exec/selfPrObfuscation.d.ts +2 -1
  77. package/dist/exec/selfPrObfuscation.js +19 -6
  78. package/dist/hub/agentDirectory.d.ts +90 -0
  79. package/dist/hub/agentDirectory.js +104 -0
  80. package/dist/hub/bindings.js +23 -3
  81. package/dist/hub/capability.d.ts +14 -2
  82. package/dist/hub/fake.d.ts +4 -2
  83. package/dist/hub/fake.js +3 -2
  84. package/dist/hub/index.d.ts +1 -0
  85. package/dist/hub/index.js +1 -0
  86. package/dist/mailbox/catalog.js +3 -0
  87. package/dist/mailbox/ipcServer.js +2 -2
  88. package/dist/mailbox/store.d.ts +14 -0
  89. package/dist/mailbox/store.js +44 -2
  90. package/dist/material/consumer.js +9 -6
  91. package/dist/material/index.d.ts +1 -0
  92. package/dist/material/index.js +1 -0
  93. package/dist/material/materialArchive.d.ts +81 -0
  94. package/dist/material/materialArchive.js +466 -0
  95. package/dist/material/materialStore.d.ts +1 -0
  96. package/dist/material/materialStore.js +6 -13
  97. package/dist/ops/savingsCore.js +1 -2
  98. package/dist/ops/selfUpdate.d.ts +10 -1
  99. package/dist/ops/selfUpdate.js +64 -15
  100. package/dist/schema/common.d.ts +1 -1
  101. package/dist/schema/common.js +1 -1
  102. package/dist/schema/material.d.ts +5 -5
  103. package/dist/trace/trajectoryExport.js +2 -2
  104. package/dist/util/fileLock.d.ts +19 -2
  105. package/dist/util/fileLock.js +166 -31
  106. package/dist/wire/geneHints.d.ts +42 -1
  107. package/dist/wire/geneHints.js +52 -1
  108. package/dist/wire/index.d.ts +14 -2
  109. package/dist/wire/index.js +1 -1
  110. package/package.json +6 -1
@@ -0,0 +1,42 @@
1
+ import type { Stats } from 'node:fs';
2
+ import { acquireLock, releaseLock, type ReleaseLockResult } from '../util/fileLock.js';
3
+ export type UnsafeAssetStorePathReason = 'symlink' | 'not_directory' | 'not_regular_file' | 'path_changed';
4
+ export type AssetStorePathRole = 'base_directory' | 'asset_file' | 'lock_file' | 'temp_file';
5
+ export declare class UnsafeAssetStorePathError extends Error {
6
+ readonly role: AssetStorePathRole;
7
+ readonly reason: UnsafeAssetStorePathReason;
8
+ readonly code = "UNSAFE_ASSET_STORE_PATH";
9
+ constructor(role: AssetStorePathRole, reason: UnsafeAssetStorePathReason);
10
+ }
11
+ export declare class AssetStoreReadLimitError extends Error {
12
+ readonly code = "ASSET_STORE_READ_LIMIT";
13
+ constructor();
14
+ }
15
+ export interface DurableWriteOptions {
16
+ syncFile?: (fd: number) => void;
17
+ syncDirectory?: (path: string) => void;
18
+ onTempPath?: (path: string) => void;
19
+ }
20
+ export interface AssetStoreLockDeps {
21
+ acquireLock?: typeof acquireLock;
22
+ releaseLock?: typeof releaseLock;
23
+ }
24
+ export declare function ensureAssetStoreDirectory(path: string): void;
25
+ /** Validate an existing store root without recreating a deleted or unmounted path. */
26
+ export declare function assertAssetStoreDirectory(path: string): void;
27
+ export declare function isReliableAssetStoreLockRelease(result: ReleaseLockResult): boolean;
28
+ /**
29
+ * Run one synchronous asset-store critical section without hiding its primary failure.
30
+ * A release failure is surfaced only after a successful operation; when both fail, the operation error wins.
31
+ */
32
+ export declare function withAssetStoreLock<T>(lockPath: string, operation: () => T, deps?: AssetStoreLockDeps): T;
33
+ export declare function assertOptionalRegularFile(path: string, role?: AssetStorePathRole): Stats | null;
34
+ export declare function regularFileFingerprint(path: string): string;
35
+ export declare function readRegularBuffer(path: string, maxBytes?: number): Buffer | null;
36
+ export declare function readUtf8Regular(path: string): string | null;
37
+ export declare function createBufferDurableExclusive(path: string, value: Buffer, opts?: DurableWriteOptions): void;
38
+ export declare function fsyncDirectoryBestEffort(path: string): void;
39
+ export declare function appendUtf8Durable(path: string, value: string, opts?: DurableWriteOptions): void;
40
+ export declare function replaceUtf8Durable(path: string, value: string, opts?: DurableWriteOptions): void;
41
+ /** Remove one exact UTF-8 suffix and fsync the new file length; false means the suffix was not current. */
42
+ export declare function truncateUtf8SuffixDurable(path: string, suffix: string, opts?: Pick<DurableWriteOptions, 'syncFile'>): boolean;
@@ -0,0 +1,318 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { closeSync, constants, fstatSync, fsyncSync, ftruncateSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, renameSync, unlinkSync, writeSync, } from 'node:fs';
3
+ import { basename, dirname, join } from 'node:path';
4
+ import { acquireLock, LockReleaseError, releaseLock, } from '../util/fileLock.js';
5
+ export class UnsafeAssetStorePathError extends Error {
6
+ role;
7
+ reason;
8
+ code = 'UNSAFE_ASSET_STORE_PATH';
9
+ constructor(role, reason) {
10
+ super(`unsafe asset store ${role}: ${reason}`);
11
+ this.role = role;
12
+ this.reason = reason;
13
+ this.name = 'UnsafeAssetStorePathError';
14
+ }
15
+ }
16
+ export class AssetStoreReadLimitError extends Error {
17
+ code = 'ASSET_STORE_READ_LIMIT';
18
+ constructor() {
19
+ super('asset store file exceeds the configured read limit');
20
+ this.name = 'AssetStoreReadLimitError';
21
+ }
22
+ }
23
+ function isErrno(error, code) {
24
+ return typeof error === 'object' && error !== null && error.code === code;
25
+ }
26
+ function noFollowFlag() {
27
+ return constants['O_NOFOLLOW'] ?? 0;
28
+ }
29
+ function statOrNull(path) {
30
+ try {
31
+ return lstatSync(path);
32
+ }
33
+ catch (error) {
34
+ if (isErrno(error, 'ENOENT'))
35
+ return null;
36
+ throw error;
37
+ }
38
+ }
39
+ function assertRegularStat(stat, role) {
40
+ if (stat.isSymbolicLink())
41
+ throw new UnsafeAssetStorePathError(role, 'symlink');
42
+ if (!stat.isFile())
43
+ throw new UnsafeAssetStorePathError(role, 'not_regular_file');
44
+ return stat;
45
+ }
46
+ export function ensureAssetStoreDirectory(path) {
47
+ let stat = statOrNull(path);
48
+ if (stat === null) {
49
+ mkdirSync(path, { recursive: true, mode: 0o700 });
50
+ stat = statOrNull(path);
51
+ }
52
+ assertAssetStoreDirectoryStat(stat);
53
+ }
54
+ function assertAssetStoreDirectoryStat(stat) {
55
+ if (stat === null)
56
+ throw new UnsafeAssetStorePathError('base_directory', 'not_directory');
57
+ if (stat.isSymbolicLink())
58
+ throw new UnsafeAssetStorePathError('base_directory', 'symlink');
59
+ if (!stat.isDirectory())
60
+ throw new UnsafeAssetStorePathError('base_directory', 'not_directory');
61
+ }
62
+ /** Validate an existing store root without recreating a deleted or unmounted path. */
63
+ export function assertAssetStoreDirectory(path) {
64
+ assertAssetStoreDirectoryStat(statOrNull(path));
65
+ }
66
+ export function isReliableAssetStoreLockRelease(result) {
67
+ return result.released
68
+ && (result.reason === 'released' || result.reason === 'released_with_cleanup_error');
69
+ }
70
+ /**
71
+ * Run one synchronous asset-store critical section without hiding its primary failure.
72
+ * A release failure is surfaced only after a successful operation; when both fail, the operation error wins.
73
+ */
74
+ export function withAssetStoreLock(lockPath, operation, deps = {}) {
75
+ assertOptionalRegularFile(lockPath, 'lock_file');
76
+ (deps.acquireLock ?? acquireLock)(lockPath);
77
+ let value;
78
+ let operationError;
79
+ let operationFailed = false;
80
+ try {
81
+ value = operation();
82
+ }
83
+ catch (error) {
84
+ operationFailed = true;
85
+ operationError = error;
86
+ }
87
+ let releaseError;
88
+ try {
89
+ const released = (deps.releaseLock ?? releaseLock)(lockPath);
90
+ if (!isReliableAssetStoreLockRelease(released))
91
+ releaseError = new LockReleaseError(released.reason);
92
+ }
93
+ catch (error) {
94
+ releaseError = error;
95
+ }
96
+ if (operationFailed)
97
+ throw operationError;
98
+ if (releaseError !== undefined)
99
+ throw releaseError;
100
+ return value;
101
+ }
102
+ export function assertOptionalRegularFile(path, role = 'asset_file') {
103
+ const stat = statOrNull(path);
104
+ return stat === null ? null : assertRegularStat(stat, role);
105
+ }
106
+ export function regularFileFingerprint(path) {
107
+ try {
108
+ const stat = lstatSync(path, { bigint: true });
109
+ if (stat.isSymbolicLink())
110
+ throw new UnsafeAssetStorePathError('asset_file', 'symlink');
111
+ if (!stat.isFile())
112
+ throw new UnsafeAssetStorePathError('asset_file', 'not_regular_file');
113
+ return `${stat.dev}:${stat.ino}:${stat.mode}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
114
+ }
115
+ catch (error) {
116
+ if (isErrno(error, 'ENOENT'))
117
+ return 'missing';
118
+ throw error;
119
+ }
120
+ }
121
+ function assertOpenedPathMatches(fd, path, role) {
122
+ const opened = fstatSync(fd, { bigint: true });
123
+ if (!opened.isFile())
124
+ throw new UnsafeAssetStorePathError(role, 'not_regular_file');
125
+ let current;
126
+ try {
127
+ current = lstatSync(path, { bigint: true });
128
+ }
129
+ catch (error) {
130
+ if (isErrno(error, 'ENOENT'))
131
+ throw new UnsafeAssetStorePathError(role, 'path_changed');
132
+ throw error;
133
+ }
134
+ if (current.isSymbolicLink())
135
+ throw new UnsafeAssetStorePathError(role, 'symlink');
136
+ if (!current.isFile())
137
+ throw new UnsafeAssetStorePathError(role, 'not_regular_file');
138
+ if (opened.dev !== current.dev || opened.ino !== current.ino) {
139
+ throw new UnsafeAssetStorePathError(role, 'path_changed');
140
+ }
141
+ }
142
+ function openNoFollow(path, flags, mode) {
143
+ return mode === undefined
144
+ ? openSync(path, flags | noFollowFlag())
145
+ : openSync(path, flags | noFollowFlag(), mode);
146
+ }
147
+ export function readRegularBuffer(path, maxBytes = Number.MAX_SAFE_INTEGER) {
148
+ if (assertOptionalRegularFile(path) === null)
149
+ return null;
150
+ const fd = openNoFollow(path, constants.O_RDONLY);
151
+ try {
152
+ assertOpenedPathMatches(fd, path, 'asset_file');
153
+ if (fstatSync(fd).size > maxBytes)
154
+ throw new AssetStoreReadLimitError();
155
+ const value = readFileSync(fd);
156
+ if (value.byteLength > maxBytes)
157
+ throw new AssetStoreReadLimitError();
158
+ return value;
159
+ }
160
+ finally {
161
+ closeSync(fd);
162
+ }
163
+ }
164
+ export function readUtf8Regular(path) {
165
+ return readRegularBuffer(path)?.toString('utf8') ?? null;
166
+ }
167
+ function writeAll(fd, value) {
168
+ const bytes = typeof value === 'string' ? Buffer.from(value, 'utf8') : value;
169
+ let offset = 0;
170
+ while (offset < bytes.length) {
171
+ const written = writeSync(fd, bytes, offset, bytes.length - offset);
172
+ if (written <= 0)
173
+ throw new Error('asset store write made no progress');
174
+ offset += written;
175
+ }
176
+ }
177
+ export function createBufferDurableExclusive(path, value, opts = {}) {
178
+ const parent = dirname(path);
179
+ assertAssetStoreDirectory(parent);
180
+ const fd = openNoFollow(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o600);
181
+ let operationError;
182
+ let openedIdentity;
183
+ try {
184
+ assertOpenedPathMatches(fd, path, 'asset_file');
185
+ const opened = fstatSync(fd, { bigint: true });
186
+ openedIdentity = { dev: opened.dev, ino: opened.ino };
187
+ writeAll(fd, value);
188
+ (opts.syncFile ?? fsyncSync)(fd);
189
+ }
190
+ catch (error) {
191
+ operationError = error;
192
+ }
193
+ finally {
194
+ try {
195
+ closeSync(fd);
196
+ }
197
+ catch (error) {
198
+ if (operationError === undefined)
199
+ operationError = error;
200
+ }
201
+ }
202
+ if (operationError !== undefined) {
203
+ try {
204
+ const current = lstatSync(path, { bigint: true });
205
+ if (openedIdentity
206
+ && !current.isSymbolicLink()
207
+ && current.isFile()
208
+ && current.dev === openedIdentity.dev
209
+ && current.ino === openedIdentity.ino) {
210
+ unlinkSync(path);
211
+ }
212
+ }
213
+ catch { /* preserve the primary write/fsync failure */ }
214
+ throw operationError;
215
+ }
216
+ (opts.syncDirectory ?? fsyncDirectoryBestEffort)(parent);
217
+ }
218
+ export function fsyncDirectoryBestEffort(path) {
219
+ let fd;
220
+ try {
221
+ fd = openSync(path, constants.O_RDONLY);
222
+ fsyncSync(fd);
223
+ }
224
+ catch {
225
+ // Windows and some filesystems do not support syncing directory handles.
226
+ }
227
+ finally {
228
+ if (fd !== undefined) {
229
+ try {
230
+ closeSync(fd);
231
+ }
232
+ catch { /* best-effort directory sync must stay best-effort */ }
233
+ }
234
+ }
235
+ }
236
+ export function appendUtf8Durable(path, value, opts = {}) {
237
+ const parent = dirname(path);
238
+ assertAssetStoreDirectory(parent);
239
+ const existed = assertOptionalRegularFile(path) !== null;
240
+ const fd = openNoFollow(path, constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT, 0o600);
241
+ try {
242
+ assertOpenedPathMatches(fd, path, 'asset_file');
243
+ writeAll(fd, value);
244
+ (opts.syncFile ?? fsyncSync)(fd);
245
+ }
246
+ finally {
247
+ closeSync(fd);
248
+ }
249
+ if (!existed)
250
+ (opts.syncDirectory ?? fsyncDirectoryBestEffort)(parent);
251
+ }
252
+ function removeTemp(path) {
253
+ try {
254
+ unlinkSync(path);
255
+ }
256
+ catch (error) {
257
+ if (!isErrno(error, 'ENOENT'))
258
+ throw error;
259
+ }
260
+ }
261
+ export function replaceUtf8Durable(path, value, opts = {}) {
262
+ const parent = dirname(path);
263
+ assertAssetStoreDirectory(parent);
264
+ assertOptionalRegularFile(path);
265
+ const tempPath = join(parent, `.${basename(path)}.compact.${process.pid}.${randomUUID()}.tmp`);
266
+ opts.onTempPath?.(tempPath);
267
+ let renamed = false;
268
+ try {
269
+ const fd = openNoFollow(tempPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o600);
270
+ try {
271
+ assertOpenedPathMatches(fd, tempPath, 'temp_file');
272
+ writeAll(fd, value);
273
+ (opts.syncFile ?? fsyncSync)(fd);
274
+ }
275
+ finally {
276
+ closeSync(fd);
277
+ }
278
+ assertOptionalRegularFile(path);
279
+ renameSync(tempPath, path);
280
+ renamed = true;
281
+ (opts.syncDirectory ?? fsyncDirectoryBestEffort)(parent);
282
+ }
283
+ finally {
284
+ if (!renamed)
285
+ removeTemp(tempPath);
286
+ }
287
+ }
288
+ /** Remove one exact UTF-8 suffix and fsync the new file length; false means the suffix was not current. */
289
+ export function truncateUtf8SuffixDurable(path, suffix, opts = {}) {
290
+ if (assertOptionalRegularFile(path) === null)
291
+ return false;
292
+ const expected = Buffer.from(suffix, 'utf8');
293
+ if (expected.length === 0)
294
+ return false;
295
+ const fd = openNoFollow(path, constants.O_RDWR);
296
+ try {
297
+ assertOpenedPathMatches(fd, path, 'asset_file');
298
+ const stat = fstatSync(fd);
299
+ if (stat.size < expected.length)
300
+ return false;
301
+ const actual = Buffer.alloc(expected.length);
302
+ let offset = 0;
303
+ while (offset < actual.length) {
304
+ const read = readSync(fd, actual, offset, actual.length - offset, stat.size - actual.length + offset);
305
+ if (read === 0)
306
+ return false;
307
+ offset += read;
308
+ }
309
+ if (!actual.equals(expected))
310
+ return false;
311
+ ftruncateSync(fd, stat.size - expected.length);
312
+ (opts.syncFile ?? fsyncSync)(fd);
313
+ return true;
314
+ }
315
+ finally {
316
+ closeSync(fd);
317
+ }
318
+ }
@@ -0,0 +1,32 @@
1
+ import type { AssetKind } from './provider.js';
2
+ export type AssetSyncSource = 'hub';
3
+ export type AssetSyncScope = 'purchased' | 'published';
4
+ export interface AssetSyncRecord {
5
+ assetId: string;
6
+ type: Extract<AssetKind, 'Gene' | 'Capsule'>;
7
+ source: AssetSyncSource;
8
+ scope: AssetSyncScope;
9
+ syncedAt: string;
10
+ remoteAssetId: string;
11
+ logicalId?: string;
12
+ status?: string;
13
+ forced?: true;
14
+ collisionWithAssetId?: string;
15
+ }
16
+ export declare class AssetSyncLedger {
17
+ private readonly now;
18
+ private readonly path;
19
+ private readonly lockPath;
20
+ private readonly index;
21
+ private fileState;
22
+ private loaded;
23
+ constructor(baseDir: string, now?: () => number);
24
+ append(rec: Omit<AssetSyncRecord, 'syncedAt'> & {
25
+ syncedAt?: string;
26
+ }): AssetSyncRecord;
27
+ get(assetId: string): AssetSyncRecord | null;
28
+ list(): AssetSyncRecord[];
29
+ private rebuildIndex;
30
+ private refreshUnderLock;
31
+ private withFreshRead;
32
+ }
@@ -0,0 +1,66 @@
1
+ import { dirname, join } from 'node:path';
2
+ import { appendUtf8Durable, assertAssetStoreDirectory, ensureAssetStoreDirectory, readUtf8Regular, regularFileFingerprint, withAssetStoreLock, } from './assetStoreStorage.js';
3
+ import { parseAssetSyncRecord, parseSidecarJsonl } from './assetSidecarRecords.js';
4
+ export class AssetSyncLedger {
5
+ now;
6
+ path;
7
+ lockPath;
8
+ index = new Map();
9
+ fileState = null;
10
+ loaded = false;
11
+ constructor(baseDir, now = Date.now) {
12
+ this.now = now;
13
+ ensureAssetStoreDirectory(baseDir);
14
+ this.path = join(baseDir, 'asset-sync.jsonl');
15
+ this.lockPath = join(baseDir, '.assetstore.lock');
16
+ }
17
+ append(rec) {
18
+ assertAssetStoreDirectory(dirname(this.path));
19
+ return withAssetStoreLock(this.lockPath, () => {
20
+ this.refreshUnderLock();
21
+ const full = immutableRecord({
22
+ ...rec,
23
+ syncedAt: rec.syncedAt ?? new Date(this.now()).toISOString(),
24
+ });
25
+ appendUtf8Durable(this.path, `${JSON.stringify(full)}\n`);
26
+ this.index.set(full.assetId, full);
27
+ this.fileState = regularFileFingerprint(this.path);
28
+ return full;
29
+ });
30
+ }
31
+ get(assetId) {
32
+ return this.withFreshRead((index) => index.get(assetId) ?? null);
33
+ }
34
+ list() {
35
+ return this.withFreshRead((index) => [...index.values()]);
36
+ }
37
+ rebuildIndex(state) {
38
+ const next = new Map();
39
+ const raw = state === 'missing' ? null : readUtf8Regular(this.path);
40
+ if (raw !== null) {
41
+ const parsed = parseSidecarJsonl(raw, parseAssetSyncRecord);
42
+ for (const record of parsed.records)
43
+ next.set(record.assetId, immutableRecord(record));
44
+ }
45
+ this.index.clear();
46
+ for (const [assetId, record] of next)
47
+ this.index.set(assetId, record);
48
+ this.fileState = state;
49
+ this.loaded = true;
50
+ }
51
+ refreshUnderLock() {
52
+ const state = regularFileFingerprint(this.path);
53
+ if (!this.loaded || state !== this.fileState)
54
+ this.rebuildIndex(state);
55
+ }
56
+ withFreshRead(read) {
57
+ assertAssetStoreDirectory(dirname(this.path));
58
+ return withAssetStoreLock(this.lockPath, () => {
59
+ this.refreshUnderLock();
60
+ return read(this.index);
61
+ });
62
+ }
63
+ }
64
+ function immutableRecord(record) {
65
+ return Object.freeze({ ...record });
66
+ }
@@ -1,8 +1,12 @@
1
1
  export * from './provider.js';
2
2
  export * from './localJsonl.js';
3
+ export * from './assetStoreHealth.js';
4
+ export * from './assetSidecarRecovery.js';
3
5
  export * from './remoteStub.js';
4
6
  export * from './learningHistory.js';
5
7
  export * from './provenance.js';
8
+ export * from './assetSyncLedger.js';
6
9
  export * from './reviewLedger.js';
7
10
  export * from './reviewFilter.js';
8
- export * from './pendingSignals.js';
11
+ export * from './pendingSignals.js';
12
+ export * from './seedGenes.js';
@@ -1,8 +1,12 @@
1
1
  export * from './provider.js';
2
2
  export * from './localJsonl.js';
3
+ export * from './assetStoreHealth.js';
4
+ export * from './assetSidecarRecovery.js';
3
5
  export * from './remoteStub.js';
4
6
  export * from './learningHistory.js';
5
7
  export * from './provenance.js';
8
+ export * from './assetSyncLedger.js';
6
9
  export * from './reviewLedger.js';
7
10
  export * from './reviewFilter.js';
8
- export * from './pendingSignals.js';
11
+ export * from './pendingSignals.js';
12
+ export * from './seedGenes.js';
@@ -2,15 +2,22 @@ import { type AssetKind, type AssetRecord, type AssetStoreProvider, type PutResu
2
2
  /**
3
3
  * 本地 jsonl 资产库(M3-2, 移植 v1 src/gep/assetStore.js 单写锁).
4
4
  * 每 kind 一文件(genes/capsules/events.jsonl); append-only; O_EXCL 文件锁防并发写撕裂;
5
- * 内存索引(asset_id→record)供 get/search; 同 asset_id 去重(内容寻址天然幂等).
5
+ * 内存索引(asset_id→record)供 get/search; 文件指纹变化时在共享锁内重建索引,保证多个
6
+ * CLI/daemon 进程之间可见; 写入也在锁内刷新后再按 asset_id 去重(内容寻址天然幂等).
6
7
  */
7
8
  export declare class LocalJsonlProvider implements AssetStoreProvider {
8
9
  readonly baseDir: string;
9
10
  private readonly index;
10
11
  private readonly lockPath;
12
+ private fileState;
11
13
  private loaded;
12
14
  constructor(baseDir: string);
13
- private ensureLoaded;
15
+ private captureFileState;
16
+ private stateChanged;
17
+ private rebuildIndex;
18
+ private refreshUnderLock;
19
+ private ensureFresh;
20
+ private updateFileStateAfterWrite;
14
21
  put(asset: AssetRecord): Promise<PutResult>;
15
22
  /**
16
23
  * 迁移专用(M8-2): 以**冻结 asset_id** 原样写入, 不经 normalizeForPut 重算/校验.
@@ -18,6 +25,7 @@ export declare class LocalJsonlProvider implements AssetStoreProvider {
18
25
  */
19
26
  putFrozen(record: AssetRecord): Promise<PutResult>;
20
27
  get(assetId: string): Promise<AssetRecord | null>;
28
+ findByLogicalId(id: string, limit?: number): Promise<AssetRecord[]>;
21
29
  list(kind?: AssetKind, limit?: number): Promise<AssetRecord[]>;
22
30
  search(q: SearchQuery): Promise<AssetRecord[]>;
23
31
  /**