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

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 (240) hide show
  1. package/assets/gep/genes.jsonl +5 -5
  2. package/dist/algo/bans.d.ts +10 -1
  3. package/dist/algo/bans.js +56 -6
  4. package/dist/algo/candidateAssembly.d.ts +25 -2
  5. package/dist/algo/candidateAssembly.js +171 -26
  6. package/dist/algo/capabilityCandidates.js +10 -0
  7. package/dist/algo/conversationSniffer.js +25 -8
  8. package/dist/algo/cycleEngine.d.ts +63 -5
  9. package/dist/algo/cycleEngine.js +320 -42
  10. package/dist/algo/cycleFailureClassifier.d.ts +1 -1
  11. package/dist/algo/evolutionEvent.d.ts +26 -0
  12. package/dist/algo/evolutionEvent.js +32 -0
  13. package/dist/algo/exploration.d.ts +7 -0
  14. package/dist/algo/exploration.js +16 -3
  15. package/dist/algo/geneHealth.d.ts +36 -3
  16. package/dist/algo/geneHealth.js +47 -4
  17. package/dist/algo/geneIntake.d.ts +46 -1
  18. package/dist/algo/geneIntake.js +121 -10
  19. package/dist/algo/geneSelection.d.ts +126 -6
  20. package/dist/algo/geneSelection.js +479 -36
  21. package/dist/algo/index.d.ts +6 -1
  22. package/dist/algo/index.js +6 -1
  23. package/dist/algo/kautoProjection.d.ts +41 -0
  24. package/dist/algo/kautoProjection.js +95 -0
  25. package/dist/algo/kautoValidator.d.ts +68 -0
  26. package/dist/algo/kautoValidator.js +256 -0
  27. package/dist/algo/memoryGraph.d.ts +62 -0
  28. package/dist/algo/memoryGraph.js +86 -0
  29. package/dist/algo/orchestrator.d.ts +17 -1
  30. package/dist/algo/orchestrator.js +30 -4
  31. package/dist/algo/publishEligibility.d.ts +34 -0
  32. package/dist/algo/publishEligibility.js +52 -0
  33. package/dist/algo/solidify.d.ts +11 -2
  34. package/dist/algo/solidify.js +37 -7
  35. package/dist/algo/ucb1.d.ts +53 -0
  36. package/dist/algo/ucb1.js +156 -0
  37. package/dist/assetrepair/hubRejection.d.ts +12 -0
  38. package/dist/assetrepair/hubRejection.js +109 -0
  39. package/dist/assetrepair/index.d.ts +2 -0
  40. package/dist/assetrepair/index.js +2 -0
  41. package/dist/assetrepair/repair.d.ts +33 -0
  42. package/dist/assetrepair/repair.js +155 -0
  43. package/dist/assetstore/assetSidecarRecords.d.ts +28 -0
  44. package/dist/assetstore/assetSidecarRecords.js +384 -0
  45. package/dist/assetstore/assetSidecarRecovery.d.ts +48 -0
  46. package/dist/assetstore/assetSidecarRecovery.js +288 -0
  47. package/dist/assetstore/assetStoreHealth.d.ts +81 -0
  48. package/dist/assetstore/assetStoreHealth.js +319 -0
  49. package/dist/assetstore/assetStoreLayout.d.ts +2 -0
  50. package/dist/assetstore/assetStoreLayout.js +6 -0
  51. package/dist/assetstore/assetStoreStorage.d.ts +42 -0
  52. package/dist/assetstore/assetStoreStorage.js +336 -0
  53. package/dist/assetstore/assetSyncLedger.d.ts +91 -1
  54. package/dist/assetstore/assetSyncLedger.js +718 -59
  55. package/dist/assetstore/foreignJsonlSource.d.ts +48 -0
  56. package/dist/assetstore/foreignJsonlSource.js +150 -0
  57. package/dist/assetstore/index.d.ts +5 -0
  58. package/dist/assetstore/index.js +5 -0
  59. package/dist/assetstore/learningHistory.js +3 -3
  60. package/dist/assetstore/localAssetStoreSnapshot.d.ts +51 -0
  61. package/dist/assetstore/localAssetStoreSnapshot.js +329 -0
  62. package/dist/assetstore/localJsonl.d.ts +12 -1
  63. package/dist/assetstore/localJsonl.js +251 -42
  64. package/dist/assetstore/provenance.d.ts +93 -4
  65. package/dist/assetstore/provenance.js +368 -84
  66. package/dist/assetstore/provider.d.ts +63 -0
  67. package/dist/assetstore/provider.js +97 -6
  68. package/dist/assetstore/reviewFilter.d.ts +19 -1
  69. package/dist/assetstore/reviewFilter.js +39 -1
  70. package/dist/assetstore/reviewLedger.d.ts +8 -2
  71. package/dist/assetstore/reviewLedger.js +71 -45
  72. package/dist/assetstore/unionReadStore.d.ts +25 -0
  73. package/dist/assetstore/unionReadStore.js +119 -0
  74. package/dist/benchmark/antiGeneBenchmark.d.ts +2 -0
  75. package/dist/benchmark/antiGeneBenchmark.js +4 -3
  76. package/dist/benchmark/antiGeneRollout.d.ts +2 -0
  77. package/dist/benchmark/antiGeneRollout.js +4 -3
  78. package/dist/benchmark/index.d.ts +3 -1
  79. package/dist/benchmark/index.js +3 -1
  80. package/dist/benchmark/selectionFlatAbstention.d.ts +152 -0
  81. package/dist/benchmark/selectionFlatAbstention.js +481 -0
  82. package/dist/benchmark/triggerShift.d.ts +62 -0
  83. package/dist/benchmark/triggerShift.js +106 -0
  84. package/dist/bootstrap/envFingerprint.d.ts +9 -0
  85. package/dist/bootstrap/envFingerprint.js +5 -0
  86. package/dist/bootstrap/index.d.ts +3 -1
  87. package/dist/bootstrap/index.js +3 -1
  88. package/dist/bootstrap/lifecycleBootstrap.d.ts +111 -0
  89. package/dist/bootstrap/lifecycleBootstrap.js +433 -0
  90. package/dist/bootstrap/v1EnvCompat.d.ts +113 -0
  91. package/dist/bootstrap/v1EnvCompat.js +303 -0
  92. package/dist/events/eventArchive.d.ts +2 -0
  93. package/dist/events/eventArchive.js +13 -3
  94. package/dist/events/eventSchema.d.ts +7 -7
  95. package/dist/events/eventStore.d.ts +2 -0
  96. package/dist/events/eventStore.js +5 -1
  97. package/dist/events/ingest.d.ts +2 -1
  98. package/dist/events/ingest.js +15 -0
  99. package/dist/events/paths.d.ts +12 -10
  100. package/dist/events/paths.js +24 -20
  101. package/dist/events/public.d.ts +2 -2
  102. package/dist/events/public.js +2 -2
  103. package/dist/events/reports.d.ts +2 -0
  104. package/dist/events/reports.js +4 -0
  105. package/dist/exec/autoExec.d.ts +68 -4
  106. package/dist/exec/autoExec.js +392 -33
  107. package/dist/exec/autonomousCycle.d.ts +31 -4
  108. package/dist/exec/autonomousCycle.js +71 -13
  109. package/dist/exec/claudeBridge.d.ts +80 -15
  110. package/dist/exec/claudeBridge.js +845 -76
  111. package/dist/exec/executionBinding.d.ts +414 -0
  112. package/dist/exec/executionBinding.js +588 -0
  113. package/dist/exec/index.d.ts +1 -0
  114. package/dist/exec/index.js +1 -0
  115. package/dist/exec/openPrRegistry.d.ts +8 -2
  116. package/dist/exec/openPrRegistry.js +32 -22
  117. package/dist/exec/prompt.js +14 -1
  118. package/dist/exec/proofOfWork.d.ts +1 -1
  119. package/dist/exec/proofOfWork.js +2 -2
  120. package/dist/exec/runnerRegistry.d.ts +153 -36
  121. package/dist/exec/runnerRegistry.js +848 -65
  122. package/dist/exec/selfPr.js +1 -7
  123. package/dist/feedback/envelope.d.ts +61 -0
  124. package/dist/feedback/envelope.js +168 -0
  125. package/dist/feedback/index.d.ts +1 -0
  126. package/dist/feedback/index.js +1 -0
  127. package/dist/hooks/hooks.js +1 -0
  128. package/dist/hub/assetCallLog.d.ts +35 -1
  129. package/dist/hub/assetCallLog.js +124 -1
  130. package/dist/hub/bindings.d.ts +8 -1
  131. package/dist/hub/bindings.js +29 -8
  132. package/dist/hub/capability.d.ts +130 -4
  133. package/dist/hub/conversationDistiller.d.ts +19 -0
  134. package/dist/hub/conversationDistiller.js +115 -37
  135. package/dist/hub/fake.d.ts +3 -2
  136. package/dist/hub/fake.js +2 -1
  137. package/dist/hub/index.d.ts +1 -0
  138. package/dist/hub/index.js +1 -0
  139. package/dist/hub/questionGenerator.d.ts +5 -1
  140. package/dist/hub/questionGenerator.js +8 -6
  141. package/dist/hub/recipeCompose.d.ts +27 -0
  142. package/dist/hub/recipeCompose.js +90 -0
  143. package/dist/hub/sanitize.js +122 -7
  144. package/dist/index.d.ts +6 -1
  145. package/dist/index.js +7 -1
  146. package/dist/issueReporter/index.d.ts +156 -0
  147. package/dist/issueReporter/index.js +1688 -0
  148. package/dist/mailbox/dispatch.d.ts +1 -1
  149. package/dist/mailbox/dispatch.js +22 -6
  150. package/dist/mailbox/envelope.d.ts +7 -1
  151. package/dist/mailbox/envelope.js +9 -2
  152. package/dist/mailbox/ipcServer.d.ts +12 -2
  153. package/dist/mailbox/ipcServer.js +183 -13
  154. package/dist/mailbox/store.d.ts +89 -3
  155. package/dist/mailbox/store.js +895 -41
  156. package/dist/modelCompatibility.d.ts +164 -0
  157. package/dist/modelCompatibility.js +309 -0
  158. package/dist/observers/valueDigestObserver.d.ts +9 -0
  159. package/dist/observers/valueDigestObserver.js +35 -2
  160. package/dist/ops/cleanup.js +1 -1
  161. package/dist/ops/evolutionGraphProjection.d.ts +20 -0
  162. package/dist/ops/evolutionGraphProjection.js +315 -0
  163. package/dist/ops/index.d.ts +2 -1
  164. package/dist/ops/index.js +2 -1
  165. package/dist/ops/savingsCore.js +1 -2
  166. package/dist/ops/selfUpdate.d.ts +18 -1
  167. package/dist/ops/selfUpdate.js +88 -23
  168. package/dist/ops/valueOutreach.d.ts +3 -1
  169. package/dist/ops/valueOutreach.js +5 -1
  170. package/dist/personality/schema.d.ts +24 -24
  171. package/dist/schema/evolutionGraph.d.ts +784 -0
  172. package/dist/schema/evolutionGraph.js +187 -0
  173. package/dist/schema/index.d.ts +1 -0
  174. package/dist/schema/index.js +1 -0
  175. package/dist/schema/proofOfWork.d.ts +125 -6
  176. package/dist/schema/proofOfWork.js +102 -4
  177. package/dist/schema/signal.d.ts +3 -3
  178. package/dist/schema/signal.js +1 -1
  179. package/dist/shadow/shadowHub.js +1 -0
  180. package/dist/signals/curriculum.d.ts +55 -0
  181. package/dist/signals/curriculum.js +202 -0
  182. package/dist/signals/cycleHistoryFromEvents.js +17 -8
  183. package/dist/signals/expand.d.ts +15 -1
  184. package/dist/signals/expand.js +169 -1
  185. package/dist/signals/extractor.d.ts +2 -2
  186. package/dist/signals/extractor.js +31 -6
  187. package/dist/signals/index.d.ts +4 -1
  188. package/dist/signals/index.js +4 -1
  189. package/dist/signals/metaSignals.d.ts +4 -0
  190. package/dist/signals/metaSignals.js +42 -0
  191. package/dist/signals/scopeVocabulary.d.ts +75 -0
  192. package/dist/signals/scopeVocabulary.js +91 -0
  193. package/dist/signals/signalGate.js +1 -1
  194. package/dist/signals/taskDomain.d.ts +22 -0
  195. package/dist/signals/taskDomain.js +43 -0
  196. package/dist/strategy/constraintAblation.d.ts +64 -0
  197. package/dist/strategy/constraintAblation.js +2820 -0
  198. package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
  199. package/dist/strategy/constraintAblationPredicates.js +339 -0
  200. package/dist/strategy/index.d.ts +2 -1
  201. package/dist/strategy/index.js +2 -1
  202. package/dist/trace/index.d.ts +3 -1
  203. package/dist/trace/index.js +3 -1
  204. package/dist/trace/learningTrace.d.ts +216 -0
  205. package/dist/trace/learningTrace.js +298 -0
  206. package/dist/trace/proxyTurns.d.ts +31 -0
  207. package/dist/trace/proxyTurns.js +137 -0
  208. package/dist/trace/trajectory.d.ts +8 -0
  209. package/dist/trace/trajectory.js +14 -2
  210. package/dist/util/fetchPort.d.ts +1 -0
  211. package/dist/util/fetchPort.js +11 -0
  212. package/dist/util/fileLock.d.ts +113 -7
  213. package/dist/util/fileLock.js +1035 -80
  214. package/dist/util/index.d.ts +3 -1
  215. package/dist/util/index.js +2 -1
  216. package/dist/verify/index.d.ts +2 -1
  217. package/dist/verify/index.js +1 -1
  218. package/dist/verify/sandboxRunner.d.ts +30 -0
  219. package/dist/verify/sandboxRunner.js +370 -27
  220. package/dist/verify/sandboxedValidation.d.ts +16 -2
  221. package/dist/verify/sandboxedValidation.js +200 -22
  222. package/dist/verify/validation.d.ts +35 -4
  223. package/dist/verify/validation.js +164 -16
  224. package/dist/wire/geneHints.d.ts +88 -16
  225. package/dist/wire/geneHints.js +124 -15
  226. package/dist/wire/index.d.ts +8 -3
  227. package/dist/wire/index.js +2 -2
  228. package/dist/wire/schemaGate.d.ts +21 -0
  229. package/dist/wire/schemaGate.js +119 -10
  230. package/dist/workflow/dsl.d.ts +24 -3
  231. package/dist/workflow/dsl.js +4 -0
  232. package/dist/workflow/engine.d.ts +5 -1
  233. package/dist/workflow/engine.js +3 -0
  234. package/dist/workflow/index.d.ts +3 -1
  235. package/dist/workflow/index.js +3 -1
  236. package/dist/workflow/runtime.d.ts +110 -0
  237. package/dist/workflow/runtime.js +1298 -0
  238. package/dist/workflow/stateStore.d.ts +172 -0
  239. package/dist/workflow/stateStore.js +1044 -0
  240. package/package.json +12 -4
