@haibun/core 3.1.3 → 3.3.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 (240) hide show
  1. package/build/currentVersion.d.ts +1 -1
  2. package/build/currentVersion.js +1 -1
  3. package/build/kireji/converter.d.ts +16 -1
  4. package/build/kireji/converter.d.ts.map +1 -1
  5. package/build/kireji/converter.js +19 -2
  6. package/build/kireji/converter.js.map +1 -1
  7. package/build/lib/EventLogger.d.ts +16 -10
  8. package/build/lib/EventLogger.d.ts.map +1 -1
  9. package/build/lib/EventLogger.js +54 -21
  10. package/build/lib/EventLogger.js.map +1 -1
  11. package/build/lib/astepper.d.ts +6 -0
  12. package/build/lib/astepper.d.ts.map +1 -1
  13. package/build/lib/astepper.js +1 -0
  14. package/build/lib/astepper.js.map +1 -1
  15. package/build/lib/base-prompt-manager.d.ts.map +1 -1
  16. package/build/lib/base-prompt-manager.js +2 -2
  17. package/build/lib/base-prompt-manager.js.map +1 -1
  18. package/build/lib/capture-locator.d.ts +1 -1
  19. package/build/lib/capture-locator.d.ts.map +1 -1
  20. package/build/lib/capture-locator.js +4 -2
  21. package/build/lib/capture-locator.js.map +1 -1
  22. package/build/lib/core/flow-runner.d.ts +5 -4
  23. package/build/lib/core/flow-runner.d.ts.map +1 -1
  24. package/build/lib/core/flow-runner.js +48 -15
  25. package/build/lib/core/flow-runner.js.map +1 -1
  26. package/build/lib/core-domains.d.ts +1 -1
  27. package/build/lib/core-domains.d.ts.map +1 -1
  28. package/build/lib/core-domains.js +1 -1
  29. package/build/lib/core-domains.js.map +1 -1
  30. package/build/lib/defs.d.ts +103 -200
  31. package/build/lib/defs.d.ts.map +1 -1
  32. package/build/lib/defs.js +4 -46
  33. package/build/lib/defs.js.map +1 -1
  34. package/build/lib/feature-variables.d.ts +2 -1
  35. package/build/lib/feature-variables.d.ts.map +1 -1
  36. package/build/lib/feature-variables.js +1 -2
  37. package/build/lib/feature-variables.js.map +1 -1
  38. package/build/lib/features.d.ts +5 -4
  39. package/build/lib/features.d.ts.map +1 -1
  40. package/build/lib/features.js +26 -16
  41. package/build/lib/features.js.map +1 -1
  42. package/build/lib/fixme.d.ts +1 -1
  43. package/build/lib/fixme.d.ts.map +1 -1
  44. package/build/lib/http-observations.d.ts +25 -0
  45. package/build/lib/http-observations.d.ts.map +1 -0
  46. package/build/lib/http-observations.js +40 -0
  47. package/build/lib/http-observations.js.map +1 -0
  48. package/build/lib/namedVars.d.ts +5 -1
  49. package/build/lib/namedVars.d.ts.map +1 -1
  50. package/build/lib/namedVars.js +11 -1
  51. package/build/lib/namedVars.js.map +1 -1
  52. package/build/lib/node-http-events.d.ts +16 -0
  53. package/build/lib/node-http-events.d.ts.map +1 -0
  54. package/build/lib/node-http-events.js +125 -0
  55. package/build/lib/node-http-events.js.map +1 -0
  56. package/build/lib/populateActionArgs.d.ts +2 -1
  57. package/build/lib/populateActionArgs.d.ts.map +1 -1
  58. package/build/lib/populateActionArgs.js.map +1 -1
  59. package/build/lib/resolver-features.d.ts +2 -1
  60. package/build/lib/resolver-features.d.ts.map +1 -1
  61. package/build/lib/resolver-features.js +2 -2
  62. package/build/lib/resolver-features.js.map +1 -1
  63. package/build/lib/step-trace.d.ts +10 -0
  64. package/build/lib/step-trace.d.ts.map +1 -0
  65. package/build/lib/step-trace.js +3 -0
  66. package/build/lib/step-trace.js.map +1 -0
  67. package/build/lib/stepper-registry.d.ts +28 -0
  68. package/build/lib/stepper-registry.d.ts.map +1 -0
  69. package/build/lib/stepper-registry.js +42 -0
  70. package/build/lib/stepper-registry.js.map +1 -0
  71. package/build/lib/test/SetTimeStepper.d.ts +2 -2
  72. package/build/lib/test/SetTimeStepper.d.ts.map +1 -1
  73. package/build/lib/test/SetTimeStepper.js +1 -1
  74. package/build/lib/test/SetTimeStepper.js.map +1 -1
  75. package/build/lib/test/TestSteps.d.ts +5 -3
  76. package/build/lib/test/TestSteps.d.ts.map +1 -1
  77. package/build/lib/test/TestStepsWithOptions.d.ts +3 -1
  78. package/build/lib/test/TestStepsWithOptions.d.ts.map +1 -1
  79. package/build/lib/test/TestStepsWithOptions.js +3 -5
  80. package/build/lib/test/TestStepsWithOptions.js.map +1 -1
  81. package/build/lib/test/lib.d.ts +4 -3
  82. package/build/lib/test/lib.d.ts.map +1 -1
  83. package/build/lib/test/lib.js +12 -12
  84. package/build/lib/test/lib.js.map +1 -1
  85. package/build/lib/test/resolvedTestFeatures.d.ts +5 -5
  86. package/build/lib/test/resolvedTestFeatures.js +1 -1
  87. package/build/lib/test/resolvedTestFeatures.js.map +1 -1
  88. package/build/lib/ttag.d.ts +2 -2
  89. package/build/lib/ttag.d.ts.map +1 -1
  90. package/build/lib/ttag.js +5 -5
  91. package/build/lib/ttag.js.map +1 -1
  92. package/build/lib/util/index.d.ts +20 -12
  93. package/build/lib/util/index.d.ts.map +1 -1
  94. package/build/lib/util/index.js +53 -17
  95. package/build/lib/util/index.js.map +1 -1
  96. package/build/lib/workspace-discovery.d.ts +37 -0
  97. package/build/lib/workspace-discovery.d.ts.map +1 -0
  98. package/build/lib/workspace-discovery.js +96 -0
  99. package/build/lib/workspace-discovery.js.map +1 -0
  100. package/build/monitor/index.d.ts +1 -10
  101. package/build/monitor/index.d.ts.map +1 -1
  102. package/build/monitor/index.js +1 -14
  103. package/build/monitor/index.js.map +1 -1
  104. package/build/phases/Executor.d.ts +3 -11
  105. package/build/phases/Executor.d.ts.map +1 -1
  106. package/build/phases/Executor.js +76 -169
  107. package/build/phases/Executor.js.map +1 -1
  108. package/build/phases/Resolver.d.ts +25 -1
  109. package/build/phases/Resolver.d.ts.map +1 -1
  110. package/build/phases/Resolver.js +88 -22
  111. package/build/phases/Resolver.js.map +1 -1
  112. package/build/phases/collector.d.ts +1 -0
  113. package/build/phases/collector.d.ts.map +1 -1
  114. package/build/phases/collector.js +7 -3
  115. package/build/phases/collector.js.map +1 -1
  116. package/build/runner.d.ts +3 -2
  117. package/build/runner.d.ts.map +1 -1
  118. package/build/runner.js +0 -1
  119. package/build/runner.js.map +1 -1
  120. package/build/schema/protocol.d.ts +1443 -0
  121. package/build/schema/protocol.d.ts.map +1 -0
  122. package/build/schema/protocol.js +429 -0
  123. package/build/schema/protocol.js.map +1 -0
  124. package/build/steps/activities-stepper.d.ts +24 -28
  125. package/build/steps/activities-stepper.d.ts.map +1 -1
  126. package/build/steps/activities-stepper.js +150 -162
  127. package/build/steps/activities-stepper.js.map +1 -1
  128. package/build/steps/conformance.d.ts +3 -1
  129. package/build/steps/conformance.d.ts.map +1 -1
  130. package/build/steps/conformance.js +1 -0
  131. package/build/steps/conformance.js.map +1 -1
  132. package/build/steps/console-monitor-stepper.d.ts +3 -6
  133. package/build/steps/console-monitor-stepper.d.ts.map +1 -1
  134. package/build/steps/console-monitor-stepper.js +23 -64
  135. package/build/steps/console-monitor-stepper.js.map +1 -1
  136. package/build/steps/debugger-stepper.d.ts +7 -4
  137. package/build/steps/debugger-stepper.d.ts.map +1 -1
  138. package/build/steps/debugger-stepper.js +36 -43
  139. package/build/steps/debugger-stepper.js.map +1 -1
  140. package/build/steps/haibun.d.ts +24 -16
  141. package/build/steps/haibun.d.ts.map +1 -1
  142. package/build/steps/haibun.js +68 -31
  143. package/build/steps/haibun.js.map +1 -1
  144. package/build/steps/lib/tts.d.ts +2 -3
  145. package/build/steps/lib/tts.d.ts.map +1 -1
  146. package/build/steps/lib/tts.js +4 -5
  147. package/build/steps/lib/tts.js.map +1 -1
  148. package/build/steps/logic-stepper.d.ts +14 -3
  149. package/build/steps/logic-stepper.d.ts.map +1 -1
  150. package/build/steps/logic-stepper.js +116 -17
  151. package/build/steps/logic-stepper.js.map +1 -1
  152. package/build/steps/narrator.d.ts +7 -5
  153. package/build/steps/narrator.d.ts.map +1 -1
  154. package/build/steps/narrator.js +30 -18
  155. package/build/steps/narrator.js.map +1 -1
  156. package/build/steps/parse.d.ts +5 -3
  157. package/build/steps/parse.d.ts.map +1 -1
  158. package/build/steps/parse.js +2 -1
  159. package/build/steps/parse.js.map +1 -1
  160. package/build/steps/variables-stepper.d.ts +285 -4
  161. package/build/steps/variables-stepper.d.ts.map +1 -1
  162. package/build/steps/variables-stepper.js +44 -50
  163. package/build/steps/variables-stepper.js.map +1 -1
  164. package/package.json +9 -16
  165. package/build/lib/Logger.d.ts +0 -48
  166. package/build/lib/Logger.d.ts.map +0 -1
  167. package/build/lib/Logger.js +0 -85
  168. package/build/lib/Logger.js.map +0 -1
  169. package/build/lib/TestLogger.d.ts +0 -12
  170. package/build/lib/TestLogger.d.ts.map +0 -1
  171. package/build/lib/TestLogger.js +0 -12
  172. package/build/lib/TestLogger.js.map +0 -1
  173. package/build/lib/Timer.d.ts +0 -7
  174. package/build/lib/Timer.d.ts.map +0 -1
  175. package/build/lib/Timer.js +0 -9
  176. package/build/lib/Timer.js.map +0 -1
  177. package/build/lib/core/protocol.d.ts +0 -58
  178. package/build/lib/core/protocol.d.ts.map +0 -1
  179. package/build/lib/core/protocol.js +0 -18
  180. package/build/lib/core/protocol.js.map +0 -1
  181. package/build/lib/event-bridge.d.ts +0 -42
  182. package/build/lib/event-bridge.d.ts.map +0 -1
  183. package/build/lib/event-bridge.js +0 -214
  184. package/build/lib/event-bridge.js.map +0 -1
  185. package/build/lib/interfaces/logger.d.ts +0 -123
  186. package/build/lib/interfaces/logger.d.ts.map +0 -1
  187. package/build/lib/interfaces/logger.js +0 -29
  188. package/build/lib/interfaces/logger.js.map +0 -1
  189. package/build/lib/util/variables.d.ts +0 -9
  190. package/build/lib/util/variables.d.ts.map +0 -1
  191. package/build/lib/util/variables.js +0 -40
  192. package/build/lib/util/variables.js.map +0 -1
  193. package/build/monitor/browser-stubs.d.ts +0 -12
  194. package/build/monitor/browser-stubs.d.ts.map +0 -1
  195. package/build/monitor/browser-stubs.js +0 -20
  196. package/build/monitor/browser-stubs.js.map +0 -1
  197. package/build/monitor/constants.d.ts +0 -9
  198. package/build/monitor/constants.d.ts.map +0 -1
  199. package/build/monitor/constants.js +0 -9
  200. package/build/monitor/constants.js.map +0 -1
  201. package/build/monitor/event-view.d.ts +0 -40
  202. package/build/monitor/event-view.d.ts.map +0 -1
  203. package/build/monitor/event-view.js +0 -104
  204. package/build/monitor/event-view.js.map +0 -1
  205. package/build/monitor/filters.d.ts +0 -17
  206. package/build/monitor/filters.d.ts.map +0 -1
  207. package/build/monitor/filters.js +0 -32
  208. package/build/monitor/filters.js.map +0 -1
  209. package/build/monitor/formatters.d.ts +0 -27
  210. package/build/monitor/formatters.d.ts.map +0 -1
  211. package/build/monitor/formatters.js +0 -89
  212. package/build/monitor/formatters.js.map +0 -1
  213. package/build/monitor/jit-serialization.d.ts +0 -9
  214. package/build/monitor/jit-serialization.d.ts.map +0 -1
  215. package/build/monitor/jit-serialization.js +0 -73
  216. package/build/monitor/jit-serialization.js.map +0 -1
  217. package/build/monitor/monitor-types.d.ts +0 -119
  218. package/build/monitor/monitor-types.d.ts.map +0 -1
  219. package/build/monitor/monitor-types.js +0 -43
  220. package/build/monitor/monitor-types.js.map +0 -1
  221. package/build/monitor/speculative-tracker.d.ts +0 -20
  222. package/build/monitor/speculative-tracker.d.ts.map +0 -1
  223. package/build/monitor/speculative-tracker.js +0 -38
  224. package/build/monitor/speculative-tracker.js.map +0 -1
  225. package/build/monitor/state.d.ts +0 -31
  226. package/build/monitor/state.d.ts.map +0 -1
  227. package/build/monitor/state.js +0 -110
  228. package/build/monitor/state.js.map +0 -1
  229. package/build/monitor/timer.d.ts +0 -11
  230. package/build/monitor/timer.d.ts.map +0 -1
  231. package/build/monitor/timer.js +0 -13
  232. package/build/monitor/timer.js.map +0 -1
  233. package/build/monitor/tree-builder.d.ts +0 -25
  234. package/build/monitor/tree-builder.d.ts.map +0 -1
  235. package/build/monitor/tree-builder.js +0 -36
  236. package/build/monitor/tree-builder.js.map +0 -1
  237. package/build/schema/events.d.ts +0 -426
  238. package/build/schema/events.d.ts.map +0 -1
  239. package/build/schema/events.js +0 -123
  240. package/build/schema/events.js.map +0 -1
