@evomap/evolver-core 2.0.0-beta.9 → 2.0.1

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 (201) hide show
  1. package/dist/algo/antiDistill.js +1 -167
  2. package/dist/algo/bans.js +1 -33
  3. package/dist/algo/candidateAssembly.d.ts +7 -2
  4. package/dist/algo/candidateAssembly.js +1 -205
  5. package/dist/algo/capabilityCandidates.js +1 -136
  6. package/dist/algo/confidence.js +1 -196
  7. package/dist/algo/conversationSniffer.js +1 -149
  8. package/dist/algo/cycleEngine.d.ts +23 -2
  9. package/dist/algo/cycleEngine.js +1 -425
  10. package/dist/algo/cycleFailureClassifier.js +1 -112
  11. package/dist/algo/epigenetics.js +1 -41
  12. package/dist/algo/evolutionEvent.js +1 -24
  13. package/dist/algo/exploration.d.ts +7 -0
  14. package/dist/algo/exploration.js +1 -55
  15. package/dist/algo/geneHealth.d.ts +2 -2
  16. package/dist/algo/geneHealth.js +1 -16
  17. package/dist/algo/geneIntake.js +1 -128
  18. package/dist/algo/genePromotion.js +1 -53
  19. package/dist/algo/geneSelection.d.ts +63 -5
  20. package/dist/algo/geneSelection.js +1 -246
  21. package/dist/algo/index.d.ts +2 -0
  22. package/dist/algo/index.js +1 -20
  23. package/dist/algo/memoryGraph.js +1 -86
  24. package/dist/algo/mutation.js +1 -22
  25. package/dist/algo/orchestrator.d.ts +8 -1
  26. package/dist/algo/orchestrator.js +1 -75
  27. package/dist/algo/publishEligibility.d.ts +34 -0
  28. package/dist/algo/publishEligibility.js +1 -0
  29. package/dist/algo/solidify.js +1 -63
  30. package/dist/algo/strategyPresets.js +1 -61
  31. package/dist/algo/ucb1.d.ts +53 -0
  32. package/dist/algo/ucb1.js +1 -0
  33. package/dist/assetstore/assetSidecarRecords.d.ts +7 -2
  34. package/dist/assetstore/assetSidecarRecords.js +238 -0
  35. package/dist/assetstore/assetSidecarRecovery.js +2 -2
  36. package/dist/assetstore/assetStoreHealth.d.ts +6 -0
  37. package/dist/assetstore/assetStoreHealth.js +56 -14
  38. package/dist/assetstore/assetStoreStorage.d.ts +1 -1
  39. package/dist/assetstore/assetStoreStorage.js +27 -9
  40. package/dist/assetstore/assetSyncLedger.d.ts +86 -0
  41. package/dist/assetstore/assetSyncLedger.js +683 -4
  42. package/dist/assetstore/index.d.ts +1 -0
  43. package/dist/assetstore/index.js +1 -0
  44. package/dist/assetstore/localAssetStoreSnapshot.d.ts +51 -0
  45. package/dist/assetstore/localAssetStoreSnapshot.js +329 -0
  46. package/dist/assetstore/localJsonl.d.ts +4 -2
  47. package/dist/assetstore/localJsonl.js +89 -9
  48. package/dist/assetstore/provenance.d.ts +80 -4
  49. package/dist/assetstore/provenance.js +313 -5
  50. package/dist/assetstore/provider.d.ts +58 -1
  51. package/dist/assetstore/provider.js +97 -6
  52. package/dist/benchmark/antiGeneBenchmark.d.ts +2 -0
  53. package/dist/benchmark/antiGeneBenchmark.js +1 -251
  54. package/dist/benchmark/antiGeneImpact.js +1 -34
  55. package/dist/benchmark/antiGeneRollout.d.ts +2 -0
  56. package/dist/benchmark/antiGeneRollout.js +1 -268
  57. package/dist/benchmark/benchmark.js +1 -26
  58. package/dist/benchmark/evolutionThesisSolver.js +1 -41
  59. package/dist/benchmark/index.d.ts +2 -1
  60. package/dist/benchmark/index.js +1 -6
  61. package/dist/benchmark/selectionFlatAbstention.d.ts +152 -0
  62. package/dist/benchmark/selectionFlatAbstention.js +1 -0
  63. package/dist/benchmark/thesis.js +1 -150
  64. package/dist/benchmark/triggerShift.js +1 -106
  65. package/dist/bootstrap/envFingerprint.d.ts +9 -0
  66. package/dist/bootstrap/envFingerprint.js +5 -0
  67. package/dist/bootstrap/index.d.ts +2 -1
  68. package/dist/bootstrap/index.js +2 -1
  69. package/dist/bootstrap/v1EnvCompat.d.ts +111 -0
  70. package/dist/bootstrap/v1EnvCompat.js +280 -0
  71. package/dist/cycle/cycleTimeline.js +1 -74
  72. package/dist/cycle/index.js +1 -2
  73. package/dist/cycle/stateMachine.js +1 -25
  74. package/dist/events/eventArchive.d.ts +2 -0
  75. package/dist/events/eventArchive.js +13 -3
  76. package/dist/events/eventSchema.d.ts +7 -7
  77. package/dist/events/eventStore.d.ts +2 -0
  78. package/dist/events/eventStore.js +5 -1
  79. package/dist/events/ingest.d.ts +1 -0
  80. package/dist/events/ingest.js +1 -0
  81. package/dist/events/paths.d.ts +3 -1
  82. package/dist/events/paths.js +4 -0
  83. package/dist/events/public.d.ts +2 -2
  84. package/dist/events/public.js +2 -2
  85. package/dist/events/reports.d.ts +2 -0
  86. package/dist/events/reports.js +4 -0
  87. package/dist/exec/autoExec.d.ts +43 -2
  88. package/dist/exec/autoExec.js +76 -9
  89. package/dist/exec/autonomousCycle.d.ts +22 -4
  90. package/dist/exec/autonomousCycle.js +64 -13
  91. package/dist/exec/claudeBridge.d.ts +32 -7
  92. package/dist/exec/claudeBridge.js +281 -29
  93. package/dist/exec/prompt.js +5 -1
  94. package/dist/exec/runnerRegistry.d.ts +68 -26
  95. package/dist/exec/runnerRegistry.js +307 -72
  96. package/dist/exec/selfPr.js +1 -7
  97. package/dist/feedback/envelope.d.ts +61 -0
  98. package/dist/feedback/envelope.js +168 -0
  99. package/dist/feedback/index.d.ts +1 -0
  100. package/dist/feedback/index.js +1 -0
  101. package/dist/hooks/hooks.js +1 -0
  102. package/dist/hub/agentDirectory.js +1 -104
  103. package/dist/hub/assetCallLog.d.ts +35 -1
  104. package/dist/hub/assetCallLog.js +1 -72
  105. package/dist/hub/bindings.d.ts +8 -1
  106. package/dist/hub/bindings.js +1 -110
  107. package/dist/hub/capability.d.ts +90 -3
  108. package/dist/hub/capability.js +1 -1
  109. package/dist/hub/conversationDistiller.js +1 -264
  110. package/dist/hub/fake.d.ts +2 -2
  111. package/dist/hub/fake.js +1 -70
  112. package/dist/hub/hubReview.js +1 -106
  113. package/dist/hub/index.js +1 -11
  114. package/dist/hub/ingest.js +1 -15
  115. package/dist/hub/questionGenerator.d.ts +5 -1
  116. package/dist/hub/questionGenerator.js +1 -406
  117. package/dist/hub/reuseDecision.js +1 -127
  118. package/dist/hub/sanitize.js +1 -322
  119. package/dist/index.d.ts +3 -1
  120. package/dist/index.js +4 -1
  121. package/dist/mailbox/dispatch.d.ts +1 -1
  122. package/dist/mailbox/dispatch.js +22 -6
  123. package/dist/mailbox/envelope.d.ts +7 -1
  124. package/dist/mailbox/envelope.js +9 -2
  125. package/dist/mailbox/ipcServer.d.ts +10 -2
  126. package/dist/mailbox/ipcServer.js +163 -13
  127. package/dist/mailbox/store.d.ts +89 -3
  128. package/dist/mailbox/store.js +895 -41
  129. package/dist/material/boundary.js +1 -14
  130. package/dist/material/consumer.js +1 -55
  131. package/dist/material/emit.js +1 -52
  132. package/dist/material/factory.js +1 -25
  133. package/dist/material/index.js +1 -9
  134. package/dist/material/materialArchive.js +1 -466
  135. package/dist/material/materialStore.js +1 -69
  136. package/dist/material/sampling.js +1 -39
  137. package/dist/material/sources.js +1 -33
  138. package/dist/material/watermark.js +1 -76
  139. package/dist/ops/evolutionGraphProjection.d.ts +20 -0
  140. package/dist/ops/evolutionGraphProjection.js +315 -0
  141. package/dist/ops/index.d.ts +2 -1
  142. package/dist/ops/index.js +2 -1
  143. package/dist/ops/selfUpdate.d.ts +8 -0
  144. package/dist/ops/selfUpdate.js +24 -8
  145. package/dist/personality/drift.js +1 -106
  146. package/dist/personality/events.js +1 -37
  147. package/dist/personality/evolveOps.js +1 -93
  148. package/dist/personality/index.js +1 -11
  149. package/dist/personality/mutate.js +1 -39
  150. package/dist/personality/pivot.js +1 -22
  151. package/dist/personality/prompt.js +1 -62
  152. package/dist/personality/riskGate.js +1 -80
  153. package/dist/personality/schema.d.ts +16 -16
  154. package/dist/personality/schema.js +1 -119
  155. package/dist/personality/select.js +1 -71
  156. package/dist/personality/stats.js +1 -82
  157. package/dist/personality/store.js +1 -92
  158. package/dist/schema/common.js +1 -17
  159. package/dist/schema/evolutionGraph.d.ts +784 -0
  160. package/dist/schema/evolutionGraph.js +1 -0
  161. package/dist/schema/index.d.ts +1 -0
  162. package/dist/schema/index.js +1 -5
  163. package/dist/schema/material.js +1 -50
  164. package/dist/schema/problem.js +1 -45
  165. package/dist/schema/proofOfWork.js +1 -9
  166. package/dist/schema/signal.d.ts +3 -3
  167. package/dist/schema/signal.js +1 -19
  168. package/dist/signals/curriculum.d.ts +55 -0
  169. package/dist/signals/curriculum.js +1 -0
  170. package/dist/signals/cycleHistoryFromEvents.js +1 -96
  171. package/dist/signals/expand.d.ts +15 -1
  172. package/dist/signals/expand.js +1 -125
  173. package/dist/signals/extractor.d.ts +2 -2
  174. package/dist/signals/extractor.js +1 -57
  175. package/dist/signals/index.d.ts +3 -1
  176. package/dist/signals/index.js +1 -6
  177. package/dist/signals/metaSignals.d.ts +4 -0
  178. package/dist/signals/metaSignals.js +1 -158
  179. package/dist/signals/signalGate.js +1 -40
  180. package/dist/signals/taskDomain.d.ts +22 -0
  181. package/dist/signals/taskDomain.js +1 -0
  182. package/dist/signals/traceSignals.js +1 -123
  183. package/dist/strategy/constraintAblation.js +1 -3074
  184. package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
  185. package/dist/strategy/constraintAblationPredicates.js +1 -0
  186. package/dist/strategy/experiment.js +1 -63
  187. package/dist/strategy/index.js +1 -3
  188. package/dist/strategy/strategyPoint.js +1 -23
  189. package/dist/trace/index.d.ts +3 -1
  190. package/dist/trace/index.js +3 -1
  191. package/dist/trace/learningTrace.d.ts +209 -0
  192. package/dist/trace/learningTrace.js +276 -0
  193. package/dist/trace/proxyTurns.d.ts +31 -0
  194. package/dist/trace/proxyTurns.js +137 -0
  195. package/dist/verify/sandboxRunner.d.ts +28 -0
  196. package/dist/verify/sandboxRunner.js +218 -19
  197. package/dist/verify/sandboxedValidation.d.ts +9 -0
  198. package/dist/verify/sandboxedValidation.js +113 -13
  199. package/dist/verify/validation.d.ts +11 -1
  200. package/dist/verify/validation.js +31 -0
  201. package/package.json +5 -2