@@ -1,4 +1,5 @@
1
- import { canTransition } from '../cycle/stateMachine.js';
1
+ import { LineTooLargeError } from '../events/eventStore.js';
2
+ import { TERMINAL, canTransition, stageForEventType } from '../cycle/stateMachine.js';
2
3
  import {} from './geneSelection.js';
3
4
  import { detectPlateau } from './exploration.js';
4
5
  import { epigeneticPenaltyForIds } from './epigenetics.js';
@@ -11,10 +12,126 @@ import { applySelectForRun, applyStatsUpdate, applyForcePivot } from '../persona
11
12
  import { solidify } from './solidify.js';
12
13
  import { buildEvolutionEvent } from './evolutionEvent.js';
13
14
  import { cycleRecordsFromEvents } from '../signals/cycleHistoryFromEvents.js';
15
+ import { withoutTaskDomainSignals } from '../signals/taskDomain.js';
14
16
  import { computeMetaSignals, deriveCycleHistory } from '../signals/metaSignals.js';
17
+ import { capabilityGapsFromSignals, curriculumOutcomesFromEvents, generateCurriculumSignals, normalizeCapabilityGaps, } from '../signals/curriculum.js';
15
18
  import { resolveStrategy } from './strategyPresets.js';
16
19
  import { classifyCycleFailure, } from './cycleFailureClassifier.js';
