@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
@@ -0,0 +1,315 @@
1
+ // Read-only Evolution Graph projection over already-emitted root_events.
2
+ //
3
+ // PURE: a function over a root-event snapshot. It never appends events, writes assets, or mutates selection —
4
+ // projecting is strictly an observability concern (same posture as ops/reuseOutcomes.ts). The graph it returns is
5
+ // evidence for "which variant was selected on what basis, and was that later contradicted", not a new actuator.
6
+ //
7
+ // Every edge carries provenance back to the root_event seq that justified it, so a dashboard card can always be
8
+ // traced to a specific emitted event instead of a narrative summary.
9
+ import { deriveEvolutionGraphDashboardSummary, EVOLUTION_GRAPH_SCHEMA_VERSION, } from '../schema/evolutionGraph.js';
10
+ import { HOST_SUPPRESS_CLASSES } from '../algo/cycleFailureClassifier.js';
11
+ const DEFAULT_MAX_EVENTS = 5_000;
12
+ const MAX_LABEL = 120;
13
+ const RESOLUTION_STATUSES = new Set([
14
+ 'pending',
15
+ 'suppressed_observationally',
16
+ 'resolved_by_evidence',
17
+ 'regressed',
18
+ 'inconclusive',
19
+ ]);
20
+ export function projectEvolutionGraph(events, options = {}) {
21
+ const maxEvents = boundedLimit(options.maxEvents, DEFAULT_MAX_EVENTS);
22
+ const window = events.length > maxEvents ? events.slice(events.length - maxEvents) : events;
23
+ const builder = new GraphBuilder();
24
+ for (const event of window)
25
+ builder.absorb(event);
26
+ const nodes = builder.nodes();
27
+ const edges = builder.edges();
28
+ const newestTs = window.length > 0 ? text(window[window.length - 1]?.ts) : '';
29
+ return {
30
+ schemaVersion: EVOLUTION_GRAPH_SCHEMA_VERSION,
31
+ graphId: options.graphId ?? 'graph:evolution:root-events',
32
+ generatedAt: options.generatedAt ?? (isoTimestamp(newestTs) ?? '1970-01-01T00:00:00.000Z'),
33
+ nodes,
34
+ edges,
35
+ dashboard: deriveEvolutionGraphDashboardSummary({ nodes, edges }),
36
+ extensions: {},
37
+ };
38
+ }
39
+ class GraphBuilder {
40
+ nodesById = new Map();
41
+ edgesById = new Map();
42
+ variantByLifecycle = new Map();
43
+ /** cycleId can be reused; only events in the current lifecycle may share evidence or graph nodes. */
44
+ lifecycleByCycle = new Map();
45
+ nodes() {
46
+ return [...this.nodesById.values()].sort((left, right) => left.id.localeCompare(right.id));
47
+ }
48
+ edges() {
49
+ return [...this.edgesById.values()].sort((left, right) => left.id.localeCompare(right.id));
50
+ }
51
+ absorb(event) {
52
+ const at = isoTimestamp(text(event.ts));
53
+ if (!at)
54
+ return;
55
+ const payload = event.payload ?? {};
56
+ const cycleId = opaqueId(payload['cycleId']);
57
+ switch (event.type) {
58
+ case 'cycle.started': {
59
+ if (cycleId)
60
+ this.beginLifecycle(cycleId);
61
+ return;
62
+ }
63
+ case 'capsule.produced': {
64
+ if (!cycleId)
65
+ return;
66
+ const capsuleId = opaqueId(payload['capsuleId']);
67
+ const status = resolutionStatus(payload['resolutionStatus']);
68
+ if (!capsuleId || !status)
69
+ return;
70
+ const lifecycle = this.activeLifecycle(cycleId);
71
+ lifecycle.capsule = {
72
+ capsuleId,
73
+ resolutionStatus: status,
74
+ producedValue: payload['producedValue'] === true,
75
+ event,
76
+ };
77
+ return;
78
+ }
79
+ case 'decision.gene_selected': {
80
+ if (!cycleId)
81
+ return;
82
+ const lifecycle = this.decisionLifecycle(cycleId, event);
83
+ const variant = this.variant(lifecycle, at);
84
+ const gene = opaqueId(payload['selectedAssetId']) ?? opaqueId(payload['selectedGeneId']);
85
+ if (gene) {
86
+ const spec = this.node(`spec:${gene}`, 'spec', `gene ${gene}`, at);
87
+ this.edge('derivation', spec, variant, at, event, 'variant derived from the selected gene');
88
+ }
89
+ const selection = this.node(`selection:${lifecycle.key}`, 'selection', `selection for cycle ${cycleId}`, at);
90
+ const reason = text(payload['selectedReason']) || text(event.human?.title) || 'gene selected by cycle engine';
91
+ this.edge('selected', variant, selection, at, event, reason);
92
+ return;
93
+ }
94
+ case 'mutation.built': {
95
+ if (!cycleId)
96
+ return;
97
+ const lifecycle = this.activeLifecycle(cycleId);
98
+ if (!lifecycle.key)
99
+ return;
100
+ const variant = this.variant(lifecycle, at);
101
+ const mutationId = opaqueId(payload['mutationId']);
102
+ if (!mutationId)
103
+ return;
104
+ const code = this.node(`code:${mutationId}`, 'code', `mutation ${mutationId}`, at);
105
+ this.edge('derivation', variant, code, at, event, 'variant produced a mutation');
106
+ return;
107
+ }
108
+ case 'cycle.solidified': {
109
+ if (!cycleId)
110
+ return;
111
+ const lifecycle = this.activeLifecycle(cycleId);
112
+ lifecycle.terminal = true;
113
+ if (!lifecycle.key)
114
+ return;
115
+ const score = terminalOutcomeScore(payload['outcome'], 'success');
116
+ if (score === null)
117
+ return;
118
+ const variant = this.variant(lifecycle, at);
119
+ const run = this.node(`run:${lifecycle.key}`, 'run', `cycle ${cycleId}`, at);
120
+ this.edge('evaluated_by', variant, run, at, event, 'cycle completed with a measured outcome');
121
+ const evidence = this.matchingCapsuleEvidence(lifecycle, payload, true);
122
+ if (evidence?.resolutionStatus === 'resolved_by_evidence'
123
+ && evidence.producedValue
124
+ && payload['producedValue'] === true) {
125
+ this.edge('fixed', variant, run, at, event, 'cycle resolved by output-backed validation evidence', {
126
+ metric: 'cycle_outcome_score',
127
+ after: score,
128
+ direction: 'improved',
129
+ }, [evidence.event]);
130
+ }
131
+ return;
132
+ }
133
+ case 'cycle.failed': {
134
+ if (!cycleId)
135
+ return;
136
+ const lifecycle = this.activeLifecycle(cycleId);
137
+ lifecycle.terminal = true;
138
+ if (!lifecycle.key)
139
+ return;
140
+ const score = terminalOutcomeScore(payload['outcome'], 'failed');
141
+ const failureClass = text(payload['failure_class']);
142
+ if (score === null || text(payload['failureKind']) || suppressesFailure(failureClass))
143
+ return;
144
+ const variant = this.variant(lifecycle, at);
145
+ const run = this.node(`run:${lifecycle.key}`, 'run', `cycle ${cycleId}`, at);
146
+ this.edge('evaluated_by', variant, run, at, event, 'cycle completed with a measured failed outcome');
147
+ const capsule = this.matchingCapsuleEvidence(lifecycle, payload);
148
+ const inlineStatus = resolutionStatus(payload['resolutionStatus']);
149
+ if (hasOwn(payload, 'resolutionStatus') && !inlineStatus)
150
+ return;
151
+ const status = inlineStatus ?? capsule?.resolutionStatus ?? null;
152
+ if (inlineStatus && capsule && inlineStatus !== capsule.resolutionStatus)
153
+ return;
154
+ if (status === 'regressed') {
155
+ this.edge('regressed', variant, run, at, event, failureClass || 'measured cycle regression', {
156
+ metric: 'cycle_outcome_score',
157
+ after: score,
158
+ direction: 'regressed',
159
+ }, capsule ? [capsule.event] : []);
160
+ }
161
+ return;
162
+ }
163
+ case 'cycle.aborted': {
164
+ if (cycleId)
165
+ this.activeLifecycle(cycleId).terminal = true;
166
+ return;
167
+ }
168
+ case 'value.reuse_outcome': {
169
+ const assetId = opaqueId(payload['assetId']);
170
+ if (!assetId)
171
+ return;
172
+ const spec = this.node(`spec:${assetId}`, 'spec', `gene ${assetId}`, at);
173
+ const feedback = this.node(`feedback:${assetId}:${event.seq}`, 'feedback', `reuse outcome ${text(payload['outcome']) || 'negative'}`, at);
174
+ this.edge('derivation', feedback, spec, at, event, 'reuse feedback recorded against the gene');
175
+ return;
176
+ }
177
+ case 'anti_gene.rollout_result': {
178
+ const suite = opaqueId(payload['suite']) ?? `seq-${event.seq}`;
179
+ const test = this.node(`test:${suite}`, 'test', `anti-gene rollout ${suite}`, at);
180
+ const verdict = text(payload['verdict']);
181
+ const variant = this.node(`variant:rollout:${suite}`, 'variant', `anti-gene arm ${suite}`, at);
182
+ this.edge('evaluated_by', variant, test, at, event, verdict || 'anti-gene rollout evaluated');
183
+ return;
184
+ }
185
+ default:
186
+ return;
187
+ }
188
+ }
189
+ beginLifecycle(cycleId) {
190
+ const lifecycle = {
191
+ cycleId,
192
+ key: '',
193
+ decisionSeen: false,
194
+ capsule: null,
195
+ terminal: false,
196
+ };
197
+ this.lifecycleByCycle.set(cycleId, lifecycle);
198
+ return lifecycle;
199
+ }
200
+ activeLifecycle(cycleId) {
201
+ const current = this.lifecycleByCycle.get(cycleId);
202
+ return !current || current.terminal ? this.beginLifecycle(cycleId) : current;
203
+ }
204
+ decisionLifecycle(cycleId, event) {
205
+ let lifecycle = this.lifecycleByCycle.get(cycleId);
206
+ if (!lifecycle || lifecycle.decisionSeen || lifecycle.capsule || lifecycle.terminal) {
207
+ lifecycle = this.beginLifecycle(cycleId);
208
+ }
209
+ lifecycle.key = `${cycleId}:seq:${event.seq}`;
210
+ lifecycle.decisionSeen = true;
211
+ lifecycle.capsule = null;
212
+ return lifecycle;
213
+ }
214
+ variant(lifecycle, at) {
215
+ const existing = this.variantByLifecycle.get(lifecycle.key);
216
+ if (existing)
217
+ return this.nodesById.get(existing);
218
+ const node = this.node(`variant:${lifecycle.key}`, 'variant', `variant from cycle ${lifecycle.cycleId}`, at);
219
+ this.variantByLifecycle.set(lifecycle.key, node.id);
220
+ return node;
221
+ }
222
+ matchingCapsuleEvidence(lifecycle, payload, requireCapsuleId = false) {
223
+ const evidence = lifecycle.capsule;
224
+ if (!evidence)
225
+ return null;
226
+ if (!hasOwn(payload, 'capsuleId'))
227
+ return requireCapsuleId ? null : evidence;
228
+ const capsuleId = opaqueId(payload['capsuleId']);
229
+ return !capsuleId || capsuleId !== evidence.capsuleId ? null : evidence;
230
+ }
231
+ node(id, kind, label, at) {
232
+ const existing = this.nodesById.get(id);
233
+ if (existing)
234
+ return existing;
235
+ const node = {
236
+ id,
237
+ kind,
238
+ label: label.slice(0, MAX_LABEL) || id,
239
+ createdAt: at,
240
+ metrics: [],
241
+ tags: [],
242
+ extensions: {},
243
+ };
244
+ this.nodesById.set(id, node);
245
+ return node;
246
+ }
247
+ edge(kind, from, to, at, event, reason, metricDelta, supportingEvents = []) {
248
+ const id = `edge:${kind}:${from.id}->${to.id}:${event.seq}`;
249
+ if (this.edgesById.has(id))
250
+ return;
251
+ this.edgesById.set(id, {
252
+ id,
253
+ kind,
254
+ from: from.id,
255
+ to: to.id,
256
+ createdAt: at,
257
+ provenance: provenanceFor([...supportingEvents, event]),
258
+ ...(metricDelta ? { metricDelta } : {}),
259
+ reason: reason.slice(0, MAX_LABEL),
260
+ extensions: {},
261
+ });
262
+ }
263
+ }
264
+ function boundedLimit(value, fallback) {
265
+ if (!Number.isFinite(value) || value === undefined || value <= 0)
266
+ return fallback;
267
+ return Math.min(fallback, Math.floor(value));
268
+ }
269
+ function text(value) {
270
+ return typeof value === 'string' ? value.replace(/[\r\n\t]+/g, ' ').trim() : '';
271
+ }
272
+ /** Opaque local identifiers only: no control characters, bounded, so a graph id cannot smuggle payload text. */
273
+ function opaqueId(value) {
274
+ const candidate = text(value);
275
+ if (!candidate || candidate.length > 160)
276
+ return null;
277
+ return /^[A-Za-z0-9][A-Za-z0-9:._-]*$/.test(candidate) ? candidate : null;
278
+ }
279
+ function isoTimestamp(value) {
280
+ if (!value)
281
+ return null;
282
+ const parsed = Date.parse(value);
283
+ return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
284
+ }
285
+ function terminalOutcomeScore(value, expectedStatus) {
286
+ if (!value || typeof value !== 'object' || Array.isArray(value))
287
+ return null;
288
+ const outcome = value;
289
+ if (outcome['status'] !== expectedStatus)
290
+ return null;
291
+ const score = outcome['score'];
292
+ return typeof score === 'number' && Number.isFinite(score) ? score : null;
293
+ }
294
+ function resolutionStatus(value) {
295
+ return typeof value === 'string' && RESOLUTION_STATUSES.has(value)
296
+ ? value
297
+ : null;
298
+ }
299
+ function suppressesFailure(value) {
300
+ return value !== '' && HOST_SUPPRESS_CLASSES.has(value);
301
+ }
302
+ function hasOwn(value, key) {
303
+ return Object.prototype.hasOwnProperty.call(value, key);
304
+ }
305
+ function provenanceFor(events) {
306
+ const seen = new Set();
307
+ return events.flatMap((event) => {
308
+ const capturedAt = isoTimestamp(text(event.ts));
309
+ const ref = `seq:${event.seq}`;
310
+ if (!capturedAt || seen.has(ref))
311
+ return [];
312
+ seen.add(ref);
313
+ return [{ kind: 'root_event', ref, capturedAt, summary: event.type }];
314
+ });
315
+ }
@@ -4,4 +4,5 @@ export * from './selfUpdate.js';
4
4
  export * from './valueLedger.js';