@@ -1,11 +1,19 @@
1
+ import { randomUUID } from 'node:crypto';
1
2
  import { dirname, join } from 'node:path';
2
- import { appendUtf8Durable, assertAssetStoreDirectory, ensureAssetStoreDirectory, readUtf8Regular, regularFileFingerprint, withAssetStoreLock, } from './assetStoreStorage.js';
3
- import { parseAssetSyncRecord, parseSidecarJsonl } from './assetSidecarRecords.js';
3
+ import { appendUtf8Durable, assertAssetStoreDirectory, ensureAssetStoreDirectory, readUtf8Regular, regularFileFingerprint, replaceUtf8Durable, withAssetStoreLock, } from './assetStoreStorage.js';
4
+ import { ASSET_SYNC_INVENTORY_MAX_SEGMENT_BYTES, parseAssetSyncInventorySegmentRecord, parseAssetSyncRecord, parseAssetSyncRunRecord, parseSidecarJsonl, } from './assetSidecarRecords.js';
5
+ export const ASSET_SYNC_INVENTORY_MAX_SEGMENTS = 24;
6
+ export const ASSET_SYNC_INVENTORY_MAX_ITEMS_PER_SEGMENT = 10_000;
7
+ export const ASSET_SYNC_INVENTORY_MAX_UNIQUE_ITEMS = 240_000;
8
+ export const ASSET_SYNC_INVENTORY_MAX_TOTAL_BYTES = 48 * 1024 * 1024;
9
+ const ASSET_SYNC_SIDECAR_MAX_BYTES = 64 * 1024 * 1024;
4
10
  export class AssetSyncLedger {
5
11
  now;
6
12
  path;
7
13
  lockPath;
8
14
  index = new Map();
15
+ runKeyIndex = new Map();
16
+ inventoryKeyIndex = new Map();
9
17
  fileState = null;
10
18
  loaded = false;
11
19
  constructor(baseDir, now = Date.now) {
@@ -24,27 +32,248 @@ export class AssetSyncLedger {
24
32
  });
25
33
  appendUtf8Durable(this.path, `${JSON.stringify(full)}\n`);
26
34
  this.index.set(full.assetId, full);
35
+ if (full.runKey) {
36
+ const scoped = this.runKeyIndex.get(full.runKey) ?? new Map();
37
+ scoped.set(full.assetId, full);
38
+ this.runKeyIndex.set(full.runKey, scoped);
39
+ }
40
+ if (full.inventoryKey) {
41
+ const scoped = this.inventoryKeyIndex.get(full.inventoryKey) ?? new Map();
42
+ scoped.set(full.assetId, full);
43
+ this.inventoryKeyIndex.set(full.inventoryKey, scoped);
44
+ }
27
45
  this.fileState = regularFileFingerprint(this.path);
28
46
  return full;
29
47
  });
