footprintjs 10.0.1 → 10.1.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 (350) hide show
  1. package/AGENTS.md +2 -1
  2. package/CLAUDE.md +43 -7
  3. package/README.md +136 -312
  4. package/dist/esm/index.d.ts +60 -2
  5. package/dist/esm/index.js +13 -6
  6. package/dist/esm/lib/attempt/anySignal.d.ts +19 -0
  7. package/dist/esm/lib/attempt/anySignal.js +39 -0
  8. package/dist/esm/lib/attempt/call.d.ts +88 -0
  9. package/dist/esm/lib/attempt/call.js +129 -0
  10. package/dist/esm/lib/attempt/category.d.ts +31 -0
  11. package/dist/esm/lib/attempt/category.js +107 -0
  12. package/dist/esm/lib/attempt/correlation.d.ts +95 -0
  13. package/dist/esm/lib/attempt/correlation.js +178 -0
  14. package/dist/esm/lib/attempt/deadline.d.ts +44 -0
  15. package/dist/esm/lib/attempt/deadline.js +101 -0
  16. package/dist/esm/lib/attempt/decision.d.ts +52 -0
  17. package/dist/esm/lib/attempt/decision.js +134 -0
  18. package/dist/esm/lib/attempt/effect.d.ts +76 -0
  19. package/dist/esm/lib/attempt/effect.js +160 -0
  20. package/dist/esm/lib/attempt/errors.d.ts +41 -0
  21. package/dist/esm/lib/attempt/errors.js +44 -0
  22. package/dist/esm/lib/attempt/index.d.ts +18 -0
  23. package/dist/esm/lib/attempt/index.js +15 -0
  24. package/dist/esm/lib/attempt/name.d.ts +21 -0
  25. package/dist/esm/lib/attempt/name.js +43 -0
  26. package/dist/esm/lib/attempt/observed.d.ts +66 -0
  27. package/dist/esm/lib/attempt/observed.js +171 -0
  28. package/dist/esm/lib/attempt/policy.d.ts +45 -0
  29. package/dist/esm/lib/attempt/policy.js +83 -0
  30. package/dist/esm/lib/attempt/record.d.ts +11 -0
  31. package/dist/esm/lib/attempt/record.js +24 -0
  32. package/dist/esm/lib/attempt/retryLoopContext.d.ts +18 -0
  33. package/dist/esm/lib/attempt/retryLoopContext.js +24 -0
  34. package/dist/esm/lib/attempt/retryLoopTypes.d.ts +18 -0
  35. package/dist/esm/lib/attempt/retryLoopTypes.js +2 -0
  36. package/dist/esm/lib/attempt/runner.d.ts +155 -0
  37. package/dist/esm/lib/attempt/runner.js +503 -0
  38. package/dist/esm/lib/attempt/standalone.d.ts +47 -0
  39. package/dist/esm/lib/attempt/standalone.js +126 -0
  40. package/dist/esm/lib/attempt/types.d.ts +453 -0
  41. package/dist/esm/lib/attempt/types.js +16 -0
  42. package/dist/esm/lib/budget/index.d.ts +9 -0
  43. package/dist/esm/lib/budget/index.js +9 -0
  44. package/dist/esm/lib/budget/timeBudget.d.ts +136 -0
  45. package/dist/esm/lib/budget/timeBudget.js +666 -0
  46. package/dist/esm/lib/builder/FlowChartBuilder.d.ts +88 -3
  47. package/dist/esm/lib/builder/FlowChartBuilder.js +199 -9
  48. package/dist/esm/lib/builder/retryLoop.d.ts +56 -0
  49. package/dist/esm/lib/builder/retryLoop.js +283 -0
  50. package/dist/esm/lib/builder/types.d.ts +35 -1
  51. package/dist/esm/lib/builder/types.js +1 -1
  52. package/dist/esm/lib/decide/decide.js +2 -2
  53. package/dist/esm/lib/decide/index.d.ts +1 -1
  54. package/dist/esm/lib/decide/index.js +2 -2
  55. package/dist/esm/lib/decide/types.d.ts +3 -0
  56. package/dist/esm/lib/decide/types.js +1 -8
  57. package/dist/esm/lib/engine/graph/StageNode.d.ts +11 -1
  58. package/dist/esm/lib/engine/graph/StageNode.js +1 -1
  59. package/dist/esm/lib/engine/graph/prefixNodeTree.js +5 -1
  60. package/dist/esm/lib/engine/handlers/ResumeEntry.js +3 -1
  61. package/dist/esm/lib/engine/handlers/StageRunner.d.ts +8 -1
  62. package/dist/esm/lib/engine/handlers/StageRunner.js +36 -9
  63. package/dist/esm/lib/engine/handlers/SubflowExecutor.js +20 -4
  64. package/dist/esm/lib/engine/handlers/stageError.d.ts +7 -0
  65. package/dist/esm/lib/engine/handlers/stageError.js +11 -1
  66. package/dist/esm/lib/engine/index.d.ts +2 -2
  67. package/dist/esm/lib/engine/index.js +1 -1
  68. package/dist/esm/lib/engine/narrative/CombinedNarrativeRecorder.d.ts +27 -13
  69. package/dist/esm/lib/engine/narrative/CombinedNarrativeRecorder.js +68 -40
  70. package/dist/esm/lib/engine/narrative/FlowRecorderDispatcher.d.ts +2 -0
  71. package/dist/esm/lib/engine/narrative/FlowRecorderDispatcher.js +14 -1
  72. package/dist/esm/lib/engine/narrative/NarrativeFlowRecorder.d.ts +22 -3
  73. package/dist/esm/lib/engine/narrative/NarrativeFlowRecorder.js +61 -9
  74. package/dist/esm/lib/engine/narrative/NullControlFlowNarrativeGenerator.d.ts +1 -0
  75. package/dist/esm/lib/engine/narrative/NullControlFlowNarrativeGenerator.js +2 -1
  76. package/dist/esm/lib/engine/narrative/formatting/sentences.d.ts +23 -0
  77. package/dist/esm/lib/engine/narrative/formatting/sentences.js +127 -1
  78. package/dist/esm/lib/engine/narrative/index.d.ts +2 -2
  79. package/dist/esm/lib/engine/narrative/index.js +1 -1
  80. package/dist/esm/lib/engine/narrative/narrativeTypes.d.ts +27 -21
  81. package/dist/esm/lib/engine/narrative/narrativeTypes.js +1 -1
  82. package/dist/esm/lib/engine/narrative/recorders/ManifestFlowRecorder.d.ts +45 -2
  83. package/dist/esm/lib/engine/narrative/recorders/ManifestFlowRecorder.js +55 -2
  84. package/dist/esm/lib/engine/narrative/recorders/index.d.ts +1 -1
  85. package/dist/esm/lib/engine/narrative/recorders/index.js +1 -1
  86. package/dist/esm/lib/engine/narrative/types.d.ts +70 -4
  87. package/dist/esm/lib/engine/narrative/types.js +1 -1
  88. package/dist/esm/lib/engine/traversal/FlowchartTraverser.d.ts +59 -19
  89. package/dist/esm/lib/engine/traversal/FlowchartTraverser.js +182 -114
  90. package/dist/esm/lib/engine/traversalContext.d.ts +7 -2
  91. package/dist/esm/lib/engine/traversalContext.js +6 -3
  92. package/dist/esm/lib/engine/types.d.ts +52 -57
  93. package/dist/esm/lib/engine/types.js +1 -1
  94. package/dist/esm/lib/{decide → filter}/evaluator.d.ts +7 -2
  95. package/dist/esm/lib/filter/evaluator.js +135 -0
  96. package/dist/esm/lib/ids/idempotencyKey.d.ts +14 -0
  97. package/dist/esm/lib/ids/idempotencyKey.js +24 -0
  98. package/dist/esm/lib/memory/StageContext.d.ts +109 -3
  99. package/dist/esm/lib/memory/StageContext.js +214 -4
  100. package/dist/esm/lib/memory/borrowedMutation.d.ts +8 -0
  101. package/dist/esm/lib/memory/borrowedMutation.js +14 -1
  102. package/dist/esm/lib/memory/frameTypes.d.ts +30 -0
  103. package/dist/esm/lib/memory/frameTypes.js +3 -1
  104. package/dist/esm/lib/memory/redaction.d.ts +12 -0
  105. package/dist/esm/lib/memory/redaction.js +54 -5
  106. package/dist/esm/lib/pause/record.js +10 -1
  107. package/dist/esm/lib/pause/types.d.ts +19 -0
  108. package/dist/esm/lib/pause/types.js +17 -1
  109. package/dist/esm/lib/reactive/createTypedScope.js +3 -1
  110. package/dist/esm/lib/reactive/types.d.ts +36 -4
  111. package/dist/esm/lib/reactive/types.js +3 -1
  112. package/dist/esm/lib/recorder/AttemptRecorder.d.ts +141 -0
  113. package/dist/esm/lib/recorder/AttemptRecorder.js +412 -0
  114. package/dist/esm/lib/recorder/ControlDepRecorder.d.ts +22 -2
  115. package/dist/esm/lib/recorder/ControlDepRecorder.js +33 -2
  116. package/dist/esm/lib/recorder/EmitRecorder.d.ts +3 -0
  117. package/dist/esm/lib/recorder/EmitRecorder.js +1 -1
  118. package/dist/esm/lib/recorder/InOutRecorder.d.ts +37 -1
  119. package/dist/esm/lib/recorder/InOutRecorder.js +75 -6
  120. package/dist/esm/lib/recorder/QualityRecorder.d.ts +32 -5
  121. package/dist/esm/lib/recorder/QualityRecorder.js +87 -29
  122. package/dist/esm/lib/recorder/TopologyRecorder.d.ts +35 -5
  123. package/dist/esm/lib/recorder/TopologyRecorder.js +76 -2
  124. package/dist/esm/lib/recorder/attemptMetricRows.d.ts +11 -0
  125. package/dist/esm/lib/recorder/attemptMetricRows.js +202 -0
  126. package/dist/esm/lib/recorder/attemptMetrics.d.ts +7 -0
  127. package/dist/esm/lib/recorder/attemptMetrics.js +165 -0
  128. package/dist/esm/lib/recorder/attemptMetricsTypes.d.ts +114 -0
  129. package/dist/esm/lib/recorder/attemptMetricsTypes.js +2 -0
  130. package/dist/esm/lib/recorder/attemptOTel.d.ts +78 -0
  131. package/dist/esm/lib/recorder/attemptOTel.js +107 -0
  132. package/dist/esm/lib/recorder/attemptSeries.d.ts +81 -0
  133. package/dist/esm/lib/recorder/attemptSeries.js +103 -0
  134. package/dist/esm/lib/recorder/hooks.d.ts +9 -0
  135. package/dist/esm/lib/recorder/hooks.js +4 -1
  136. package/dist/esm/lib/runner/DeferredObserverTier.js +13 -1
  137. package/dist/esm/lib/runner/ExecutionRuntime.d.ts +54 -6
  138. package/dist/esm/lib/runner/ExecutionRuntime.js +57 -5
  139. package/dist/esm/lib/runner/FlowChartExecutor.d.ts +42 -2
  140. package/dist/esm/lib/runner/FlowChartExecutor.js +238 -45
  141. package/dist/esm/lib/runner/RunnableChart.d.ts +6 -0
  142. package/dist/esm/lib/runner/RunnableChart.js +5 -1
  143. package/dist/esm/lib/runner/attach.d.ts +2 -1
  144. package/dist/esm/lib/runner/attach.js +8 -2
  145. package/dist/esm/lib/runner/checkpoint.js +3 -1
  146. package/dist/esm/lib/runner/deadline.d.ts +42 -0
  147. package/dist/esm/lib/runner/deadline.js +90 -0
  148. package/dist/esm/lib/runner/index.d.ts +3 -1
  149. package/dist/esm/lib/runner/index.js +2 -1
  150. package/dist/esm/lib/runner/options.d.ts +17 -0
  151. package/dist/esm/lib/runner/options.js +3 -1
  152. package/dist/esm/lib/runner/recordSink.d.ts +128 -0
  153. package/dist/esm/lib/runner/recordSink.js +296 -0
  154. package/dist/esm/lib/runner/resume.d.ts +3 -0
  155. package/dist/esm/lib/runner/resume.js +22 -2
  156. package/dist/esm/lib/runner/snapshot.d.ts +2 -2
  157. package/dist/esm/lib/runner/snapshot.js +6 -2
  158. package/dist/esm/lib/scope/ScopeFacade.d.ts +118 -4
  159. package/dist/esm/lib/scope/ScopeFacade.js +304 -7
  160. package/dist/esm/lib/scope/recorders/DebugRecorder.d.ts +17 -3
  161. package/dist/esm/lib/scope/recorders/DebugRecorder.js +79 -13
  162. package/dist/esm/lib/scope/recorders/MetricRecorder.d.ts +10 -0
  163. package/dist/esm/lib/scope/recorders/MetricRecorder.js +15 -2
  164. package/dist/esm/lib/scope/runtime.d.ts +3 -0
  165. package/dist/esm/lib/scope/runtime.js +1 -1
  166. package/dist/esm/lib/scope/types.d.ts +6 -0
  167. package/dist/esm/lib/scope/types.js +1 -1
  168. package/dist/esm/lib/sinks/fileSink.d.ts +135 -0
  169. package/dist/esm/lib/sinks/fileSink.js +387 -0
  170. package/dist/esm/recorders.d.ts +8 -2
  171. package/dist/esm/recorders.js +7 -1
  172. package/dist/esm/sinks.d.ts +23 -0
  173. package/dist/esm/sinks.js +23 -0
  174. package/dist/esm/trace.d.ts +2 -2
  175. package/dist/esm/trace.js +1 -1
  176. package/dist/index.js +77 -60
  177. package/dist/lib/attempt/anySignal.js +43 -0
  178. package/dist/lib/attempt/call.js +134 -0
  179. package/dist/lib/attempt/category.js +111 -0
  180. package/dist/lib/attempt/correlation.js +186 -0
  181. package/dist/lib/attempt/deadline.js +112 -0
  182. package/dist/lib/attempt/decision.js +139 -0
  183. package/dist/lib/attempt/effect.js +174 -0
  184. package/dist/lib/attempt/errors.js +49 -0
  185. package/dist/lib/attempt/index.js +26 -0
  186. package/dist/lib/attempt/name.js +48 -0
  187. package/dist/lib/attempt/observed.js +177 -0
  188. package/dist/lib/attempt/policy.js +92 -0
  189. package/dist/lib/attempt/record.js +29 -0
  190. package/dist/lib/attempt/retryLoopContext.js +31 -0
  191. package/dist/lib/attempt/retryLoopTypes.js +3 -0
  192. package/dist/lib/attempt/runner.js +510 -0
  193. package/dist/lib/attempt/standalone.js +130 -0
  194. package/dist/lib/attempt/types.js +17 -0
  195. package/dist/lib/budget/index.js +17 -0
  196. package/dist/lib/budget/timeBudget.js +674 -0
  197. package/dist/lib/builder/FlowChartBuilder.js +199 -9
  198. package/dist/lib/builder/retryLoop.js +287 -0
  199. package/dist/lib/builder/types.js +1 -1
  200. package/dist/lib/decide/decide.js +2 -2
  201. package/dist/lib/decide/index.js +2 -2
  202. package/dist/lib/decide/types.js +1 -8
  203. package/dist/lib/engine/graph/StageNode.js +1 -1
  204. package/dist/lib/engine/graph/prefixNodeTree.js +5 -1
  205. package/dist/lib/engine/handlers/ResumeEntry.js +3 -1
  206. package/dist/lib/engine/handlers/StageRunner.js +36 -9
  207. package/dist/lib/engine/handlers/SubflowExecutor.js +20 -4
  208. package/dist/lib/engine/handlers/stageError.js +13 -2
  209. package/dist/lib/engine/index.js +1 -1
  210. package/dist/lib/engine/narrative/CombinedNarrativeRecorder.js +67 -39
  211. package/dist/lib/engine/narrative/FlowRecorderDispatcher.js +14 -1
  212. package/dist/lib/engine/narrative/NarrativeFlowRecorder.js +60 -8
  213. package/dist/lib/engine/narrative/NullControlFlowNarrativeGenerator.js +2 -1
  214. package/dist/lib/engine/narrative/formatting/sentences.js +131 -2
  215. package/dist/lib/engine/narrative/index.js +1 -1
  216. package/dist/lib/engine/narrative/narrativeTypes.js +1 -1
  217. package/dist/lib/engine/narrative/recorders/ManifestFlowRecorder.js +55 -2
  218. package/dist/lib/engine/narrative/recorders/index.js +1 -1
  219. package/dist/lib/engine/narrative/types.js +1 -1
  220. package/dist/lib/engine/traversal/FlowchartTraverser.js +181 -113
  221. package/dist/lib/engine/traversalContext.js +6 -3
  222. package/dist/lib/engine/types.js +1 -1
  223. package/dist/lib/filter/evaluator.js +139 -0
  224. package/dist/lib/ids/idempotencyKey.js +28 -0
  225. package/dist/lib/memory/StageContext.js +215 -5
  226. package/dist/lib/memory/borrowedMutation.js +16 -2
  227. package/dist/lib/memory/frameTypes.js +3 -1
  228. package/dist/lib/memory/redaction.js +54 -5
  229. package/dist/lib/pause/record.js +10 -1
  230. package/dist/lib/pause/types.js +17 -1
  231. package/dist/lib/reactive/createTypedScope.js +3 -1
  232. package/dist/lib/reactive/types.js +3 -1
  233. package/dist/lib/recorder/AttemptRecorder.js +417 -0
  234. package/dist/lib/recorder/ControlDepRecorder.js +33 -2
  235. package/dist/lib/recorder/EmitRecorder.js +1 -1
  236. package/dist/lib/recorder/InOutRecorder.js +75 -6
  237. package/dist/lib/recorder/QualityRecorder.js +87 -29
  238. package/dist/lib/recorder/TopologyRecorder.js +76 -2
  239. package/dist/lib/recorder/attemptMetricRows.js +209 -0
  240. package/dist/lib/recorder/attemptMetrics.js +174 -0
  241. package/dist/lib/recorder/attemptMetricsTypes.js +3 -0
  242. package/dist/lib/recorder/attemptOTel.js +111 -0
  243. package/dist/lib/recorder/attemptSeries.js +113 -0
  244. package/dist/lib/recorder/hooks.js +4 -1
  245. package/dist/lib/runner/DeferredObserverTier.js +13 -1
  246. package/dist/lib/runner/ExecutionRuntime.js +57 -5
  247. package/dist/lib/runner/FlowChartExecutor.js +238 -45
  248. package/dist/lib/runner/RunnableChart.js +5 -1
  249. package/dist/lib/runner/attach.js +8 -2
  250. package/dist/lib/runner/checkpoint.js +3 -1
  251. package/dist/lib/runner/deadline.js +94 -0
  252. package/dist/lib/runner/index.js +4 -2
  253. package/dist/lib/runner/options.js +3 -1
  254. package/dist/lib/runner/recordSink.js +302 -0
  255. package/dist/lib/runner/resume.js +22 -2
  256. package/dist/lib/runner/snapshot.js +6 -2
  257. package/dist/lib/scope/ScopeFacade.js +303 -6
  258. package/dist/lib/scope/recorders/DebugRecorder.js +79 -13
  259. package/dist/lib/scope/recorders/MetricRecorder.js +15 -2
  260. package/dist/lib/scope/runtime.js +1 -1
  261. package/dist/lib/scope/types.js +1 -1
  262. package/dist/lib/sinks/fileSink.js +392 -0
  263. package/dist/recorders.js +10 -2
  264. package/dist/sinks.js +32 -0
  265. package/dist/trace.js +1 -1
  266. package/dist/types/index.d.ts +60 -2
  267. package/dist/types/lib/attempt/anySignal.d.ts +19 -0
  268. package/dist/types/lib/attempt/call.d.ts +88 -0
  269. package/dist/types/lib/attempt/category.d.ts +31 -0
  270. package/dist/types/lib/attempt/correlation.d.ts +95 -0
  271. package/dist/types/lib/attempt/deadline.d.ts +44 -0
  272. package/dist/types/lib/attempt/decision.d.ts +52 -0
  273. package/dist/types/lib/attempt/effect.d.ts +76 -0
  274. package/dist/types/lib/attempt/errors.d.ts +41 -0
  275. package/dist/types/lib/attempt/index.d.ts +18 -0
  276. package/dist/types/lib/attempt/name.d.ts +21 -0
  277. package/dist/types/lib/attempt/observed.d.ts +66 -0
  278. package/dist/types/lib/attempt/policy.d.ts +45 -0
  279. package/dist/types/lib/attempt/record.d.ts +11 -0
  280. package/dist/types/lib/attempt/retryLoopContext.d.ts +18 -0
  281. package/dist/types/lib/attempt/retryLoopTypes.d.ts +18 -0
  282. package/dist/types/lib/attempt/runner.d.ts +155 -0
  283. package/dist/types/lib/attempt/standalone.d.ts +47 -0
  284. package/dist/types/lib/attempt/types.d.ts +453 -0
  285. package/dist/types/lib/budget/index.d.ts +9 -0
  286. package/dist/types/lib/budget/timeBudget.d.ts +136 -0
  287. package/dist/types/lib/builder/FlowChartBuilder.d.ts +88 -3
  288. package/dist/types/lib/builder/retryLoop.d.ts +56 -0
  289. package/dist/types/lib/builder/types.d.ts +35 -1
  290. package/dist/types/lib/decide/index.d.ts +1 -1
  291. package/dist/types/lib/decide/types.d.ts +3 -0
  292. package/dist/types/lib/engine/graph/StageNode.d.ts +11 -1
  293. package/dist/types/lib/engine/handlers/StageRunner.d.ts +8 -1
  294. package/dist/types/lib/engine/handlers/stageError.d.ts +7 -0
  295. package/dist/types/lib/engine/index.d.ts +2 -2
  296. package/dist/types/lib/engine/narrative/CombinedNarrativeRecorder.d.ts +27 -13
  297. package/dist/types/lib/engine/narrative/FlowRecorderDispatcher.d.ts +2 -0
  298. package/dist/types/lib/engine/narrative/NarrativeFlowRecorder.d.ts +22 -3
  299. package/dist/types/lib/engine/narrative/NullControlFlowNarrativeGenerator.d.ts +1 -0
  300. package/dist/types/lib/engine/narrative/formatting/sentences.d.ts +23 -0
  301. package/dist/types/lib/engine/narrative/index.d.ts +2 -2
  302. package/dist/types/lib/engine/narrative/narrativeTypes.d.ts +27 -21
  303. package/dist/types/lib/engine/narrative/recorders/ManifestFlowRecorder.d.ts +45 -2
  304. package/dist/types/lib/engine/narrative/recorders/index.d.ts +1 -1
  305. package/dist/types/lib/engine/narrative/types.d.ts +70 -4
  306. package/dist/types/lib/engine/traversal/FlowchartTraverser.d.ts +59 -19
  307. package/dist/types/lib/engine/traversalContext.d.ts +7 -2
  308. package/dist/types/lib/engine/types.d.ts +52 -57
  309. package/dist/types/lib/{decide → filter}/evaluator.d.ts +7 -2
  310. package/dist/types/lib/ids/idempotencyKey.d.ts +14 -0
  311. package/dist/types/lib/memory/StageContext.d.ts +109 -3
  312. package/dist/types/lib/memory/borrowedMutation.d.ts +8 -0
  313. package/dist/types/lib/memory/frameTypes.d.ts +30 -0
  314. package/dist/types/lib/memory/redaction.d.ts +12 -0
  315. package/dist/types/lib/pause/types.d.ts +19 -0
  316. package/dist/types/lib/reactive/types.d.ts +36 -4
  317. package/dist/types/lib/recorder/AttemptRecorder.d.ts +141 -0
  318. package/dist/types/lib/recorder/ControlDepRecorder.d.ts +22 -2
  319. package/dist/types/lib/recorder/EmitRecorder.d.ts +3 -0
  320. package/dist/types/lib/recorder/InOutRecorder.d.ts +37 -1
  321. package/dist/types/lib/recorder/QualityRecorder.d.ts +32 -5
  322. package/dist/types/lib/recorder/TopologyRecorder.d.ts +35 -5
  323. package/dist/types/lib/recorder/attemptMetricRows.d.ts +11 -0
  324. package/dist/types/lib/recorder/attemptMetrics.d.ts +7 -0
  325. package/dist/types/lib/recorder/attemptMetricsTypes.d.ts +114 -0
  326. package/dist/types/lib/recorder/attemptOTel.d.ts +78 -0
  327. package/dist/types/lib/recorder/attemptSeries.d.ts +81 -0
  328. package/dist/types/lib/recorder/hooks.d.ts +9 -0
  329. package/dist/types/lib/runner/ExecutionRuntime.d.ts +54 -6
  330. package/dist/types/lib/runner/FlowChartExecutor.d.ts +42 -2
  331. package/dist/types/lib/runner/RunnableChart.d.ts +6 -0
  332. package/dist/types/lib/runner/attach.d.ts +2 -1
  333. package/dist/types/lib/runner/deadline.d.ts +42 -0
  334. package/dist/types/lib/runner/index.d.ts +3 -1
  335. package/dist/types/lib/runner/options.d.ts +17 -0
  336. package/dist/types/lib/runner/recordSink.d.ts +128 -0
  337. package/dist/types/lib/runner/resume.d.ts +3 -0
  338. package/dist/types/lib/runner/snapshot.d.ts +2 -2
  339. package/dist/types/lib/scope/ScopeFacade.d.ts +118 -4
  340. package/dist/types/lib/scope/recorders/DebugRecorder.d.ts +17 -3
  341. package/dist/types/lib/scope/recorders/MetricRecorder.d.ts +10 -0
  342. package/dist/types/lib/scope/runtime.d.ts +3 -0
  343. package/dist/types/lib/scope/types.d.ts +6 -0
  344. package/dist/types/lib/sinks/fileSink.d.ts +135 -0
  345. package/dist/types/recorders.d.ts +8 -2
  346. package/dist/types/sinks.d.ts +23 -0
  347. package/dist/types/trace.d.ts +2 -2
  348. package/package.json +16 -2
  349. package/dist/esm/lib/decide/evaluator.js +0 -130
  350. package/dist/lib/decide/evaluator.js +0 -134