5
5
  export * from './valueOutreach.js';
6
6
  export * from './recall.js';
7
- export * from './reuseOutcomes.js';
7
+ export * from './reuseOutcomes.js';
8
+ export * from './evolutionGraphProjection.js';
package/dist/ops/index.js CHANGED
@@ -4,4 +4,5 @@ export * from './selfUpdate.js';
4
4
  export * from './valueLedger.js';
5
5
  export * from './valueOutreach.js';
6
6
  export * from './recall.js';
7
- export * from './reuseOutcomes.js';
7
+ export * from './reuseOutcomes.js';
8
+ export * from './evolutionGraphProjection.js';
@@ -6,8 +6,7 @@
6
6
  // gate (it reads the vendored files at runtime, so this module stays free of cross-rootDir JSON
7
7
  // imports), and savings-core's daily drift-check locks the vendored copies to upstream. To change a
8
8
  // coefficient: change savings-core first, regenerate vectors there, bump spec_version, then
9
- // re-vendor here AND in evomap-hub / evomap-private / evox-online-deck / evomap-desktop / evox /
10
- // evolver-private-dev / evolver-v2-enterprise-dev.
9
+ // re-vendor it into each consuming distribution.
11
10
  //
12
11
  // Core purity contract holds: no I/O, no Date.now/Math.random — pure functions over numbers.