30
48
  }
49
+ appendRun(rec) {
50
+ assertAssetStoreDirectory(dirname(this.path));
51
+ return withAssetStoreLock(this.lockPath, () => {
52
+ this.refreshUnderLock();
53
+ const full = parseAssetSyncRunRecord({
54
+ recordType: 'run',
55
+ ...rec,
56
+ syncedAt: rec.syncedAt ?? new Date(this.now()).toISOString(),
57
+ });
58
+ if (!full)
59
+ throw new Error('invalid asset sync run record');
60
+ appendUtf8Durable(this.path, `${JSON.stringify(full)}\n`);
61
+ this.fileState = regularFileFingerprint(this.path);
62
+ return full;
63
+ });
64
+ }
65
+ appendInventorySegment(rec) {
66
+ assertAssetStoreDirectory(dirname(this.path));
67
+ return withAssetStoreLock(this.lockPath, () => {
68
+ this.refreshUnderLock();
69
+ const full = parseAssetSyncInventorySegmentRecord({
70
+ recordType: 'inventory_scan',
71
+ ...rec,
72
+ syncedAt: rec.syncedAt ?? new Date(this.now()).toISOString(),
73
+ });
74
+ if (!full)
75
+ return null;
76
+ const state = readUtf8Regular(this.path) ?? '';
77
+ const line = `${JSON.stringify(full)}\n`;
78
+ if (full.index === 0) {
79
+ const compacted = removeInventoryScan(state, full.inventoryKey);
80
+ const replacement = `${compacted}${line}`;
81
+ if (!canAppendInventorySegment(undefined, full)
82
+ || inventoryScanBytes(replacement) > ASSET_SYNC_INVENTORY_MAX_TOTAL_BYTES
83
+ || Buffer.byteLength(replacement, 'utf8') > ASSET_SYNC_SIDECAR_MAX_BYTES) {
84
+ return null;
85
+ }
86
+ replaceUtf8Durable(this.path, replacement);
87
+ this.rebuildIndex(regularFileFingerprint(this.path));
88
+ return full;
89
+ }
90
+ const current = this.latestInventorySnapshotUnderLock(full.inventoryKey);
91
+ if (!canAppendInventorySegment(current, full))
92
+ return null;
93
+ if (inventoryScanBytes(state) + Buffer.byteLength(line, 'utf8') > ASSET_SYNC_INVENTORY_MAX_TOTAL_BYTES
94
+ || Buffer.byteLength(state, 'utf8') + Buffer.byteLength(line, 'utf8') > ASSET_SYNC_SIDECAR_MAX_BYTES) {
95
+ return null;
96
+ }
97
+ appendUtf8Durable(this.path, line);
98
+ this.fileState = regularFileFingerprint(this.path);
99
+ return full;
100
+ });
101
+ }
102
+ appendInventoryBatch(rec) {
103
+ assertAssetStoreDirectory(dirname(this.path));
104
+ return withAssetStoreLock(this.lockPath, () => {
105
+ this.refreshUnderLock();
106
+ const records = createInventoryBatchRecords({
107
+ ...rec,
108
+ syncedAt: rec.syncedAt ?? new Date(this.now()).toISOString(),
109
+ });
110
+ if (!records)
111
+ return null;
112
+ const state = readUtf8Regular(this.path) ?? '';
113
+ const lines = records.map((record) => `${JSON.stringify(record)}\n`).join('');
114
+ const current = rec.index === 0 ? undefined : this.latestInventorySnapshotUnderLock(rec.inventoryKey);
115
+ if (!canAppendInventoryBatch(current, records))
116
+ return null;
117
+ if (rec.index === 0) {
118
+ const compacted = removeInventoryScan(state, rec.inventoryKey);
119
+ const replacement = `${compacted}${lines}`;
120
+ if (inventoryScanBytes(replacement) > ASSET_SYNC_INVENTORY_MAX_TOTAL_BYTES
121
+ || Buffer.byteLength(replacement, 'utf8') > ASSET_SYNC_SIDECAR_MAX_BYTES) {
122
+ return null;
123
+ }
124
+ replaceUtf8Durable(this.path, replacement);
125
+ this.rebuildIndex(regularFileFingerprint(this.path));
126
+ return records;
127
+ }
128
+ if (inventoryScanBytes(state) + Buffer.byteLength(lines, 'utf8') > ASSET_SYNC_INVENTORY_MAX_TOTAL_BYTES
129
+ || Buffer.byteLength(state, 'utf8') + Buffer.byteLength(lines, 'utf8') > ASSET_SYNC_SIDECAR_MAX_BYTES) {
130
+ return null;
131
+ }
132
+ appendUtf8Durable(this.path, lines);
133
+ this.fileState = regularFileFingerprint(this.path);
134
+ return records;
135
+ });
136
+ }
137
+ replaceInventoryRetryBatch(rec) {
138
+ assertAssetStoreDirectory(dirname(this.path));
139
+ return withAssetStoreLock(this.lockPath, () => {
140
+ this.refreshUnderLock();
141
+ const state = readUtf8Regular(this.path) ?? '';
142
+ const current = this.latestInventorySnapshotUnderLock(rec.inventoryKey);
143
+ if (!current
144
+ || current.retryIndex === undefined
145
+ || rec.index !== current.retryIndex
146
+ || rec.scanId !== current.scanId
147
+ || rec.scope !== current.scope
148
+ || !cursorFingerprintsEqual(rec.inputCursorFingerprints, current.nextCursorFingerprints))
149
+ return null;
150
+ const retryTail = findInventoryRetryTail(state, current);
151
+ if (!retryTail)
152
+ return null;
153
+ const mergedItems = mergeInventoryRetryItems(retryTail.records, rec.items);
154
+ if (!mergedItems)
155
+ return null;
156
+ const previousAnonymousBlocked = retryTail.records.reduce((total, record) => total + record.anonymousBlocked, 0);
157
+ const records = createInventoryBatchRecords({
158
+ ...rec,
159
+ items: mergedItems,
160
+ anonymousBlocked: Math.max(previousAnonymousBlocked, rec.anonymousBlocked),
161
+ syncedAt: rec.syncedAt ?? new Date(this.now()).toISOString(),
162
+ });
163
+ if (!records)
164
+ return null;
165
+ const preserved = removeInventoryRetryTail(state, retryTail.lineIndexes);
166
+ const prefix = inventorySnapshotFromRaw(preserved, rec.inventoryKey);
167
+ if (!canAppendInventoryBatch(prefix, records))
168
+ return null;
169
+ const replacement = `${preserved}${records.map((record) => `${JSON.stringify(record)}\n`).join('')}`;
170
+ if (inventoryScanBytes(replacement) > ASSET_SYNC_INVENTORY_MAX_TOTAL_BYTES
171
+ || Buffer.byteLength(replacement, 'utf8') > ASSET_SYNC_SIDECAR_MAX_BYTES)
172
+ return null;
173
+ replaceUtf8Durable(this.path, replacement);
174
+ this.rebuildIndex(regularFileFingerprint(this.path));
175
+ return records;
176
+ });
177
+ }
178
+ clearInventoryScan(inventoryKey) {
179
+ assertAssetStoreDirectory(dirname(this.path));
180
+ withAssetStoreLock(this.lockPath, () => {
181
+ this.refreshUnderLock();
182
+ const state = readUtf8Regular(this.path) ?? '';
183
+ const compacted = removeInventoryScan(state, inventoryKey);
184
+ if (compacted === state)
185
+ return;
186
+ replaceUtf8Durable(this.path, compacted);
187
+ this.rebuildIndex(regularFileFingerprint(this.path));
188
+ });
189
+ }
190
+ runRecords(runId) {
191
+ assertAssetStoreDirectory(dirname(this.path));
192
+ return withAssetStoreLock(this.lockPath, () => {
193
+ this.refreshUnderLock();
194
+ return this.readRunRecordsUnderLock().filter((record) => record.runId === runId);
195
+ });
196
+ }
197
+ latestIncompleteRun(runKey) {
198
+ assertAssetStoreDirectory(dirname(this.path));
199
+ return withAssetStoreLock(this.lockPath, () => {
200
+ this.refreshUnderLock();
201
+ let current;
202
+ for (const record of this.readRunRecordsUnderLock()) {
203
+ if (record.runKey !== runKey)
204
+ continue;
205
+ if (record.state === 'started') {
206
+ current = Object.freeze({
207
+ ...record,
208
+ processed: current?.runId === record.runId ? current.processed : new Map(),
209
+ });
210
+ }
211
+ if (record.state === 'progress' && record.remoteAssetId && record.outcome) {
212
+ if (current?.runId === record.runId) {
213
+ const processed = new Map(current.processed);
214
+ processed.set(record.remoteAssetId, record);
215
+ current = Object.freeze({ ...current, processed });
216
+ }
217
+ }
218
+ if (record.state === 'completed' && current?.runId === record.runId)
219
+ current = undefined;
220
+ }
221
+ return current;
222
+ });
223
+ }
224
+ latestInventoryScan(inventoryKey) {
225
+ assertAssetStoreDirectory(dirname(this.path));
226
+ return withAssetStoreLock(this.lockPath, () => {
227
+ this.refreshUnderLock();
228
+ return this.latestInventorySnapshotUnderLock(inventoryKey);
229
+ });
230
+ }
31
231
  get(assetId) {
32
232
  return this.withFreshRead((index) => index.get(assetId) ?? null);
33
233
  }