package/AGENTS.md CHANGED
@@ -23,6 +23,7 @@ FootPrint 10 has six engine doors; the record has three separate `foottrace` doo
23
23
  | `footprintjs/advanced` | Engine internals (`StageContext`, `FlowchartTraverser`, `ScopeFacade`, scope providers, `SCOPE_METHOD_NAMES`, `ArrayMergeMode`, run policy and `RedactionRule`). No record declarations are exported here. |
24
24
  | `footprintjs/detach` | Fire-and-forget child charts and their drivers |
25
25
  | `footprintjs/zod` | Opt-in zod bridge (`defineScopeFromZod`, …) — the core never imports zod |
26
+ | `footprintjs/sinks` | NODE ONLY — `fileSink(path)` / `readRecordFile(path)`: the crash-safe record as JSON lines (the executor's `sink` option; read back with foottrace's `readStoredRecord`) |
26
27
 
27
28
  The engine module map and layering rule live in `CLAUDE.md` and `scripts/layering.config.cjs`, enforced by lint and `npm run check:layering`. The standalone record has its own fence in foottrace. Engine code must use named public foottrace imports, never a private path or a local copy of a record rule.
28
29
 
@@ -401,7 +402,7 @@ console.log(first, second);
401
402
  - Built-in: `MetricRecorder`, `DebugRecorder`