13
12
  export const SAVINGS_SPEC_VERSION = '0.3.0';
@@ -91,6 +91,14 @@ export interface DownloadedArtifact {
91
91
  /** Or a precomputed lowercase-hex sha256, if the caller hashed during streaming (avoids buffering). */
92
92
  sha256?: string;
93
93
  }
94
+ /**
95
+ * Parse a configured public key (PEM, raw 32-byte base64, SPKI DER base64, or a KeyObject) into a node KeyObject.
96
+ *
97
+ * The base64 forms mirror scripts/release-update-manifest.mjs parsePublicKey exactly: the release pipeline and the
98
+ * built-in channel key ship the full SPKI DER blob (44 bytes for Ed25519), while operator overrides may supply the
99
+ * bare 32-byte raw key. Anything that does not decode to a valid Ed25519 SPKI key throws (fail-closed).
100
+ */
101
+ export declare function toPublicKey(publicKey: string | KeyObject): KeyObject;
94
102
  /**
95
103
  * Verify downloaded artifacts against the manifest. PURE + deterministic (takes already-read bytes/hashes; does
96
104
  * NO I/O). Two layers:
@@ -103,4 +111,13 @@ export interface DownloadedArtifact {
103
111
  *
104
112
  * Fail-closed everywhere: any doubt (bad structure, hash gap, signature failure, thrown crypto error) → reject.
105
113
  */