234
+ getForRunKey(runKey, assetId) {
235
+ return this.withFreshRead(() => this.runKeyIndex.get(runKey)?.get(assetId) ?? null);
236
+ }
34
237
  list() {
35
238
  return this.withFreshRead((index) => [...index.values()]);
36
239
  }
240
+ listForRunKey(runKey) {
241
+ return this.withFreshRead(() => [...(this.runKeyIndex.get(runKey)?.values() ?? [])]);
242
+ }
243
+ listForInventoryKey(inventoryKey) {
244
+ return this.withFreshRead(() => [...(this.inventoryKeyIndex.get(inventoryKey)?.values() ?? [])]);
245
+ }
37
246
  rebuildIndex(state) {
38
247
  const next = new Map();
248
+ const nextRunKeyIndex = new Map();
249
+ const nextInventoryKeyIndex = new Map();
39
250
  const raw = state === 'missing' ? null : readUtf8Regular(this.path);
40
251
  if (raw !== null) {
41
252
  const parsed = parseSidecarJsonl(raw, parseAssetSyncRecord);
42
- for (const record of parsed.records)
43
- next.set(record.assetId, immutableRecord(record));
253
+ for (const parsedRecord of parsed.records) {
254
+ const record = immutableRecord(parsedRecord);
255
+ next.set(record.assetId, record);
256
+ if (record.runKey) {
257
+ const scoped = nextRunKeyIndex.get(record.runKey) ?? new Map();
258
+ scoped.set(record.assetId, record);
259
+ nextRunKeyIndex.set(record.runKey, scoped);
260
+ }
261
+ if (record.inventoryKey) {
262
+ const scoped = nextInventoryKeyIndex.get(record.inventoryKey) ?? new Map();
263
+ scoped.set(record.assetId, record);
264
+ nextInventoryKeyIndex.set(record.inventoryKey, scoped);
265
+ }
266
+ }
44
267
  }
45
268
  this.index.clear();
46
269
  for (const [assetId, record] of next)
47
270
  this.index.set(assetId, record);
271
+ this.runKeyIndex.clear();
272
+ for (const [runKey, records] of nextRunKeyIndex)
273
+ this.runKeyIndex.set(runKey, records);
274
+ this.inventoryKeyIndex.clear();
275
+ for (const [inventoryKey, records] of nextInventoryKeyIndex)
276
+ this.inventoryKeyIndex.set(inventoryKey, records);
48
277
  this.fileState = state;
49
278
  this.loaded = true;
50
279
  }