@@ -1,14 +1,16 @@
1
1
  import { AStepper } from '../lib/astepper.js';
2
- import { OK, CycleWhen } from '../lib/defs.js';
3
- import { actionOK, actionNotOK, getActionable } from '../lib/util/index.js';
2
+ import { CycleWhen } from '../lib/defs.js';
3
+ import { OK } from '../schema/protocol.js';
4
+ import { actionOK, actionNotOK, getActionable, formatCurrentSeqPath } from '../lib/util/index.js';
4
5
  import { DOMAIN_STATEMENT } from '../lib/domain-types.js';
5
- import { EExecutionMessageType } from '../lib/interfaces/logger.js';
6
6
  import { FlowRunner } from '../lib/core/flow-runner.js';
7
+ import { ControlEvent, LifecycleEvent } from '../schema/protocol.js';
7
8
  /**
8
9
  * Stepper that dynamically builds virtual steps from `waypoint` statements.
9
10
  * implements this logic: P ∨ (¬P ∧ [A]P)
10
11
  */
11
12
  export class ActivitiesStepper extends AStepper {
13
+ description = 'Define and reuse activities with waypoints and proofs';
12
14
  runner;
13
15
  backgroundOutcomePatterns = new Set();
14
16
  featureOutcomePatterns = new Set();
@@ -20,15 +22,14 @@ export class ActivitiesStepper extends AStepper {
20
22
  ensuredInstances = new Map();
21
23
  ensureAttempts = new Map();
22
24
  registeredOutcomeMetadata = new Map();
25
+ backgroundSteps = {};
23
26
  inActivityBlock = false;
24
27
  cycles = {
25
28
  startExecution: () => {
26
29
  this.sendGraphLinkMessages();
27
30
  },
28
31
  startFeature: (startFeature) => {
29
- this.getWorld().logger.debug(`ActivitiesStepper.startFeature: starting feature at path "${startFeature.resolvedFeature.path}"`);
30
32
  if (this.lastFeaturePath && this.lastFeaturePath !== startFeature.resolvedFeature.path) {
31
- this.getWorld().logger.debug(`ActivitiesStepper.startFeature: clearing outcomes from previous feature "${this.lastFeaturePath}"`);
32
33
  const previousSteps = this.featureSteps.get(this.lastFeaturePath);
33
34
  if (previousSteps) {
34
35
  for (const outcome of Object.keys(previousSteps)) {
@@ -42,8 +43,12 @@ export class ActivitiesStepper extends AStepper {
42
43
  this.steps[outcome] = step;
43
44
  }
44
45
  }
46
+ // Always reload background steps
47
+ for (const [outcome, step] of Object.entries(this.backgroundSteps)) {
48
+ this.steps[outcome] = step;
49
+ }
45
50
  this.currentFeaturePath = startFeature.resolvedFeature.path;
46
- this.sendGraphLinkMessages();
51
+ this.inActivityBlock = false;
47
52
  },
48
53
  endFeature: () => {
49
54
  this.lastFeaturePath = this.currentFeaturePath;
@@ -55,37 +60,59 @@ export class ActivitiesStepper extends AStepper {
55
60
  startExecution: CycleWhen.FIRST,
56
61
  startFeature: CycleWhen.FIRST,
57
62
  };
63
+ /**
64
+ * Called during resolution phase to clear feature-scoped steps.
65
+ * This prevents activity patterns from leaking between features during resolution.
66
+ */
67
+ startFeatureResolution(path) {
68
+ // Clear steps from previous feature (resolution phase)
69
+ if (this.lastResolutionPath && this.lastResolutionPath !== path) {
70
+ const previousSteps = this.featureSteps.get(this.lastResolutionPath);
71
+ if (previousSteps) {
72
+ for (const outcome of Object.keys(previousSteps)) {
73
+ delete this.steps[outcome];
74
+ }
75
+ }
76
+ }
77
+ // Reload current feature's steps if already registered
78
+ const currentSteps = this.featureSteps.get(path);
79
+ if (currentSteps) {
80
+ for (const [outcome, step] of Object.entries(currentSteps)) {
81
+ this.steps[outcome] = step;
82
+ }
83
+ }
84
+ // Always reload background steps
85
+ for (const [outcome, step] of Object.entries(this.backgroundSteps)) {
86
+ this.steps[outcome] = step;
87
+ }
88
+ this.lastResolutionPath = path;
89
+ }
90
+ clearAllBackgroundSteps() {
91
+ this.backgroundSteps = {};
92
+ this.backgroundOutcomePatterns.clear();
93
+ }
58
94
  baseSteps = {
59
95
  activity: {
60
96
  gwta: 'Activity: {activity}',
61
97
  action: () => OK,
62
- resolveFeatureLine: (line, path, _stepper, _backgrounds, allLines, lineIndex) => {
63
- if (this.lastResolutionPath && this.lastResolutionPath !== path) {
64
- const previousSteps = this.featureSteps.get(this.lastResolutionPath);
65
- if (previousSteps) {
66
- for (const outcome of Object.keys(previousSteps)) {
67
- delete this.steps[outcome];
68
- }
69
- }
70
- }
98
+ resolveFeatureLine: (line, path, _stepper, _backgrounds, allLines, lineIndex, actualSourcePath) => {
71
99
  this.lastResolutionPath = path;
72
100
  if (line.match(/^Activity:/i)) {
73
101
  this.inActivityBlock = true;
74
- return true; // Skip the Activity definition line itself
102
+ return true;
75
103
  }
76
104
  if (this.inActivityBlock) {
77
- // Check for block terminators
78
105
  if (line.match(/^(Feature|Scenario|Background|Activity):/i)) {
79
106
  this.inActivityBlock = false;
80
- return false; // Process this line normally
107
+ return false;
81
108
  }
82
109
  if (line.match(/^waypoint\s+/i)) {
83
- this.resolveWaypointCommon(line, path, allLines, lineIndex, line.includes(' with '));
84
- // Check if this is the last waypoint in the block
110
+ // Use actualSourcePath for VSCode linking, path for registration
111
+ this.resolveWaypointCommon(line, path, allLines, lineIndex, line.includes(' with '), actualSourcePath);
85
112
  let hasMoreWaypoints = false;
86
113
  if (allLines && lineIndex !== undefined) {
87
114
  for (let i = lineIndex + 1; i < allLines.length; i++) {
88
- const nextLine = allLines[i].trim();
115
+ const nextLine = (allLines[i] || '').trim();
89
116
  if (nextLine.match(/^(Feature|Scenario|Background|Activity):/i)) {
90
117
  break;
91
118
  }
@@ -98,19 +125,17 @@ export class ActivitiesStepper extends AStepper {
98
125
  if (!hasMoreWaypoints) {
99
126
  this.inActivityBlock = false;
100
127
  }
101
- return true; // Skip the waypoint definition line
128
+ return true;
102
129
  }
103
- // Inside block
104
- return true; // Skip lines inside the block
130
+ return true;
105
131
  }
106
132
  return false;
107
- }
133
+ },
108
134
  },
109
135
  waypointWithProof: {
110
136
  gwta: `waypoint {outcome} with {proof:${DOMAIN_STATEMENT}}`,
111
137
  precludes: ['ActivitiesStepper.waypointLabel'],
112
138
  action: async ({ proof }, featureStep) => {
113
- this.getWorld().logger.debug(`waypoint action: executing ${proof?.length || 0} proof steps`);
114
139
  try {
115
140
  const result = await this.runner.runSteps(proof, { intent: { mode: 'authoritative' }, parentStep: featureStep });
116
141
  if (result.kind !== 'ok') {
@@ -120,12 +145,7 @@ export class ActivitiesStepper extends AStepper {
120
145
  }
121
146
  catch (err) {
122
147
  const msg = err instanceof Error ? err.message : String(err);
123
- this.getWorld().logger.debug(`waypoint action: exception executing proof steps: ${msg}`);
124
- const messageContext = {
125
- incident: EExecutionMessageType.ACTION,
126
- incidentDetails: { error: msg }
127
- };
128
- return actionNotOK(`waypoint: failed to execute proof steps: ${msg}`, { messageContext });
148
+ return actionNotOK(`waypoint: failed to execute proof steps: ${msg}`);
129
149
  }
130
150
  },
131
151
  },
@@ -136,48 +156,47 @@ export class ActivitiesStepper extends AStepper {
136
156
  ensure: {
137
157
  description: 'Ensure a waypoint condition by always running the proof. If proof passes, waypoint is already satisfied. If proof fails, run the full activity, then try the proof again',
138
158
  gwta: `ensure {outcome:${DOMAIN_STATEMENT}}`,
139
- unique: true, // Ensure takes priority over dynamically registered outcomes
159
+ unique: true,
140
160
  action: async ({ outcome }, featureStep) => {
141
161
  const outcomeKey = outcome.map(step => step.in).join(' ');
142
- // Guard: prevent infinite ensure retry loops by counting attempts per outcome+seq
143
- const attemptKey = outcomeKey; // key by outcome only to avoid differing seqPaths
162
+ const attemptKey = outcomeKey;
144
163
  const prevAttempts = this.ensureAttempts.get(attemptKey) ?? 0;
145
164
  const MAX_ENSURE_ATTEMPTS = 10;
146
165
  this.ensureAttempts.set(attemptKey, prevAttempts + 1);
147
166
  if (prevAttempts + 1 > MAX_ENSURE_ATTEMPTS) {
148
- this.getWorld().logger.warn(`ensure: exceeded max attempts (${MAX_ENSURE_ATTEMPTS}) for ${outcomeKey}`);
149
167
  if (this.getWorld().runtime) {
150
168
  this.getWorld().runtime.exhaustionError = 'ensure max attempts exceeded';
151
169
  }
152
- const messageContext = {
153
- incident: EExecutionMessageType.ACTION,
154
- incidentDetails: { waypoint: outcomeKey, satisfied: false, error: 'max ensure attempts exceeded', terminal: true }
155
- };
156
- return actionNotOK(`ensure: max attempts exceeded for waypoint "${outcomeKey}"`, { messageContext });
170
+ return actionNotOK(`ensure: max attempts exceeded for waypoint "${outcomeKey}"`);
157
171
  }
158
- // Log ENSURE_START
159
- const startMessageContext = {
160
- incident: EExecutionMessageType.ENSURE_START,
161
- incidentDetails: { waypoint: outcomeKey, step: featureStep }
162
- };
163
- this.getWorld().logger.log(`⏳ Ensuring ${outcomeKey}`, startMessageContext);
164
- this.getWorld().logger.debug(`ensure: verifying waypoint "${outcomeKey}"`);
172
+ // Emit ensure start for monitors
173
+ this.getWorld().eventLogger.emit(LifecycleEvent.parse({
174
+ id: formatCurrentSeqPath(featureStep.seqPath) + '.ensure',
175
+ timestamp: Date.now(),
176
+ kind: 'lifecycle',
177
+ completeness: 'full',
178
+ type: 'ensure',
179
+ stage: 'start',
180
+ in: outcomeKey,
181
+ lineNumber: featureStep.source.lineNumber,
182
+ featurePath: featureStep.source.path,
183
+ status: 'running',
184
+ }));
165
185
  const pattern = outcome[0]?.action?.actionName || outcomeKey;
166
186
  const registeredWaypoint = this.steps[pattern];
167
187
  if (!registeredWaypoint) {
188
+ this.emitEnsureEnd(featureStep, outcomeKey, false, `"${outcomeKey}" is not a registered waypoint`);
168
189
  return actionNotOK(`ensure: "${outcomeKey}" is not a registered waypoint. ensure can only be used with waypoints.`);
169
190
  }
170
191
  const metadata = this.registeredOutcomeMetadata.get(pattern);
171
192
  if (!metadata || metadata.proofStatements.length === 0) {
193
+ this.emitEnsureEnd(featureStep, outcomeKey, false, 'no proof defined');
172
194
  return actionNotOK(`ensure: waypoint "${outcomeKey}" has no proof. ensure can only be used with waypoints that have a proof.`);
173
195
  }
174
- // Extract args from the outcome step(s) to pass to the activity
175
- // This ensures that variables (e.g. {name}) defined in the waypoint are available in the activity
176
196
  const activityArgs = {};
177
197
  for (const step of outcome) {
178
198
  if (step.action.stepValuesMap) {
179
199
  for (const [key, val] of Object.entries(step.action.stepValuesMap)) {
180
- // Use the value if available (it should be resolved), otherwise term
181
200
  const value = val.value !== undefined ? String(val.value) : val.term;
182
201
  if (value !== undefined) {
183
202
  activityArgs[key] = value;
@@ -187,57 +206,26 @@ export class ActivitiesStepper extends AStepper {
187
206
  }
188
207
  let proofStatements;
189
208
  try {
190
- // Use FlowRunner for the proof execution
191
- // Pass activityArgs so they are available to the Outcome Action
192
209
  const flowResult = await this.runner.runSteps(outcome, { intent: { mode: 'authoritative', usage: featureStep.intent?.usage, stepperOptions: { isEnsure: true } }, parentStep: featureStep });
193
210
  if (flowResult.kind !== 'ok') {
194
- // Log ENSURE_END for failure
195
- const endMessageContext = {
196
- incident: EExecutionMessageType.ENSURE_END,
197
- incidentDetails: { waypoint: outcomeKey, satisfied: false, error: flowResult.message, actionResult: { ok: false } }
198
- };
199
- this.getWorld().logger.log(`❌ Failed ensuring ${outcomeKey}`, endMessageContext);
200
- const messageContext = {
201
- incident: EExecutionMessageType.ACTION,
202
- incidentDetails: { waypoint: outcomeKey, satisfied: false, error: flowResult.message }
203
- };
204
- return actionNotOK(`ensure: waypoint "${outcomeKey}" proof failed: ${flowResult.message}`, { messageContext });
211
+ this.emitEnsureEnd(featureStep, outcomeKey, false, flowResult.message);
212
+ return actionNotOK(`ensure: waypoint "${outcomeKey}" proof failed: ${flowResult.message}`);
205
213
  }
206
- proofStatements = flowResult.payload?.messageContext?.incidentDetails?.proofStatements;
214
+ proofStatements = flowResult.topics?.topics?.proofStatements;
207
215
  if (!proofStatements) {
216
+ this.emitEnsureEnd(featureStep, outcomeKey, false, 'no proofStatements returned');
208
217
  return actionNotOK(`ensure: waypoint "${outcomeKey}" succeeded but returned no proofStatements`);
209
218
  }
210
219
  }
211
220
  catch (err) {
212
221
  const msg = err instanceof Error ? err.message : String(err);
213
- this.getWorld().logger.debug(`ensure: exception while executing proof for ${outcomeKey}: ${msg}`);
214
- const messageContext = {
215
- incident: EExecutionMessageType.ACTION,
216
- incidentDetails: { waypoint: outcomeKey, satisfied: false, error: msg }
217
- };
218
- return actionNotOK(`ensure: waypoint "${outcomeKey}" proof execution error: ${msg}`, { messageContext });
222
+ this.emitEnsureEnd(featureStep, outcomeKey, false, msg);
223
+ return actionNotOK(`ensure: waypoint "${outcomeKey}" proof execution error: ${msg}`);
219
224
  }
220
- // FIXME: We don't have easy access to proofStatements from FlowRunner result yet unless we pass them back
221
- // For now, we assume if it passed, it passed.
222
225
  this.ensuredInstances.set(outcomeKey, { proof: proofStatements, valid: true });
223
- // On success or after one ensure action completes, reset attempt counter for this outcome
224
226
  this.ensureAttempts.delete(attemptKey);
225
- this.getWorld().logger.debug(`ensure: waypoint "${outcomeKey}" verified and satisfied`);
226
- // Log ENSURE_END for success at trace level (just to hide the ENSURE_START)
227
- const endMessageContext = {
228
- incident: EExecutionMessageType.ENSURE_END,
229
- incidentDetails: { waypoint: outcomeKey, satisfied: true, proofStatements, actionResult: { ok: true } }
230
- };
231
- this.getWorld().logger.trace(`✓ Ensured ${outcomeKey}`, endMessageContext);
232
- const messageContext = {
233
- incident: EExecutionMessageType.ACTION,
234
- incidentDetails: {
235
- waypoint: outcomeKey,
236
- satisfied: true,
237
- proofStatements
238
- }
239
- };
240
- return actionOK({ messageContext });
227
+ this.emitEnsureEnd(featureStep, outcomeKey, true);
228
+ return actionOK();
241
229
  },
242
230
  },
243
231
  showWaypoints: {
@@ -245,9 +233,7 @@ export class ActivitiesStepper extends AStepper {
245
233
  action: async (_args, featureStep) => {
246
234
  const waypointResults = {};
247
235
  for (const [instanceKey, instanceData] of this.ensuredInstances.entries()) {
248
- this.getWorld().logger.debug(`show waypoints: verifying "${instanceKey}"`);
249
236
  try {
250
- // Use FlowRunner to run the proof statements directly
251
237
  const result = await this.runner.runStatements(instanceData.proof, { intent: { mode: 'speculative' }, parentStep: featureStep });
252
238
  waypointResults[instanceKey] = {
253
239
  proof: instanceData.proof.join('; '),
@@ -262,8 +248,7 @@ export class ActivitiesStepper extends AStepper {
262
248
  };
263
249
  }
264
250
  }
265
- this.getWorld().logger.info(`Waypoints (${Object.keys(waypointResults).length} ensured):\n${JSON.stringify(waypointResults, null, 2)}`);
266
- return actionOK({ messageContext: { incident: EExecutionMessageType.ACTION, incidentDetails: { waypoints: waypointResults } } });
251
+ return actionOK();
267
252
  },
268
253
  },
269
254
  };
@@ -273,49 +258,62 @@ export class ActivitiesStepper extends AStepper {
273
258
  await super.setWorld(world, steppers);
274
259
  this.runner = new FlowRunner(world, steppers);
275
260
  }
276
- /**
277
- * Register a dynamic outcome step.
278
- * This is called when parsing `waypoint` statements.
279
- *
280
- * @param outcome - The outcome pattern (e.g., "Is logged in as {user}")
281
- * @param proofStatements - Array of statement strings from the DOMAIN_STATEMENT proof
282
- * @param proofPath - The path of the feature containing the proof
283
- * @param isBackground - Whether this outcome is defined in a background (persists across features)
284
- * @param activityBlockSteps - Optional array of all steps in the containing activity block
285
- */
286
- registerOutcome(outcome, proofStatements, proofPath, isBackground, activityBlockSteps) {
287
- // Prevent duplicate outcome registration
261
+ emitEnsureEnd(featureStep, outcomeKey, ok, error) {
262
+ this.getWorld().eventLogger.emit(LifecycleEvent.parse({
263
+ id: formatCurrentSeqPath(featureStep.seqPath) + '.ensure',
264
+ timestamp: Date.now(),
265
+ kind: 'lifecycle',
266
+ completeness: 'full',
267
+ type: 'ensure',
268
+ stage: 'end',
269
+ in: outcomeKey,
270
+ lineNumber: featureStep.source.lineNumber,
271
+ featurePath: featureStep.source.path,
272
+ status: ok ? 'completed' : 'failed',
273
+ error,
274
+ }));
275
+ }
276
+ registerOutcome(outcome, proofStatements, proofPath, isBackground, activityBlockSteps, lineNumber, actualSourcePath) {
288
277
  if (this.steps[outcome]) {
289
- throw new Error(`Outcome "${outcome}" is already registered. Each outcome can only be defined once.`);
278
+ const existing = this.steps[outcome];
279
+ if (existing.source?.path === (actualSourcePath || proofPath) && existing.source?.lineNumber === lineNumber) {
280
+ return;
281
+ }
282
+ throw new Error(`Outcome "${outcome}" is already registered. Each outcome can only be defined once. (Existing: ${existing.source?.path}:${existing.source?.lineNumber}, New: ${actualSourcePath || proofPath}:${lineNumber})`);
290
283
  }
291
- // Normalize activity steps (split multiline strings)
292
- const normalizedActivitySteps = activityBlockSteps?.flatMap(s => s.split('\n')).map(s => s.trim()).filter(s => s.length > 0) ?? [];
293
- // Store metadata for runtime re-emission via TEST_LINKS messages
284
+ // Normalize activity steps and proofs to ensure they carry source location
285
+ const sourcePath = actualSourcePath || proofPath;
286
+ const normalizedActivitySteps = activityBlockSteps?.map(s => {
287
+ return typeof s === 'string' ? { in: s, source: { path: sourcePath } } : s;
288
+ }) ?? [];
289
+ const normalizedProofSteps = proofStatements.map(s => ({
290
+ in: s,
291
+ source: { path: sourcePath }
292
+ }));
294
293
  this.registeredOutcomeMetadata.set(outcome, {
295
294
  proofStatements,
296
295
  proofPath,
297
296
  isBackground: isBackground ?? false,
298
297
  activityBlockSteps: normalizedActivitySteps,
298
+ lineNumber,
299
299
  });
300
- // Track whether this is a background or feature outcome
301
300
  if (isBackground) {
302
301
  this.backgroundOutcomePatterns.add(outcome);
303
302
  }
304
303
  else {
305
304
  this.featureOutcomePatterns.add(outcome);
306
- // Track which feature this outcome belongs to
307
305
  this.outcomeToFeaturePath.set(outcome, proofPath);
308
306
  }
309
- this.getWorld().logger.debug(`ActivitiesStepper: registerOutcome called with ${proofStatements.length} proof steps for "${outcome}"`);
310
- this.getWorld().logger.debug(`ActivitiesStepper: outcome is background=${isBackground}, will be added to ${isBackground ? 'backgroundOutcomePatterns' : 'featureOutcomePatterns'}`);
311
- // FIXME: maybe we don't care if normalizedActivitySteps is empty
312
307
  const step = {
313
308
  gwta: outcome,
314
- virtual: true, // Dynamically registered outcomes are virtual
315
- handlesUndefined: true, // FIXME they should not need to handle undefined at the virtual stepper level
309
+ virtual: true,
310
+ handlesUndefined: true,
311
+ source: {
312
+ lineNumber,
313
+ path: actualSourcePath || proofPath,
314
+ },
316
315
  description: `Outcome: ${outcome}. Proof: ${proofStatements.join('; ')}`,
317
316
  action: async (args, featureStep) => {
318
- // Reconstruct args to include unresolved terms (skipped by strict populateActionArgs)
319
317
  const robustArgs = { ...args };
320
318
  if (featureStep.action.stepValuesMap) {
321
319
  for (const [key, val] of Object.entries(featureStep.action.stepValuesMap)) {
@@ -324,59 +322,43 @@ export class ActivitiesStepper extends AStepper {
324
322
  }
325
323
  }
326
324
  }
327
- this.getWorld().logger.debug(`ActivitiesStepper: executing recipe for outcome "${outcome}" with args ${JSON.stringify(robustArgs)}`);
328
325
  // 1. Check Proof (Speculative)
329
- if (proofStatements.length > 0) {
330
- const proof = await this.runner.runStatements(proofStatements, { args: robustArgs, intent: { mode: 'speculative' }, parentStep: featureStep });
326
+ if (normalizedProofSteps.length > 0) {
327
+ const proof = await this.runner.runStatements(normalizedProofSteps, { args: robustArgs, intent: { mode: 'speculative' }, parentStep: featureStep });
331
328
  if (proof.kind === 'ok') {
332
- this.getWorld().logger.debug(`ActivitiesStepper: proof passed for outcome "${outcome}", skipping activity body`);
333
- return actionOK({
334
- messageContext: {
335
- incident: EExecutionMessageType.ACTION,
336
- incidentDetails: { proofStatements, proofSatisfied: true }
337
- }
338
- });
329
+ return actionOK({ topics: { proofStatements } });
339
330
  }
340
331
  }
341
332
  // 2. Proof Failed or not present
342
333
  if (!featureStep.intent?.stepperOptions?.isEnsure) {
343
334
  if (normalizedActivitySteps && normalizedActivitySteps.length > 0) {
344
- this.getWorld().logger.debug(`ActivitiesStepper: running activity body for outcome "${outcome}" (no ensure)`);
345
335
  const mode = featureStep.intent?.mode === 'speculative' ? 'speculative' : 'authoritative';
346
336
  const act = await this.runner.runStatements(normalizedActivitySteps, { args: robustArgs, intent: { mode, usage: featureStep.intent?.usage }, parentStep: featureStep });
347
337
  if (act.kind !== 'ok') {
348
338
  return actionNotOK(`ActivitiesStepper: activity body failed for outcome "${outcome}": ${act.message}`);
349
339
  }
350
- return actionOK();
340
+ return actionOK({ topics: { proofStatements } });
351
341
  }
352
342
  if (proofStatements.length > 0) {
353
343
  return actionNotOK(`ActivitiesStepper: proof failed for outcome "${outcome}"`);
354
344
  }
355
- // No proof (waypointLabel) and not ensure: do nothing.
356
- return actionOK();
345
+ return actionOK({ topics: { proofStatements } });
357
346
  }
358
347
  // 3. Ensure Mode: Run Activity Body
359
348
  if (normalizedActivitySteps && normalizedActivitySteps.length > 0) {
360
- this.getWorld().logger.debug(`ActivitiesStepper: proof failed for outcome "${outcome}", running activity body`);
361
349
  const mode = featureStep.intent?.mode === 'speculative' ? 'speculative' : 'authoritative';
362
350
  const act = await this.runner.runStatements(normalizedActivitySteps, { args: robustArgs, intent: { mode, usage: featureStep.intent?.usage }, parentStep: featureStep });
363
351
  if (act.kind !== 'ok') {
364
352
  return actionNotOK(`ActivitiesStepper: activity body failed for outcome "${outcome}": ${act.message}`);
365
353
  }
366
354
  // 4. Verify Proof After Activity
367
- this.getWorld().logger.debug(`ActivitiesStepper: verifying proof after activity body for outcome "${outcome}"`);
368
- if (proofStatements.length > 0) {
369
- const verify = await this.runner.runStatements(proofStatements, { args: robustArgs, intent: { mode, usage: featureStep.intent?.usage }, parentStep: featureStep });
355
+ if (normalizedProofSteps.length > 0) {
356
+ const verify = await this.runner.runStatements(normalizedProofSteps, { args: robustArgs, intent: { mode, usage: featureStep.intent?.usage }, parentStep: featureStep });
370
357
  if (verify.kind !== 'ok') {
371
358
  return actionNotOK(`ActivitiesStepper: proof verification failed after activity body for outcome "${outcome}": ${verify.message}`);
372
359
  }
373
360
  }
374
- return actionOK({
375
- messageContext: {
376
- incident: EExecutionMessageType.ACTION,
377
- incidentDetails: { proofStatements, proofSatisfied: true }
378
- }
379
- });
361
+ return actionOK({ topics: { proofStatements } });
380
362
  }
381
363
  return actionNotOK(`ActivitiesStepper: no activity body for outcome "${outcome}"`);
382
364
  }
@@ -386,30 +368,35 @@ export class ActivitiesStepper extends AStepper {
386
368
  if (!this.featureSteps.has(proofPath)) {
387
369
  this.featureSteps.set(proofPath, {});
388
370
  }
389
- this.featureSteps.get(proofPath)[outcome] = step;
371
+ const steps = this.featureSteps.get(proofPath);
372
+ if (steps) {
373
+ steps[outcome] = step;
374
+ }
375
+ }
376
+ else {
377
+ this.backgroundSteps[outcome] = step;
390
378
  }
391
- this.getWorld().logger.debug(`ActivitiesStepper: registered outcome pattern "${outcome}" with ${proofStatements.length} proof steps`);
392
379
  }
393
- /**
394
- * Re-emit GRAPH_LINK messages for waypoint metadata.
395
- * MonitorHandler subscribes after resolution, so we retransmit stored metadata.
396
- */
397
380
  sendGraphLinkMessages() {
398
381
  for (const [outcome, metadata] of this.registeredOutcomeMetadata.entries()) {
399
- const messageContext = {
400
- incident: EExecutionMessageType.GRAPH_LINK,
401
- incidentDetails: {
382
+ this.getWorld().eventLogger.emit(ControlEvent.parse({
383
+ id: `graph-link-${outcome}`,
384
+ timestamp: Date.now(),
385
+ kind: 'control',
386
+ level: 'debug',
387
+ signal: 'graph-link',
388
+ topics: {
402
389
  outcome,
403
390
  proofStatements: metadata.proofStatements,
404
391
  proofPath: metadata.proofPath,
405
392
  isBackground: metadata.isBackground,
406
393
  activityBlockSteps: metadata.activityBlockSteps ?? null,
394
+ lineNumber: metadata.lineNumber,
407
395
  }
408
- };
409
- this.getWorld().logger.debug(`waypoint registered: "${outcome}"`, messageContext);
396
+ }));
410
397
  }
411
398
  }
412
- resolveWaypointCommon(line, path, allLines, lineIndex, requireProof) {
399
+ resolveWaypointCommon(line, path, allLines, lineIndex, requireProof, actualSourcePath) {
413
400
  if (!line.match(/^waypoint\s+/i)) {
414
401
  return false;
415
402
  }
@@ -419,14 +406,12 @@ export class ActivitiesStepper extends AStepper {
419
406
  if (!line.match(/^waypoint\s+.+?\s+with\s+/i)) {
420
407
  return false;
421
408
  }
422
- // Find the LAST occurrence of ' with ' to separate outcome from proof
423
- // This allows 'with' to appear in outcome names (e.g., "{name} agreed with {concern}")
424
409
  const withoutPrefix = line.replace(/^waypoint\s+/i, '');
425
410
  const lastWithIndex = withoutPrefix.lastIndexOf(' with ');
426
411
  if (lastWithIndex === -1)
427
412
  return false;
428
413
  outcome = withoutPrefix.substring(0, lastWithIndex).trim();
429
- const proofRaw = withoutPrefix.substring(lastWithIndex + 6).trim(); // ' with ' is 6 chars
414
+ const proofRaw = withoutPrefix.substring(lastWithIndex + 6).trim();
430
415
  proofStatements = proofRaw.split('\n').map(s => s.trim()).filter(s => s.length > 0);
431
416
  }
432
417
  else {
@@ -438,12 +423,10 @@ export class ActivitiesStepper extends AStepper {
438
423
  return false;
439
424
  outcome = match[1].trim();
440
425
  }
441
- // Skip if already registered (prevents infinite loops)
442
426
  if (this.backgroundOutcomePatterns.has(outcome) || this.featureOutcomePatterns.has(outcome)) {
443
427
  return true;
444
428
  }
445
429
  const isBackground = path.includes('backgrounds/');
446
- // Scan backwards to find containing Activity block
447
430
  let activityBlockSteps;
448
431
  if (allLines && lineIndex !== undefined) {
449
432
  let activityStartLine = -1;
@@ -458,18 +441,23 @@ export class ActivitiesStepper extends AStepper {
458
441
  }
459
442
  }
460
443
  if (activityStartLine !== -1) {
461
- // Collect steps between Activity: and waypoint (excluding waypoint itself)
462
444
  const blockLines = [];
463
445
  for (let i = activityStartLine + 1; i < lineIndex; i++) {
464
446
  const stepLine = getActionable(allLines[i]);
465
447
  if (stepLine && !stepLine.match(/^waypoint\s+/i)) {
466
- blockLines.push(stepLine);
448
+ blockLines.push({
449
+ in: stepLine,
450
+ source: {
451
+ lineNumber: i + 1,
452
+ path: actualSourcePath || path
453
+ }
454
+ });
467
455
  }
468
456
  }
469
457
  activityBlockSteps = blockLines;
470
458
  }
471
459
  }
472
- this.registerOutcome(outcome, proofStatements, path, isBackground, activityBlockSteps);
460
+ this.registerOutcome(outcome, proofStatements, path, isBackground, activityBlockSteps, lineIndex !== undefined ? lineIndex + 1 : undefined, actualSourcePath);
473
461
  return true;
474
462
  }
475
463
  }