17
20
  import { isDistilledGeneId } from './geneIntake.js';
21
+ import { deriveUcb1History } from './ucb1.js';
22
+ function persistedTerminalAfterLatestSelection(ingestor, cycleId, attemptStartedSeq) {
23
+ const events = ingestor.readAll();
24
+ let selectedSeq = -1;
25
+ for (let index = events.length - 1; index >= 0; index -= 1) {
26
+ const event = events[index];
27
+ if (event.seq <= attemptStartedSeq)
28
+ break;
29
+ if (event.type === 'decision.gene_selected' && event.payload['cycleId'] === cycleId) {
30
+ selectedSeq = event.seq;
31
+ break;
32
+ }
33
+ }
34
+ if (selectedSeq < 0)
35
+ return null;
36
+ for (let index = events.length - 1; index >= 0; index -= 1) {
37
+ const event = events[index];
38
+ if (event.seq <= selectedSeq)
39
+ break;
40
+ if (event.payload['cycleId'] !== cycleId)
41
+ continue;
42
+ const terminal = stageForEventType(event.type);
43
+ if (terminal && TERMINAL.has(terminal))
44
+ return terminal;
45
+ }
46
+ return null;
47
+ }
48
+ function selectionPolicyEventProjection(trace) {
49
+ return {
50
+ requested: trace.requested,
51
+ effective: trace.effective,
52
+ version: trace.selectionPolicyVersion,
53
+ rewardVersion: trace.rewardPolicyVersion,
54
+ ...(trace.arm ? {
55
+ arm: {
56
+ id: trace.arm.armId,
57
+ pulls: trace.arm.pulls,
58
+ completed: trace.arm.completedPulls,
59
+ total: trace.arm.totalPulls,
60
+ mean: trace.arm.meanReward,
61
+ index: trace.arm.index,
62
+ },
63
+ } : {}),
64
+ ...(trace.shadowDisagrees !== undefined ? { disagrees: trace.shadowDisagrees } : {}),
65
+ ...(trace.fallbackReason ? { fallback: trace.fallbackReason } : {}),
66
+ };
67
+ }
68
+ function minimalSelectionPolicyEventProjection(trace) {
69
+ return {
70
+ requested: trace.requested,
71
+ effective: trace.effective,
72
+ version: trace.selectionPolicyVersion,
73
+ rewardVersion: trace.rewardPolicyVersion,
74
+ };
75
+ }
76
+ function selectionGuardEventProjection(trace) {
77
+ return {
78
+ mode: trace.mode,
79
+ version: trace.version,
80
+ status: trace.status,
81
+ ...(trace.reason ? { reason: trace.reason } : {}),
82
+ ...(trace.maxMatch !== undefined ? { maxMatch: trace.maxMatch } : {}),
83
+ ...(trace.matchSpread !== undefined ? { spread: trace.matchSpread } : {}),
84
+ };
85
+ }
86
+ function minimalSelectionGuardEventProjection(trace) {
87
+ return {
88
+ mode: trace.mode,
89
+ version: trace.version,
90
+ status: trace.status,
91
+ ...(trace.reason ? { reason: trace.reason } : {}),
92
+ };
93
+ }
94
+ function selectionCandidateEventProjection(candidate) {
95
+ // matchScore and scoring diagnostics stay off decision.gene_selected payloads so
96
+ // UCB1/plateau telemetry retains the 4096B root-event line budget.
97
+ const { matchScore: _matchScore, scoreBase: _scoreBase, kautoContribution: _kautoContribution, ...persisted } = candidate;
98
+ return persisted;
99
+ }
100
+ async function ingestGeneSelectedWithTraceBudget(ingestor, event, policyTrace, guardTrace) {
101
+ const guard = guardTrace ? selectionGuardEventProjection(guardTrace) : undefined;
102
+ const minimalGuard = guardTrace ? minimalSelectionGuardEventProjection(guardTrace) : undefined;
103
+ const projections = [];
104
+ if (policyTrace) {
105
+ projections.push({ selectionPolicy: selectionPolicyEventProjection(policyTrace), ...(guard ? { selectionGuard: guard } : {}) });
106
+ projections.push({ selectionPolicy: minimalSelectionPolicyEventProjection(policyTrace), ...(guard ? { selectionGuard: guard } : {}) });
107
+ if (minimalGuard) {
108
+ projections.push({ selectionPolicy: minimalSelectionPolicyEventProjection(policyTrace), selectionGuard: minimalGuard });
109
+ projections.push({ selectionGuard: minimalGuard });
110
+ }
111
+ projections.push({ selectionPolicy: minimalSelectionPolicyEventProjection(policyTrace) });
112
+ }
113
+ else if (guard) {
114
+ projections.push({ selectionGuard: guard });
115
+ if (minimalGuard)
116
+ projections.push({ selectionGuard: minimalGuard });
117
+ }
118
+ projections.push({});
119
+ for (let index = 0; index < projections.length; index += 1) {
120
+ const projection = projections[index];
121
+ try {
122
+ await ingestor.ingest({
123
+ type: event.type,
124
+ human: event.human,
125
+ payload: { ...event.payload, ...projection },
126
+ });
127
+ return;
128
+ }
129
+ catch (error) {
130
+ if (!(error instanceof LineTooLargeError) || index === projections.length - 1)
131
+ throw error;
132
+ }
133
+ }
134
+ }
18
135
  /** Read the trailing run of cycle outcomes (newest last) from the event log, for plateau detection. */