@@ -60,7 +289,457 @@ export class AssetSyncLedger {
60
289
  return read(this.index);
61
290
  });
62
291
  }
292
+ readRunRecordsUnderLock() {
293
+ const raw = readUtf8Regular(this.path);
294
+ if (raw === null)
295
+ return [];
296
+ const records = [];
297
+ for (const line of raw.split(/\r?\n/)) {
298
+ if (!line.trim())
299
+ continue;
300
+ try {
301
+ const record = parseAssetSyncRunRecord(JSON.parse(line));
302
+ if (record)
303
+ records.push(record);
304
+ }
305
+ catch {
306
+ // Corrupt sidecar lines cannot make all resumable work unreadable.
307
+ }
308
+ }
309
+ return records;
310
+ }
311
+ latestInventorySnapshotUnderLock(inventoryKey) {
312
+ const raw = readUtf8Regular(this.path);
313
+ return raw === null ? undefined : inventorySnapshotFromRaw(raw, inventoryKey);
314
+ }
315
+ }
316
+ function inventorySnapshotFromRaw(raw, inventoryKey) {
317
+ let current;
318
+ let pendingBatch = [];
319
+ for (const line of raw.split(/\r?\n/)) {
320
+ if (!line.trim())
321
+ continue;
322
+ const record = parseInventoryLine(line);
323
+ if (!record || record.inventoryKey !== inventoryKey)
324
+ continue;
325
+ if (record.batchId) {
326
+ if (record.batchIndex === 0) {
327
+ pendingBatch = [record];
328
+ }
329
+ else if (continuesPendingBatch(pendingBatch, record)) {
330
+ pendingBatch.push(record);
331
+ }
332
+ else {
333
+ pendingBatch = [];
334
+ }
335
+ if (pendingBatch.length === record.batchSize) {
336
+ const batch = pendingBatch;
337
+ pendingBatch = [];
338
+ if (!canAppendInventoryBatch(current, batch)) {
339
+ if (current?.scanId === record.scanId)
340
+ current = undefined;
341
+ continue;
342
+ }
343
+ current = snapshotFromInventoryBatch(current, batch);
344
+ }
345
+ continue;
346
+ }
347
+ pendingBatch = [];
348
+ if (record.index === 0) {
349
+ current = snapshotFromFirstSegment(record);
350
+ continue;
351
+ }
352
+ if (!current || !canAppendInventorySegment(current, record)) {
353
+ if (current?.scanId === record.scanId)
354
+ current = undefined;
355
+ continue;
356
+ }
357
+ current = appendSegmentToSnapshot(current, record);
358
+ }
359
+ return current;
63
360
  }
