@evomap/evolver-core 2.0.0-beta.2 → 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 (58) hide show
  1. package/dist/algo/candidateAssembly.js +9 -6
  2. package/dist/algo/cycleEngine.d.ts +11 -0
  3. package/dist/algo/cycleEngine.js +12 -6
  4. package/dist/algo/cycleFailureClassifier.d.ts +1 -1
  5. package/dist/algo/geneSelection.d.ts +12 -1
  6. package/dist/algo/geneSelection.js +24 -8
  7. package/dist/algo/index.d.ts +1 -0
  8. package/dist/algo/index.js +1 -0
  9. package/dist/algo/memoryGraph.d.ts +62 -0
  10. package/dist/algo/memoryGraph.js +86 -0
  11. package/dist/algo/orchestrator.d.ts +3 -0
  12. package/dist/algo/orchestrator.js +14 -2
  13. package/dist/assetstore/assetSidecarRecords.d.ts +23 -0
  14. package/dist/assetstore/assetSidecarRecords.js +142 -0
  15. package/dist/assetstore/assetSidecarRecovery.d.ts +48 -0
  16. package/dist/assetstore/assetSidecarRecovery.js +288 -0
  17. package/dist/assetstore/assetStoreHealth.d.ts +75 -0
  18. package/dist/assetstore/assetStoreHealth.js +277 -0
  19. package/dist/assetstore/assetStoreLayout.d.ts +2 -0
  20. package/dist/assetstore/assetStoreLayout.js +6 -0
  21. package/dist/assetstore/assetStoreStorage.d.ts +42 -0
  22. package/dist/assetstore/assetStoreStorage.js +318 -0
  23. package/dist/assetstore/assetSyncLedger.d.ts +5 -1
  24. package/dist/assetstore/assetSyncLedger.js +44 -64
  25. package/dist/assetstore/index.d.ts +2 -0
  26. package/dist/assetstore/index.js +2 -0
  27. package/dist/assetstore/localJsonl.d.ts +1 -0
  28. package/dist/assetstore/localJsonl.js +36 -32
  29. package/dist/assetstore/provenance.d.ts +13 -0
  30. package/dist/assetstore/provenance.js +60 -84
  31. package/dist/assetstore/provider.d.ts +2 -0
  32. package/dist/assetstore/reviewFilter.js +3 -1
  33. package/dist/assetstore/reviewLedger.d.ts +8 -2
  34. package/dist/assetstore/reviewLedger.js +71 -45
  35. package/dist/benchmark/index.d.ts +2 -1
  36. package/dist/benchmark/index.js +2 -1
  37. package/dist/benchmark/triggerShift.d.ts +62 -0
  38. package/dist/benchmark/triggerShift.js +106 -0
  39. package/dist/events/ingest.d.ts +1 -1
  40. package/dist/events/ingest.js +2 -0
  41. package/dist/events/paths.d.ts +1 -1
  42. package/dist/events/paths.js +2 -2
  43. package/dist/exec/autoExec.d.ts +6 -1
  44. package/dist/exec/autoExec.js +31 -0
  45. package/dist/exec/autonomousCycle.d.ts +2 -0
  46. package/dist/exec/autonomousCycle.js +5 -0
  47. package/dist/exec/claudeBridge.d.ts +7 -2
  48. package/dist/exec/claudeBridge.js +92 -14
  49. package/dist/exec/prompt.js +9 -0
  50. package/dist/exec/runnerRegistry.d.ts +56 -12
  51. package/dist/exec/runnerRegistry.js +272 -22
  52. package/dist/hub/bindings.js +12 -2
  53. package/dist/ops/savingsCore.js +1 -2
  54. package/dist/ops/selfUpdate.d.ts +10 -1
  55. package/dist/ops/selfUpdate.js +64 -15
  56. package/dist/util/fileLock.d.ts +19 -2
  57. package/dist/util/fileLock.js +166 -31
  58. package/package.json +5 -1