19
136
  function recentCycleOutcomes(ingestor, window) {
20
137
  let events;
@@ -106,7 +223,7 @@ function categoryForStrategy(category, strategy) {
106
223
  * Reading from the same append-only log makes this fully replayable. ad-hoc (innovate) cycles have no gene to
107
224
  * credit and are skipped.
108
225
  */
109
- function recentConfidenceObservations(ingestor, window) {
226
+ function recentConfidenceObservations(ingestor, window, opts = {}) {
110
227
  let events;
111
228
  try {
112
229
  events = ingestor.tail(window);
@@ -146,8 +263,9 @@ function recentConfidenceObservations(ingestor, window) {
146
263
  // Older events predate the producedValue tag; when it is absent we treat the cycle as a real success, so we
147
264
  // never retroactively reclassify history into a surprise ban.
148
265
  const status = e.type === 'cycle.failed' ? 'failed' : p?.producedValue === false ? 'inert' : 'success';
266
+ const fingerprintSignals = opts.ignoreTaskDomain ? withoutTaskDomainSignals(signals) : signals;
149
267
  out.push({
150
- signalFingerprint: signalFingerprint(signals),
268
+ signalFingerprint: signalFingerprint(fingerprintSignals),
151
269
  geneId: gene,
152
270
  status,
153
271
  at: Number.isFinite(at) ? at : 0,
@@ -164,14 +282,44 @@ export class CycleEngine {
164
282
  constructor(deps) {
165
283
  this.deps = deps;
166
284
  }
285
+ /**
286
+ * Add V1-compatible curriculum targets from V2's replayable event history. Public so the orchestrator can run
287
+ * it before candidate assembly; runCycle calls it again for direct callers. Set merging makes that idempotent.
288
+ */
289
+ prepareCurriculumSignals(signals) {
290
+ let externalCapabilityGaps = [];
291
+ try {
292
+ externalCapabilityGaps = normalizeCapabilityGaps(this.deps.capabilityGaps?.() ?? []);
293
+ }
294
+ catch {
295
+ // An optional lifecycle-state reader must never suppress local curriculum generation.
296
+ }
297
+ try {
298
+ const localCapabilityGaps = capabilityGapsFromSignals(signals);
299
+ const curriculumSignals = generateCurriculumSignals({
300
+ outcomes: curriculumOutcomesFromEvents(this.deps.ingestor.readAll()),
301
+ // Preserve existing explicit local-signal precedence; Hub gaps fill the otherwise-missing V1 source.
302
+ capabilityGaps: normalizeCapabilityGaps([...localCapabilityGaps, ...externalCapabilityGaps]),
303
+ });
304
+ return { signals: mergeSignals(signals, curriculumSignals), curriculumSignals };
305
+ }
306
+ catch {
307
+ return { signals: [...signals], curriculumSignals: [] };
308
+ }
309
+ }
167
310
  async runCycle(input) {
168
311
  const { ingestor, store } = this.deps;
169
312
  const now = this.deps.now();
170
313
  const envKey = envFingerprintKey((this.deps.envFingerprint ?? captureEnvFingerprint)());
171
314
  const reasons = [];
172
- const baseSignals = input.signals;
315
+ const baseSignals = [...input.signals];
316
+ const preparedSignals = input.curriculumSignals === undefined
317
+ ? this.prepareCurriculumSignals(input.signals)
318
+ : { signals: mergeSignals(input.signals, input.curriculumSignals), curriculumSignals: [...input.curriculumSignals] };
319
+ const selectionSignals = preparedSignals.signals;
320
+ const curriculumSignals = preparedSignals.curriculumSignals;
173
321
  const metaSignals = historyMetaSignals(ingestor, 100);
174
- const cycleSignals = mergeSignals(baseSignals, metaSignals);
322
+ const cycleSignals = mergeSignals(selectionSignals, metaSignals);
175
323
  // PORT v1 #279 issue-reporter: classify cycle.failed payloads when the caller supplies host context. The
176
324
  // post-solidify failed path adds V2-local context (current gene + blast radius) so hard-filtered local no-op
177
325
  // genes are reachable without re-emitting the old ban_gene soft signal.
@@ -200,6 +348,22 @@ export class CycleEngine {
200
348
  });
201
349
  return r.reason ? { failure_class: r.failureClass, failure_class_reason: r.reason } : { failure_class: r.failureClass };
202
350
  };
351
+ const executionMetadata = (exec) => ({
352
+ ...(exec.failureKind !== undefined ? { failureKind: exec.failureKind } : {}),
353
+ ...(exec.exitCode !== undefined ? { exitCode: exec.exitCode } : {}),
354
+ ...(exec.bindingCorrelation ? { bindingDigest: exec.bindingCorrelation.bindingDigest, runId: exec.bindingCorrelation.runId } : {}),
355
+ ...(exec.executionTerminal ? { terminalDisposition: exec.executionTerminal.disposition } : {}),
356
+ ...(exec.hubLifecycle ? { hubLifecycle: exec.hubLifecycle } : {}),
357
+ ...(exec.provenance ? {
358
+ provenance: {
359
+ geneIds: exec.provenance.gene_ids,
360
+ capsuleIds: exec.provenance.capsule_ids,
361
+ resultAssetRefs: exec.provenance.result_asset_refs,
362
+ proofRefs: exec.provenance.proof_refs,
363
+ terminalDisposition: exec.provenance.terminal_disposition,
364
+ },
365
+ } : {}),
366
+ });
203
367
  const strategy = resolveStrategy({ name: input.strategyName, signals: cycleSignals, cycleCount: historyCycleCount(ingestor, 1000, input.cycleId) });
204
368
  const cycleCategory = categoryForStrategy(input.category, strategy);
205
369
  let stage = 'none';
@@ -208,7 +372,7 @@ export class CycleEngine {
208
372
  throw new Error(`非法 cycle 迁移 ${stage}→${to}`);
209
373
  stage = to;
210
374
  };
211
- await ingestor.ingest({ type: 'cycle.started', human: { title: `cycle ${input.cycleId} 启动` }, payload: { cycleId: input.cycleId, problemId: input.problem.id } });
375
+ const cycleStarted = await ingestor.ingest({ type: 'cycle.started', human: { title: `cycle ${input.cycleId} 启动` }, payload: { cycleId: input.cycleId, problemId: input.problem.id } });
212
376
  advance('started');
213
377
  await ingestor.ingest({
214
378
  type: 'cycle.signals_collected',
@@ -217,6 +381,7 @@ export class CycleEngine {
217
381
  cycleId: input.cycleId,
218
382
  signals: cycleSignals,
219
383
  baseSignals,
384
+ ...(curriculumSignals.length > 0 ? { curriculumSignals } : {}),
220
385
  ...(metaSignals.length > 0 ? { metaSignals, strategy: strategy.name } : {}),
221
386
  },
222
387
  });
@@ -226,7 +391,7 @@ export class CycleEngine {
226
391
  if (!trig.trigger) {
227
392
  await ingestor.ingest({ type: 'cycle.aborted', human: { title: `cycle ${input.cycleId} 抑制` }, payload: { cycleId: input.cycleId, reasons: trig.reasons } });
228
393
  advance('aborted');
229
- return { cycleId: input.cycleId, triggered: false, finalStage: stage, reasons: trig.reasons };
394
+ return { cycleId: input.cycleId, triggered: false, finalStage: stage, producedValue: false, reasons: trig.reasons };
230
395
  }
231
396
  // 选 gene(可解释决策). Exploration: when recent cycles plateau, enable drift to escape local optima.
232
397
  const recentOutcomes = recentCycleOutcomes(ingestor, 100);
@@ -246,7 +411,7 @@ export class CycleEngine {
246
411
  // recentEvents 由事件日志的近期 outcome 派生, 让"失败连击触发变异"在环内真正生效.
247
412
  const sel = await applySelectForRun(personalityDeps, {
248
413
  driftEnabled: plateau.active,
249
- signals: baseSignals,
414
+ signals: selectionSignals,
250
415
  recentEvents: recentOutcomes.map((status) => ({ outcome: { status } })),
251
416
  });
252
417
  personalityForRun = sel.state;
@@ -259,8 +424,24 @@ export class CycleEngine {
259
424
  return;
260
425
  await applyStatsUpdate({ store: this.deps.personality, ingestor, cycleId: input.cycleId }, { personality: personalityForRun, outcome, score });
261
426
  };
427
+ let ucb1History;
428
+ if (plateau.active && input.selectionPolicy && input.selectionPolicy !== 'engine-health') {
429
+ try {
430
+ ucb1History = deriveUcb1History(ingestor.readAllStrict());
431
+ }
432
+ catch {
433
+ // Active UCB1 fails safe to legacy drift; shadow records missing_history in its compact policy trace.
434
+ ucb1History = undefined;
435
+ }
436
+ }
262
437
  const exploration = plateau.active
263
- ? { plateau, driftEnabled: true, totalAttempts: input.candidates.reduce((s, c) => s + (c.view?.total ?? 0), 0) }
438
+ ? {
439
+ plateau,
440
+ driftEnabled: true,
441
+ totalAttempts: input.candidates.reduce((s, c) => s + (c.view?.total ?? 0), 0),
442
+ ...(input.selectionPolicy ? { policy: input.selectionPolicy } : {}),
443
+ ...(ucb1History ? { ucb1History } : {}),
444
+ }
264
445
  : undefined;
265
446
  // Epigenetic: penalize candidates that fail in THIS environment class (derived from per-env outcome history).
266
447
  const geneOutcomes = recentGeneOutcomes(ingestor, 200);
@@ -268,7 +449,7 @@ export class CycleEngine {
268
449
  // signal fingerprint. Derived from the same event log, applied as a soft fourth factor in selection.
269
450
  const confidenceObs = recentConfidenceObservations(ingestor, 200);
270
451
  const confidenceEdges = deriveConfidenceEdges(confidenceObs);
271
- const currentFingerprint = signalFingerprint(baseSignals);
452
+ const currentConfidenceFingerprint = signalFingerprint(baseSignals);
272
453
  // #195: drop genes stuck producing only inert (zero-work) cycles on THIS signal. A sole-matching do-nothing
273
454
  // gene is otherwise re-selected every cycle (drift can't diversify a single candidate) and the failure-streak
274
455
  // ban never fires because nothing "fails" — so it dominates --loop forever while producing no artifacts.
@@ -278,46 +459,85 @@ export class CycleEngine {
278
459
  // cycles evict its inert run (or the earlier real success that exempts it) and the ban would flap. Recomputing
279
460
  // it over INERT_BAN_OBSERVATION_WINDOW events (mirroring v1's memory-graph read horizon) keeps the trailing run
280
461
  // and the successCount exemption alive across intervening cycles. tail() reads the full log either way (no I/O cost).
281
- const inertBanObs = recentConfidenceObservations(ingestor, INERT_BAN_OBSERVATION_WINDOW);
282
- const inertBanned = inertBannedGeneIds(deriveConfidenceEdges(inertBanObs), inertBanObs, currentFingerprint);
462
+ const inertBanObs = recentConfidenceObservations(ingestor, INERT_BAN_OBSERVATION_WINDOW, { ignoreTaskDomain: true });
463
+ const inertBanFingerprint = signalFingerprint(withoutTaskDomainSignals(baseSignals));
464
+ const inertBanned = inertBannedGeneIds(deriveConfidenceEdges(inertBanObs), inertBanObs, inertBanFingerprint);
283
465
  const candidates = input.candidates
284
466
  .filter((c) => !isCandidateInSet(c, inertBanned))
285
467
  .map((c) => {
286
468
  const epi = epigeneticPenaltyForIds(candidateIds(c), envKey, geneOutcomes);
287
- const conf = confidenceFor(confidenceEdges, currentFingerprint, c.geneId, now);
469
+ const conf = confidenceFor(confidenceEdges, currentConfidenceFingerprint, c.geneId, now);
288
470
  const withEpi = epi > 0 ? { ...c, epigeneticPenalty: epi } : c;
289
471
  return conf > 0 ? { ...withEpi, confidence: conf } : withEpi;
290
472
  });
291
473
  // #97 distilled-gene fallback pool: attach the env-scoped epigenetic penalty (so selection can hard-skip a
292
474
  // suppressed fallback, v1 parity) and drop any inert-banned id (composes with #195). Selection uses these only
293
- // when no normal candidate clears the floor.
475
+ // when normal selection has no reusable positive choice.
294
476
  const distilledFallback = (input.distilledFallback ?? [])
295
477
  .filter((c) => !isCandidateInSet(c, inertBanned))
296
478
  .map((c) => {
297
479
  const epi = epigeneticPenaltyForIds(candidateIds(c), envKey, geneOutcomes);
298
480
  return epi > 0 ? { ...c, epigeneticPenalty: epi } : c;
299
481
  });
300
- 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 } : {}) }));
301
- await ingestor.ingest({
482
+ const semanticCorpus = input.semanticCorpus?.filter((c) => !isCandidateInSet(c, inertBanned));
483
+ const decision = (await this.deps.selection.run({ signals: selectionSignals, candidates, ...(semanticCorpus ? { semanticCorpus } : {}), ...(input.disableSemanticIdf ? { disableSemanticIdf: true } : {}), floor: input.selectionFloor, ...(input.selectionGuard ? { selectionGuard: input.selectionGuard } : {}), ...(input.forcedGeneId !== undefined ? { forcedGeneId: input.forcedGeneId } : {}), ...(exploration ? { exploration } : {}), ...(distilledFallback.length > 0 ? { distilledFallback } : {}), ...(input.antiWarnings && input.antiWarnings.length > 0 ? { antiWarnings: input.antiWarnings } : {}), ...(input.memoryEvidence && input.memoryEvidence.length > 0 ? { memoryEvidence: input.memoryEvidence } : {}) }, { now, cycleId: input.cycleId, ...(this.deps.rng ? { rng: this.deps.rng } : {}) }));
484
+ const geneId = decision.selectedGeneId ?? 'ad-hoc';
485
+ const runPostSelectionStep = async (phase, step) => {
486
+ try {
487
+ return await step();
488
+ }
489
+ catch (error) {
490
+ let persistedTerminal = null;
491
+ try {
492
+ persistedTerminal = persistedTerminalAfterLatestSelection(ingestor, input.cycleId, cycleStarted.seq);
493
+ }
494
+ catch {
495
+ // Readback is best-effort; compensation below remains the fallback when the log is unavailable.
496
+ }
497
+ if (persistedTerminal && !TERMINAL.has(stage) && canTransition(stage, persistedTerminal)) {
498
+ advance(persistedTerminal);
499
+ }
500
+ if (!persistedTerminal && !TERMINAL.has(stage)) {
501
+ try {
502
+ await ingestor.ingest({
503
+ type: 'cycle.failed',
504
+ human: { title: `cycle ${input.cycleId} selection 后异常` },
505
+ payload: { cycleId: input.cycleId, reason: 'post_selection_error', phase, gene: geneId, env: envKey },
506
+ });
507
+ advance('failed');
508
+ try {
509
+ await recordPersonalityOutcome('failed', null);
510
+ }
511
+ catch {
512
+ // Personality is soft state; never let its writeback replace the original failure.
513
+ }
514
+ }
515
+ catch {
516
+ // The event store itself may be unavailable. Preserve and rethrow the original step failure.
517
+ }
518
+ }
519
+ throw error;
520
+ }
521
+ };
522
+ await runPostSelectionStep('decision_event', () => ingestGeneSelectedWithTraceBudget(ingestor, {
302
523
  type: 'decision.gene_selected',
303
524
  human: { title: `选 gene ${decision.selectedGeneId ?? '(innovate)'}`, why: decision.candidates.map((c) => `${c.geneId}:${c.score.toFixed(3)}`).join(', ') || '无候选→innovate' },
304
- 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] } : {}) },
305
- });
525
+ payload: { cycleId: input.cycleId, selectedGeneId: decision.selectedGeneId, ...(decision.selectedAssetId ? { selectedAssetId: decision.selectedAssetId } : {}), ...(decision.selectedReason ? { selectedReason: decision.selectedReason } : {}), candidates: decision.candidates.map(selectionCandidateEventProjection), ...(decision.antiWarnings && decision.antiWarnings.length > 0 ? { antiWarnings: decision.antiWarnings } : {}), ...(decision.memoryEvidence && decision.memoryEvidence.length > 0 ? { memoryEvidence: decision.memoryEvidence } : {}), weightsVersion: decision.weightsVersion, ...(decision.semanticProfileVersion ? { semanticProfileVersion: decision.semanticProfileVersion } : {}), ...(decision.semanticDocumentCount !== undefined ? { semanticDocumentCount: decision.semanticDocumentCount } : {}), strategy: decision.strategyName, strategyPreset: strategy.name, ...(plateau.active ? { plateau } : {}), ...(inertBanned.size > 0 ? { inertBanned: [...inertBanned] } : {}) },
526
+ }, decision.selectionPolicy, decision.selectionGuard));
306
527
  advance('gene_selected');
307
- const geneId = decision.selectedGeneId ?? 'ad-hoc';
308
528
  // 变异
309
- const builtMutation = buildMutation({ decision, category: cycleCategory, signals: cycleSignals, target: input.target, expectedEffect: input.expectedEffect });
529
+ const builtMutation = await runPostSelectionStep('mutation_build', () => buildMutation({ decision, category: cycleCategory, signals: cycleSignals, target: input.target, expectedEffect: input.expectedEffect }));
310
530
  // 人格风险闸(用途②): 高危人格禁 innovate / 未达阈值的 high-risk 降级. 未注入人格 ⇒ 原样放行.
311
531
  // 用本轮 applySelectForRun 选出的状态(personalityForRun), 而非再读一次 store.currentState() ——
312
532
  // 保证"喂 prompt 的风格 / 过风险闸 / 回写统计"三者用的是同一个当轮人格.
313
533
  let mutation = builtMutation;
314
534
  if (this.deps.personality && personalityForRun) {
315
- await emitPersonalitySelected(ingestor, { cycleId: input.cycleId, personality: personalityForRun, known: personalityKnown });
316
- const gate = gatePersonalityRisk(builtMutation, personalityForRun);
535
+ await runPostSelectionStep('personality_selected', () => emitPersonalitySelected(ingestor, { cycleId: input.cycleId, personality: personalityForRun, known: personalityKnown }));
536
+ const gate = await runPostSelectionStep('personality_risk_gate', () => gatePersonalityRisk(builtMutation, personalityForRun));
317
537
  mutation = gate.mutation;
318
- await emitPersonalityRiskGated(ingestor, { cycleId: input.cycleId, personality: personalityForRun, gate });
538
+ await runPostSelectionStep('personality_risk_gate', () => emitPersonalityRiskGated(ingestor, { cycleId: input.cycleId, personality: personalityForRun, gate }));
319
539
  }
320
- 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 } });
540
+ await runPostSelectionStep('mutation_event', () => 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 } }));
321
541
  advance('mutation_built');
