@evomap/evolver-core 2.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (373) hide show
  1. package/dist/algo/antiDistill.d.ts +35 -0
  2. package/dist/algo/antiDistill.js +167 -0
  3. package/dist/algo/bans.d.ts +6 -0
  4. package/dist/algo/bans.js +33 -0
  5. package/dist/algo/candidateAssembly.d.ts +65 -0
  6. package/dist/algo/candidateAssembly.js +196 -0
  7. package/dist/algo/capabilityCandidates.d.ts +38 -0
  8. package/dist/algo/capabilityCandidates.js +131 -0
  9. package/dist/algo/confidence.d.ts +106 -0
  10. package/dist/algo/confidence.js +196 -0
  11. package/dist/algo/cycleEngine.d.ts +143 -0
  12. package/dist/algo/cycleEngine.js +417 -0
  13. package/dist/algo/cycleFailureClassifier.d.ts +52 -0
  14. package/dist/algo/cycleFailureClassifier.js +104 -0
  15. package/dist/algo/epigenetics.d.ts +14 -0
  16. package/dist/algo/epigenetics.js +41 -0
  17. package/dist/algo/evolutionEvent.d.ts +23 -0
  18. package/dist/algo/evolutionEvent.js +24 -0
  19. package/dist/algo/exploration.d.ts +34 -0
  20. package/dist/algo/exploration.js +55 -0
  21. package/dist/algo/geneHealth.d.ts +24 -0
  22. package/dist/algo/geneHealth.js +16 -0
  23. package/dist/algo/geneIntake.d.ts +37 -0
  24. package/dist/algo/geneIntake.js +100 -0
  25. package/dist/algo/genePromotion.d.ts +48 -0
  26. package/dist/algo/genePromotion.js +53 -0
  27. package/dist/algo/geneSelection.d.ts +128 -0
  28. package/dist/algo/geneSelection.js +222 -0
  29. package/dist/algo/index.d.ts +18 -0
  30. package/dist/algo/index.js +18 -0
  31. package/dist/algo/mutation.d.ts +12 -0
  32. package/dist/algo/mutation.js +22 -0
  33. package/dist/algo/orchestrator.d.ts +65 -0
  34. package/dist/algo/orchestrator.js +63 -0
  35. package/dist/algo/solidify.d.ts +43 -0
  36. package/dist/algo/solidify.js +63 -0
  37. package/dist/algo/strategyPresets.d.ts +32 -0
  38. package/dist/algo/strategyPresets.js +61 -0
  39. package/dist/assetstore/index.d.ts +8 -0
  40. package/dist/assetstore/index.js +8 -0
  41. package/dist/assetstore/learningHistory.d.ts +25 -0
  42. package/dist/assetstore/learningHistory.js +38 -0
  43. package/dist/assetstore/localJsonl.d.ts +34 -0
  44. package/dist/assetstore/localJsonl.js +182 -0
  45. package/dist/assetstore/pendingSignals.d.ts +22 -0
  46. package/dist/assetstore/pendingSignals.js +171 -0
  47. package/dist/assetstore/provenance.d.ts +39 -0
  48. package/dist/assetstore/provenance.js +109 -0
  49. package/dist/assetstore/provider.d.ts +48 -0
  50. package/dist/assetstore/provider.js +36 -0
  51. package/dist/assetstore/remoteStub.d.ts +32 -0
  52. package/dist/assetstore/remoteStub.js +36 -0
  53. package/dist/assetstore/reviewFilter.d.ts +20 -0
  54. package/dist/assetstore/reviewFilter.js +44 -0
  55. package/dist/assetstore/reviewLedger.d.ts +55 -0
  56. package/dist/assetstore/reviewLedger.js +121 -0
  57. package/dist/benchmark/antiGeneBenchmark.d.ts +79 -0
  58. package/dist/benchmark/antiGeneBenchmark.js +241 -0
  59. package/dist/benchmark/antiGeneRollout.d.ts +80 -0
  60. package/dist/benchmark/antiGeneRollout.js +256 -0
  61. package/dist/benchmark/benchmark.d.ts +32 -0
  62. package/dist/benchmark/benchmark.js +26 -0
  63. package/dist/benchmark/evolutionThesisSolver.d.ts +25 -0
  64. package/dist/benchmark/evolutionThesisSolver.js +41 -0
  65. package/dist/benchmark/index.d.ts +5 -0
  66. package/dist/benchmark/index.js +5 -0
  67. package/dist/benchmark/thesis.d.ts +85 -0
  68. package/dist/benchmark/thesis.js +150 -0
  69. package/dist/bootstrap/envFingerprint.d.ts +45 -0
  70. package/dist/bootstrap/envFingerprint.js +72 -0
  71. package/dist/bootstrap/index.d.ts +2 -0
  72. package/dist/bootstrap/index.js +2 -0
  73. package/dist/bootstrap/selfEvolve.d.ts +36 -0
  74. package/dist/bootstrap/selfEvolve.js +26 -0
  75. package/dist/cycle/cycleTimeline.d.ts +35 -0
  76. package/dist/cycle/cycleTimeline.js +74 -0
  77. package/dist/cycle/index.d.ts +2 -0
  78. package/dist/cycle/index.js +2 -0
  79. package/dist/cycle/stateMachine.d.ts +6 -0
  80. package/dist/cycle/stateMachine.js +25 -0
  81. package/dist/daemon/cycleContext.d.ts +12 -0
  82. package/dist/daemon/cycleContext.js +13 -0
  83. package/dist/daemon/drain.d.ts +11 -0
  84. package/dist/daemon/drain.js +16 -0
  85. package/dist/daemon/guards.d.ts +87 -0
  86. package/dist/daemon/guards.js +142 -0
  87. package/dist/daemon/idempotency.d.ts +11 -0
  88. package/dist/daemon/idempotency.js +36 -0
  89. package/dist/daemon/idleScheduler.d.ts +55 -0
  90. package/dist/daemon/idleScheduler.js +181 -0
  91. package/dist/daemon/index.d.ts +8 -0
  92. package/dist/daemon/index.js +8 -0
  93. package/dist/daemon/stepRunner.d.ts +12 -0
  94. package/dist/daemon/stepRunner.js +31 -0
  95. package/dist/daemon/unhandledRejectionWindow.d.ts +21 -0
  96. package/dist/daemon/unhandledRejectionWindow.js +52 -0
  97. package/dist/daemon/watchdog.d.ts +11 -0
  98. package/dist/daemon/watchdog.js +14 -0
  99. package/dist/events/eventSchema.d.ts +132 -0
  100. package/dist/events/eventSchema.js +30 -0
  101. package/dist/events/eventStore.d.ts +32 -0
  102. package/dist/events/eventStore.js +137 -0
  103. package/dist/events/ingest.d.ts +34 -0
  104. package/dist/events/ingest.js +61 -0
  105. package/dist/events/paths.d.ts +22 -0
  106. package/dist/events/paths.js +44 -0
  107. package/dist/events/projectors.d.ts +9 -0
  108. package/dist/events/projectors.js +11 -0
  109. package/dist/events/public.d.ts +14 -0
  110. package/dist/events/public.js +8 -0
  111. package/dist/events/replayer.d.ts +26 -0
  112. package/dist/events/replayer.js +67 -0
  113. package/dist/events/reports.d.ts +90 -0
  114. package/dist/events/reports.js +244 -0
  115. package/dist/events/retention.d.ts +70 -0
  116. package/dist/events/retention.js +177 -0
  117. package/dist/events/sink.d.ts +5 -0
  118. package/dist/events/sink.js +1 -0
  119. package/dist/exec/autoExec.d.ts +137 -0
  120. package/dist/exec/autoExec.js +221 -0
  121. package/dist/exec/autonomousCycle.d.ts +48 -0
  122. package/dist/exec/autonomousCycle.js +78 -0
  123. package/dist/exec/claudeBridge.d.ts +112 -0
  124. package/dist/exec/claudeBridge.js +219 -0
  125. package/dist/exec/index.d.ts +9 -0
  126. package/dist/exec/index.js +11 -0
  127. package/dist/exec/openPrRegistry.d.ts +60 -0
  128. package/dist/exec/openPrRegistry.js +130 -0
  129. package/dist/exec/policy/blastRadius.d.ts +31 -0
  130. package/dist/exec/policy/blastRadius.js +67 -0
  131. package/dist/exec/policy/constraints.d.ts +26 -0
  132. package/dist/exec/policy/constraints.js +62 -0
  133. package/dist/exec/policy/destructive.d.ts +45 -0
  134. package/dist/exec/policy/destructive.js +108 -0
  135. package/dist/exec/policy/failureMode.d.ts +33 -0
  136. package/dist/exec/policy/failureMode.js +49 -0
  137. package/dist/exec/policy/index.d.ts +24 -0
  138. package/dist/exec/policy/index.js +30 -0
  139. package/dist/exec/policy/protectedPaths.d.ts +17 -0
  140. package/dist/exec/policy/protectedPaths.js +65 -0
  141. package/dist/exec/policyCheck.d.ts +2 -0
  142. package/dist/exec/policyCheck.js +7 -0
  143. package/dist/exec/prompt.d.ts +34 -0
  144. package/dist/exec/prompt.js +108 -0
  145. package/dist/exec/proofOfWork.d.ts +9 -0
  146. package/dist/exec/proofOfWork.js +11 -0
  147. package/dist/exec/runnerRegistry.d.ts +123 -0
  148. package/dist/exec/runnerRegistry.js +237 -0
  149. package/dist/exec/selfPr.d.ts +83 -0
  150. package/dist/exec/selfPr.js +104 -0
  151. package/dist/exec/selfPrObfuscation.d.ts +21 -0
  152. package/dist/exec/selfPrObfuscation.js +85 -0
  153. package/dist/hooks/hooks.d.ts +117 -0
  154. package/dist/hooks/hooks.js +89 -0
  155. package/dist/hooks/index.d.ts +1 -0
  156. package/dist/hooks/index.js +1 -0
  157. package/dist/hub/assetCallLog.d.ts +43 -0
  158. package/dist/hub/assetCallLog.js +72 -0
  159. package/dist/hub/bindings.d.ts +28 -0
  160. package/dist/hub/bindings.js +90 -0
  161. package/dist/hub/capability.d.ts +278 -0
  162. package/dist/hub/capability.js +1 -0
  163. package/dist/hub/conversationDistiller.d.ts +88 -0
  164. package/dist/hub/conversationDistiller.js +264 -0
  165. package/dist/hub/fake.d.ts +45 -0
  166. package/dist/hub/fake.js +69 -0
  167. package/dist/hub/hubReview.d.ts +91 -0
  168. package/dist/hub/hubReview.js +106 -0
  169. package/dist/hub/index.d.ts +10 -0
  170. package/dist/hub/index.js +10 -0
  171. package/dist/hub/ingest.d.ts +15 -0
  172. package/dist/hub/ingest.js +15 -0
  173. package/dist/hub/questionGenerator.d.ts +59 -0
  174. package/dist/hub/questionGenerator.js +406 -0
  175. package/dist/hub/reuseDecision.d.ts +87 -0
  176. package/dist/hub/reuseDecision.js +127 -0
  177. package/dist/hub/sanitize.d.ts +52 -0
  178. package/dist/hub/sanitize.js +321 -0
  179. package/dist/index.d.ts +27 -0
  180. package/dist/index.js +27 -0
  181. package/dist/mailbox/catalog.d.ts +17 -0
  182. package/dist/mailbox/catalog.js +53 -0
  183. package/dist/mailbox/daemon.d.ts +48 -0
  184. package/dist/mailbox/daemon.js +61 -0
  185. package/dist/mailbox/dispatch.d.ts +46 -0
  186. package/dist/mailbox/dispatch.js +62 -0
  187. package/dist/mailbox/envelope.d.ts +43 -0
  188. package/dist/mailbox/envelope.js +52 -0
  189. package/dist/mailbox/index.d.ts +8 -0
  190. package/dist/mailbox/index.js +8 -0
  191. package/dist/mailbox/ipcServer.d.ts +46 -0
  192. package/dist/mailbox/ipcServer.js +168 -0
  193. package/dist/mailbox/requestReply.d.ts +40 -0
  194. package/dist/mailbox/requestReply.js +77 -0
  195. package/dist/mailbox/store.d.ts +74 -0
  196. package/dist/mailbox/store.js +308 -0
  197. package/dist/mailbox/wake.d.ts +45 -0
  198. package/dist/mailbox/wake.js +60 -0
  199. package/dist/material/boundary.d.ts +7 -0
  200. package/dist/material/boundary.js +14 -0
  201. package/dist/material/consumer.d.ts +21 -0
  202. package/dist/material/consumer.js +52 -0
  203. package/dist/material/emit.d.ts +55 -0
  204. package/dist/material/emit.js +52 -0
  205. package/dist/material/factory.d.ts +27 -0
  206. package/dist/material/factory.js +25 -0
  207. package/dist/material/index.d.ts +8 -0
  208. package/dist/material/index.js +8 -0
  209. package/dist/material/materialStore.d.ts +24 -0
  210. package/dist/material/materialStore.js +76 -0
  211. package/dist/material/sampling.d.ts +21 -0
  212. package/dist/material/sampling.js +39 -0
  213. package/dist/material/sources.d.ts +25 -0
  214. package/dist/material/sources.js +33 -0
  215. package/dist/material/watermark.d.ts +21 -0
  216. package/dist/material/watermark.js +76 -0
  217. package/dist/observers/cursorRewriteObserver.d.ts +46 -0
  218. package/dist/observers/cursorRewriteObserver.js +103 -0
  219. package/dist/observers/distillObserver.d.ts +31 -0
  220. package/dist/observers/distillObserver.js +93 -0
  221. package/dist/observers/index.d.ts +5 -0
  222. package/dist/observers/index.js +5 -0
  223. package/dist/observers/observerBus.d.ts +44 -0
  224. package/dist/observers/observerBus.js +73 -0
  225. package/dist/observers/reflectionObserver.d.ts +31 -0
  226. package/dist/observers/reflectionObserver.js +124 -0
  227. package/dist/observers/valueDigestObserver.d.ts +50 -0
  228. package/dist/observers/valueDigestObserver.js +70 -0
  229. package/dist/ops/cleanup.d.ts +23 -0
  230. package/dist/ops/cleanup.js +68 -0
  231. package/dist/ops/health.d.ts +37 -0
  232. package/dist/ops/health.js +96 -0
  233. package/dist/ops/index.d.ts +7 -0
  234. package/dist/ops/index.js +7 -0
  235. package/dist/ops/recall.d.ts +65 -0
  236. package/dist/ops/recall.js +152 -0
  237. package/dist/ops/reuseOutcomes.d.ts +68 -0
  238. package/dist/ops/reuseOutcomes.js +126 -0
  239. package/dist/ops/savingsCore.d.ts +47 -0
  240. package/dist/ops/savingsCore.js +112 -0
  241. package/dist/ops/selfUpdate.d.ts +106 -0
  242. package/dist/ops/selfUpdate.js +294 -0
  243. package/dist/ops/valueLedger.d.ts +205 -0
  244. package/dist/ops/valueLedger.js +238 -0
  245. package/dist/ops/valueOutreach.d.ts +70 -0
  246. package/dist/ops/valueOutreach.js +244 -0
  247. package/dist/personality/drift.d.ts +45 -0
  248. package/dist/personality/drift.js +106 -0
  249. package/dist/personality/events.d.ts +25 -0
  250. package/dist/personality/events.js +37 -0
  251. package/dist/personality/evolveOps.d.ts +20 -0
  252. package/dist/personality/evolveOps.js +93 -0
  253. package/dist/personality/index.d.ts +11 -0
  254. package/dist/personality/index.js +11 -0
  255. package/dist/personality/mutate.d.ts +21 -0
  256. package/dist/personality/mutate.js +39 -0
  257. package/dist/personality/pivot.d.ts +23 -0
  258. package/dist/personality/pivot.js +22 -0
  259. package/dist/personality/prompt.d.ts +6 -0
  260. package/dist/personality/prompt.js +62 -0
  261. package/dist/personality/riskGate.d.ts +42 -0
  262. package/dist/personality/riskGate.js +80 -0
  263. package/dist/personality/schema.d.ts +255 -0
  264. package/dist/personality/schema.js +119 -0
  265. package/dist/personality/select.d.ts +46 -0
  266. package/dist/personality/select.js +71 -0
  267. package/dist/personality/stats.d.ts +46 -0
  268. package/dist/personality/stats.js +82 -0
  269. package/dist/personality/store.d.ts +35 -0
  270. package/dist/personality/store.js +92 -0
  271. package/dist/schema/common.d.ts +28 -0
  272. package/dist/schema/common.js +17 -0
  273. package/dist/schema/index.d.ts +5 -0
  274. package/dist/schema/index.js +5 -0
  275. package/dist/schema/material.d.ts +201 -0
  276. package/dist/schema/material.js +50 -0
  277. package/dist/schema/problem.d.ts +160 -0
  278. package/dist/schema/problem.js +45 -0
  279. package/dist/schema/proofOfWork.d.ts +97 -0
  280. package/dist/schema/proofOfWork.js +9 -0
  281. package/dist/schema/signal.d.ts +41 -0
  282. package/dist/schema/signal.js +19 -0
  283. package/dist/shadow/index.d.ts +6 -0
  284. package/dist/shadow/index.js +6 -0
  285. package/dist/shadow/jsonlSink.d.ts +17 -0
  286. package/dist/shadow/jsonlSink.js +27 -0
  287. package/dist/shadow/redact.d.ts +11 -0
  288. package/dist/shadow/redact.js +18 -0
  289. package/dist/shadow/shadowHub.d.ts +12 -0
  290. package/dist/shadow/shadowHub.js +42 -0
  291. package/dist/shadow/shadowMailbox.d.ts +17 -0
  292. package/dist/shadow/shadowMailbox.js +25 -0
  293. package/dist/shadow/shadowStore.d.ts +18 -0
  294. package/dist/shadow/shadowStore.js +55 -0
  295. package/dist/shadow/sink.d.ts +37 -0
  296. package/dist/shadow/sink.js +18 -0
  297. package/dist/signals/cycleHistoryFromEvents.d.ts +11 -0
  298. package/dist/signals/cycleHistoryFromEvents.js +96 -0
  299. package/dist/signals/expand.d.ts +34 -0
  300. package/dist/signals/expand.js +125 -0
  301. package/dist/signals/extractor.d.ts +25 -0
  302. package/dist/signals/extractor.js +57 -0
  303. package/dist/signals/index.d.ts +6 -0
  304. package/dist/signals/index.js +6 -0
  305. package/dist/signals/metaSignals.d.ts +57 -0
  306. package/dist/signals/metaSignals.js +158 -0
  307. package/dist/signals/signalGate.d.ts +15 -0
  308. package/dist/signals/signalGate.js +40 -0
  309. package/dist/signals/traceSignals.d.ts +44 -0
  310. package/dist/signals/traceSignals.js +123 -0
  311. package/dist/signatures/classifier.d.ts +16 -0
  312. package/dist/signatures/classifier.js +29 -0
  313. package/dist/signatures/index.d.ts +2 -0
  314. package/dist/signatures/index.js +2 -0
  315. package/dist/signatures/signatures.d.ts +22 -0
  316. package/dist/signatures/signatures.js +42 -0
  317. package/dist/strategy/experiment.d.ts +48 -0
  318. package/dist/strategy/experiment.js +63 -0
  319. package/dist/strategy/index.d.ts +2 -0
  320. package/dist/strategy/index.js +2 -0
  321. package/dist/strategy/strategyPoint.d.ts +30 -0
  322. package/dist/strategy/strategyPoint.js +23 -0
  323. package/dist/trace/index.d.ts +2 -0
  324. package/dist/trace/index.js +2 -0
  325. package/dist/trace/trajectory.d.ts +82 -0
  326. package/dist/trace/trajectory.js +117 -0
  327. package/dist/trace/trajectoryExport.d.ts +237 -0
  328. package/dist/trace/trajectoryExport.js +1818 -0
  329. package/dist/trigger/budget.d.ts +16 -0
  330. package/dist/trigger/budget.js +20 -0
  331. package/dist/trigger/engine.d.ts +12 -0
  332. package/dist/trigger/engine.js +23 -0
  333. package/dist/trigger/index.d.ts +6 -0
  334. package/dist/trigger/index.js +6 -0
  335. package/dist/trigger/problemBuilder.d.ts +24 -0
  336. package/dist/trigger/problemBuilder.js +32 -0
  337. package/dist/trigger/trigger.d.ts +22 -0
  338. package/dist/trigger/trigger.js +27 -0
  339. package/dist/trigger/valueModel.d.ts +26 -0
  340. package/dist/trigger/valueModel.js +25 -0
  341. package/dist/trigger/wfq.d.ts +13 -0
  342. package/dist/trigger/wfq.js +25 -0
  343. package/dist/util/fileLock.d.ts +24 -0
  344. package/dist/util/fileLock.js +227 -0
  345. package/dist/util/index.d.ts +2 -0
  346. package/dist/util/index.js +2 -0
  347. package/dist/util/version.d.ts +12 -0
  348. package/dist/util/version.js +51 -0
  349. package/dist/verify/index.d.ts +4 -0
  350. package/dist/verify/index.js +4 -0
  351. package/dist/verify/rollback.d.ts +22 -0
  352. package/dist/verify/rollback.js +33 -0
  353. package/dist/verify/sandboxRunner.d.ts +39 -0
  354. package/dist/verify/sandboxRunner.js +116 -0
  355. package/dist/verify/sandboxedValidation.d.ts +35 -0
  356. package/dist/verify/sandboxedValidation.js +56 -0
  357. package/dist/verify/validation.d.ts +41 -0
  358. package/dist/verify/validation.js +85 -0
  359. package/dist/wire/canonicalize.golden.d.ts +23 -0
  360. package/dist/wire/canonicalize.golden.js +46 -0
  361. package/dist/wire/geneHints.d.ts +64 -0
  362. package/dist/wire/geneHints.js +100 -0
  363. package/dist/wire/index.d.ts +91 -0
  364. package/dist/wire/index.js +3 -0
  365. package/dist/wire/schemaGate.d.ts +11 -0
  366. package/dist/wire/schemaGate.js +49 -0
  367. package/dist/workflow/dsl.d.ts +54 -0
  368. package/dist/workflow/dsl.js +15 -0
  369. package/dist/workflow/engine.d.ts +30 -0
  370. package/dist/workflow/engine.js +94 -0
  371. package/dist/workflow/index.d.ts +2 -0
  372. package/dist/workflow/index.js +2 -0
  373. package/package.json +29 -0