64
361
  function immutableRecord(record) {
65
362
  return Object.freeze({ ...record });
363
+ }
364
+ function createInventoryBatchRecords(rec) {
365
+ if (!Array.isArray(rec.items) || !Number.isInteger(rec.anonymousBlocked) || rec.anonymousBlocked < 0)
366
+ return null;
367
+ const syncedAt = rec.syncedAt ?? new Date().toISOString();
368
+ const common = parseAssetSyncInventorySegmentRecord({
369
+ recordType: 'inventory_scan',
370
+ ...rec,
371
+ items: [],
372
+ anonymousBlocked: 0,
373
+ syncedAt,
374
+ });
375
+ const totalItems = rec.items.length + rec.anonymousBlocked;
376
+ const remoteAssetIds = new Set();
377
+ for (const item of rec.items) {
378
+ if (!item || typeof item.remoteAssetId !== 'string' || remoteAssetIds.has(item.remoteAssetId))
379
+ return null;
380
+ remoteAssetIds.add(item.remoteAssetId);
381
+ }
382
+ if (!common
383
+ || totalItems > ASSET_SYNC_INVENTORY_MAX_UNIQUE_ITEMS)
384
+ return null;
385
+ if (splitInventoryUnits(rec.items, rec.anonymousBlocked).length === 1) {
386
+ const singleton = parseAssetSyncInventorySegmentRecord({
387
+ ...common,
388
+ items: rec.items,
389
+ anonymousBlocked: rec.anonymousBlocked,
390
+ });
391
+ if (singleton)
392
+ return Object.freeze([singleton]);
393
+ }
394
+ const batchId = randomUUID();
395
+ const initialChunks = splitInventoryUnits(rec.items, rec.anonymousBlocked);
396
+ const chunks = [];
397
+ for (const chunk of initialChunks) {
398
+ const remainingSegments = ASSET_SYNC_INVENTORY_MAX_SEGMENTS - rec.index - chunks.length;
399
+ const fitted = splitInventoryChunkToFit(common, chunk, batchId, chunks.length, remainingSegments);
400
+ if (!fitted)
401
+ return null;
402
+ chunks.push(...fitted);
403
+ }
404
+ if (chunks.length === 0
405
+ || chunks.length > ASSET_SYNC_INVENTORY_MAX_SEGMENTS
406
+ || rec.index + chunks.length > ASSET_SYNC_INVENTORY_MAX_SEGMENTS)
407
+ return null;
408
+ const records = [];
409
+ for (const [batchIndex, chunk] of chunks.entries()) {
410
+ const parsed = parseAssetSyncInventorySegmentRecord({
411
+ ...common,
412
+ index: rec.index + batchIndex,
413
+ items: chunk.items,
414
+ anonymousBlocked: chunk.anonymousBlocked,
415
+ ...(chunks.length > 1 ? { batchId, batchIndex, batchSize: chunks.length } : {}),
416
+ });
417
+ if (!parsed)
418
+ return null;
419
+ records.push(parsed);
420
+ }
421
+ return Object.freeze(records);
422
+ }
423
+ function splitInventoryUnits(items, anonymousBlocked) {
424
+ if (items.length === 0 && anonymousBlocked === 0)
425
+ return [{ items: [], anonymousBlocked: 0 }];
426
+ const chunks = [];
427
+ let itemOffset = 0;
428
+ let anonymousRemaining = anonymousBlocked;
429
+ while (itemOffset < items.length || anonymousRemaining > 0) {
430
+ const itemCount = Math.min(ASSET_SYNC_INVENTORY_MAX_ITEMS_PER_SEGMENT, items.length - itemOffset);
431
+ const anonymousCount = Math.min(ASSET_SYNC_INVENTORY_MAX_ITEMS_PER_SEGMENT - itemCount, anonymousRemaining);
432
+ chunks.push({
433
+ items: items.slice(itemOffset, itemOffset + itemCount),
434
+ anonymousBlocked: anonymousCount,
435
+ });
436
+ itemOffset += itemCount;
437
+ anonymousRemaining -= anonymousCount;
438
+ }
439
+ return chunks;
440
+ }
441
+ function splitInventoryChunkToFit(common, chunk, batchId, batchIndex, remainingSegments) {
442
+ if (remainingSegments < 1)
443
+ return null;
444
+ const parsed = parseAssetSyncInventorySegmentRecord({
445
+ ...common,
446
+ index: common.index + batchIndex,
447
+ items: chunk.items,
448
+ anonymousBlocked: chunk.anonymousBlocked,
449
+ });
450
+ const batchProbe = parsed ? {
451
+ ...parsed,
452
+ batchId,
453
+ batchIndex,
454
+ batchSize: ASSET_SYNC_INVENTORY_MAX_SEGMENTS,
455
+ } : null;
456
+ if (batchProbe
457
+ && Buffer.byteLength(JSON.stringify(batchProbe), 'utf8') <= ASSET_SYNC_INVENTORY_MAX_SEGMENT_BYTES)
458
+ return [chunk];
459
+ const units = chunk.items.length + chunk.anonymousBlocked;
460
+ if (units <= 1)
461
+ return null;
462
+ const leftUnits = Math.floor(units / 2);
463
+ const leftItemCount = Math.min(leftUnits, chunk.items.length);
464
+ const left = {
465
+ items: chunk.items.slice(0, leftItemCount),
466
+ anonymousBlocked: leftUnits - leftItemCount,
467
+ };
468
+ const right = {
469
+ items: chunk.items.slice(leftItemCount),
470
+ anonymousBlocked: chunk.anonymousBlocked - left.anonymousBlocked,
471
+ };
472
+ const fittedLeft = splitInventoryChunkToFit(common, left, batchId, batchIndex, remainingSegments);
473
+ if (!fittedLeft)
474
+ return null;
475
+ const fittedRight = splitInventoryChunkToFit(common, right, batchId, batchIndex + fittedLeft.length, remainingSegments - fittedLeft.length);
476
+ return fittedLeft && fittedRight ? [...fittedLeft, ...fittedRight] : null;
477
+ }
478
+ function continuesPendingBatch(pending, record) {
479
+ const first = pending[0];
480
+ return Boolean(first?.batchId
481
+ && record.batchId === first.batchId
482
+ && record.batchSize === first.batchSize
483
+ && record.batchIndex === pending.length
484
+ && record.index === first.index + pending.length
485
+ && record.scanId === first.scanId
486
+ && record.inventoryKey === first.inventoryKey
487
+ && record.scope === first.scope
488
+ && record.syncedAt === first.syncedAt
489
+ && record.cursorHeld === first.cursorHeld
490
+ && cursorFingerprintsEqual(record.inputCursorFingerprints, first.inputCursorFingerprints)
491
+ && cursorFingerprintsEqual(record.nextCursorFingerprints, first.nextCursorFingerprints));
492
+ }
493
+ function canAppendInventoryBatch(current, records) {
494
+ const first = records[0];
495
+ if (!first)
496
+ return false;
497
+ if (records.length === 1)
498
+ return !first.batchId && canAppendInventorySegment(current, first);
499
+ if (first.batchIndex !== 0
500
+ || first.batchSize !== records.length
501
+ || !records.every((record, index) => index === 0 || continuesPendingBatch(records.slice(0, index), record))
502
+ || (first.cursorHeld === true && !inventoryBatchHasRetryableOutcome(records)))
503
+ return false;
504
+ if (first.index === 0) {
505
+ if (!cursorFingerprintsEqual(first.inputCursorFingerprints, { purchased: null, published: null }))
506
+ return false;
507
+ }
508
+ else if (!current
509
+ || current.complete
510
+ || current.retryIndex !== undefined
511
+ || current.scanId !== first.scanId
512
+ || current.inventoryKey !== first.inventoryKey
513
+ || current.scope !== first.scope
514
+ || current.segmentCount !== first.index
515
+ || !cursorFingerprintsEqual(current.nextCursorFingerprints, first.inputCursorFingerprints)) {
516
+ return false;
517
+ }
518
+ if ((current?.segmentCount ?? 0) + records.length > ASSET_SYNC_INVENTORY_MAX_SEGMENTS)
519
+ return false;
520
+ const outcomes = new Set(current?.outcomes.keys() ?? []);
521
+ let anonymousBlocked = current?.anonymousBlocked ?? 0;
522
+ for (const record of records) {
523
+ for (const item of record.items)
524
+ outcomes.add(item.remoteAssetId);
525
+ anonymousBlocked += record.anonymousBlocked;
526
+ }
527
+ return outcomes.size + anonymousBlocked <= ASSET_SYNC_INVENTORY_MAX_UNIQUE_ITEMS;
528
+ }
529
+ function snapshotFromInventoryBatch(current, records) {
530
+ const first = records[0];
531
+ if (!first)
532
+ throw new Error('inventory batch is empty');
533
+ const outcomes = new Map(current?.outcomes ?? []);
534
+ let anonymousBlocked = current?.anonymousBlocked ?? 0;
535
+ for (const record of records) {
536
+ for (const item of record.items) {
537
+ if (!outcomes.has(item.remoteAssetId))
538
+ outcomes.set(item.remoteAssetId, item.outcome);
539
+ }
540
+ anonymousBlocked += record.anonymousBlocked;
541
+ }
542
+ const retryIndex = inventoryBatchRetryIndex(first);
543
+ return immutableInventorySnapshot({
544
+ scanId: first.scanId,
545
+ inventoryKey: first.inventoryKey,
546
+ scope: first.scope,
547
+ segmentCount: (current?.segmentCount ?? 0) + records.length,
548
+ nextCursorFingerprints: first.nextCursorFingerprints,
549
+ outcomes,
550
+ anonymousBlocked,
551
+ complete: retryIndex === undefined && inventoryScanComplete(first.scope, first.nextCursorFingerprints),
552
+ ...(retryIndex === undefined ? {} : { retryIndex }),
553
+ });
554
+ }
555
+ function canAppendInventorySegment(current, record) {
556
+ if (record.cursorHeld === true && !inventoryBatchHasRetryableOutcome([record]))
557
+ return false;
558
+ if (record.index === 0) {
559
+ return cursorFingerprintsEqual(record.inputCursorFingerprints, { purchased: null, published: null });
560
+ }
561
+ if (!current
562
+ || current.complete
563
+ || current.retryIndex !== undefined
564
+ || current.scanId !== record.scanId
565
+ || current.inventoryKey !== record.inventoryKey
566
+ || current.scope !== record.scope
567
+ || current.segmentCount !== record.index
568
+ || !cursorFingerprintsEqual(current.nextCursorFingerprints, record.inputCursorFingerprints)
569
+ || current.segmentCount >= ASSET_SYNC_INVENTORY_MAX_SEGMENTS) {
570
+ return false;
571
+ }
572
+ const newUniqueItems = record.items.reduce((count, item) => count + (current.outcomes.has(item.remoteAssetId) ? 0 : 1), 0);
573
+ return current.outcomes.size + current.anonymousBlocked + newUniqueItems + record.anonymousBlocked
574
+ <= ASSET_SYNC_INVENTORY_MAX_UNIQUE_ITEMS;
575
+ }
576
+ function snapshotFromFirstSegment(record) {
577
+ if (record.items.length + record.anonymousBlocked > ASSET_SYNC_INVENTORY_MAX_UNIQUE_ITEMS)
578
+ return undefined;
579
+ if (record.cursorHeld === true && !inventoryBatchHasRetryableOutcome([record]))
580
+ return undefined;
581
+ const retryIndex = inventoryBatchRetryIndex(record);
582
+ return immutableInventorySnapshot({
583
+ scanId: record.scanId,
584
+ inventoryKey: record.inventoryKey,
585
+ scope: record.scope,
586
+ segmentCount: 1,
587
+ nextCursorFingerprints: record.nextCursorFingerprints,
588
+ outcomes: new Map(record.items.map((item) => [item.remoteAssetId, item.outcome])),
589
+ anonymousBlocked: record.anonymousBlocked,
590
+ complete: retryIndex === undefined && inventoryScanComplete(record.scope, record.nextCursorFingerprints),
591
+ ...(retryIndex === undefined ? {} : { retryIndex }),
592
+ });
593
+ }
594
+ function appendSegmentToSnapshot(current, record) {
595
+ const outcomes = new Map(current.outcomes);
596
+ for (const item of record.items) {
597
+ if (!outcomes.has(item.remoteAssetId))
598
+ outcomes.set(item.remoteAssetId, item.outcome);
599
+ }
600
+ const retryIndex = inventoryBatchRetryIndex(record);
601
+ return immutableInventorySnapshot({
602
+ ...current,
603
+ segmentCount: current.segmentCount + 1,
604
+ nextCursorFingerprints: record.nextCursorFingerprints,
605
+ outcomes,
606
+ anonymousBlocked: current.anonymousBlocked + record.anonymousBlocked,
607
+ complete: retryIndex === undefined && inventoryScanComplete(record.scope, record.nextCursorFingerprints),
608
+ retryIndex,
609
+ });
610
+ }
611
+ function inventoryBatchRetryIndex(first) {
612
+ return first.cursorHeld === true ? first.index : undefined;
613
+ }
614
+ function inventoryBatchHasRetryableOutcome(records) {
615
+ return records.some((record) => record.items.some((item) => (item.outcome === 'failed' || item.outcome === 'pending')));
616
+ }
617
+ function findInventoryRetryTail(raw, snapshot) {
618
+ if (snapshot.retryIndex === undefined)
619
+ return undefined;
620
+ const lines = raw.split('\n');
621
+ let found;
622
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
623
+ const first = parseInventoryLine(lines[lineIndex]);
624
+ if (!first
625
+ || first.inventoryKey !== snapshot.inventoryKey
626
+ || first.scanId !== snapshot.scanId
627
+ || first.index !== snapshot.retryIndex
628
+ || first.batchIndex !== undefined && first.batchIndex !== 0)
629
+ continue;
630
+ const records = [first];
631
+ const lineIndexes = new Set([lineIndex]);
632
+ if (first.batchSize !== undefined) {
633
+ for (let offset = 1; offset < first.batchSize; offset += 1) {
634
+ const next = parseInventoryLine(lines[lineIndex + offset]);
635
+ if (!next || !continuesPendingBatch(records, next))
636
+ break;
637
+ records.push(next);
638
+ lineIndexes.add(lineIndex + offset);
639
+ }
640
+ if (records.length !== first.batchSize)
641
+ continue;
642
+ }
643
+ if (records.length !== snapshot.segmentCount - snapshot.retryIndex
644
+ || inventoryBatchRetryIndex(first) !== snapshot.retryIndex)
645
+ continue;
646
+ found = { records: Object.freeze(records), lineIndexes };
647
+ }
648
+ return found;
649
+ }
650
+ function parseInventoryLine(line) {
651
+ if (!line?.trim())
652
+ return null;
653
+ try {
654
+ return parseAssetSyncInventorySegmentRecord(JSON.parse(line));
655
+ }
656
+ catch {
657
+ return null;
658
+ }
659
+ }
660
+ function mergeInventoryRetryItems(previousRecords, retriedItems) {
661
+ const retriedById = new Map();
662
+ for (const item of retriedItems) {
663
+ if (retriedById.has(item.remoteAssetId))
664
+ return null;
665
+ retriedById.set(item.remoteAssetId, item);
666
+ }
667
+ const previousIds = new Set();
668
+ const merged = [];
669
+ for (const record of previousRecords) {
670
+ for (const item of record.items) {
671
+ previousIds.add(item.remoteAssetId);
672
+ const retried = retriedById.get(item.remoteAssetId);
673
+ merged.push(retried ? {
674
+ remoteAssetId: item.remoteAssetId,
675
+ outcome: mergeInventoryRetryOutcome(item.outcome, retried.outcome),
676
+ } : item);
677
+ }
678
+ }
679
+ for (const item of retriedItems) {
680
+ if (!previousIds.has(item.remoteAssetId))
681
+ merged.push(item);
682
+ }
683
+ return merged;
684
+ }
685
+ function mergeInventoryRetryOutcome(previous, retried) {
686
+ if (previous !== 'failed' && previous !== 'pending')
687
+ return previous;
688
+ if (retried === 'imported' || retried === 'already_local' || retried === 'blocked')
689
+ return retried;
690
+ if (previous === 'failed' && retried === 'pending')
691
+ return previous;
692
+ return retried;
693
+ }
694
+ function removeInventoryRetryTail(raw, removedLineIndexes) {
695
+ return raw.split('\n')
696
+ .filter((line, index) => line.trim() && !removedLineIndexes.has(index))
697
+ .map((line) => `${line}\n`)
698
+ .join('');
699
+ }
700
+ function immutableInventorySnapshot(snapshot) {
701
+ return Object.freeze({
702
+ ...snapshot,
703
+ nextCursorFingerprints: Object.freeze({ ...snapshot.nextCursorFingerprints }),
704
+ outcomes: new Map(snapshot.outcomes),
705
+ });
706
+ }
707
+ function inventoryScanComplete(scope, cursors) {
708
+ if (scope === 'purchased')
709
+ return cursors.purchased === null;
710
+ if (scope === 'published')
711
+ return cursors.published === null;
712
+ return cursors.purchased === null && cursors.published === null;
713
+ }
714
+ function cursorFingerprintsEqual(left, right) {
715
+ return left.purchased === right.purchased && left.published === right.published;
716
+ }
717
+ function removeInventoryScan(raw, inventoryKey) {
718
+ return raw.split('\n').filter((line) => {
719
+ if (!line.trim())
720
+ return false;
721
+ try {
722
+ const record = JSON.parse(line);
723
+ return record['recordType'] !== 'inventory_scan' || record['inventoryKey'] !== inventoryKey;
724
+ }
725
+ catch {
726
+ return true;
727
+ }
728
+ }).map((line) => `${line}\n`).join('');
729
+ }
730
+ function inventoryScanBytes(raw) {
731
+ let bytes = 0;
732
+ for (const line of raw.split('\n')) {
733
+ if (!line.trim())
734
+ continue;
735
+ try {
736
+ const record = JSON.parse(line);
737
+ if (record['recordType'] === 'inventory_scan')
738
+ bytes += Buffer.byteLength(`${line}\n`, 'utf8');
739
+ }
740
+ catch {
741
+ // Unknown corrupt rows are preserved but cannot grow through this API.
742
+ }
743
+ }
744
+ return bytes;
66
745
  }