322
542
  // 执行(agent/runtime)
323
543
  let exec;
@@ -328,16 +548,19 @@ export class CycleEngine {
328
548
  const msg = e instanceof Error ? e.message : String(e);
329
549
  // execute threw before returning a transcript; the error text IS the host context (a provider error that
330
550
  // surfaces as a thrown exception still classifies as host_provider_error). Pass it as the sessionLog.
331
- await ingestor.ingest({ type: 'cycle.failed', human: { title: `cycle ${input.cycleId} 执行抛错` }, payload: { cycleId: input.cycleId, error: msg, gene: geneId, env: envKey, ...failureClassPayload({ geneId }, msg) } });
551
+ await runPostSelectionStep('terminal_event', () => ingestor.ingest({ type: 'cycle.failed', human: { title: `cycle ${input.cycleId} 执行抛错` }, payload: { cycleId: input.cycleId, error: msg, gene: geneId, env: envKey, ...failureClassPayload({ geneId }, msg) } }));
332
552
  advance('failed');
333
553
  await recordPersonalityOutcome('failed', null);
334
- return { cycleId: input.cycleId, triggered: true, finalStage: stage, decision, mutation, reasons: [...reasons, `执行异常: ${msg}`] };
554
+ return { cycleId: input.cycleId, triggered: true, finalStage: stage, producedValue: false, decision, mutation, reasons: [...reasons, `执行异常: ${msg}`] };
335
555
  }