@@ -0,0 +1,417 @@
1
+ import { canTransition } from '../cycle/stateMachine.js';
2
+ import {} from './geneSelection.js';
3
+ import { detectPlateau } from './exploration.js';
4
+ import { epigeneticPenaltyForIds } from './epigenetics.js';
5
+ import { deriveConfidenceEdges, confidenceFor, signalFingerprint, inertBannedGeneIds, INERT_BAN_OBSERVATION_WINDOW, } from './confidence.js';
6
+ import { captureEnvFingerprint, envFingerprintKey } from '../bootstrap/envFingerprint.js';
7
+ import { buildMutation } from './mutation.js';
8
+ import { gatePersonalityRisk } from '../personality/riskGate.js';
9
+ import { emitPersonalityRiskGated, emitPersonalitySelected } from '../personality/events.js';
10
+ import { applySelectForRun, applyStatsUpdate, applyForcePivot } from '../personality/evolveOps.js';
11
+ import { solidify } from './solidify.js';
12
+ import { buildEvolutionEvent } from './evolutionEvent.js';
13
+ import { cycleRecordsFromEvents } from '../signals/cycleHistoryFromEvents.js';
14
+ import { computeMetaSignals, deriveCycleHistory } from '../signals/metaSignals.js';
15
+ import { resolveStrategy } from './strategyPresets.js';
16
+ import { classifyCycleFailure, } from './cycleFailureClassifier.js';
17
+ import { isDistilledGeneId } from './geneIntake.js';
18
+ /** Read the trailing run of cycle outcomes (newest last) from the event log, for plateau detection. */
19
+ function recentCycleOutcomes(ingestor, window) {
20
+ let events;
21
+ try {
22
+ events = ingestor.tail(window);
23
+ }
24
+ catch {
25
+ return [];
26
+ }
27
+ const out = [];
28
+ for (const e of events) {
29
+ if (e.type === 'cycle.solidified')
30
+ out.push('success');
31
+ else if (e.type === 'cycle.failed')
32
+ out.push('failed');
33
+ }
34
+ return out;
35
+ }
36
+ /** Read recent per-(gene, env) cycle outcomes from the event log, for epigenetic per-environment suppression. */
37
+ function recentGeneOutcomes(ingestor, window) {
38
+ let events;
39
+ try {
40
+ events = ingestor.tail(window);
41
+ }
42
+ catch {
43
+ return [];
44
+ }
45
+ const out = [];
46
+ for (const e of events) {
47
+ if (e.type !== 'cycle.solidified' && e.type !== 'cycle.failed')
48
+ continue;
49
+ const p = e.payload;
50
+ const gene = typeof p?.gene === 'string' ? p.gene : undefined;
51
+ const env = typeof p?.env === 'string' ? p.env : undefined;
52
+ if (!gene || !env)
53
+ continue; // pre-epigenetic events lack the tags; skip
54
+ out.push({ geneId: gene, envKey: env, status: e.type === 'cycle.solidified' ? 'success' : 'failed' });
55
+ }
56
+ return out;
57
+ }
58
+ function historyMetaSignals(ingestor, window) {
59
+ try {
60
+ return computeMetaSignals(deriveCycleHistory(cycleRecordsFromEvents(ingestor.tail(window))));
61
+ }
62
+ catch {
63
+ return [];
64
+ }
65
+ }
66
+ function historyCycleCount(ingestor, window, currentCycleId) {
67
+ let events;
68
+ try {
69
+ events = ingestor.tail(window);
70
+ }
71
+ catch {
72
+ return 1;
73
+ }
74
+ const cycleIds = new Set();
75
+ for (const e of events) {
76
+ if (e.type !== 'cycle.started' && e.type !== 'cycle.solidified' && e.type !== 'cycle.failed' && e.type !== 'cycle.aborted')
77
+ continue;
78
+ const p = e.payload;
79
+ if (typeof p?.cycleId === 'string')
80
+ cycleIds.add(p.cycleId);
81
+ }
82
+ cycleIds.add(currentCycleId);
83
+ return cycleIds.size;
84
+ }
85
+ function mergeSignals(signals, metaSignals) {
86
+ return [...new Set([...signals, ...metaSignals])];
87
+ }
88
+ function candidateIds(c) {
89
+ return [...new Set([c.geneId, c.assetId].filter((id) => typeof id === 'string' && id.length > 0))];
90
+ }
91
+ function isCandidateInSet(c, ids) {
92
+ return candidateIds(c).some((id) => ids.has(id));
93
+ }
94
+ function categoryForStrategy(category, strategy) {
95
+ if (strategy.name === 'innovate')
96
+ return 'innovate';
97
+ if (strategy.name === 'steady-state')
98
+ return 'optimize';
99
+ return category;
100
+ }
101
+ /**
102
+ * Derive preferred-gene confidence observations from the event log (positive cross-cycle learning).
103
+ * Each terminal cycle (cycle.solidified / cycle.failed) carries the gene that ran and the outcome; the signals
104
+ * that drove it are carried by the cycle.signals_collected event of the SAME cycleId. We join the two on
105
+ * cycleId, fingerprint the signals, and emit one (signalFingerprint, geneId, status, at) observation per cycle.
106
+ * Reading from the same append-only log makes this fully replayable. ad-hoc (innovate) cycles have no gene to
107
+ * credit and are skipped.
108
+ */
109
+ function recentConfidenceObservations(ingestor, window) {
110
+ let events;
111
+ try {
112
+ events = ingestor.tail(window);
113
+ }
114
+ catch {
115
+ return [];
116
+ }
117
+ // First pass: map cycleId → its collected signals.
118
+ const signalsByCycle = new Map();
119
+ for (const e of events) {
120
+ if (e.type !== 'cycle.signals_collected')
121
+ continue;
122
+ const p = e.payload;
123
+ const cycleId = typeof p?.cycleId === 'string' ? p.cycleId : undefined;
124
+ const signalSource = Array.isArray(p?.baseSignals) ? p.baseSignals : p?.signals;
125
+ const signals = Array.isArray(signalSource) ? signalSource.map(String) : undefined;
126
+ if (!cycleId || !signals)
127
+ continue;
128
+ signalsByCycle.set(cycleId, signals);
129
+ }
130
+ // Second pass: for each terminal cycle with a real gene, emit an observation under its signal fingerprint.
131
+ const out = [];
132
+ for (const e of events) {
133
+ if (e.type !== 'cycle.solidified' && e.type !== 'cycle.failed')
134
+ continue;
135
+ const p = e.payload;
136
+ const cycleId = typeof p?.cycleId === 'string' ? p.cycleId : undefined;
137
+ const gene = typeof p?.gene === 'string' ? p.gene : undefined;
138
+ if (!cycleId || !gene || gene === 'ad-hoc')
139
+ continue; // innovate cycles credit no existing gene
140
+ const signals = signalsByCycle.get(cycleId);
141
+ if (!signals)
142
+ continue; // cannot fingerprint without the cycle's signals
143
+ const at = Date.parse(e.ts);
144
+ // #195: a solidified cycle that produced no measurable value (producedValue === false) is INERT, not a
145
+ // success — it must build no confidence and instead feeds the inert-streak ban. Failures stay 'failed'.
146
+ // Older events predate the producedValue tag; when it is absent we treat the cycle as a real success, so we
147
+ // never retroactively reclassify history into a surprise ban.
148
+ const status = e.type === 'cycle.failed' ? 'failed' : p?.producedValue === false ? 'inert' : 'success';
149
+ out.push({
150
+ signalFingerprint: signalFingerprint(signals),
151
+ geneId: gene,
152
+ status,
153
+ at: Number.isFinite(at) ? at : 0,
154
+ });
155
+ }
156
+ return out;
157
+ }
158
+ /**
159
+ * cycle engine(M4A-1d): signals → trigger → 选 gene → mutation → 执行 → solidify → Capsule + EvolutionEvent.
160
+ * 每阶段 emit root_events 并校验状态机迁移; 写入序列守硬化 A4(capsule asset_id 先 → 填 capsule_id → event asset_id).
161
+ */
162
+ export class CycleEngine {
163
+ deps;
164
+ constructor(deps) {
165
+ this.deps = deps;
166
+ }
167
+ async runCycle(input) {
168
+ const { ingestor, store } = this.deps;
169
+ const now = this.deps.now();
170
+ const envKey = envFingerprintKey((this.deps.envFingerprint ?? captureEnvFingerprint)());
171
+ const reasons = [];
172
+ const baseSignals = input.signals;
173
+ const metaSignals = historyMetaSignals(ingestor, 100);
174
+ const cycleSignals = mergeSignals(baseSignals, metaSignals);
175
+ // PORT v1 #279 issue-reporter: classify cycle.failed payloads when the caller supplies host context. The
176
+ // post-solidify failed path adds V2-local context (current gene + blast radius) so hard-filtered local no-op
177
+ // genes are reachable without re-emitting the old ban_gene soft signal.
178
+ const failureClassPayload = (current, execSessionLog) => {
179
+ // The agent transcript (#279 production wiring) comes from the execution layer on a failed outcome; the
180
+ // explicit failureContext.sessionLog (tests / external callers) is the fallback. EITHER one lets the
181
+ // host_* buckets fire on the real path.
182
+ const sessionLog = execSessionLog ?? input.failureContext?.sessionLog;
183
+ // Locality must recognise V2's real gene prefix `gene_distilled_` (isDistilledGeneId), not just the
184
+ // V1-only `gene_auto_` / content-hash `sha256:` forms — otherwise this gate stays shut for every real V2
185
+ // gene and classifyCycleFailure never runs (mirror of isLocalGeneratedGene in cycleFailureClassifier.ts).
186
+ const localCurrentGene = current?.geneId !== undefined && (isDistilledGeneId(current.geneId) || /^sha256:/.test(current.geneId) || /^gene_auto_/.test(current.geneId));
187
+ // Run classification when we have ANY host context (an exec transcript or an injected failureContext) OR a
188
+ // local-gene blast to judge; otherwise stay default-open (unclassified, no payload).
189
+ const hasHostContext = sessionLog !== undefined || input.failureContext !== undefined;
190
+ if (!hasHostContext && (current?.blastRadius === undefined || !localCurrentGene))
191
+ return {};
192
+ const r = classifyCycleFailure({
193
+ signals: cycleSignals,
194
+ ...(sessionLog !== undefined ? { sessionLog } : {}),
195
+ ...(input.failureContext?.recentFailedEvents !== undefined ? { recentEvents: input.failureContext.recentFailedEvents } : {}),
196
+ ...(current?.geneId !== undefined ? { geneId: current.geneId } : {}),
197
+ ...(current?.blastRadius !== undefined ? { blastRadius: current.blastRadius } : {}),
198
+ });
199
+ return r.reason ? { failure_class: r.failureClass, failure_class_reason: r.reason } : { failure_class: r.failureClass };
200
+ };
201
+ const strategy = resolveStrategy({ name: input.strategyName, signals: cycleSignals, cycleCount: historyCycleCount(ingestor, 1000, input.cycleId) });
202
+ const cycleCategory = categoryForStrategy(input.category, strategy);
203
+ let stage = 'none';
204
+ const advance = (to) => {
205
+ if (!canTransition(stage, to))
206
+ throw new Error(`非法 cycle 迁移 ${stage}→${to}`);
207
+ stage = to;
208
+ };
209
+ await ingestor.ingest({ type: 'cycle.started', human: { title: `cycle ${input.cycleId} 启动` }, payload: { cycleId: input.cycleId, problemId: input.problem.id } });
210
+ advance('started');
211
+ await ingestor.ingest({
212
+ type: 'cycle.signals_collected',
213
+ human: { title: `采集 ${cycleSignals.length} 信号` },
214
+ payload: {
215
+ cycleId: input.cycleId,
216
+ signals: cycleSignals,
217
+ baseSignals,
218
+ ...(metaSignals.length > 0 ? { metaSignals, strategy: strategy.name } : {}),
219
+ },
220
+ });
221
+ advance('signals_collected');
222
+ // 触发闸
223
+ const trig = this.deps.trigger ? await this.deps.trigger(input.problem, now) : { trigger: true, reasons: ['无 trigger 注入, 直接触发'], valueScore: 0 };
224
+ if (!trig.trigger) {
225
+ await ingestor.ingest({ type: 'cycle.aborted', human: { title: `cycle ${input.cycleId} 抑制` }, payload: { cycleId: input.cycleId, reasons: trig.reasons } });
226
+ advance('aborted');
227
+ return { cycleId: input.cycleId, triggered: false, finalStage: stage, reasons: trig.reasons };
228
+ }
229
+ // 选 gene(可解释决策). Exploration: when recent cycles plateau, enable drift to escape local optima.
230
+ const recentOutcomes = recentCycleOutcomes(ingestor, 100);
231
+ const plateau = detectPlateau(recentOutcomes);
232
+ // 可进化人格(可选). 未注入 ⇒ personalityForRun 留 undefined, 整段跳过, 后续行为逐字不变.
233
+ // 顺序: ③平台期先强制转向(拉高 creativity/risk 进探索) → ①自然选择朝最佳桶靠拢 + 规则触发变异.
234
+ // 选出的状态落盘(applySelectForRun 内 save), exec bridge 之后读回注入 prompt(用途①); 也喂本轮风险闸.
235
+ let personalityForRun;
236
+ let personalityKnown = false;
237
+ if (this.deps.personality) {
238
+ const personalityDeps = { store: this.deps.personality, ingestor, cycleId: input.cycleId };
239
+ // ③ plateau pivot: 在 select 之前, 让 select 从 pivot 后的 current 起步(personality.pivoted).
240
+ if (plateau.active) {
241
+ await applyForcePivot(personalityDeps, { severity: plateau.severity, evalsSinceImprovement: plateau.count });
242
+ }
243
+ // ① 自然选择 + 触发变异(真变异才发 personality.mutated). driftEnabled 跟随 plateau;
244
+ // recentEvents 由事件日志的近期 outcome 派生, 让"失败连击触发变异"在环内真正生效.
245
+ const sel = await applySelectForRun(personalityDeps, {
246
+ driftEnabled: plateau.active,
247
+ signals: baseSignals,
248
+ recentEvents: recentOutcomes.map((status) => ({ outcome: { status } })),
249
+ });
250
+ personalityForRun = sel.state;
251
+ personalityKnown = sel.known;
252
+ }
253
+ // 每轮结束把 outcome/score 回写到当轮所用人格桶(applyStatsUpdate: 只动 stats/history, 不挪 current).
254
+ // 在每个终止点调用, 使下一轮 applySelectForRun 的自然选择能朝这个桶靠拢. 未注入 ⇒ no-op.
255
+ const recordPersonalityOutcome = async (outcome, score) => {
256
+ if (!this.deps.personality || !personalityForRun)
257
+ return;
258
+ await applyStatsUpdate({ store: this.deps.personality, ingestor, cycleId: input.cycleId }, { personality: personalityForRun, outcome, score });
259
+ };
260
+ const exploration = plateau.active
261
+ ? { plateau, driftEnabled: true, totalAttempts: input.candidates.reduce((s, c) => s + (c.view?.total ?? 0), 0) }
262
+ : undefined;
263
+ // Epigenetic: penalize candidates that fail in THIS environment class (derived from per-env outcome history).
264
+ const geneOutcomes = recentGeneOutcomes(ingestor, 200);
265
+ // Confidence (positive cross-cycle learning): reward candidates that keep succeeding under the CURRENT
266
+ // signal fingerprint. Derived from the same event log, applied as a soft fourth factor in selection.
267
+ const confidenceObs = recentConfidenceObservations(ingestor, 200);
268
+ const confidenceEdges = deriveConfidenceEdges(confidenceObs);
269
+ const currentFingerprint = signalFingerprint(baseSignals);
270
+ // #195: drop genes stuck producing only inert (zero-work) cycles on THIS signal. A sole-matching do-nothing
271
+ // gene is otherwise re-selected every cycle (drift can't diversify a single candidate) and the failure-streak
272
+ // ban never fires because nothing "fails" — so it dominates --loop forever while producing no artifacts.
273
+ // Removing it makes selection fall through to mutation (selected:null → fresh gene), restoring diversity.
274
+ // The ban is derived from a SEPARATE, much wider window than confidence scoring above: a banned gene runs as
275
+ // ad-hoc and emits no observation for itself, so a 200-event (~28-cycle) window would let intervening innovate
276
+ // cycles evict its inert run (or the earlier real success that exempts it) and the ban would flap. Recomputing
277
+ // it over INERT_BAN_OBSERVATION_WINDOW events (mirroring v1's memory-graph read horizon) keeps the trailing run
278
+ // and the successCount exemption alive across intervening cycles. tail() reads the full log either way (no I/O cost).
279
+ const inertBanObs = recentConfidenceObservations(ingestor, INERT_BAN_OBSERVATION_WINDOW);
280
+ const inertBanned = inertBannedGeneIds(deriveConfidenceEdges(inertBanObs), inertBanObs, currentFingerprint);
281
+ const candidates = input.candidates
282
+ .filter((c) => !isCandidateInSet(c, inertBanned))
283
+ .map((c) => {
284
+ const epi = epigeneticPenaltyForIds(candidateIds(c), envKey, geneOutcomes);
285
+ const conf = confidenceFor(confidenceEdges, currentFingerprint, c.geneId, now);
286
+ const withEpi = epi > 0 ? { ...c, epigeneticPenalty: epi } : c;
287
+ return conf > 0 ? { ...withEpi, confidence: conf } : withEpi;
288
+ });
289
+ // #97 distilled-gene fallback pool: attach the env-scoped epigenetic penalty (so selection can hard-skip a
290
+ // suppressed fallback, v1 parity) and drop any inert-banned id (composes with #195). Selection uses these only
291
+ // when no normal candidate clears the floor.
292
+ const distilledFallback = (input.distilledFallback ?? [])
293
+ .filter((c) => !isCandidateInSet(c, inertBanned))
294
+ .map((c) => {
295
+ const epi = epigeneticPenaltyForIds(candidateIds(c), envKey, geneOutcomes);
296
+ return epi > 0 ? { ...c, epigeneticPenalty: epi } : c;
297
+ });
298
+ const decision = (await this.deps.selection.run({ signals: baseSignals, candidates, floor: input.selectionFloor, ...(input.forcedGeneId !== undefined ? { forcedGeneId: input.forcedGeneId } : {}), ...(exploration ? { exploration } : {}), ...(distilledFallback.length > 0 ? { distilledFallback } : {}), ...(input.antiWarnings && input.antiWarnings.length > 0 ? { antiWarnings: input.antiWarnings } : {}) }, { now, cycleId: input.cycleId, ...(this.deps.rng ? { rng: this.deps.rng } : {}) }));
299
+ await ingestor.ingest({
300
+ type: 'decision.gene_selected',
301
+ human: { title: `选 gene ${decision.selectedGeneId ?? '(innovate)'}`, why: decision.candidates.map((c) => `${c.geneId}:${c.score.toFixed(3)}`).join(', ') || '无候选→innovate' },
302
+ payload: { cycleId: input.cycleId, selectedGeneId: decision.selectedGeneId, ...(decision.selectedAssetId ? { selectedAssetId: decision.selectedAssetId } : {}), candidates: decision.candidates, ...(decision.antiWarnings && decision.antiWarnings.length > 0 ? { antiWarnings: decision.antiWarnings } : {}), weightsVersion: decision.weightsVersion, strategy: decision.strategyName, strategyPreset: strategy.name, ...(plateau.active ? { plateau } : {}), ...(inertBanned.size > 0 ? { inertBanned: [...inertBanned] } : {}) },
303
+ });
304
+ advance('gene_selected');
305
+ const geneId = decision.selectedGeneId ?? 'ad-hoc';
306
+ // 变异
307
+ const builtMutation = buildMutation({ decision, category: cycleCategory, signals: cycleSignals, target: input.target, expectedEffect: input.expectedEffect });
308
+ // 人格风险闸(用途②): 高危人格禁 innovate / 未达阈值的 high-risk 降级. 未注入人格 ⇒ 原样放行.
309
+ // 用本轮 applySelectForRun 选出的状态(personalityForRun), 而非再读一次 store.currentState() ——
310
+ // 保证"喂 prompt 的风格 / 过风险闸 / 回写统计"三者用的是同一个当轮人格.
311
+ let mutation = builtMutation;
312
+ if (this.deps.personality && personalityForRun) {
313
+ await emitPersonalitySelected(ingestor, { cycleId: input.cycleId, personality: personalityForRun, known: personalityKnown });
314
+ const gate = gatePersonalityRisk(builtMutation, personalityForRun);
315
+ mutation = gate.mutation;
316
+ await emitPersonalityRiskGated(ingestor, { cycleId: input.cycleId, personality: personalityForRun, gate });
317
+ }
318
+ await ingestor.ingest({ type: 'mutation.built', human: { title: `变异 ${mutation.category} → ${mutation.target}` }, payload: { cycleId: input.cycleId, mutationId: mutation.id, risk: mutation.risk_level, category: mutation.category, strategyPreset: strategy.name } });
319
+ advance('mutation_built');
320
+ // 执行(agent/runtime)
321
+ let exec;
322
+ try {
323
+ exec = await input.execute(mutation, decision);
324
+ }
325
+ catch (e) {
326
+ const msg = e instanceof Error ? e.message : String(e);
327
+ // execute threw before returning a transcript; the error text IS the host context (a provider error that
328
+ // surfaces as a thrown exception still classifies as host_provider_error). Pass it as the sessionLog.
329
+ await ingestor.ingest({ type: 'cycle.failed', human: { title: `cycle ${input.cycleId} 执行抛错` }, payload: { cycleId: input.cycleId, error: msg, gene: geneId, env: envKey, ...failureClassPayload({ geneId }, msg) } });
330
+ advance('failed');
331
+ await recordPersonalityOutcome('failed', null);
332
+ return { cycleId: input.cycleId, triggered: true, finalStage: stage, decision, mutation, reasons: [...reasons, `执行异常: ${msg}`] };
333
+ }
334
+ if (input.solidifyPermit) {
335
+ let permit;
336
+ try {
337
+ permit = await input.solidifyPermit({
338
+ cycleId: input.cycleId,
339
+ geneId,
340
+ signals: cycleSignals,
341
+ mutation,
342
+ decision,
343
+ outcome: exec.outcome,
344
+ ...(exec.proofOfWork ? { proofOfWork: exec.proofOfWork } : {}),
345
+ ...(exec.strongEvidence !== undefined ? { strongEvidence: exec.strongEvidence } : {}),
346
+ });
347
+ }
348
+ catch {
349
+ permit = { ok: false, reason: 'solidify_permit_error' };
350
+ }
351
+ if (!permit.ok) {
352
+ const reason = `solidify permit denied: ${permit.reason}`;
353
+ await ingestor.ingest({
354
+ type: 'cycle.failed',
355
+ human: { title: `cycle ${input.cycleId} permit denied` },
356
+ payload: {
357
+ cycleId: input.cycleId,
358
+ reason: permit.reason,
359
+ gene: geneId,
360
+ env: envKey,
361
+ ...failureClassPayload({ geneId }),
362
+ },
363
+ });
364
+ advance('failed');
365
+ await recordPersonalityOutcome('failed', null);
366
+ return { cycleId: input.cycleId, triggered: true, finalStage: stage, decision, mutation, reasons: [...reasons, reason] };
367
+ }
368
+ }
369
+ // solidify → Capsule
370
+ const sol = solidify({
371
+ geneId, trigger: cycleSignals, summary: input.summary, confidence: input.confidence,
372
+ outcome: exec.outcome, proofOfWork: exec.proofOfWork, strongEvidence: exec.strongEvidence,
373
+ });
374
+ reasons.push(...sol.reasons);
375
+ await store.put(sol.capsule);
376
+ await ingestor.ingest({ type: 'capsule.produced', human: { title: `Capsule ${sol.resolutionStatus}` }, payload: { cycleId: input.cycleId, capsuleId: sol.capsule.asset_id, resolutionStatus: sol.resolutionStatus, producedValue: sol.producedValue, blastRadius: sol.capsule.blast_radius } });
377
+ // EvolutionEvent(capsule_id 引 Capsule; outcome 真值在此)
378
+ const event = buildEvolutionEvent({
379
+ intent: cycleCategory, signals: cycleSignals, genesUsed: decision.selectedGeneId ? [decision.selectedGeneId] : [],
380
+ mutationId: mutation.id, blastRadius: sol.capsule.blast_radius, outcome: exec.outcome,
381
+ capsuleId: sol.capsule.asset_id, sourceType: decision.selectedGeneId ? 'reused' : 'generated',
382
+ });
383
+ await store.put(event);
384
+ await ingestor.ingest({ type: 'evolution_event.projected', human: { title: `世代记录 ${exec.outcome.status}` }, payload: { cycleId: input.cycleId, eventId: event.asset_id, capsuleId: sol.capsule.asset_id, outcome: exec.outcome } });
385
+ if (exec.outcome.status === 'failed') {
386
+ await ingestor.ingest({
387
+ type: 'cycle.failed',
388
+ human: { title: `cycle ${input.cycleId} 失败` },
389
+ payload: {
390
+ cycleId: input.cycleId,
391
+ resolutionStatus: sol.resolutionStatus,
392
+ outcome: exec.outcome,
393
+ gene: geneId,
394
+ env: envKey,
395
+ // Only forward blast radius as a no-op signal when it was measured from a git_diff proof. solidify.ts
396
+ // forces blast to {0,0} for non-git_diff proofs (artifact_hash/external_receipt/tool_call_trace),
397
+ // where {0,0} means "blast unknown", NOT "no change" — forwarding it would mis-classify a productive
398
+ // run as local_gene_no_blast. Undefined here falls through to default-open unclassified.
399
+ ...failureClassPayload({
400
+ geneId,
401
+ ...((exec.proofOfWork === undefined || exec.proofOfWork.kind === 'git_diff')
402
+ ? { blastRadius: sol.capsule.blast_radius }
403
+ : {}),
404
+ }, exec.sessionLog),
405
+ },
406
+ });
407
+ advance('failed');
408
+ }
409
+ else {
410
+ await ingestor.ingest({ type: 'cycle.solidified', human: { title: `cycle ${input.cycleId} 固化` }, payload: { cycleId: input.cycleId, capsuleId: sol.capsule.asset_id, outcome: exec.outcome, gene: geneId, env: envKey, producedValue: sol.producedValue } });
411
+ advance('solidified');
412
+ }
413
+ // 人格统计回写(用途②): 用本轮真实 outcome/score 回写当轮人格桶, 供下一轮自然选择靠拢. 未注入 ⇒ no-op.
414
+ await recordPersonalityOutcome(exec.outcome.status, exec.outcome.score);
415
+ return { cycleId: input.cycleId, triggered: true, finalStage: stage, decision, mutation, capsule: sol.capsule, event, resolutionStatus: sol.resolutionStatus, reasons };
416
+ }
417
+ }
@@ -0,0 +1,52 @@
1
+ /** Bucket the failure falls into. unclassified = default-open (treat as a real evolver bug worth filing).
2
+ * Bucket string matches V1 (EvoMap/evolver-private-dev#279 commit a89e123e) byte-for-byte so wire / UI
3
+ * consumers can branch on the same value regardless of producer. */
4
+ export type CycleFailureClass = 'host_no_transcript' | 'host_provider_error' | 'local_gene_no_blast' | 'unclassified';
5
+ /** Shape of a recent cycle event the classifier reads. Loose so callers can pass partial DTOs. */
6
+ export interface ClassifyRecentEvent {
7
+ gene?: string;
8
+ outcome?: {
9
+ status?: string;
10
+ };
11
+ blast_radius?: {
12
+ files?: number;
13
+ lines?: number;
14
+ };
15
+ blastRadius?: {
16
+ files?: number;
17
+ lines?: number;
18
+ };
19
+ meta?: {
20
+ empty_cycle?: boolean;
21
+ };
22
+ }
23
+ export interface ClassifyCycleFailureInput {
24
+ /** Cycle signals — used to detect ban_gene:<id> and locality (gene_distilled_/sha256:/gene_auto_). */
25
+ signals?: readonly string[];
26
+ /** Current failed gene in V2's hard-ban/filtering path. */
27
+ geneId?: string;
28
+ /** Current failed capsule blast radius when available. */
29
+ blastRadius?: {
30
+ files?: number;
31
+ lines?: number;
32
+ };
33
+ /** Recent cycle events (failed cycles only matter — blast_radius / meta.empty_cycle are checked). */
34
+ recentEvents?: readonly ClassifyRecentEvent[];
35
+ /** Optional session transcript. Empty / sentinel / provider-error text drives host_* buckets. */
36
+ sessionLog?: string;
37
+ }
38
+ export interface CycleFailureClassification {
39
+ /** The bucket. */
40
+ failureClass: CycleFailureClass;
41
+ /** Short human reason (≤ ~100 chars). Empty for 'unclassified'. */
42
+ reason: string;
43
+ }
44
+ /**
45
+ * Triage a cycle failure into a root-cause bucket. Default-open: if no rule fires, returns 'unclassified',
46
+ * which means "treat this as a real evolver bug" (so unclassified faults are never hidden).
47
+ *
48
+ * Pure — does not read filesystem, env, or events store.
49
+ */
50
+ export declare function classifyCycleFailure(opts: ClassifyCycleFailureInput | null | undefined): CycleFailureClassification;
51
+ /** Buckets we are confident are host-side; everything else is default-open (filed / surfaced). */
52
+ export declare const HOST_SUPPRESS_CLASSES: ReadonlySet<CycleFailureClass>;
@@ -0,0 +1,104 @@
1
+ // Local root-cause triage for cycle failures (ported from v1 src/gep/issueReporter.js: classifyFailure, V1 PR #279).
2
+ //
3
+ // V1 #279 split signal-side (zero-blast cycles no longer feed failure-loop) from issue-reporter-side (triage host vs.
4
+ // evolver faults before opening a public issue). V2 #216 already ported the signal-side via the inert-gene path
5
+ // (producedValue===false → 'inert' in confidence/inertBan). This module is the missing issue-reporter half: it
6
+ // classifies a cycle failure into one of four buckets so downstream consumers (CLI/issue-reporter, hub UI, telemetry)
7
+ // can split confidently-host-side failures from genuine local-gene failures.
8
+ //
9
+ // Pure function — same input shape V1 used (signals, recentEvents, sessionLog). Inert-handling in cycleEngine
10
+ // (#216) is untouched; if the cycle engine threads context here at the failed-write site, the classification is
11
+ // written to the cycle.failed event payload as `failure_class`. Otherwise it is purely opt-in / advisory.
12
+ import { isDistilledGeneId } from './geneIntake.js';
13
+ // "[NO SESSION LOGS FOUND]" et al. — the host gave evolver nothing to evolve from.
14
+ const HOST_NO_TRANSCRIPT_RE = /\[no session logs? found\]|no session log|no transcript/i;
15
+ // Host LLM-provider errors. None of these strings are emitted by evolver core (v1 #279 verified the same;
16
+ // keeping the regex byte-for-byte equivalent), so their presence means the host Agent's provider call failed.
17
+ const HOST_PROVIDER_ERR_RE = /\bLLM ERROR\b|\bMaxTokens\b|field MaxTokens|max_tokens[^\n]{0,24}invalid|insufficient_quota|invalid_api_key|\brate limit(?:ed| exceeded)?\b|quota exceeded|context length exceeded|maximum context length/i;
18
+ // Locality prefixes a "locally-generated" gene id can carry:
19
+ // - `gene_distilled_` — the ONLY prefix V2 actually mints (geneIntake.DISTILLED_ID_PREFIX,
20
+ // surfaced via isDistilledGeneId). Without this the whole `local_gene_no_blast` bucket never
21
+ // fired for real V2 genes (they all carry this prefix). Reuse the single-source-of-truth helper.
22
+ // - `gene_auto_` — V1-only legacy form (V2 never produces it); kept so V1-shaped inputs still match.
23
+ // - `sha256:` — a content asset_id (not a logical gene id); kept for back-compat / loose callers.
24
+ function isLocalGeneratedGene(geneId) {
25
+ return isDistilledGeneId(geneId) || /^sha256:/.test(geneId) || /^gene_auto_/.test(geneId);
26
+ }
27
+ function eventBlast(e) {
28
+ return e.blast_radius ?? e.blastRadius;
29
+ }
30
+ function isZeroBlast(e) {
31
+ const br = eventBlast(e);
32
+ return Boolean(e.meta?.empty_cycle) || (br !== undefined && br.files === 0 && br.lines === 0);
33
+ }
34
+ // NB: blast={files:0,lines:0} only means "real no-op" when it came from a git_diff proof. solidify.ts forces
35
+ // blast to {0,0} for non-git_diff proofs (artifact_hash/external_receipt/tool_call_trace), where it signals
36
+ // "blast unknown", not "no change". The cycleEngine guard only forwards blastRadius for git_diff proofs, so by
37
+ // the time we see a defined br here it is a true measured zero. If a caller ever passes a non-git_diff blast
38
+ // directly, this would mis-classify a productive run as no-op — keep the git_diff guard at the call site.
39
+ function blastIsZero(br) {
40
+ return br !== undefined && br.files === 0 && br.lines === 0;
41
+ }
42
+ function localNoBlastReason(geneId) {
43
+ return {
44
+ failureClass: 'local_gene_no_blast',
45
+ reason: `locally-generated gene ${geneId.slice(0, 20)}… produces no blast radius`,
46
+ };
47
+ }
48
+ /**
49
+ * Triage a cycle failure into a root-cause bucket. Default-open: if no rule fires, returns 'unclassified',
50
+ * which means "treat this as a real evolver bug" (so unclassified faults are never hidden).
51
+ *
52
+ * Pure — does not read filesystem, env, or events store.
53
+ */
54
+ export function classifyCycleFailure(opts) {
55
+ const signals = Array.isArray(opts?.signals) ? opts.signals : [];
56
+ const events = Array.isArray(opts?.recentEvents) ? opts.recentEvents : [];
57
+ const hasSessionLog = typeof opts?.sessionLog === 'string';
58
+ const log = hasSessionLog ? opts.sessionLog : undefined;
59
+ // (a) No transcript -> the host gave evolver nothing to evolve from.
60
+ if (hasSessionLog && (!log.trim() || HOST_NO_TRANSCRIPT_RE.test(log))) {
61
+ return { failureClass: 'host_no_transcript', reason: 'no session transcript to evolve from' };
62
+ }
63
+ // (b) Host LLM-provider error surfaced in the transcript.
64
+ const prov = hasSessionLog ? log.match(HOST_PROVIDER_ERR_RE) : null;
65
+ if (prov) {
66
+ return { failureClass: 'host_provider_error', reason: `host LLM-provider error: ${String(prov[0]).slice(0, 60)}` };
67
+ }
68
+ // (c-v2) V2 hard-bans/filters local no-op genes instead of emitting a soft ban_gene signal. Use the current
69
+ // failed gene + current blast radius when the engine supplies them, while keeping the legacy ban_gene branch below.
70
+ const currentGeneId = typeof opts?.geneId === 'string' ? opts.geneId : undefined;
71
+ if (currentGeneId && isLocalGeneratedGene(currentGeneId)) {
72
+ if (blastIsZero(opts?.blastRadius))
73
+ return localNoBlastReason(currentGeneId);
74
+ const failedForGene = events.filter((e) => e.outcome?.status === 'failed' && (e.gene === undefined || e.gene === currentGeneId));
75
+ if (failedForGene.length > 0 && failedForGene.every(isZeroBlast))
76
+ return localNoBlastReason(currentGeneId);
77
+ }
78
+ if (!currentGeneId) {
79
+ const failedLocalEvents = events.filter((e) => e.outcome?.status === 'failed' && typeof e.gene === 'string' && isLocalGeneratedGene(e.gene));
80
+ for (const e of failedLocalEvents) {
81
+ if (e.gene && isZeroBlast(e))
82
+ return localNoBlastReason(e.gene);
83
+ }
84
+ }
85
+ // (c) The banned gene is locally generated AND every recent failing cycle changed nothing — a no-op gene,
86
+ // not an evolver bug. (V2 #216 already drops such genes via the inert ban; this is the issue-reporter
87
+ // analogue, classifying after a failure_loop fires anyway.)
88
+ const banned = signals.find((s) => typeof s === 'string' && s.startsWith('ban_gene:'));
89
+ if (banned) {
90
+ const geneId = banned.replace('ban_gene:', '');
91
+ const failed = events.filter((e) => e?.outcome?.status === 'failed');
92
+ const allZeroBlast = failed.length > 0 && failed.every(isZeroBlast);
93
+ if (isLocalGeneratedGene(geneId) && allZeroBlast) {
94
+ return localNoBlastReason(geneId);
95
+ }
96
+ }
97
+ return { failureClass: 'unclassified', reason: '' };
98
+ }
99
+ /** Buckets we are confident are host-side; everything else is default-open (filed / surfaced). */
100
+ export const HOST_SUPPRESS_CLASSES = new Set([
101
+ 'host_no_transcript',
102
+ 'host_provider_error',
103
+ 'local_gene_no_blast',
104
+ ]);
@@ -0,0 +1,14 @@
1
+ import { envFingerprintKey } from '../bootstrap/envFingerprint.js';
2
+ export { envFingerprintKey };
3
+ export interface GeneEnvOutcome {
4
+ geneId: string;
5
+ envKey: string;
6
+ status: 'success' | 'failed';
7
+ }
8
+ /**
9
+ * Epigenetic penalty in [0, MAX_PENALTY] for `geneId` in the current `envKey`, from recent per-env outcomes.
10
+ * 0 when there is not enough evidence (< MIN_ENV_ATTEMPTS) or the in-env fail rate is below SUPPRESS_FAIL_RATE;
11
+ * scales linearly from 0 at SUPPRESS_FAIL_RATE up to MAX_PENALTY at a 100% in-env fail rate.
12
+ */
13
+ export declare function epigeneticPenaltyForIds(ids: readonly string[], envKey: string, outcomes: readonly GeneEnvOutcome[]): number;
14
+ export declare function epigeneticPenalty(geneId: string, envKey: string, outcomes: readonly GeneEnvOutcome[]): number;
@@ -0,0 +1,41 @@
1
+ // Epigenetic per-environment gene suppression (ported from v1 src/gep/epigenetics.js, adapted to v2's
2
+ // derive-from-events model). A gene that fails in the CURRENT environment class (but works elsewhere)
3
+ // should be penalized HERE only, not globally — its global health is untouched. v1 stored marks on the
4
+ // gene asset; v2 derives the penalty from recent per-(gene, env) outcomes at selection time, so no asset
5
+ // mutation and no schema churn. The penalty is subtracted from the gene's selection score; a gene that
6
+ // fails hard in this env falls in ranking (and below the floor → innovate) without being globally banned.
7
+ import { envFingerprintKey } from '../bootstrap/envFingerprint.js';
8
+ export { envFingerprintKey };
9
+ const MIN_ENV_ATTEMPTS = 3; // need this many attempts of the gene in this env before the penalty engages
10
+ const SUPPRESS_FAIL_RATE = 0.7; // fail rate (in this env) at/above which the penalty starts
11
+ const MAX_PENALTY = 0.5; // cap (subtracted from a [0,1]-ish score)
12
+ /**
13
+ * Epigenetic penalty in [0, MAX_PENALTY] for `geneId` in the current `envKey`, from recent per-env outcomes.
14
+ * 0 when there is not enough evidence (< MIN_ENV_ATTEMPTS) or the in-env fail rate is below SUPPRESS_FAIL_RATE;
15
+ * scales linearly from 0 at SUPPRESS_FAIL_RATE up to MAX_PENALTY at a 100% in-env fail rate.
16
+ */
17
+ export function epigeneticPenaltyForIds(ids, envKey, outcomes) {
18
+ if (!envKey)
19
+ return 0;
20
+ const idSet = new Set(ids.filter((id) => id.length > 0));
21
+ if (idSet.size === 0)
22
+ return 0;
23
+ let attempts = 0;
24
+ let fails = 0;
25
+ for (const o of outcomes) {
26
+ if (!idSet.has(o.geneId) || o.envKey !== envKey)
27
+ continue;
28
+ attempts += 1;
29
+ if (o.status === 'failed')
30
+ fails += 1;
31
+ }
32
+ if (attempts < MIN_ENV_ATTEMPTS)
33
+ return 0;
34
+ const failRate = fails / attempts;
35
+ if (failRate < SUPPRESS_FAIL_RATE)
36
+ return 0;
37
+ return Math.min(MAX_PENALTY, MAX_PENALTY * ((failRate - SUPPRESS_FAIL_RATE) / (1 - SUPPRESS_FAIL_RATE)));
38
+ }
39
+ export function epigeneticPenalty(geneId, envKey, outcomes) {
40
+ return epigeneticPenaltyForIds([geneId], envKey, outcomes);
41
+ }
@@ -0,0 +1,23 @@
1
+ import { type EvolutionEvent, type GepCategory } from '../wire/index.js';
2
+ export interface BuildEvolutionEventInput {
3
+ intent: GepCategory;
4
+ signals: readonly string[];
5
+ genesUsed: readonly string[];
6
+ mutationId: string;
7
+ blastRadius: {
8
+ files: number;
9
+ lines: number;
10
+ };
11
+ outcome: {
12
+ status: 'success' | 'failed';
13
+ score: number;
14
+ };
15
+ capsuleId: string | null;
16
+ sourceType: 'generated' | 'reused' | 'reference';
17
+ parent?: string | null;
18
+ }
19
+ /**
20
+ * EvolutionEvent = 世代记录(outcome 真值在此, 硬化 A4). 单向引 Capsule(capsule_id);
21
+ * 写入序列: capsule asset_id 先算 → 填 capsule_id → 再算 event asset_id.
22
+ */
23
+ export declare function buildEvolutionEvent(input: BuildEvolutionEventInput): EvolutionEvent;