@@ -0,0 +1,142 @@
1
+ export class CorruptAssetSidecarError extends Error {
2
+ sidecar;
3
+ reason;
4
+ code = 'CORRUPT_ASSET_SIDECAR';
5
+ constructor(sidecar, reason) {
6
+ super(`corrupt asset sidecar: ${sidecar}/${reason}`);
7
+ this.sidecar = sidecar;
8
+ this.reason = reason;
9
+ this.name = 'CorruptAssetSidecarError';
10
+ }
11
+ }
12
+ export function parseSidecarJsonl(raw, parseRecord) {
13
+ const records = [];
14
+ let rows = 0;
15
+ let corruptRows = 0;
16
+ for (const line of raw.split('\n')) {
17
+ if (!line.trim())
18
+ continue;
19
+ rows += 1;
20
+ try {
21
+ const record = parseRecord(JSON.parse(line));
22
+ if (record)
23
+ records.push(record);
24
+ else
25
+ corruptRows += 1;
26
+ }
27
+ catch {
28
+ corruptRows += 1;
29
+ }
30
+ }
31
+ return {
32
+ records,
33
+ rows,
34
+ validRows: records.length,
35
+ corruptRows,
36
+ unterminated: raw.length > 0 && !raw.endsWith('\n'),
37
+ };
38
+ }
39
+ export function assertTrustSidecarHealthy(sidecar, parsed) {
40
+ if (parsed.unterminated)
41
+ throw new CorruptAssetSidecarError(sidecar, 'unterminated');
42
+ if (parsed.corruptRows > 0)
43
+ throw new CorruptAssetSidecarError(sidecar, 'invalid_row');
44
+ }
45
+ export function parseProvenanceRecord(value) {
46
+ const record = objectRecord(value);
47
+ if (!record)
48
+ return null;
49
+ const assetId = stringField(record, 'assetId');
50
+ const source = stringField(record, 'source');
51
+ const at = stringField(record, 'at');
52
+ if (!assetId
53
+ || (source !== 'local' && source !== 'migrated' && source !== 'hub')
54
+ || typeof record['trusted'] !== 'boolean'
55
+ || !at
56
+ || Number.isNaN(Date.parse(at))) {
57
+ return null;
58
+ }
59
+ const decision = record['decision'] === 'promoted' || record['decision'] === 'revoked'
60
+ ? record['decision']
61
+ : undefined;
62
+ const decidedBy = stringField(record, 'decidedBy');
63
+ const promotedBy = stringField(record, 'promotedBy');
64
+ const reason = stringField(record, 'reason');
65
+ return {
66
+ assetId,
67
+ source,
68
+ trusted: record['trusted'],
69
+ at,
70
+ ...(decision ? { decision } : {}),
71
+ ...(decidedBy ? { decidedBy } : {}),
72
+ ...(promotedBy ? { promotedBy } : {}),
73
+ ...(reason ? { reason } : {}),
74
+ };
75
+ }
76
+ export function parseReviewRecord(value) {
77
+ const record = objectRecord(value);
78
+ if (!record)
79
+ return null;
80
+ const assetId = stringField(record, 'assetId');
81
+ const state = stringField(record, 'state');
82
+ const at = stringField(record, 'at');
83
+ if (!assetId
84
+ || (state !== 'quarantined' && state !== 'approved' && state !== 'rejected')
85
+ || !at
86
+ || Number.isNaN(Date.parse(at))) {
87
+ return null;
88
+ }
89
+ const by = stringField(record, 'by');
90
+ const reason = stringField(record, 'reason');
91
+ return {
92
+ assetId,
93
+ state,
94
+ at,
95
+ ...(by ? { by } : {}),
96
+ ...(reason ? { reason } : {}),
97
+ };
98
+ }
99
+ export function parseAssetSyncRecord(value) {
100
+ const record = objectRecord(value);
101
+ if (!record)
102
+ return null;
103
+ const assetId = stringField(record, 'assetId');
104
+ const type = stringField(record, 'type');
105
+ const source = stringField(record, 'source');
106
+ const scope = stringField(record, 'scope');
107
+ const syncedAt = stringField(record, 'syncedAt');
108
+ const remoteAssetId = stringField(record, 'remoteAssetId');
109
+ if (!assetId
110
+ || (type !== 'Gene' && type !== 'Capsule')
111
+ || source !== 'hub'
112
+ || (scope !== 'purchased' && scope !== 'published')
113
+ || !syncedAt
114
+ || !remoteAssetId) {
115
+ return null;
116
+ }
117
+ const logicalId = stringField(record, 'logicalId');
118
+ const status = stringField(record, 'status');
119
+ const forced = record['forced'] === true;
120
+ const collisionWithAssetId = stringField(record, 'collisionWithAssetId');
121
+ return {
122
+ assetId,
123
+ type,
124
+ source,
125
+ scope,
126
+ syncedAt,
127
+ remoteAssetId,
128
+ ...(logicalId ? { logicalId } : {}),
129
+ ...(status ? { status } : {}),
130
+ ...(forced ? { forced: true } : {}),
131
+ ...(collisionWithAssetId ? { collisionWithAssetId } : {}),
132
+ };
133
+ }
134
+ function objectRecord(value) {
135
+ return value && typeof value === 'object' && !Array.isArray(value)
136
+ ? value
137
+ : null;
138
+ }
139
+ function stringField(value, key) {
140
+ const raw = value[key];
141
+ return typeof raw === 'string' && raw.trim() ? raw.trim() : undefined;
142
+ }
@@ -0,0 +1,48 @@
1
+ import { type ReleaseLockReason } from '../util/fileLock.js';
2
+ import { type AssetStoreLockDeps } from './assetStoreStorage.js';
3
+ import { type AssetSidecarKind } from './assetSidecarRecords.js';
4
+ export type { AssetSidecarKind } from './assetSidecarRecords.js';
5
+ export type AssetSidecarRecoveryFailureReason = 'sidecar_missing' | 'sidecar_not_degraded' | 'replacement_missing' | 'replacement_too_large' | 'replacement_invalid_utf8' | 'replacement_invalid' | 'replacement_unterminated' | 'valid_history_missing' | 'corrective_record_required' | 'unsafe_corrective_record' | 'acknowledgement_required' | 'sidecar_too_large' | 'sidecar_changed' | 'replacement_changed' | 'backup_conflict';
6
+ export declare class AssetSidecarRecoveryError extends Error {
7
+ readonly reason: AssetSidecarRecoveryFailureReason;
8
+ readonly code = "ASSET_SIDECAR_RECOVERY_FAILED";
9
+ constructor(reason: AssetSidecarRecoveryFailureReason);
10
+ }
11
+ export interface AssetSidecarRecoveryFileReport {
12
+ rows: number;
13
+ validRows: number;
14
+ corruptRows: number;
15
+ unterminated: boolean;
16
+ digest: string;
17
+ }
18
+ export interface AssetSidecarReplacementReport extends AssetSidecarRecoveryFileReport {
19
+ preservedValidRows: number;
20
+ addedRecords: number;
21
+ correctiveRecords: number;
22
+ }
23
+ export interface AssetSidecarRecoveryReport {
24
+ mode: 'preview' | 'write';
25
+ sidecar: AssetSidecarKind;
26
+ changed: boolean;
27
+ wouldWrite: boolean;
28
+ acknowledgementRequired: boolean;
29
+ current: AssetSidecarRecoveryFileReport;
30
+ replacement: AssetSidecarReplacementReport;
31
+ backupId?: string;
32
+ lockReleaseWarning?: ReleaseLockReason;
33
+ }
34
+ export interface RecoverAssetSidecarOptions {
35
+ baseDir: string;
36
+ sidecar: AssetSidecarKind;
37
+ replacementPath: string;
38
+ write?: boolean;
39
+ acknowledgeCorruptHistory?: boolean;
40
+ maxFileBytes?: number;
41
+ deps?: {
42
+ beforeBackup?: () => void;
43
+ beforeReplace?: () => void;
44
+ lock?: AssetStoreLockDeps;
45
+ };
46
+ }
47
+ export declare const DEFAULT_SIDECAR_RECOVERY_MAX_FILE_BYTES: number;
48
+ export declare function recoverAssetSidecar(opts: RecoverAssetSidecarOptions): AssetSidecarRecoveryReport;
@@ -0,0 +1,288 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, mkdirSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { TextDecoder } from 'node:util';
5
+ import { canonicalize } from '../wire/index.js';
6
+ import { LockReleaseError } from '../util/fileLock.js';
7
+ import { AssetStoreReadLimitError, assertAssetStoreDirectory, createBufferDurableExclusive, fsyncDirectoryBestEffort, readRegularBuffer, regularFileFingerprint, replaceUtf8Durable, withAssetStoreLock, } from './assetStoreStorage.js';
8
+ import { parseAssetSyncRecord, parseProvenanceRecord, parseReviewRecord, } from './assetSidecarRecords.js';
9
+ export class AssetSidecarRecoveryError extends Error {
10
+ reason;
11
+ code = 'ASSET_SIDECAR_RECOVERY_FAILED';
12
+ constructor(reason) {
13
+ super(`asset sidecar recovery failed: ${reason}`);
14
+ this.reason = reason;
15
+ this.name = 'AssetSidecarRecoveryError';
16
+ }
17
+ }
18
+ export const DEFAULT_SIDECAR_RECOVERY_MAX_FILE_BYTES = 64 * 1024 * 1024;
19
+ function recoveryFileLimit(value) {
20
+ if (value === undefined || !Number.isFinite(value))
21
+ return DEFAULT_SIDECAR_RECOVERY_MAX_FILE_BYTES;
22
+ return Math.min(DEFAULT_SIDECAR_RECOVERY_MAX_FILE_BYTES, Math.max(1, Math.floor(value)));
23
+ }
24
+ const SIDECAR_FILES = {
25
+ provenance: 'provenance.jsonl',
26
+ review: 'review.jsonl',
27
+ 'asset-sync': 'asset-sync.jsonl',
28
+ };
29
+ const RECORD_PARSERS = {
30
+ provenance: parseProvenanceRecord,
31
+ review: parseReviewRecord,
32
+ 'asset-sync': parseAssetSyncRecord,
33
+ };
34
+ function isErrno(error, code) {
35
+ return typeof error === 'object' && error !== null && error.code === code;
36
+ }
37
+ function digest(bytes) {
38
+ return createHash('sha256').update(bytes).digest('hex');
39
+ }
40
+ function readBounded(path, limit, role) {
41
+ try {
42
+ const before = regularFileFingerprint(path);
43
+ if (before === 'missing') {
44
+ throw new AssetSidecarRecoveryError(role === 'sidecar' ? 'sidecar_missing' : 'replacement_missing');
45
+ }
46
+ const value = readRegularBuffer(path, limit);
47
+ if (value === null) {
48
+ throw new AssetSidecarRecoveryError(role === 'sidecar' ? 'sidecar_missing' : 'replacement_missing');
49
+ }
50
+ const after = regularFileFingerprint(path);
51
+ if (before !== after) {
52
+ throw new AssetSidecarRecoveryError(role === 'sidecar' ? 'sidecar_changed' : 'replacement_changed');
53
+ }
54
+ return { bytes: value, fingerprint: after };
55
+ }
56
+ catch (error) {
57
+ if (error instanceof AssetStoreReadLimitError) {
58
+ throw new AssetSidecarRecoveryError(role === 'sidecar' ? 'sidecar_too_large' : 'replacement_too_large');
59
+ }
60
+ throw error;
61
+ }
62
+ }
63
+ function parseCurrent(bytes, sidecar) {
64
+ const hex = digest(bytes);
65
+ try {
66
+ const text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
67
+ return { bytes, text, parsed: parseRecoveryJsonl(text, RECORD_PARSERS[sidecar]), digest: `sha256:${hex}` };
68
+ }
69
+ catch {
70
+ return {
71
+ bytes,
72
+ text: null,
73
+ parsed: {
74
+ records: [],
75
+ rows: bytes.length > 0 ? 1 : 0,
76
+ validRows: 0,
77
+ corruptRows: bytes.length > 0 ? 1 : 0,
78
+ unterminated: bytes.length > 0 && bytes[bytes.length - 1] !== 0x0a,
79
+ },
80
+ digest: `sha256:${hex}`,
81
+ };
82
+ }
83
+ }
84
+ function parseReplacement(bytes, sidecar) {
85
+ let text;
86
+ try {
87
+ text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
88
+ }
89
+ catch {
90
+ throw new AssetSidecarRecoveryError('replacement_invalid_utf8');
91
+ }
92
+ const parsed = parseRecoveryJsonl(text, RECORD_PARSERS[sidecar]);
93
+ if (parsed.corruptRows > 0)
94
+ throw new AssetSidecarRecoveryError('replacement_invalid');
95
+ if (parsed.unterminated)
96
+ throw new AssetSidecarRecoveryError('replacement_unterminated');
97
+ return { bytes, text, parsed, digest: `sha256:${digest(bytes)}` };
98
+ }
99
+ function parseRecoveryJsonl(raw, parseRecord) {
100
+ const records = [];
101
+ let rows = 0;
102
+ let corruptRows = 0;
103
+ for (const line of raw.split('\n')) {
104
+ if (!line.trim())
105
+ continue;
106
+ rows += 1;
107
+ try {
108
+ const original = JSON.parse(line);
109
+ const normalized = parseRecord(original);
110
+ if (normalized)
111
+ records.push({ original, normalized });
112
+ else
113
+ corruptRows += 1;
114
+ }
115
+ catch {
116
+ corruptRows += 1;
117
+ }
118
+ }
119
+ return {
120
+ records,
121
+ rows,
122
+ validRows: records.length,
123
+ corruptRows,
124
+ unterminated: raw.length > 0 && !raw.endsWith('\n'),
125
+ };
126
+ }
127
+ function canonicalRecords(records) {
128
+ return records.map((record) => canonicalize(record.original));
129
+ }
130
+ function compareHistory(currentRecords, replacementRecords) {
131
+ const current = canonicalRecords(currentRecords);
132
+ const replacement = canonicalRecords(replacementRecords);
133
+ if (current.some((record, index) => replacement[index] !== record)) {
134
+ throw new AssetSidecarRecoveryError('valid_history_missing');
135
+ }
136
+ const extra = replacement.slice(current.length);
137
+ const existing = new Set(current);
138
+ return {
139
+ preservedValidRows: current.length,
140
+ addedRecords: extra.length,
141
+ correctiveRecords: extra.filter((record) => !existing.has(record)).length,
142
+ added: replacementRecords.slice(current.length),
143
+ };
144
+ }
145
+ function isSafeCorrectiveRecord(sidecar, value) {
146
+ if (sidecar === 'asset-sync')
147
+ return true;
148
+ if (!value.normalized || typeof value.normalized !== 'object' || Array.isArray(value.normalized))
149
+ return false;
150
+ const record = value.normalized;
151
+ return sidecar === 'provenance'
152
+ ? record['trusted'] === false
153
+ : record['state'] === 'quarantined' || record['state'] === 'rejected';
154
+ }
155
+ function fileReport(file) {
156
+ return {
157
+ rows: file.parsed.rows,
158
+ validRows: file.parsed.validRows,
159
+ corruptRows: file.parsed.corruptRows,
160
+ unterminated: file.parsed.unterminated,
161
+ digest: file.digest,
162
+ };
163
+ }
164
+ function ensureExactBackup(baseDir, sidecar, current) {
165
+ const backupDir = join(baseDir, 'sidecar-backups');
166
+ assertAssetStoreDirectory(baseDir);
167
+ let backupDirCreated = false;
168
+ if (!existsSync(backupDir)) {
169
+ try {
170
+ mkdirSync(backupDir, { recursive: false, mode: 0o700 });
171
+ backupDirCreated = true;
172
+ }
173
+ catch (error) {
174
+ if (!isErrno(error, 'EEXIST'))
175
+ throw error;
176
+ }
177
+ }
178
+ assertAssetStoreDirectory(backupDir);
179
+ if (backupDirCreated)
180
+ fsyncDirectoryBestEffort(baseDir);
181
+ const hex = current.digest.slice('sha256:'.length);
182
+ const backupId = `${sidecar}-${hex}.jsonl`;
183
+ const backupPath = join(backupDir, backupId);
184
+ try {
185
+ createBufferDurableExclusive(backupPath, current.bytes);
186
+ }
187
+ catch (error) {
188
+ if (!isErrno(error, 'EEXIST'))
189
+ throw error;
190
+ let existing;
191
+ try {
192
+ existing = readRegularBuffer(backupPath, current.bytes.length);
193
+ }
194
+ catch (readError) {
195
+ if (readError instanceof AssetStoreReadLimitError) {
196
+ throw new AssetSidecarRecoveryError('backup_conflict');
197
+ }
198
+ throw readError;
199
+ }
200
+ if (!existing?.equals(current.bytes))
201
+ throw new AssetSidecarRecoveryError('backup_conflict');
202
+ }
203
+ return backupId;
204
+ }
205
+ export function recoverAssetSidecar(opts) {
206
+ assertAssetStoreDirectory(opts.baseDir);
207
+ const maxFileBytes = recoveryFileLimit(opts.maxFileBytes);
208
+ const targetPath = join(opts.baseDir, SIDECAR_FILES[opts.sidecar]);
209
+ const lockPath = join(opts.baseDir, '.assetstore.lock');
210
+ let committed;
211
+ try {
212
+ return withAssetStoreLock(lockPath, () => {
213
+ const currentRead = readBounded(targetPath, maxFileBytes, 'sidecar');
214
+ const replacementRead = readBounded(opts.replacementPath, maxFileBytes, 'replacement');
215
+ const current = parseCurrent(currentRead.bytes, opts.sidecar);
216
+ const replacement = parseReplacement(replacementRead.bytes, opts.sidecar);
217
+ const history = compareHistory(current.parsed.records, replacement.parsed.records);
218
+ const degraded = current.parsed.corruptRows > 0 || current.parsed.unterminated;
219
+ const sameBytes = current.bytes.equals(replacement.bytes);
220
+ if (!degraded && !sameBytes)
221
+ throw new AssetSidecarRecoveryError('sidecar_not_degraded');
222
+ if (current.parsed.corruptRows > 0
223
+ && opts.sidecar !== 'asset-sync'
224
+ && history.correctiveRecords === 0) {
225
+ throw new AssetSidecarRecoveryError('corrective_record_required');
226
+ }
227
+ if (history.added.some((record) => !isSafeCorrectiveRecord(opts.sidecar, record))) {
228
+ throw new AssetSidecarRecoveryError('unsafe_corrective_record');
229
+ }
230
+ const acknowledgementRequired = degraded && !sameBytes;
231
+ const mode = opts.write ? 'write' : 'preview';
232
+ const baseReport = {
233
+ mode,
234
+ sidecar: opts.sidecar,
235
+ current: fileReport(current),
236
+ replacement: {
237
+ ...fileReport(replacement),
238
+ preservedValidRows: history.preservedValidRows,
239
+ addedRecords: history.addedRecords,
240
+ correctiveRecords: history.correctiveRecords,
241
+ },
242
+ };
243
+ if (sameBytes) {
244
+ return {
245
+ ...baseReport,
246
+ changed: false,
247
+ wouldWrite: false,
248
+ acknowledgementRequired: false,
249
+ };
250
+ }
251
+ if (!opts.write) {
252
+ return {
253
+ ...baseReport,
254
+ changed: false,
255
+ wouldWrite: true,
256
+ acknowledgementRequired,
257
+ };
258
+ }
259
+ if (!opts.acknowledgeCorruptHistory) {
260
+ throw new AssetSidecarRecoveryError('acknowledgement_required');
261
+ }
262
+ opts.deps?.beforeBackup?.();
263
+ const backupId = ensureExactBackup(opts.baseDir, opts.sidecar, current);
264
+ opts.deps?.beforeReplace?.();
265
+ if (regularFileFingerprint(targetPath) !== currentRead.fingerprint) {
266
+ throw new AssetSidecarRecoveryError('sidecar_changed');
267
+ }
268
+ if (regularFileFingerprint(opts.replacementPath) !== replacementRead.fingerprint) {
269
+ throw new AssetSidecarRecoveryError('replacement_changed');
270
+ }
271
+ replaceUtf8Durable(targetPath, replacement.text);
272
+ committed = {
273
+ ...baseReport,
274
+ changed: true,
275
+ wouldWrite: false,
276
+ acknowledgementRequired: false,
277
+ backupId,
278
+ };
279
+ return committed;
280
+ }, opts.deps?.lock);
281
+ }
282
+ catch (error) {
283
+ if (committed && error instanceof LockReleaseError) {
284
+ return { ...committed, lockReleaseWarning: error.reason };
285
+ }
286
+ throw error;
287
+ }
288
+ }
@@ -0,0 +1,75 @@
1
+ import { acquireLock, releaseLock } from '../util/fileLock.js';
2
+ import { type UnsafeAssetStorePathReason } from './assetStoreStorage.js';
3
+ import type { AssetKind } from './provider.js';
4
+ import { type AssetSidecarKind } from './assetSidecarRecords.js';
5
+ export type AssetStoreHealthStatus = 'healthy' | 'degraded' | 'unsafe' | 'unavailable';
6
+ export type AssetStoreFileStatus = 'missing' | 'ok' | 'degraded' | 'unsafe' | 'unavailable';
7
+ export interface AssetStoreFileHealth {
8
+ kind: AssetKind;
9
+ file: string;
10
+ status: AssetStoreFileStatus;
11
+ bytes: number;
12
+ rows: number;
13
+ validRows: number;
14
+ uniqueAssets: number;
15
+ duplicateRows: number;
16
+ corruptRows: number;
17
+ hashMismatchRows: number;
18
+ schemaInvalidRows: number;
19
+ unterminated: boolean;
20
+ reason?: UnsafeAssetStorePathReason | 'base_directory' | 'lock_unavailable' | 'read_unavailable' | 'scan_limit_exceeded';
21
+ }
22
+ export interface AssetStoreHealthTotals {
23
+ files: number;
24
+ missingFiles: number;
25
+ unsafeFiles: number;
26
+ unavailableFiles: number;
27
+ bytes: number;
28
+ rows: number;
29
+ validRows: number;
30
+ uniqueAssets: number;
31
+ duplicateRows: number;
32
+ corruptRows: number;
33
+ hashMismatchRows: number;
34
+ schemaInvalidRows: number;
35
+ unterminatedFiles: number;
36
+ }
37
+ export interface AssetStoreSidecarHealth {
38
+ kind: AssetSidecarKind;
39
+ file: string;
40
+ status: AssetStoreFileStatus;
41
+ bytes: number;
42
+ rows: number;
43
+ validRows: number;
44
+ corruptRows: number;
45
+ unterminated: boolean;
46
+ reason?: AssetStoreFileHealth['reason'];
47
+ }
48
+ export interface AssetStoreSidecarHealthTotals {
49
+ files: number;
50
+ missingFiles: number;
51
+ unsafeFiles: number;
52
+ unavailableFiles: number;
53
+ bytes: number;
54
+ rows: number;
55
+ validRows: number;
56
+ corruptRows: number;
57
+ unterminatedFiles: number;
58
+ }
59
+ export interface AssetStoreHealthReport {
60
+ ok: boolean;
61
+ status: AssetStoreHealthStatus;
62
+ totals: AssetStoreHealthTotals;
63
+ files: AssetStoreFileHealth[];
64
+ sidecarTotals: AssetStoreSidecarHealthTotals;
65
+ sidecars: AssetStoreSidecarHealth[];
66
+ }
67
+ export interface InspectLocalAssetStoreOptions {
68
+ maxFileBytes?: number;
69
+ }
70
+ export interface InspectLocalAssetStoreDeps {
71
+ acquireLock?: typeof acquireLock;
72
+ releaseLock?: typeof releaseLock;
73
+ }
74
+ export declare const DEFAULT_ASSET_HEALTH_MAX_FILE_BYTES: number;
75
+ export declare function inspectLocalAssetStore(baseDir: string, opts?: InspectLocalAssetStoreOptions, deps?: InspectLocalAssetStoreDeps): AssetStoreHealthReport;