336
556
  if (input.solidifyPermit) {
337
557
  let permit;
338
558
  try {
339
559
  permit = await input.solidifyPermit({
340
560
  cycleId: input.cycleId,
561
+ ...(input.executionBinding ? { bindingDigest: input.executionBinding.binding_digest, runId: input.executionBinding.correlation.run_id } : {}),
562
+ ...(exec.executionTerminal ? { executionTerminal: exec.executionTerminal } : {}),
563
+ ...(exec.provenance ? { provenance: exec.provenance } : {}),
341
564
  geneId,
342
565
  signals: cycleSignals,
343
566
  mutation,
@@ -352,7 +575,15 @@ export class CycleEngine {
352
575
  }
353
576
  if (!permit.ok) {
354
577
  const reason = `solidify permit denied: ${permit.reason}`;
355
- await ingestor.ingest({
578
+ exec = {
579
+ ...exec,
580
+ outcome: { ...exec.outcome, status: 'failed', reason },
581
+ executionTerminal: { status: 'failed', disposition: 'denied' },
582
+ provenance: exec.provenance
583
+ ? { ...exec.provenance, permit, terminal_disposition: 'denied' }
584
+ : undefined,
585
+ };
586
+ await runPostSelectionStep('terminal_event', () => ingestor.ingest({
356
587
  type: 'cycle.failed',
357
588
  human: { title: `cycle ${input.cycleId} permit denied` },
358
589
  payload: {
@@ -360,32 +591,63 @@ export class CycleEngine {
360
591
  reason: permit.reason,
361
592
  gene: geneId,
362
593
  env: envKey,
594
+ ...executionMetadata(exec),
363
595
  ...failureClassPayload({ geneId }),
364
596
  },
365
- });
597
+ }));
366
598
  advance('failed');
367
599
  await recordPersonalityOutcome('failed', null);
368
- return { cycleId: input.cycleId, triggered: true, finalStage: stage, decision, mutation, reasons: [...reasons, reason] };
600
+ return {
601
+ cycleId: input.cycleId,
602
+ triggered: true,
603
+ finalStage: stage,
604
+ producedValue: false,
605
+ decision,
606
+ mutation,
607
+ execution: exec,
608
+ ...(exec.bindingCorrelation ? { bindingDigest: exec.bindingCorrelation.bindingDigest, runId: exec.bindingCorrelation.runId } : {}),
609
+ ...(exec.executionTerminal ? { terminalDisposition: exec.executionTerminal.disposition } : {}),
610
+ ...executionMetadata(exec),
611
+ reasons: [...reasons, reason],
612
+ };
369
613
  }
370
614
  }
371
615
  // solidify → Capsule
372
- const sol = solidify({
616
+ const sol = await runPostSelectionStep('solidify', () => solidify({
373
617
  geneId, trigger: cycleSignals, summary: input.summary, confidence: input.confidence,
374
618
  outcome: exec.outcome, proofOfWork: exec.proofOfWork, strongEvidence: exec.strongEvidence,
375
- });
619
+ ...(exec.failureIdentity ? { failureIdentity: exec.failureIdentity } : {}),
620
+ }));
376
621
  reasons.push(...sol.reasons);
377
- await store.put(sol.capsule);
378
- 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 } });
379
- // EvolutionEvent(capsule_id 引 Capsule; outcome 真值在此)
380
- const event = buildEvolutionEvent({
622
+ await runPostSelectionStep('capsule_store', () => store.put(sol.capsule));
623
+ await runPostSelectionStep('capsule_event', () => 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 } }));
624
+ // EvolutionEvent(capsule_id 引 Capsule; outcome 真值在此).
625
+ // Selection stamp: selected_asset_id / kauto_member / evolver_version / selection_stage=applied
626
+ // live in event.meta so production cohort collection can separate writer membership from
627
+ // selected/applied member share. Only true kauto membership is stamped (omit-not-null).
628
+ const selectedCandidate = decision.selectedGeneId
629
+ ? input.candidates.find((c) => (c.geneId === decision.selectedGeneId
630
+ && (decision.selectedAssetId === undefined || c.assetId === decision.selectedAssetId)))
631
+ ?? input.candidates.find((c) => c.geneId === decision.selectedGeneId)
632
+ ?? (input.distilledFallback ?? []).find((c) => (c.geneId === decision.selectedGeneId
633
+ && (decision.selectedAssetId === undefined || c.assetId === decision.selectedAssetId)))
634
+ : undefined;
635
+ const envFp = (this.deps.envFingerprint ?? captureEnvFingerprint)();
636
+ const event = await runPostSelectionStep('evolution_event_build', () => buildEvolutionEvent({
381
637
  intent: cycleCategory, signals: cycleSignals, genesUsed: decision.selectedGeneId ? [decision.selectedGeneId] : [],
382
638
  mutationId: mutation.id, blastRadius: sol.capsule.blast_radius, outcome: exec.outcome,
383
639
  capsuleId: sol.capsule.asset_id, sourceType: decision.selectedGeneId ? 'reused' : 'generated',
384
- });
385
- await store.put(event);
386
- 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 } });
640
+ selection: {
641
+ selectionStage: 'applied',
642
+ ...(decision.selectedAssetId ? { selectedAssetId: decision.selectedAssetId } : {}),
643
+ ...(selectedCandidate?.kautoMember === true ? { kautoMember: true } : {}),
644
+ ...(envFp.evolver_version ? { evolverVersion: envFp.evolver_version } : {}),
645
+ },
646
+ }));
647
+ await runPostSelectionStep('evolution_event_store', () => store.put(event));
648
+ await runPostSelectionStep('evolution_event_projection', () => 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 } }));
387
649
  if (exec.outcome.status === 'failed') {
388
- await ingestor.ingest({
650
+ await runPostSelectionStep('terminal_event', () => ingestor.ingest({
389
651
  type: 'cycle.failed',
390
652
  human: { title: `cycle ${input.cycleId} 失败` },
391
653
  payload: {
@@ -394,6 +656,7 @@ export class CycleEngine {
394
656
  outcome: exec.outcome,
395
657
  gene: geneId,
396
658
  env: envKey,
659
+ ...executionMetadata(exec),
397
660
  // Only forward blast radius as a no-op signal when it was measured from a git_diff proof. solidify.ts
398
661
  // forces blast to {0,0} for non-git_diff proofs (artifact_hash/external_receipt/tool_call_trace),
399
662
  // where {0,0} means "blast unknown", NOT "no change" — forwarding it would mis-classify a productive
@@ -405,15 +668,30 @@ export class CycleEngine {
405
668
  : {}),
406
669
  }, exec.sessionLog),
407
670
  },
408
- });
671
+ }));
409
672
  advance('failed');