402
403
 
403
404
  **FlowRecorder** (control flow — fires AFTER the stage's commit):
404
- - `onStageExecuted` (universal "did this stage run", carries `stageType: 'linear' | 'decider' | 'fork' | 'selector' | 'subflow-mount'`), `onNext`, `onDecision`, `onFork`, `onSelected`, `onSubflowEntry/Exit`, `onSubflowRegistered`, `onLoop`, `onBreak`, `onStageRetry`, `onError`, `onPause`/`onResume`, `onRunStart`/`onRunEnd`, `onRunFailed`
405
+ - `onStageExecuted` (universal "did this stage run", carries `stageType: 'linear' | 'decider' | 'fork' | 'selector' | 'subflow-mount'`), `onNext`, `onDecision`, `onFork`, `onSelected`, `onSubflowEntry/Exit`, `onSubflowRegistered`, `onLoop`, `onBreak`, `onAttempt` (every attempt of a retried stage, with its record and decision), `onStageRetry`, `onError`, `onPause`/`onResume`, `onRunStart`/`onRunEnd`, `onRunFailed`
405
406
  - Events carry an optional `traversalContext: TraversalContext` (includes per-run `runId`)
406
407
  - `onDecision`/`onSelected` carry optional `evidence` from decide()/select()
407
408
  - Built-in: 9 strategies (Narrative, Adaptive, Windowed, RLE, Milestone, Progressive, Separate, Manifest, Silent)
package/CLAUDE.md CHANGED
@@ -4,7 +4,7 @@
4
4
  Self-explaining flowchart engine: a fluent builder emits a static chart; one DFS pass executes it while observer channels + a commit log capture everything ("collect during traversal, never post-process"). This file maps seams and blast radius; for API description read the code/README — **trust the code** where any doc disagrees.
5
5
 
6
6
  ## Module map
7
- Entry points: `footprintjs` (engine API), `/recorders`, `/trace` (engine recorders, stores and structure walkers), `/advanced` (engine internals), `/detach`, `/zod`. The separate `foottrace` package owns records: root for readers/types/IDs, `/write` for writer primitives, `/paths` for path rules and nested diagnostic writes. No record declaration is re-exported by FootPrint 10. See `docs/design/2026-10-trace-extraction.md` for the extraction design and migration map.
7
+ Entry points: `footprintjs` (engine API), `/recorders`, `/trace` (engine recorders, stores and structure walkers), `/advanced` (engine internals), `/detach`, `/zod`, `/sinks` (10.1, NODE ONLY — `fileSink`, the one door that imports `node:fs`). The separate `foottrace` package owns records: root for readers/types/IDs, `/write` for writer primitives, `/paths` for path rules and nested diagnostic writes. No record declaration is re-exported by FootPrint 10. See `docs/design/2026-10-trace-extraction.md` for the extraction design and migration map.
8
8
 
9
9
  | src/lib/ | one job |
10
10
  |---|---|
@@ -12,14 +12,19 @@ Entry points: `footprintjs` (engine API), `/recorders`, `/trace` (engine recorde
12
12
  | memory/ | engine composition and policy: StageContext composes `RecordFrame` from `foottrace/write`; ExecutionRuntime owns run state and observer wiring; run addresses, redaction verdicts, frame types and diagnostics remain here. DiagnosticCollector uses `foottrace/paths`; replay/write rules are not copied here. |
13
13
  | schema/ | duck-typed validation (`.safeParse/.parse` ⇒ 'parseable') |
14
14
  | builder/ | fluent DSL → FlowChartSpec → StageNode graph; build-time StructureRecorder channel. NOT standalone: imports runner's makeRunnable (FlowChartBuilder.ts:18) |
15
+ | builder/retryLoop.ts | `addRetryLoop` (10.1, P3b) composes ordinary attempt/reconcile/decide/wait/done/stop nodes over the shared `retryRules` table. One operation key survives passes and pause/resume; native physical calls stay unchanged and `retryLoop` metadata links logical passes/decisions. The helper declares conservative total-pass budget metadata, preserved through subflow prefixing; see src/lib/builder/README.md. |
16
+ | budget/ | the static time budget (10.1, P5): `timeBudget(chart)` walks the BUILT graph once (explicit stack, each chain memoised per start, capped at `MAX_WALK` visits, saturating sums) — stage = attempts × timeoutMs + waits, sequence = sum, decider/selector/fork = own stage + slowest child, `parallelForEach` = one branch; a FUNCTION backoff is never called (counted at `RetryPolicy.maxBackoffMs`, which `attempt/policy.ts · cappedWaitMs` makes the runner enforce; else unbounded `'backoff'`); loops NEVER iterated (unbounded unless `assume.loops` or an explicit helper's declared pass bound). Read by the builder (`.budget(ms)` refuses at `build()` when `knownMs > budgetMs`) and the runner (`chart.timeBudget()`, the run-start warning) — its own module because a runner → builder edge closes the builder ⇄ runner cycle; see src/lib/budget/README.md |
15
17
  | scope/ | ScopeFacade per stage (tracked get/set, $emit, frozen args/env); built-in ScopeRecorders in scope/recorders/ |
16
18
  | reactive/ | TypedScope deep Proxy over the facade — four write laws + the honest boundary (what `find`/`for…of` hands back is raw and WARNED ABOUT in dev mode, not intercepted; a proxy held past its stage IS refused — `ScopeFacade` · `assertLive`); handles.ts = the registry of every proxy the factories built → the value behind it, asked wherever an app value enters the record (the set traps since 9.24.0 — no JSON round-trip any more; fan-out `resolveItems`, subflow in/out mappers, `$setValue`/`$update`/`$batchArray` since 9.23.3, after a fan-out over object items seeded the handle and lost every child); see src/lib/reactive/README.md |
17
19
  | decide/ | decide()/select() evidence capture |
18
- | recorder/ | engine stores (KeyedStore/SequenceStore/BoundaryStateStore), CombinedRecorder routing, and built-in Topology/InOut/ControlDep/Quality recorders. `CommitRangeIndex` is owned and exported by foottrace. |
20
+ | filter/ | `evaluator.ts · evaluateFilter` — the ONE filter-rule law, shared by decide() and the attempt rule table; its own leaf module since P2 (10.1), because in decide/ it made attempt/ reach scope/ and `scope.$call` closed an attempt ⇄ decide ⇄ scope module cycle |
21
+ | attempt/ | the ONE retry loop and its record (10.1, docs/design/2026-10-time-and-failure-evidence.md): `runner.ts · runAttempts` runs a policy and makes one frozen `AttemptRecord` per try (timing, outcome, category, the retry-or-stop decision as `DecisionEvidence`) + an `AttemptSummary`; `decision.ts · retryRules` = the one rule table as `decide()` rules (first match, labels ARE the reasons) + `decideAttempt`, decide()'s evaluation without a scope (filter rows from `filter/evaluator`; a property test pins equal evidence); `category.ts · errorCategory` = the one classifier (seven fixed values); `errors.ts`, `anySignal.ts`. L5 beside decide/, but `types.ts` (with `decide/types.ts`) is an L0 leaf because `StageSnapshot` (L4) names it. `RetryPolicy` is declared here, re-exported by engine/types.ts |
22
+ | recorder/ | engine stores (KeyedStore/SequenceStore/BoundaryStateStore), CombinedRecorder routing, and built-in Topology/InOut/ControlDep/Quality recorders. `CommitRangeIndex` is owned and exported by foottrace. `attemptSeries.ts` = the ONE predicate every built-in recorder uses for attempt rows (P1c, 10.1: a series that RETRIED, GAVE UP or TIMED OUT; a first-time success adds no row anywhere, so no-retry runs stay byte-identical) — it lives here, not in attempt/, because a recorder → attempt runtime edge closes the attempt⇄decide⇄recorder⇄scope module cycle. Quality/Debug see `onAttempt` only through `attachCombinedRecorder` |
19
23
  | pause/ | PauseSignal / FlowchartCheckpoint types + `interrupt(scope, payload)` (throws InterruptSignal; converted to PauseSignal at the StageRunner boundary, `pausedBy:'interrupt'`) |
20
24
  | engine/ | FlowchartTraverser + handlers/; narrative/ = the whole FlowRecorder channel (dispatcher + 9 strategies), not just text; `lib/ids/branchSegment.ts` (moved to the `ids/` leaf in 9.37.0) = the ONE owner of the generated-segment grammar (`<stageId>~<index>`, marker `~` RESERVED); `executeStage` (:720) = the ONE funnel every stage fn passes through — and therefore the ONE owner of the declarative `retry` loop; `handlers/ResumeEntry.ts` = the ONE owner of a resume's ONE-SHOT re-entry (M2, 9.28.0: start + one hop per subflow on the pause path, each level's dispatcher continuation read from the chart, waiting sibling pauses queued at their fan-out) |
21
25
  | runner/ | FlowChartExecutor wiring — composes `attach.ts` (RunObservers) · `resume.ts` · `checkpoint.ts` · `snapshot.ts` · `options.ts` (F9, 9.41.0); (runtime, recorders, pause/resume, snapshot, DeferredObserverTier). RunContext.ts is a fluent run builder, NOT the threaded per-run context |
22
26
  | observer-queue/ | pure deferred-delivery pipeline (engine imports IT, never reverse) |
27
+ | sinks/ | (10.1, P6) `fileSink` / `readRecordFile` — the crash-safe record on a Node host: one JSON line per entry, each written asynchronously at its own offset, only the path given (O_NOFOLLOW, 0o600; hard links / foreign / shared-writable refused), every resource bounded. The ONLY `node:` importer in src/ (layering.test pins it and that only `src/sinks.ts` reaches it). The run's side is `runner/recordSink.ts · RunRecord` |
23
28
  | contract/, detach/ | I/O schema normalization (OpenAPI generation belongs to runner/RunnableChart); fire-and-forget child charts (drivers passed explicitly) |
24
29
 
25
30
  **The fence:** engine files import only their own layer or below, using `scripts/layering.config.cjs` as the single table for lint, the checker and architecture tests. Named engine exceptions remain explicit and live-checked. The record is no longer a local L0–L3 subset: it lives in the standalone foottrace repository, whose closed source graph cannot import this engine, Node APIs or other packages. All engine access to the record uses named imports from `foottrace`, `foottrace/write` or `foottrace/paths`, never package internals. Export tests reject any FootPrint door handing out a declaration owned by foottrace. See the extraction plan for the E3/E6 published-door gate.
@@ -28,15 +33,19 @@ Entry points: `footprintjs` (engine API), `/recorders`, `/trace` (engine recorde
28
33
  - `SharedMemory` (`foottrace/write`) is the copy-on-write live heap. StageContext decides the address with the engine's `runAddress` rule and composes a `RecordFrame`. Record encoding, staging, commit and replay laws are implemented only in foottrace; the engine supplies policy and commit identity.
29
34
  - `StageContext` (`memory/StageContext.ts`) owns per-stage run context. `stageWrite` decides redaction and identity inheritance, passes the resulting scrub bytes to `RecordFrame.write`, then records the engine's write marks. Commit delegates to the record frame, delivers engine hooks, and releases stage-local tracking. `runId` is readonly; the engine decides the record address once.
30
35
  - `CommitBundle` (`foottrace`) / `EventLog` (`foottrace/write`) retain the existing bundle/index and full/delta encoding law. Neither is a FootPrint export.
31
- - `TraversalContext` (engine/narrative/types.ts:172) — THE per-event context (runId, runtimeStageId, parentRuntimeStageId, subflowPath, loopIteration); stamped on every FlowRecorder event.
36
+ - `TraversalContext` (engine/narrative/types.ts:172) — THE per-event context (runId, runtimeStageId, parentRuntimeStageId, subflowPath, loopIteration, and `correlation` when the env gave one — P4, `attempt/correlation.ts · resolveCorrelation`, one frozen object per leg, also on scope/emit events and `getSnapshot().correlation`); stamped on every FlowRecorder event.
32
37
  - `runtimeStageId` = `[subflowPath/]stageId#executionIndex`; counter shared by reference into subflow traversers → globally unique; NOT reset on resume.
33
38
  - Subflows get a fresh isolated ExecutionRuntime (SubflowExecutor.ts:119); inputMapper seeds, outputMapper merges back (arrays CONCAT unless `arrayMerge: Replace`).
34
39
 
35
- **Retry (9.15.0):** `StageNode.retry` is a POLICY on a stage, not a kind — no new node type, no `computeNodeType` edit. The loop lives in `FlowchartTraverser.executeStage` (:720), so linear/streaming/pausable/decider/selector/fork-child/subflow-inner all get it from one implementation. A failed non-final attempt calls `StageContext.discardStaged()` (`RecordFrame · discard`, which drops the buffer, the first-touch base and readKeys, plus _stageWrites/_stageReads + _untrackedSources); the final attempt is today's law untouched. PauseSignal and abort are NEVER retried. One runtimeStageId + one CommitBundle per stage regardless of attempts; retries do not consume `maxIterations`.
40
+ **Retry (9.15.0; on the attempt runner since 10.1):** `StageNode.retry` is a POLICY on a stage, not a kind — no new node type, no `computeNodeType` edit. `FlowchartTraverser.executeStage` is the one call site, so linear/streaming/pausable/decider/selector/fork-child/subflow-inner all get it from one implementation; the LOOP is `attempt/runner.ts · runAttempts`, handed the engine's half (one attempt = `StageRunner · run`, `between` = `discardStaged`, `serve` = `servedStageError`, the frame's `recordAttempt` / `endAttempts`, `onAttempt` / `onStageRetry`). `executeStage` is deliberately NOT async: a policy stage runs on the runner's promise (one await per attempt, the 9.15 shape) and a no-policy (or `attempts: 1`) stage on `runStageOnce`, the pre-10.1 async frame — so microtask order, hence parallel siblings' settle order, never moved. Every attempt is an `AttemptRecord` on `StageContext · recordAttempt` → `StageSnapshot.attempts` (+ `attemptSummary`), NOT cleared by `discardStaged`, NEVER on the commit bundle (Q1: logs byte-identical); a declined stop (`retryOn → false`) is now a record (`reason: 'declined'`). A failed non-final attempt calls `StageContext.discardStaged()` (`RecordFrame · discard`, which drops the buffer, the first-touch base and readKeys, plus _stageWrites/_stageReads + _untrackedSources); the final attempt is today's law untouched. PauseSignal and abort are NEVER retried. One runtimeStageId + one CommitBundle per stage regardless of attempts; retries do not consume `maxIterations`.
41
+
42
+ **In-stage calls (10.1, P2):** `scope.$call(target, fn, policy?)` (four-file checklist: `ScopeFacade · call`, `ReactiveTarget.call`, `ScopeMethods.$call`, `METHOD_ROUTES.$call`, `SCOPE_METHOD_NAMES`) runs on the same runner via `attempt/call.ts · runCall` (kind `'call'`, `target` on every record, no policy = `{ attempts: 1 }` — still ONE record). The facade is the host: signal = its stage attempt's, deadline = `runner.ts · attemptEndsAt` (start + cap; the cap's reason is branded a time limit, `markTimeLimit`, so a cut call is `timeout`), series = `callSeries` (`<rsid>@<n>.<k>`, k from `StageContext · nextCallOrdinal`). Records land through `StageContext · reportCallAttempt` (bounded, `MAX_CALL_RECORDS` = 1000, the rest counted in `attemptsDropped`) → the reporter `executeStage` installs on every stage (`useAttemptReporter` → `onAttempt` with the stage's traversalContext/correlation). A call lives within its stage attempt: a record arriving after that attempt ended or committed is refused (`lateWrites`, channel `'attempt'`, `call:<target>`) and the call stops; a call attempt CUT by the stage attempt's own abort is admitted. `AttemptContext.observed(report)` (`attempt/observed.ts`) records kind `'observed'` (named fields only, read once; decision = the rule table under the default policy). `attempt/name.ts` = the one name rule (1–128, no Cc/Cf/Cs/Zl/Zp). Standalone `attempt()` = `attempt/standalone.ts` (records to `onAttempt` only, its own `armDeadline`).
43
+
44
+ **Time budget (10.1, P1b):** `armRun` (`runner/deadline.ts`) composes `RunOptions.signal`, `env.signal` and the deadline (`deadline` / `timeoutMs` / `env.deadline`, earliest wins) into ONE signal and stamps `env.deadline` + `env.signal` (only when a deadline exists or the caller gave an env — an env-less signal-only run stays byte-identical). At the deadline the signal aborts with a BRANDED reason (`attempt/deadline.ts`): an attempt it ends is `timeout`, never `aborted`. `executeStage` runs a stage on the runner when its policy retries, declares `timeoutMs`, or the run has a deadline (so a lone stage records `attempts`, `of: 1`). The runner hands `StageRunner` an `AttemptContext` (`scope.$attempt()`; StageRunner races ITS signal) and calls `onAbandon(n)` synchronously when the attempt ends without an answer → `StageContext · endAttempt` fences that attempt's facade: later writes/emits/reads are refused, writes/emits recorded in `StageSnapshot.lateWrites`. Under a deadline `backoffMs` is asked before the discard (rule 6 needs the wait).
36
45
 
37
46
  **Declared tags (9.21.0):** `StageNode.tags?: readonly string[]` is a NAME on a stage, declared at build time — POLICY-like (the retry precedent): no node type, no `computeNodeType` edit, no type-union site, and the one prefixer (`engine/graph/prefixNodeTree.ts`, 9.37.0) spreads `{ ...node }` so it rides subflow prefixing untouched. ONE landing site `FlowChartBuilder.ts · applyTags` (twin of `applyRetryPolicy`) serves the `.tag(...names)` cursor modifier and every `options.tags` site; refuses empty/non-string/`~`/duplicate/double-declare at build. Stamp: `FlowchartTraverser.executeNodeStep` sets `context.tags = node.tags` beside `runtimeStageId` (unconditionally — a re-used context must not keep a prior node's names); `memory/recordCommit.ts · recordCommit` (C1, 9.44.3; was `StageContext · bundleFor`) stamps `tags` beside `untrackedSources` on BOTH commit paths and `StageContext · commit` RELEASES them, so a double-commit path (fork child fan-out repeat, mount exit) records it once — absent when empty (`CommitBundle.tags?`; pinned byte-identical to 9.20.0 by test/lib/engine/scenario/declared-tags-byte-identity.test.ts). NOT inherited by createNext/createChild; the one synthetic node that could lose it is `FlowChartExecutor.resume`'s stand-in for the paused stage — built by `runner/resume.ts · standInFor` from the paused node ITSELF (fn swapped), so it keeps the tags on both re-entries; it runs ONCE — a later visit of the paused id runs the chart's real node, which carries its own tags. The spec carries `tags` as-is (Map advertises the vocabulary; `RuntimeStructureManager.stageNodeToStructure` copies it for run-time-resolved nodes). Reader: foottrace's `tagStops` = `filterStops(commitStops(...), any-of over log[stop.commitIdx].tags, meta = the array)`, exported from `foottrace`. Never a runtime `$tag()` — a value would ride past every redaction point. Design: docs/design/2026-09-declared-tags.md.
38
47
 
39
- **Event order (code-verified; older docs had 3/4 swapped):** onStageStart → onRead/onWrite (live, PRE-commit) → **onStageEnd (StageRunner.ts:81) → onCommit (`StageContext · commit`, after `RecordFrame · commit`)** → FlowRecorder onDecision/onFork/onSelected/onSubflowEntry → onStageExecuted (uniform, with stageType). Error path: commit STILL happens (FlowchartTraverser.ts:1088) before onError+rethrow — a failing stage's writes land. For ANY thrown value (null, null-prototype, hostile Proxy) catch blocks describe it with `errors/errorInfo · thrownText` (never throws), onError fires, and run() rejects with the ORIGINAL value; a failed commit (e.g. an uncloneable write) also fires onError (`handlers/commitStage.ts`, 9.40.0). `interrupt()` rides the ERROR path's shape, not the pausable one: the stage function threw, so onStageEnd does NOT fire (the `addPausableFunction` pause DOES fire it — there the function returned normally).
48
+ **Event order (code-verified; older docs had 3/4 swapped):** onStageStart → onRead/onWrite (live, PRE-commit) → **onStageEnd (StageRunner.ts:81) →** [a retry policy: `onAttempt` per attempt, then `onStageRetry` for each one retried — both DURING the stage] **→ onCommit (`StageContext · commit`, after `RecordFrame · commit`)** → FlowRecorder onDecision/onFork/onSelected/onSubflowEntry → onStageExecuted (uniform, with stageType). Error path: commit STILL happens (FlowchartTraverser.ts:1088) before onError+rethrow — a failing stage's writes land. For ANY thrown value (null, null-prototype, hostile Proxy) catch blocks describe it with `errors/errorInfo · thrownText` (never throws), onError fires, and run() rejects with the ORIGINAL value; a failed commit (e.g. an uncloneable write) also fires onError (`handlers/commitStage.ts`, 9.40.0). `interrupt()` rides the ERROR path's shape, not the pausable one: the stage function threw, so onStageEnd does NOT fire (the `addPausableFunction` pause DOES fire it — there the function returned normally).
40
49
 
41
50
  ## Extension points
42
51
  - **New stage kind**: boolean flag on `StageNode` (engine/graph/StageNode.ts:39 — kinds are flags, not an enum) + builder method on FlowChartBuilder (pattern: addPausableFunction :1264; fire structure events endpoint-before-edge) + phase in the hard-coded chain `executeNodeStep` (FlowchartTraverser.ts:801 — no handler registry; new handler class takes HandlerDeps, engine/types.ts:344, instantiated in traverser ctor :402-413). Must also edit 5 type-union sites: engine/types.ts:477, builder/types.ts:50, builder/types.ts:104, engine/types.ts:427, and computeNodeType's flag→type mapping (RuntimeStructureManager.ts:19-21 — the only one the compiler won't flag; miss it and the new kind silently serializes as 'stage'). Zero-engine alternative: pure sugar over addFunction (pattern: addDetachAndForget :1343). WORKED EXAMPLE (9.14.0 addParallelForEach): flag `isDynamicParallel` + ParallelForEachHandler + Phase 0b (BEFORE Phase 1 VALIDATE — the node has no fn/children/decider by design); it added NO new node type — the spec carries a BOOLEAN and computeNodeType returns 'fork' (precedent: isPausable/isLazy/isStreaming are flags on a 'stage'), which is the cheaper choice whenever the new kind is a variant of an existing shape.
@@ -47,10 +56,12 @@ Entry points: `footprintjs` (engine API), `/recorders`, `/trace` (engine recorde
47
56
  - **Scope provider**: ProviderResolver via registerScopeResolver (exported from /advanced), or a custom scopeFactory. Runtime ownership is explicit in `scope/runtime.ts`: `registerScopeRuntime(scope, { target, handlesAssignments, setBreak? })` maps the user object to its engine port without probing its fields. ScopeFacade, TypedScope, attachScopeMethods and canonical Zod factories auto-register. Other executor/decide/select scopes must register; no duck-typed fallback. One facade per stage owns recording, redaction, argument ownership and commit sealing; adapters use its path-aware accessors instead of writing directly to StageContext.
48
57
  - **Detach driver**: `detach/types.ts · DetachDriver`; no driver registry — passed explicitly. The three batching adapters delegate to `detach/drivers/batch.ts · createBatchSchedule`, the one owner of queued work, dispatch, settlement and handle-registry cleanup. A scheduling failure returns a failed handle, never a stuck queue or a parent exception. `validate()` is explicit caller preflight, not an automatic entry-point hook; do not use it instead of containing failures in the driver. No implicit fallback or retry.
49
58
  - **Observability dial** (pattern): capture/ primitive + FlowChartExecutorOptions option + snapshot discriminant — FOUR dials now: readTracking/writeTracking/commitValues/writeProvenance (#P1: `'reads-prefix'` stamps `TraceEntry.readKeys` = keys tracked-read before each write; consumed by causalChain `edgeAttribution: 'per-write'` and sliceForKey's default). Propagation is the same 6-site pattern for all four.
50
- - **Worked example of a new FLOW hook (9.15.0 `onStageRetry`)**: `FlowStageRetryEvent` + `FlowRecorder.onStageRetry?` + `IControlFlowNarrative.onStageRetry` (engine/narrative/types.ts) → `FlowRecorderDispatcher` impl → `NullControlFlowNarrativeGenerator` no-op (interface is REQUIRED there) → **its `HOOKS` entry (recorder/hooks.ts)** → `NarrativeFlowRecorder` sentence (the 8 strategies extend it, so they inherit free) → `CombinedNarrativeRecorder` + `CombinedNarrativeEntry.type` + `NarrativeFormatter.renderRetry` → barrels (the composite fan-out is generated). Retry is the only flow event that fires DURING a stage, so the narrative BUFFERS it via `bufferOp` (the `onEmit` precedent) — pushing it straight to the store would place the line before its own stage header.
51
- - **Closed seams that look open**: ExecutionEnv (fixed type); decide() operators (evaluator.ts:19 module-private); METHOD_ROUTES; the executeNodeStep phase chain; emit dispatcher (rides scope channel).
59
+ - **Second worked example (10.1 `onAttempt`)** — the same chain: `FlowAttemptEvent` + `FlowRecorder.onAttempt?` + `IControlFlowNarrative.onAttempt` → `FlowRecorderDispatcher · onAttempt` → `NullControlFlowNarrativeGenerator` no-op → `HOOKS.onAttempt` (placed before `onStageRetry`; `hooks.test.ts` pins the list and the 25/28 counts) → `NarrativeFlowRecorder · onAttempt` (`formatting/sentences.ts · attemptSentence`) → `CombinedNarrativeRecorder` buffers it (`bufferOp`) as `CombinedNarrativeEntry.type: 'attempt'`, rendered by `NarrativeFormatter.renderAttempt` (string / null / undefined) → barrels. Two narrative laws it added: the attempt line REPLACES the retry line (`onStageRetry` still fires, the built-ins no longer narrate it; `NarrativeFormatter.renderRetry` and `CombinedNarrativeRecorder · onStageRetry` were removed in 10.1), and a FAILED stage's buffered attempt lines are flushed beneath its `[Error]` line (`flushAttempts` — its reads/writes still are not, so a no-policy failure narrates byte-identically).
60
+ - **Worked example of a new FLOW hook (9.15.0 `onStageRetry`)**: `FlowStageRetryEvent` + `FlowRecorder.onStageRetry?` + `IControlFlowNarrative.onStageRetry` (engine/narrative/types.ts) → `FlowRecorderDispatcher` impl → `NullControlFlowNarrativeGenerator` no-op (interface is REQUIRED there) → **its `HOOKS` entry (recorder/hooks.ts)** → `NarrativeFlowRecorder` sentence (the 8 strategies extend it, so they inherit free) → `CombinedNarrativeRecorder` + `CombinedNarrativeEntry.type` + `NarrativeFormatter.renderRetry` (removed in 10.1 — the attempt line replaced it) → barrels (the composite fan-out is generated). Retry is the only flow event that fires DURING a stage, so the narrative BUFFERS it via `bufferOp` (the `onEmit` precedent) — pushing it straight to the store would place the line before its own stage header.
61
+ - **Closed seams that look open**: ExecutionEnv (fixed type); decide() operators (`filter/evaluator.ts · OPERATOR_HANDLERS`, module-private); METHOD_ROUTES; the executeNodeStep phase chain; emit dispatcher (rides scope channel).
52
62
 
53
63
  ## Change-impact map
64
+ - **Record sink** (10.1, P6) → foottrace's `RecordSink` port; the engine composes. `runner/recordSink.ts · RunRecord` (one per run; a same-executor resume keeps it) wraps the app's adapter: it SERVES before any byte leaves (the `opened` base through `RedactionRule · retainState`, `opened`/`closed` meta through `retainBoundary`; commits are the log's scrubbed bundles; attempt notes carry the served `AttemptRecord`), counts entries/losses (`getSnapshot().sink`), and under `onFailure: 'fail'` aborts the run's signal (`armRun`'s `stop`). `ExecutionRuntime`'s 6th ctor arg (`RuntimeRecord`) opens the root log at `{ logRunId: runId, drillPath: [] }`; `recordFor(mount)` hands a mount `[...parent drillPath, mountRuntimeStageId]` (`SubflowExecutor` closes it ok/failed/paused). `StageContext · noteAttempt` notes each kept attempt as it ENDS. `FlowChartExecutor · settleRecorded` closes the root log and flushes — a run WITHOUT a sink never enters it (no extra await, byte-identical). Same-executor resume continues the same root log (`resume` note, second `closed`; `RunRecord · beginLeg` resets the `'fail'` stop per leg); fresh-executor resume opens a new one (`resumedFrom`). KNOWN DIVERGENCE: with a sink a mount in a same-executor resumed leg is addressed under the ROOT log's `logRunId`; a plain log binds the current leg's runId (pinned in record-sink-mounts.test.ts; unify in a later packet). `fileSink` writes asynchronously (positional writes handed to the I/O pool at accept time, no O_APPEND; a NUL hole marks a torn write) with four bounds: maxEntryBytes, maxQueuedBytes (drop-and-count), maxBytes (then full), flushTimeoutMs; one sink per file per process.
54
65
  - **Nested diagnostic writes** → `DiagnosticCollector` uses the public `foottrace/paths` nested writers. They refuse denied selector segments, create own properties without inherited setters, and keep diagnostic concatenation/shallow-merge semantics separate from record union-merge semantics. Path rules have one implementation in foottrace; the engine does not copy them.
55
66
  - **Trace verbs** (`set|merge|append|delete`, `TraceEntry.verb`) → the record law lives only in foottrace. Its writer, replay, keyed fold, provenance and unknown-verb refusal share that implementation. Use the public record doors; do not add a verb switch to the engine. The canonical record contract and record-only differential controls moved with the package; FootPrint retains the engine byte-identity and admission witnesses.
56
67
  - **Write address** → ONE constant, `runAddress.ts · RUN_NAMESPACE` (an L4 leaf, C4), read by `runAddress` / `useAddressOf` and by `RedactionRule · verdictOfRead` / `retainState`; the frame's `RecordFrame` holds the address (C3). `SharedMemory` and the nested writers take the address as data and refuse a non-array; `TransactionBuffer` takes it too (C2, 9.45.0). `test/architecture/write-address.test.ts` refuses the namespace below L4, as a literal or a property — no exceptions since C4.
@@ -99,3 +110,28 @@ Seven mechanisms, no state rollback anywhere: **M1** TransactionBuffer staging +
99
110
  - [.claude/rules/backtracking.md](.claude/rules/backtracking.md) — the 5 mechanisms with step tables + pseudocode
100
111
  - [docs/guides/](docs/guides/) (error-handling, observers-deferred) · [examples/](examples/) — mandatory integration tests (Convention 2); 7 test types per feature (Convention 3)
101
112
  - Build/test: `npm run build` (CJS+ESM+types), `npm test`
113
+
114
+ ## Attempt metrics (10.1, P7)
115
+
116
+ `recorder/AttemptRecorder.ts` owns the opt-in `attempts()` flow recorder (`footprintjs/recorders`). It composes
117
+ `KeyedStore` and collects named scalar rows at onAttempt/onDecision/run lifecycle time, never by walking a tree.
118
+ `attemptMetrics.ts` aggregates exact retained facts and nearest-rank latency; `attemptMetricRows.ts` is the single
119
+ live/store row admission boundary; `attemptOTel.ts` projects typed histogram/sum JSON. Keys, inputs, outputs, error
120
+ text/raw causes and unknown envelope fields never enter metric rows. Physical IDs/outcomes stay physical; explicit
121
+ logical controls use recorded retryLoop decisions. Kinds remain visible when target names collide. Memory rows,
122
+ exemplars and all owned I/O are bounded; asynchronous losses/timeouts remain disclosed. A timed-out I/O retains its
123
+ slot until actual settlement. Sum of durations is work time, not wall-clock budget; absent original budget stays
124
+ absent. Governing guide: docs/guides/attempt-metrics.md; source rules: src/lib/recorder/README.md.
125
+
126
+ P7 admission reads plain own data descriptors (including nested loops and array windows) before field access;
127
+ external getters/coercion hooks never run. Stored logical decisions join exact physical IDs/targets/loop identity.
128
+ `AttemptMetricRef` preserves the compact existing `AttemptRef`; target, run and OTLP views retain nonzero exemplar
129
+ capacity. Optional store `stats()` discloses known retention omissions, including reference-memory truncation.
130
+
131
+ Queued-sibling re-raise notifications use current-leg root context at `FlowchartTraverser.walkThenRaise`; they do
132
+ not pretend the sibling re-executed or overwrite its original pausedExecution link. P7 pause lifecycle updates are
133
+ idempotent across sibling hooks, exact run IDs isolate shared recorders, and a real failure can supersede a pause.
134
+
135
+ A shared recorder's auto-clear preserves already-emitted rows for active/paused ownership; completed projections
136
+ reset normally. Keep omission accounting while active ownership exists, never rebuild an apparently complete empty
137
+ run header after deleting its attempts. All retained row/identity maps remain bounded.