@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,25 @@
1
+ import { ulid as makeUlid } from 'ulid';
2
+ import { material } from '../schema/material.js';
3
+ /** Fresh ULID for a Material record. Kept in core so the ULID constraint lives next to the schema. */
4
+ export function newMaterialId() {
5
+ return makeUlid();
6
+ }
7
+ /**
8
+ * Build a schema-valid Material from a recording event. The composition layer (CLI) owns the source content;
9
+ * core owns the record shape + id/clock generation. Re-record idempotency is the caller's concern (see the
10
+ * file watermark cursor): identical content yields a fresh materialId here, so dedup must gate the call, not
11
+ * the build (a Material's identity is its ULID, not its content — that is the M1 substrate's design).
12
+ */
13
+ export function buildMaterial(input) {
14
+ return material.parse({
15
+ materialId: input.materialId ?? newMaterialId(),
16
+ ...(input.sourceAgent ? { sourceAgent: input.sourceAgent } : {}),
17
+ sourceKind: input.sourceKind ?? 'runtime_session',
18
+ sourcePath: input.sourcePath,
19
+ kind: input.kind,
20
+ watermark: input.watermark,
21
+ capturedAt: input.capturedAt ?? new Date().toISOString(),
22
+ consumerGroup: input.consumerGroup,
23
+ ...(input.payload !== undefined ? { payload: input.payload } : {}),
24
+ });
25
+ }
@@ -0,0 +1,8 @@
1
+ export * from './materialStore.js';
2
+ export * from './watermark.js';
3
+ export * from './sources.js';
4
+ export * from './consumer.js';
5
+ export * from './sampling.js';
6
+ export * from './boundary.js';
7
+ export * from './emit.js';
8
+ export * from './factory.js';
@@ -0,0 +1,8 @@
1
+ export * from './materialStore.js';
2
+ export * from './watermark.js';
3
+ export * from './sources.js';
4
+ export * from './consumer.js';
5
+ export * from './sampling.js';
6
+ export * from './boundary.js';
7
+ export * from './emit.js';
8
+ export * from './factory.js';
@@ -0,0 +1,24 @@
1
+ import { type Material } from '../schema/material.js';
2
+ export interface MaterialStoreOptions {
3
+ path: string;
4
+ }
5
+ /** 原材料库 (append-only jsonl + single-writer + materialId 去重). */
6
+ export declare class MaterialStore {
7
+ readonly path: string;
8
+ private readonly lockPath;
9
+ private readonly seen;
10
+ private chain;
11
+ constructor(opts: MaterialStoreOptions);
12
+ /** 幂等 put: 已存在 materialId 跳过. */
13
+ put(input: Material): Promise<{
14
+ material: Material;
15
+ stored: boolean;
16
+ }>;
17
+ private putLocked;
18
+ get(materialId: string): Material | undefined;
19
+ readAll(): Material[];
20
+ iterate(opts?: {
21
+ consumerGroup?: string;
22
+ since?: string;
23
+ }): Generator<Material>;
24
+ }
@@ -0,0 +1,76 @@
1
+ import { openSync, writeSync, fsyncSync, closeSync, existsSync, readFileSync, mkdirSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import { material } from '../schema/material.js';
4
+ import { acquireLock, releaseLock } from '../util/fileLock.js';
5
+ import { assertMaterialSource } from './boundary.js';
6
+ /** 原材料库 (append-only jsonl + single-writer + materialId 去重). */
7
+ export class MaterialStore {
8
+ path;
9
+ lockPath;
10
+ seen = new Set();
11
+ chain = Promise.resolve();
12
+ constructor(opts) {
13
+ this.path = opts.path;
14
+ this.lockPath = `${opts.path}.lock`;
15
+ mkdirSync(dirname(this.path), { recursive: true });
16
+ for (const m of this.readAll())
17
+ this.seen.add(m.materialId);
18
+ }
19
+ /** 幂等 put: 已存在 materialId 跳过. */
20
+ async put(input) {
21
+ const run = this.chain.then(() => this.putLocked(input));
22
+ this.chain = run.then(() => undefined, () => undefined);
23
+ return run;
24
+ }
25
+ putLocked(input) {
26
+ const m = material.parse(input);
27
+ assertMaterialSource(m);
28
+ if (this.seen.has(m.materialId))
29
+ return { material: m, stored: false };
30
+ acquireLock(this.lockPath);
31
+ try {
32
+ const fd = openSync(this.path, 'a');
33
+ try {
34
+ writeSync(fd, `${JSON.stringify(m)}\n`);
35
+ fsyncSync(fd);
36
+ }
37
+ finally {
38
+ closeSync(fd);
39
+ }
40
+ this.seen.add(m.materialId);
41
+ return { material: m, stored: true };
42
+ }
43
+ finally {
44
+ releaseLock(this.lockPath);
45
+ }
46
+ }
47
+ get(materialId) {
48
+ for (const m of this.readAll())
49
+ if (m.materialId === materialId)
50
+ return m;
51
+ return undefined;
52
+ }
53
+ readAll() {
54
+ if (!existsSync(this.path))
55
+ return [];
56
+ const out = [];
57
+ for (const l of readFileSync(this.path, 'utf8').split('\n')) {
58
+ if (!l)
59
+ continue;
60
+ try {
61
+ out.push(material.parse(JSON.parse(l)));
62
+ }
63
+ catch { /* skip */ }
64
+ }
65
+ return out;
66
+ }
67
+ *iterate(opts = {}) {
68
+ for (const m of this.readAll()) {
69
+ if (opts.consumerGroup !== undefined && m.consumerGroup !== opts.consumerGroup)
70
+ continue;
71
+ if (opts.since !== undefined && m.capturedAt < opts.since)
72
+ continue;
73
+ yield m;
74
+ }
75
+ }
76
+ }
@@ -0,0 +1,21 @@
1
+ export interface ToolEvent {
2
+ toolName?: string;
3
+ toolResult?: string;
4
+ errorMessage?: string;
5
+ }
6
+ export interface SamplingConfig {
7
+ defaultRate: number;
8
+ toolRates?: Record<string, number>;
9
+ collapseConsecutive: boolean;
10
+ maxPerBatch?: number;
11
+ }
12
+ export declare const CONSERVATIVE_SAMPLING: SamplingConfig;
13
+ /** tool_event 采样/过滤 (批注#12): 强信号(error)全留, 低价值降采样, 连续折叠, 突发限速. */
14
+ export declare class ToolEventSampler {
15
+ private readonly cfg;
16
+ private readonly counts;
17
+ private lastTool;
18
+ constructor(cfg: SamplingConfig);
19
+ shouldKeep(e: ToolEvent): boolean;
20
+ filter(events: readonly ToolEvent[]): ToolEvent[];
21
+ }
@@ -0,0 +1,39 @@
1
+ export const CONSERVATIVE_SAMPLING = {
2
+ defaultRate: 1, toolRates: { read: 100, ls: 100, glob: 50, grep: 20 }, collapseConsecutive: true, maxPerBatch: 500,
3
+ };
4
+ /** tool_event 采样/过滤 (批注#12): 强信号(error)全留, 低价值降采样, 连续折叠, 突发限速. */
5
+ export class ToolEventSampler {
6
+ cfg;
7
+ counts = new Map();
8
+ lastTool;
9
+ constructor(cfg) {
10
+ this.cfg = cfg;
11
+ }
12
+ shouldKeep(e) {
13
+ if (e.errorMessage) {
14
+ this.lastTool = e.toolName;
15
+ return true;
16
+ } // 错误 100% 保留
17
+ const name = e.toolName ?? '';
18
+ if (this.cfg.collapseConsecutive && name === this.lastTool) {
19
+ return false;
20
+ } // 折叠连续相同
21
+ this.lastTool = name;
22
+ const rate = this.cfg.toolRates?.[name] ?? this.cfg.defaultRate;
23
+ if (rate <= 1)
24
+ return true;
25
+ const n = (this.counts.get(name) ?? 0) + 1;
26
+ this.counts.set(name, n);
27
+ return n % rate === 1; // 每 N 留第 1 个
28
+ }
29
+ filter(events) {
30
+ const out = [];
31
+ for (const e of events) {
32
+ if (this.cfg.maxPerBatch !== undefined && out.length >= this.cfg.maxPerBatch)
33
+ break;
34
+ if (this.shouldKeep(e))
35
+ out.push(e);
36
+ }
37
+ return out;
38
+ }
39
+ }
@@ -0,0 +1,25 @@
1
+ import type { SourceAgent } from '../schema/common.js';
2
+ export interface RegisteredSource {
3
+ agent: SourceAgent;
4
+ kind: 'session_log' | 'tool_event';
5
+ globs: string[];
6
+ }
7
+ export declare const DEFAULT_DISCOVERY_WHITELIST: readonly string[];
8
+ export interface SourceRegistryOptions {
9
+ path?: string;
10
+ autoDiscover?: boolean;
11
+ whitelist?: readonly string[];
12
+ }
13
+ /** 源注册/发现 (批注#33): 默认只扫显式 register; 自动发现 opt-in + 白名单. */
14
+ export declare class SourceRegistry {
15
+ private readonly sources;
16
+ private readonly autoDiscover;
17
+ private readonly whitelist;
18
+ private readonly path;
19
+ constructor(opts?: SourceRegistryOptions);
20
+ register(s: RegisteredSource): void;
21
+ list(): readonly RegisteredSource[];
22
+ /** 自动发现根: 默认 off→空; opt-in→白名单(实际 glob 扫描在集成层). */
23
+ discoveryRoots(): string[];
24
+ private persist;
25
+ }
@@ -0,0 +1,33 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ export const DEFAULT_DISCOVERY_WHITELIST = [
5
+ join(homedir(), '.claude', 'projects'),
6
+ join(homedir(), '.codex', 'sessions'),
7
+ join(homedir(), '.cursor'),
8
+ ];
9
+ /** 源注册/发现 (批注#33): 默认只扫显式 register; 自动发现 opt-in + 白名单. */
10
+ export class SourceRegistry {
11
+ sources = [];
12
+ autoDiscover;
13
+ whitelist;
14
+ path;
15
+ constructor(opts = {}) {
16
+ this.autoDiscover = opts.autoDiscover ?? false;
17
+ this.whitelist = opts.whitelist ?? DEFAULT_DISCOVERY_WHITELIST;
18
+ this.path = opts.path;
19
+ if (this.path && existsSync(this.path)) {
20
+ this.sources.push(...JSON.parse(readFileSync(this.path, 'utf8')));
21
+ }
22
+ }
23
+ register(s) { this.sources.push(s); this.persist(); }
24
+ list() { return this.sources; }
25
+ /** 自动发现根: 默认 off→空; opt-in→白名单(实际 glob 扫描在集成层). */
26
+ discoveryRoots() { return this.autoDiscover ? [...this.whitelist] : []; }
27
+ persist() {
28
+ if (!this.path)
29
+ return;
30
+ mkdirSync(dirname(this.path), { recursive: true });
31
+ writeFileSync(this.path, JSON.stringify(this.sources, null, 2));
32
+ }
33
+ }
@@ -0,0 +1,21 @@
1
+ import type { Watermark } from '../schema/material.js';
2
+ export declare function fileWatermark(path: string, hashBytes?: number): Watermark;
3
+ export interface ScanResult {
4
+ changed: boolean;
5
+ newBytes: {
6
+ start: number;
7
+ end: number;
8
+ } | null;
9
+ watermark: Watermark;
10
+ }
11
+ /** 增量去重 (批注#15/#16): 快路径 (mtime,size,hash); 前缀完好+size 增→append 增量; 否则全量(rewrite/rename/truncate). */
12
+ export declare function scanFile(path: string, prev: Watermark | undefined, hashBytes?: number): ScanResult;
13
+ export declare function readRange(path: string, start: number, end: number): string;
14
+ /** watermark 游标持久化. */
15
+ export declare class WatermarkStore {
16
+ private readonly path;
17
+ private readonly map;
18
+ constructor(path: string);
19
+ get(filePath: string): Watermark | undefined;
20
+ set(filePath: string, wm: Watermark): void;
21
+ }
@@ -0,0 +1,76 @@
1
+ import { statSync, openSync, readSync, closeSync, existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import { createHash } from 'node:crypto';
4
+ /** hash 文件前 length 字节. */
5
+ function prefixHash(path, length) {
6
+ const fd = openSync(path, 'r');
7
+ try {
8
+ const n = Math.max(0, length);
9
+ const buf = Buffer.alloc(n);
10
+ if (n > 0)
11
+ readSync(fd, buf, 0, n, 0);
12
+ return `sha256:${createHash('sha256').update(buf).digest('hex')}`;
13
+ }
14
+ finally {
15
+ closeSync(fd);
16
+ }
17
+ }
18
+ export function fileWatermark(path, hashBytes = 4096) {
19
+ const st = statSync(path);
20
+ return { path, mtime: st.mtimeMs, size: st.size, contentHash: prefixHash(path, Math.min(hashBytes, st.size)) };
21
+ }
22
+ /** 增量去重 (批注#15/#16): 快路径 (mtime,size,hash); 前缀完好+size 增→append 增量; 否则全量(rewrite/rename/truncate). */
23
+ export function scanFile(path, prev, hashBytes = 4096) {
24
+ const wm = fileWatermark(path, hashBytes);
25
+ if (!prev)
26
+ return { changed: true, newBytes: { start: 0, end: wm.size }, watermark: wm };
27
+ if (prev.mtime === wm.mtime && prev.size === wm.size && prev.contentHash === wm.contentHash)
28
+ return { changed: false, newBytes: null, watermark: wm };
29
+ if (wm.size >= prev.size) {
30
+ const cover = Math.min(hashBytes, prev.size);
31
+ if (prev.contentHash === prefixHash(path, cover)) { // prev 前缀完好 → append
32
+ const newBytes = wm.size > prev.size ? { start: prev.size, end: wm.size } : null;
33
+ return { changed: newBytes !== null, newBytes, watermark: wm };
34
+ }
35
+ }
36
+ return { changed: true, newBytes: { start: 0, end: wm.size }, watermark: wm };
37
+ }
38
+ export function readRange(path, start, end) {
39
+ const fd = openSync(path, 'r');
40
+ try {
41
+ const buf = Buffer.alloc(end - start);
42
+ if (buf.length > 0)
43
+ readSync(fd, buf, 0, buf.length, start);
44
+ return buf.toString('utf8');
45
+ }
46
+ finally {
47
+ closeSync(fd);
48
+ }
49
+ }
50
+ /** watermark 游标持久化. */
51
+ export class WatermarkStore {
52
+ path;
53
+ map = new Map();
54
+ constructor(path) {
55
+ this.path = path;
56
+ // A corrupt watermark file (crash mid-write) must NOT crash the daemon at startup: degrade to empty → re-scan
57
+ // every source file. materialStore dedups by content/watermark, so a full re-scan is idempotent, not harmful.
58
+ if (existsSync(path)) {
59
+ try {
60
+ const o = JSON.parse(readFileSync(path, 'utf8'));
61
+ for (const [k, v] of Object.entries(o))
62
+ this.map.set(k, v);
63
+ }
64
+ catch { /* corrupt watermark → start empty (idempotent re-scan) */ }
65
+ }
66
+ }
67
+ get(filePath) { return this.map.get(filePath); }
68
+ set(filePath, wm) {
69
+ this.map.set(filePath, wm);
70
+ mkdirSync(dirname(this.path), { recursive: true });
71
+ // Atomic write: a crash mid-write keeps the previous watermark, never a truncated/corrupt file.
72
+ const tmp = `${this.path}.tmp`;
73
+ writeFileSync(tmp, JSON.stringify(Object.fromEntries(this.map)));
74
+ renameSync(tmp, this.path);
75
+ }
76
+ }
@@ -0,0 +1,46 @@
1
+ import type { Observer } from './observerBus.js';
2
+ /**
3
+ * Event types that mean "the APPROVED gene set the agent should see may have changed":
4
+ * - cycle.solidified / capsule.produced : a cycle solidified a gene — its learning history (and therefore the
5
+ * top-genes ordering) just moved. Emitted by the CycleEngine through the daemon's Ingestor → genuinely live.
6
+ * - actor.human.teach : a human taught a gene directly (`evolver distill`); it enters the pool
7
+ * UN-quarantined, so it is immediately part of the rendered set.
8
+ * - actor.human.review.approve : a human approved a quarantined draft (`evolver review --approve`) — it
9
+ * now passes the read-side review gate (A2a), so the rendered set genuinely changed (A2b).
10
+ * NOT subscribed: `gene.distilled`. An auto-distilled draft enters QUARANTINED, and the read side (A2a) withholds
11
+ * it until approved — so re-rendering on distill would only ever be a no-op. The meaningful change is the APPROVE
12
+ * above. Subscribing to this exact set (not all events) keeps the observer quiet on unrelated traffic.
13
+ */
14
+ export declare const GENE_SET_CHANGE_EVENT_TYPES: readonly string[];
15
+ /** Default debounce window — a cycle emits several gene-set-change events in quick succession; coalesce them
16
+ * into a single rewrite rather than re-rendering the file once per event. */
17
+ export declare const DEFAULT_CURSOR_REWRITE_DEBOUNCE_MS = 2000;
18
+ export interface CursorRewriteObserverDeps {
19
+ /**
20
+ * Re-render `.cursor/rules/evolver.mdc` from the CURRENT top genes. Injected by the composition layer (it reads
21
+ * the gene pool + writes the file). May return whether the file actually changed (for the optional change
22
+ * callback); a void return is treated as "rewritten". Async is awaited so the bus's timeout/DLQ apply.
23
+ */
24
+ rewrite(): boolean | void | Promise<boolean | void>;
25
+ /** Debounce window in ms (a burst of gene events ⇒ one rewrite). Default 2s. */
26
+ debounceMs?: number;
27
+ /** Injected clock (epoch ms). Default Date.now. */
28
+ now?: () => number;
29
+ /** Observer handle timeout — the bus quarantines on timeout. Default 5s (plenty for a local file render). */
30
+ timeoutMs?: number;
31
+ /** Optional: called after a rewrite that actually changed the file (the composition layer can emit value.inject
32
+ * here so the inject rail stays consistent across runtimes; deduped to "on real change" only). */
33
+ onChange?: () => void | Promise<void>;
34
+ }
35
+ /**
36
+ * Build the cursor rewrite observer. Subscribes to the gene-set-change event types only. On each such event it
37
+ * (re)arms a debounce timer; when the timer fires it calls `rewrite()` once for the whole burst. Because the bus
38
+ * dispatches asynchronously and the debounce is timer-based, the observer's own `handle` returns immediately
39
+ * (just arming the timer) — the actual rewrite runs on the timer and reports its own errors to `onError`.
40
+ *
41
+ * idempotent=false: a rewrite has an external side effect (a file write), so the bus must not assume free re-run.
42
+ */
43
+ export declare function cursorRewriteObserver(deps: CursorRewriteObserverDeps): Observer & {
44
+ flush(): Promise<void>;
45
+ pending(): boolean;
46
+ };
@@ -0,0 +1,103 @@
1
+ // Cursor rewrite observer (#124) — the observer-bus member that keeps cursor's injected gene memory FRESH.
2
+ //
3
+ // Cursor has no SessionStart hook (the CC/codex freshness mechanism): a cursor `alwaysApply:true` rule file is
4
+ // read as-is on every request, so there is no pull-at-start moment to re-render it. Freshness therefore comes
5
+ // from REWRITE-ON-CHANGE: when the gene set changes, we re-render .cursor/rules/evolver.mdc. This observer is
6
+ // the live trigger for that — it is hung off the SAME ObserverBus that fans out from the daemon's Ingestor, so a
7
+ // real cycle solidifying a gene (or a distill landing one) ticks it. It is the cursor analogue of the value
8
+ // digest observer: a side-effect hung off the bus, fully fault-isolated by the bus (a throwing rewrite
9
+ // quarantines the observer without ever touching the main write path).
10
+ //
11
+ // Boundaries: the observer owns ONLY the trigger policy (which events count as a gene-set change + debounce). It
12
+ // does NOT render or write the file itself — a `rewrite` callback is injected (the CLI composition layer wires
13
+ // it to the cursorRulesInstaller). So core stays free of fs/cursor knowledge and the observer is testable with a
14
+ // spy. Debounced so a burst of gene events (a cycle emits several) coalesces into ONE rewrite.
15
+ /**
16
+ * Event types that mean "the APPROVED gene set the agent should see may have changed":
17
+ * - cycle.solidified / capsule.produced : a cycle solidified a gene — its learning history (and therefore the
18
+ * top-genes ordering) just moved. Emitted by the CycleEngine through the daemon's Ingestor → genuinely live.
19
+ * - actor.human.teach : a human taught a gene directly (`evolver distill`); it enters the pool
20
+ * UN-quarantined, so it is immediately part of the rendered set.
21
+ * - actor.human.review.approve : a human approved a quarantined draft (`evolver review --approve`) — it
22
+ * now passes the read-side review gate (A2a), so the rendered set genuinely changed (A2b).
23
+ * NOT subscribed: `gene.distilled`. An auto-distilled draft enters QUARANTINED, and the read side (A2a) withholds
24
+ * it until approved — so re-rendering on distill would only ever be a no-op. The meaningful change is the APPROVE
25
+ * above. Subscribing to this exact set (not all events) keeps the observer quiet on unrelated traffic.
26
+ */
27
+ export const GENE_SET_CHANGE_EVENT_TYPES = [
28
+ 'cycle.solidified',
29
+ 'capsule.produced',
30
+ 'actor.human.teach',
31
+ 'actor.human.review.approve',
32
+ ];
33
+ /** Default debounce window — a cycle emits several gene-set-change events in quick succession; coalesce them
34
+ * into a single rewrite rather than re-rendering the file once per event. */
35
+ export const DEFAULT_CURSOR_REWRITE_DEBOUNCE_MS = 2_000;
36
+ /**
37
+ * Build the cursor rewrite observer. Subscribes to the gene-set-change event types only. On each such event it
38
+ * (re)arms a debounce timer; when the timer fires it calls `rewrite()` once for the whole burst. Because the bus
39
+ * dispatches asynchronously and the debounce is timer-based, the observer's own `handle` returns immediately
40
+ * (just arming the timer) — the actual rewrite runs on the timer and reports its own errors to `onError`.
41
+ *
42
+ * idempotent=false: a rewrite has an external side effect (a file write), so the bus must not assume free re-run.
43
+ */
44
+ export function cursorRewriteObserver(deps) {
45
+ const debounceMs = deps.debounceMs ?? DEFAULT_CURSOR_REWRITE_DEBOUNCE_MS;
46
+ const now = deps.now ?? (() => Date.now());
47
+ let timer;
48
+ let armedAt;
49
+ let lastError;
50
+ const meta = {
51
+ name: 'cursor-rewrite',
52
+ eventTypes: GENE_SET_CHANGE_EVENT_TYPES,
53
+ idempotent: false,
54
+ timeoutMs: deps.timeoutMs ?? 5_000,
55
+ };
56
+ // The actual rewrite, run once per coalesced burst. Swallows nothing on its own — but since it runs on a timer
57
+ // (out of the bus's handle path) it cannot use the bus DLQ, so it records lastError for flush() to surface.
58
+ async function fire() {
59
+ timer = undefined;
60
+ armedAt = undefined;
61
+ try {
62
+ const changed = await deps.rewrite();
63
+ if (changed !== false && deps.onChange)
64
+ await deps.onChange();
65
+ lastError = undefined;
66
+ }
67
+ catch (e) {
68
+ lastError = e;
69
+ }
70
+ }
71
+ function arm() {
72
+ armedAt = now();
73
+ if (timer)
74
+ clearTimeout(timer);
75
+ timer = setTimeout(() => { void fire(); }, debounceMs);
76
+ // Do not keep the process alive solely for a pending rewrite (the daemon is resident anyway; tests exit).
77
+ timer.unref?.();
78
+ }
79
+ return {
80
+ meta,
81
+ handle(_event) {
82
+ // Just (re)arm the debounce — fast + non-blocking so the bus never stalls on a gene event.
83
+ arm();
84
+ },
85
+ /** Force any pending debounced rewrite to run now and await it (graceful shutdown / tests). Re-throws the
86
+ * last rewrite error so a broken rewrite is not silently swallowed when explicitly flushed. */
87
+ async flush() {
88
+ if (timer) {
89
+ clearTimeout(timer);
90
+ timer = undefined;
91
+ armedAt = undefined;
92
+ await fire();
93
+ }
94
+ if (lastError) {
95
+ const e = lastError;
96
+ lastError = undefined;
97
+ throw e instanceof Error ? e : new Error(String(e));
98
+ }
99
+ },
100
+ /** Whether a rewrite is currently armed (debounce in flight). */
101
+ pending() { return armedAt !== undefined; },
102
+ };
103
+ }
@@ -0,0 +1,31 @@
1
+ import type { Observer } from './observerBus.js';
2
+ /** The root_event ingestion emits when a batch of material lands (see CLI ingest). The only trigger this observer
3
+ * subscribes to — it claims from the consumer group, so the event is a tick, not a payload it reads. */
4
+ export declare const MATERIAL_READY_EVENT_TYPE = "material.batch_ready";
5
+ export interface DistillObserverDeps {
6
+ /**
7
+ * Drain newly-ready material into UNPROVEN quarantined gene drafts. Injected by the CLI composition (it owns the
8
+ * consumer-group claim, session re-parse, intake, quarantine, and the gene.distilled emit). Returns TRUE when the
9
+ * drain STALLED — it stopped on a stuck head (un-acked material it could not advance past, e.g. a transient
10
+ * persist/audit failure) with a backlog still behind it. A stalled drain needs another attempt that does NOT
11
+ * depend on a future material.batch_ready, so the observer self-schedules a backoff retry. Falsy = fully drained.
12
+ */
13
+ distill(): boolean | void | Promise<boolean | void>;
14
+ /** Backoff before retrying a STALLED drain (no new material.batch_ready needed). Default 30s. */
15
+ retryDelayMs?: number;
16
+ /** Observer handle timeout the bus enforces. Default 30s (a drain re-reads + parses several session files). */
17
+ timeoutMs?: number;
18
+ }
19
+ /**
20
+ * Build the distill observer. Subscribes to `material.batch_ready` only. On each such event it runs `distill()`
21
+ * once, SINGLE-FLIGHT: an event arriving while a drain is in flight is coalesced into ONE trailing drain (never
22
+ * dropped). If a drain STALLS on a stuck head, the observer self-schedules a backoff retry so a transient write
23
+ * failure cannot stall the queue forever waiting on an external event. idempotent=false: distill writes a gene +
24
+ * a root_event, so the bus must not assume a free re-run.
25
+ */
26
+ export declare function distillObserver(deps: DistillObserverDeps): Observer & {
27
+ flush(): Promise<void>;
28
+ running(): boolean;
29
+ retryScheduled(): boolean;
30
+ kick(): void;
31
+ };
@@ -0,0 +1,93 @@
1
+ /** The root_event ingestion emits when a batch of material lands (see CLI ingest). The only trigger this observer
2
+ * subscribes to — it claims from the consumer group, so the event is a tick, not a payload it reads. */
3
+ export const MATERIAL_READY_EVENT_TYPE = 'material.batch_ready';
4
+ /**
5
+ * Build the distill observer. Subscribes to `material.batch_ready` only. On each such event it runs `distill()`
6
+ * once, SINGLE-FLIGHT: an event arriving while a drain is in flight is coalesced into ONE trailing drain (never
7
+ * dropped). If a drain STALLS on a stuck head, the observer self-schedules a backoff retry so a transient write
8
+ * failure cannot stall the queue forever waiting on an external event. idempotent=false: distill writes a gene +
9
+ * a root_event, so the bus must not assume a free re-run.
10
+ */
11
+ export function distillObserver(deps) {
12
+ const retryDelayMs = deps.retryDelayMs ?? 30_000;
13
+ let inFlight;
14
+ let pending = false; // a wakeup arrived while a drain was running — coalesce it into ONE trailing drain
15
+ let retryTimer;
16
+ let lastError;
17
+ function trigger() {
18
+ // Single-flight with coalesce: if a drain is running, REMEMBER the wakeup (pending); otherwise start one.
19
+ if (inFlight) {
20
+ pending = true;
21
+ return;
22
+ }
23
+ // Wrap in an IIFE that AWAITS run(): even when run() settles synchronously (e.g. deps.distill() throws before
24
+ // any real await), the `await` suspends, so `inFlight = …` is ASSIGNED before we clear it. Clearing inside
25
+ // run()'s own finally would otherwise race the assignment, leave inFlight pointing at an already-settled
26
+ // promise, and make flush()'s `while (inFlight) await inFlight` spin forever. Clear + retry live HERE (after
27
+ // the await) so inFlight is undefined before scheduleRetry checks it.
28
+ inFlight = (async () => {
29
+ const stalled = await run();
30
+ inFlight = undefined;
31
+ if (stalled)
32
+ scheduleRetry();
33
+ })();
34
+ }
35
+ function scheduleRetry() {
36
+ if (retryTimer || inFlight)
37
+ return; // a retry/run is already coming
38
+ retryTimer = setTimeout(() => { retryTimer = undefined; trigger(); }, retryDelayMs);
39
+ retryTimer.unref?.(); // never keep the process alive solely for a pending retry
40
+ }
41
+ /** One drain cycle. Returns true if it STALLED on a stuck head (un-acked backlog), false if fully drained.
42
+ * Trailing edge: loops while wakeups arrive mid-drain so a coalesced wakeup is never dropped. Does NOT touch
43
+ * inFlight (the caller owns it — see trigger). */
44
+ async function run() {
45
+ let stalled = false;
46
+ try {
47
+ do {
48
+ pending = false;
49
+ stalled = Boolean(await deps.distill());
50
+ } while (pending);
51
+ lastError = undefined;
52
+ }
53
+ catch (e) {
54
+ // A THROWN drain (e.g. store.list / readAll failed) is a transient failure that left the backlog un-drained
55
+ // — treat it as stalled so a backoff retry is scheduled, same as an explicit stuck head. Without this a
56
+ // throw would silently stop all draining until an unrelated future event.
57
+ lastError = e;
58
+ stalled = true;
59
+ }
60
+ return stalled;
61
+ }
62
+ const meta = {
63
+ name: 'distill',
64
+ eventTypes: [MATERIAL_READY_EVENT_TYPE],
65
+ idempotent: false,
66
+ timeoutMs: deps.timeoutMs ?? 30_000,
67
+ };
68
+ return {
69
+ meta,
70
+ handle(_event) {
71
+ trigger(); // returns immediately (the drain runs in the background) so the bus never stalls on a slow drain
72
+ },
73
+ /** Await the drain chain to settle (a pending trailing drain may chain another) and re-throw its last error
74
+ * (graceful shutdown / tests) — so a broken drain is not silently swallowed when explicitly flushed. */
75
+ async flush() {
76
+ while (inFlight)
77
+ await inFlight;
78
+ if (lastError) {
79
+ const e = lastError;
80
+ lastError = undefined;
81
+ throw e instanceof Error ? e : new Error(String(e));
82
+ }
83
+ },
84
+ /** Whether a drain is currently in flight. */
85
+ running() { return inFlight !== undefined; },
86
+ /** Whether a backoff retry of a stalled drain is armed (introspection for tests / shutdown). */
87
+ retryScheduled() { return retryTimer !== undefined; },
88
+ /** Trigger an initial drain WITHOUT a material.batch_ready event — the daemon calls this once after
89
+ * registration so a process restart recovers any un-acked backlog (the in-memory backoff retry does not
90
+ * survive a restart). Same single-flight/coalesce/retry path as an event-driven wakeup. */
91
+ kick() { trigger(); },
92
+ };
93
+ }
@@ -0,0 +1,5 @@
1
+ export * from './observerBus.js';
2
+ export * from './valueDigestObserver.js';
3
+ export * from './cursorRewriteObserver.js';
4
+ export * from './distillObserver.js';
5
+ export * from './reflectionObserver.js';
@@ -0,0 +1,5 @@
1
+ export * from './observerBus.js';
2
+ export * from './valueDigestObserver.js';
3
+ export * from './cursorRewriteObserver.js';
4
+ export * from './distillObserver.js';
5
+ export * from './reflectionObserver.js';