410
673
  }
411
674
  else {
412
- 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 } });
675
+ await runPostSelectionStep('terminal_event', () => 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 } }));
413
676
  advance('solidified');
414
677
  }
415
678
  // 人格统计回写(用途②): 用本轮真实 outcome/score 回写当轮人格桶, 供下一轮自然选择靠拢. 未注入 ⇒ no-op.
416
679
  await recordPersonalityOutcome(exec.outcome.status, exec.outcome.score);
417
- return { cycleId: input.cycleId, triggered: true, finalStage: stage, decision, mutation, capsule: sol.capsule, event, resolutionStatus: sol.resolutionStatus, reasons };
680
+ return {
681
+ cycleId: input.cycleId,
682
+ triggered: true,
683
+ finalStage: stage,
684
+ producedValue: sol.producedValue,
685
+ decision,
686
+ mutation,
687
+ capsule: sol.capsule,
688
+ event,
689
+ resolutionStatus: sol.resolutionStatus,
690
+ execution: exec,
691
+ ...(exec.bindingCorrelation ? { bindingDigest: exec.bindingCorrelation.bindingDigest, runId: exec.bindingCorrelation.runId } : {}),
692
+ ...(exec.executionTerminal ? { terminalDisposition: exec.executionTerminal.disposition } : {}),
693
+ ...executionMetadata(exec),
694
+ reasons,
695
+ };
418
696
  }