106
- export declare function verifyManifest(manifest: unknown, downloaded: readonly DownloadedArtifact[], publicKey?: string | KeyObject): VerifyResult;
114
+ export declare function verifyManifest(manifest: unknown, downloaded: readonly DownloadedArtifact[], publicKey?: string | KeyObject): VerifyResult;
115
+ /**
116
+ * Verify exactly one explicitly selected download against a complete release manifest.
117
+ *
118
+ * Unlike verifyManifest, this verifier does not require downloading every platform artifact. When a public key is
119
+ * configured it first verifies the Ed25519 signature over the complete canonical manifest, then requires exactly
120
+ * one downloaded artifact and exactly one manifest entry with the same path, and finally checks that artifact's
121
+ * sha256. The complete manifest is never filtered or reconstructed before signature verification.
122
+ */
123
+ export declare function verifySelectedManifestArtifact(manifest: unknown, downloaded: readonly DownloadedArtifact[], publicKey?: string | KeyObject): VerifyResult;
@@ -224,19 +224,54 @@ export function canonicalManifestBytes(manifest) {
224
224
  };
225
225
  return new TextEncoder().encode(JSON.stringify(canonical));
226
226
  }
227
- /** Parse a configured public key (PEM, raw 32-byte base64, or a KeyObject) into a node KeyObject. */
228
- function toPublicKey(publicKey) {
227
+ /**
228
+ * Parse a configured public key (PEM, raw 32-byte base64, SPKI DER base64, or a KeyObject) into a node KeyObject.
229
+ *
230
+ * The base64 forms mirror scripts/release-update-manifest.mjs parsePublicKey exactly: the release pipeline and the
231
+ * built-in channel key ship the full SPKI DER blob (44 bytes for Ed25519), while operator overrides may supply the
232
+ * bare 32-byte raw key. Anything that does not decode to a valid Ed25519 SPKI key throws (fail-closed).
233
+ */
234
+ export function toPublicKey(publicKey) {
229
235
  if (typeof publicKey !== 'string')
230
236
  return publicKey;
231
237
  const trimmed = publicKey.trim();
232
238
  if (trimmed.includes('BEGIN PUBLIC KEY'))
233
239
  return createPublicKey(trimmed);
234
- // Raw 32-byte Ed25519 key, base64 → wrap in DER SPKI so createPublicKey accepts it.
235
- const raw = Buffer.from(trimmed, 'base64');
236
- if (raw.length !== 32)
237
- throw new Error('ed25519 raw key must be 32 bytes');
238
- const der = Buffer.concat([Buffer.from('302a300506032b6570032100', 'hex'), raw]);
239
- return createPublicKey({ key: der, format: 'der', type: 'spki' });
240
+ const decoded = Buffer.from(trimmed, 'base64');
241
+ // Raw 32-byte Ed25519 key, base64 → wrap in DER SPKI so createPublicKey accepts it;
242
+ // any other length is assumed to already be a full SPKI DER blob.
243
+ const der = decoded.length === 32
244
+ ? Buffer.concat([Buffer.from('302a300506032b6570032100', 'hex'), decoded])
245
+ : decoded;
246
+ let key;
247
+ try {
248
+ key = createPublicKey({ key: der, format: 'der', type: 'spki' });
249
+ }
250
+ catch {
251
+ throw new Error('ed25519 public key must be a raw 32-byte or SPKI DER base64 key');
252
+ }
253
+ if (key.asymmetricKeyType !== 'ed25519')
254
+ throw new Error('ed25519 public key must be an Ed25519 key');
255
+ return key;
256
+ }
257
+ /** Verify the signature over the complete canonical manifest when a public key is configured. */
258
+ function verifyConfiguredManifestSignature(manifest, publicKey) {
259
+ if (!publicKey)
260
+ return { ok: true, reason: 'signature_not_required' };
261
+ if (!manifest.signature)
262
+ return { ok: false, reason: 'signature_required_but_missing' };
263
+ if (manifest.signatureAlg && manifest.signatureAlg !== 'ed25519') {
264
+ return { ok: false, reason: 'signature_alg_unsupported' };
265
+ }
266
+ try {
267
+ const key = toPublicKey(publicKey);
268
+ const ok = cryptoVerify(null, // Ed25519 ignores the digest-name arg
269
+ canonicalManifestBytes(manifest), key, Buffer.from(manifest.signature, 'base64'));
270
+ return ok ? { ok: true, reason: 'signature_verified' } : { ok: false, reason: 'signature_invalid' };
271
+ }
272
+ catch (err) {
273
+ return { ok: false, reason: `signature_verify_error: ${err instanceof Error ? err.message : String(err)}` };
274
+ }
240
275
  }
241
276
  /**
242
277
  * Verify downloaded artifacts against the manifest. PURE + deterministic (takes already-read bytes/hashes; does
@@ -274,21 +309,51 @@ export function verifyManifest(manifest, downloaded, publicKey) {
274
309
  return { ok: false, reason: `sha256_mismatch: ${art.path}` };
275
310
  }
276
311
  // Layer 2: Ed25519 signature — enforced ONLY when a key is configured (then fail-closed).
277
- if (publicKey) {
278
- if (!m.signature)
279
- return { ok: false, reason: 'signature_required_but_missing' };
280
- if (m.signatureAlg && m.signatureAlg !== 'ed25519')
281
- return { ok: false, reason: 'signature_alg_unsupported' };
282
- try {
283
- const key = toPublicKey(publicKey);
284
- const ok = cryptoVerify(null, // Ed25519 ignores the digest-name arg
285
- canonicalManifestBytes(m), key, Buffer.from(m.signature, 'base64'));
286
- if (!ok)
287
- return { ok: false, reason: 'signature_invalid' };
288
- }
289
- catch (err) {
290
- return { ok: false, reason: `signature_verify_error: ${err instanceof Error ? err.message : String(err)}` };
291
- }
312
+ const signature = verifyConfiguredManifestSignature(m, publicKey);
313
+ if (!signature.ok)
314
+ return signature;
315
+ return { ok: true, reason: 'verified' };
316
+ }
317
+ /**
318
+ * Verify exactly one explicitly selected download against a complete release manifest.
319
+ *
320
+ * Unlike verifyManifest, this verifier does not require downloading every platform artifact. When a public key is
321
+ * configured it first verifies the Ed25519 signature over the complete canonical manifest, then requires exactly
322
+ * one downloaded artifact and exactly one manifest entry with the same path, and finally checks that artifact's
323
+ * sha256. The complete manifest is never filtered or reconstructed before signature verification.
324
+ */
325
+ export function verifySelectedManifestArtifact(manifest, downloaded, publicKey) {
326
+ const structErr = manifestStructuralError(manifest);
327
+ if (structErr)
328
+ return { ok: false, reason: structErr };
329
+ const m = manifest;
330
+ const signature = verifyConfiguredManifestSignature(m, publicKey);
331
+ if (!signature.ok)
332
+ return signature;
333
+ if (downloaded.length !== 1) {
334
+ return { ok: false, reason: `downloaded_artifact_count_invalid: ${downloaded.length}` };
335
+ }
336
+ const selected = downloaded[0];
337
+ if (!selected || typeof selected.path !== 'string' || !selected.path) {
338
+ return { ok: false, reason: 'downloaded_artifact_path_invalid' };
339
+ }
340
+ const matches = m.artifacts.filter((artifact) => artifact.path === selected.path);
341
+ if (matches.length === 0)
342
+ return { ok: false, reason: `artifact_missing: ${selected.path}` };
343
+ if (matches.length !== 1)
344
+ return { ok: false, reason: `artifact_duplicate: ${selected.path}` };
345
+ const expected = matches[0];
346
+ if (!expected)
347
+ return { ok: false, reason: `artifact_missing: ${selected.path}` };
348
+ let digest;
349
+ if (selected.sha256)
350
+ digest = selected.sha256.toLowerCase();
351
+ else if (selected.bytes)
352
+ digest = sha256Hex(selected.bytes);
353
+ else
354
+ return { ok: false, reason: `artifact_no_content: ${selected.path}` };
355
+ if (digest !== expected.sha256.toLowerCase()) {
356
+ return { ok: false, reason: `sha256_mismatch: ${selected.path}` };
292
357
  }
293
358
  return { ok: true, reason: 'verified' };
294
359
  }
@@ -67,4 +67,6 @@ export declare function digestShouldSend(summary: ValueSummary): boolean;
67
67
  * caller then delivers nothing — no empty digest, ever). measured and estimated stay on separate lines. Pure:
68
68
  * the window label + the period string are passed in so the digest is deterministic.
69
69
  */
70
- export declare function buildValueDigest(summary: ValueSummary, period: string): string | null;
70
+ export declare function buildValueDigest(summary: ValueSummary, period: string, extras?: {
71
+ pendingReviewCount?: number;
72
+ }): string | null;
@@ -221,7 +221,7 @@ export function digestShouldSend(summary) {
221
221
  * caller then delivers nothing — no empty digest, ever). measured and estimated stay on separate lines. Pure:
222
222
  * the window label + the period string are passed in so the digest is deterministic.
223
223
  */
224
- export function buildValueDigest(summary, period) {
224
+ export function buildValueDigest(summary, period, extras = {}) {
225
225
  if (!digestShouldSend(summary))
226
226
  return null;
227
227
  const L = [];
@@ -238,6 +238,10 @@ export function buildValueDigest(summary, period) {
238
238
  for (const g of summary.topGenes.slice(0, 5))
239
239
  L.push(`- ${g.assetId} — reused ×${g.reuses}`);
240
240
  }
241
+ if ((extras.pendingReviewCount ?? 0) > 0) {
242
+ L.push('');
243
+ L.push(`${extras.pendingReviewCount} gene(s) still awaiting review — run \`evolver review\` so they can enter SessionStart.`);
244
+ }
241
245
  L.push('');
242
246
  L.push('_Run `evolver value` any time for the full breakdown. Turn this digest off with `EVOLVER_VALUE_DIGEST=0`._');
243
247
  return L.join('\n');