419
697
  }
@@ -1,5 +1,5 @@
1
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
2
+ * Bucket string matches V1 byte-for-byte so wire / UI
3
3
  * consumers can branch on the same value regardless of producer. */
4
4
  export type CycleFailureClass = 'host_no_transcript' | 'host_provider_error' | 'local_gene_no_blast' | 'unclassified';
5
5
  /** Shape of a recent cycle event the classifier reads. Loose so callers can pass partial DTOs. */
@@ -1,4 +1,19 @@
1
1
  import { type EvolutionEvent, type GepCategory } from '../wire/index.js';
2
+ /** Selection-side lifecycle stamp carried in EvolutionEvent.meta (schema-allowed object). */
3
+ export type EvolutionSelectionStage = 'selected' | 'applied';
4
+ export interface EvolutionSelectionStamp {
5
+ /** Content asset_id of the selected Gene when known (sha256:…). */
6
+ selectedAssetId?: string;
7
+ /** True only when assembly stamped kautoMember on the selected candidate. */
8
+ kautoMember?: boolean;
9
+ /** Producer evolver version from env fingerprint, when known. */
10
+ evolverVersion?: string;
11
+ /**
12
+ * selected = gene was chosen for the cycle;
13
+ * applied = cycle produced an EvolutionEvent after execution (this builder always emits applied).
14
+ */
15
+ selectionStage?: EvolutionSelectionStage;
16
+ }
2
17
  export interface BuildEvolutionEventInput {
3
18
  intent: GepCategory;
4
19
  signals: readonly string[];
@@ -15,9 +30,20 @@ export interface BuildEvolutionEventInput {
15
30
  capsuleId: string | null;
16
31
  sourceType: 'generated' | 'reused' | 'reference';
17
32
  parent?: string | null;
33
+ /** Optional selection lifecycle stamp; omitted keys stay absent (omit-not-null). */
34
+ selection?: EvolutionSelectionStamp;
18
35
  }
36
+ /**
37
+ * Build the schema-allowed `meta` object for selection telemetry.
38
+ * Absent optional fields are omitted so they do not enter the asset_id hash as null.
39
+ */
40
+ export declare function buildEvolutionSelectionMeta(selection: EvolutionSelectionStamp | undefined): Record<string, unknown> | undefined;
19
41
  /**
20
42
  * EvolutionEvent = 世代记录(outcome 真值在此, 硬化 A4). 单向引 Capsule(capsule_id);
21
43
  * 写入序列: capsule asset_id 先算 → 填 capsule_id → 再算 event asset_id.
44
+ *
45
+ * Optional selection stamp lands in `meta` (schema-allowed) so production cohort
46
+ * collection can separate writer membership from selected/applied member share
47
+ * without inventing a LearningAsset bridge.
22
48
  */
23
49
  export declare function buildEvolutionEvent(input: BuildEvolutionEventInput): EvolutionEvent;
@@ -1,10 +1,41 @@
1
1
  import { ulid as makeUlid } from 'ulid';
2
2
  import { computeAssetId, SCHEMA_VERSION } from '../wire/index.js';
3
+ function nonEmptyString(value) {
4
+ return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
5
+ }
6
+ /**
7
+ * Build the schema-allowed `meta` object for selection telemetry.
8
+ * Absent optional fields are omitted so they do not enter the asset_id hash as null.
9
+ */
10
+ export function buildEvolutionSelectionMeta(selection) {
11
+ if (!selection)
12
+ return undefined;
13
+ const selectedAssetId = nonEmptyString(selection.selectedAssetId);
14
+ const evolverVersion = nonEmptyString(selection.evolverVersion);
15
+ const selectionStage = selection.selectionStage === 'selected' || selection.selectionStage === 'applied'
16
+ ? selection.selectionStage
17
+ : undefined;
18
+ const meta = {};
19
+ if (selectedAssetId)
20
+ meta['selected_asset_id'] = selectedAssetId;
21
+ if (selection.kautoMember === true)
22
+ meta['kauto_member'] = true;
23
+ if (evolverVersion)
24
+ meta['evolver_version'] = evolverVersion;
25
+ if (selectionStage)
26
+ meta['selection_stage'] = selectionStage;
27
+ return Object.keys(meta).length > 0 ? meta : undefined;
28
+ }
3
29
  /**
4
30
  * EvolutionEvent = 世代记录(outcome 真值在此, 硬化 A4). 单向引 Capsule(capsule_id);
5
31
  * 写入序列: capsule asset_id 先算 → 填 capsule_id → 再算 event asset_id.
32
+ *
33
+ * Optional selection stamp lands in `meta` (schema-allowed) so production cohort
34
+ * collection can separate writer membership from selected/applied member share
35
+ * without inventing a LearningAsset bridge.
6
36
  */
7
37
  export function buildEvolutionEvent(input) {
38
+ const meta = buildEvolutionSelectionMeta(input.selection);
8
39
  const base = {
9
40
  type: 'EvolutionEvent',
10
41
  schema_version: SCHEMA_VERSION,
@@ -18,6 +49,7 @@ export function buildEvolutionEvent(input) {
18
49
  outcome: input.outcome,
19
50
  capsule_id: input.capsuleId,
20
51
  source_type: input.sourceType,
52
+ ...(meta ? { meta } : {}),
21
53
  };
22
54
  const asset_id = computeAssetId(base);
23
55
  return { ...base, asset_id };