@ductape/sdk 0.1.110 → 0.1.114

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 (69) hide show
  1. package/dist/api/services/processorApi.service.d.ts +2 -0
  2. package/dist/api/services/processorApi.service.js +51 -0
  3. package/dist/api/services/processorApi.service.js.map +1 -1
  4. package/dist/api/services/productsApi.service.d.ts +7 -1
  5. package/dist/api/services/productsApi.service.js.map +1 -1
  6. package/dist/api/urls.d.ts +1 -0
  7. package/dist/api/urls.js +5 -4
  8. package/dist/api/urls.js.map +1 -1
  9. package/dist/context/execution-context.d.ts +37 -0
  10. package/dist/context/execution-context.js +38 -0
  11. package/dist/context/execution-context.js.map +1 -0
  12. package/dist/context/index.d.ts +1 -0
  13. package/dist/context/index.js +18 -0
  14. package/dist/context/index.js.map +1 -0
  15. package/dist/database/adapters/mongodb.adapter.js +7 -7
  16. package/dist/database/adapters/mongodb.adapter.js.map +1 -1
  17. package/dist/database/databases.service.d.ts +4 -0
  18. package/dist/database/databases.service.js +7 -1
  19. package/dist/database/databases.service.js.map +1 -1
  20. package/dist/features/feature-executor.d.ts +19 -0
  21. package/dist/features/feature-executor.js +330 -98
  22. package/dist/features/feature-executor.js.map +1 -1
  23. package/dist/features/features.service.d.ts +21 -1
  24. package/dist/features/features.service.js +136 -33
  25. package/dist/features/features.service.js.map +1 -1
  26. package/dist/features/index.d.ts +2 -1
  27. package/dist/features/index.js +3 -1
  28. package/dist/features/index.js.map +1 -1
  29. package/dist/features/types/features.types.d.ts +17 -2
  30. package/dist/features/types/features.types.js.map +1 -1
  31. package/dist/functions/functions.runtime.d.ts +25 -0
  32. package/dist/functions/functions.runtime.js +278 -0
  33. package/dist/functions/functions.runtime.js.map +1 -0
  34. package/dist/functions/http-handler.d.ts +17 -0
  35. package/dist/functions/http-handler.js +51 -0
  36. package/dist/functions/http-handler.js.map +1 -0
  37. package/dist/functions/index.d.ts +3 -0
  38. package/dist/functions/index.js +20 -0
  39. package/dist/functions/index.js.map +1 -0
  40. package/dist/functions/types.d.ts +100 -0
  41. package/dist/functions/types.js +15 -0
  42. package/dist/functions/types.js.map +1 -0
  43. package/dist/graph/graphs.service.d.ts +1 -1
  44. package/dist/graph/graphs.service.js +73 -43
  45. package/dist/graph/graphs.service.js.map +1 -1
  46. package/dist/graph/types/traversal.interface.d.ts +2 -0
  47. package/dist/index.d.ts +18 -2
  48. package/dist/index.js +27 -3
  49. package/dist/index.js.map +1 -1
  50. package/dist/processor/services/processor.service.d.ts +6 -0
  51. package/dist/processor/services/processor.service.js +8 -13
  52. package/dist/processor/services/processor.service.js.map +1 -1
  53. package/dist/products/bootstrap-cache.js +7 -2
  54. package/dist/products/bootstrap-cache.js.map +1 -1
  55. package/dist/products/services/products.service.d.ts +2 -2
  56. package/dist/products/services/products.service.js +36 -1
  57. package/dist/products/services/products.service.js.map +1 -1
  58. package/dist/sessions/sessions.helper.js +13 -1
  59. package/dist/sessions/sessions.helper.js.map +1 -1
  60. package/dist/tsconfig.tsbuildinfo +1 -1
  61. package/dist/types/processor.types.d.ts +15 -0
  62. package/dist/types/processor.types.js.map +1 -1
  63. package/dist/types/productsBuilder.types.d.ts +3 -0
  64. package/dist/types/productsBuilder.types.js +2 -0
  65. package/dist/types/productsBuilder.types.js.map +1 -1
  66. package/dist/vector/vector-database.service.d.ts +2 -0
  67. package/dist/vector/vector-database.service.js +21 -12
  68. package/dist/vector/vector-database.service.js.map +1 -1
  69. package/package.json +1 -1
@@ -11,9 +11,9 @@
11
11
  var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
- var _a, _b;
14
+ var _a, _b, _c, _d;
15
15
  Object.defineProperty(exports, "__esModule", { value: true });
16
- exports.FeatureExecutor = void 0;
16
+ exports.FeatureExecutor = exports.UnresolvedInputReferenceError = void 0;
17
17
  const crypto_1 = require("crypto");
18
18
  const processor_service_1 = __importDefault(require("../processor/services/processor.service"));
19
19
  const products_service_1 = __importDefault(require("../products/services/products.service"));
@@ -32,12 +32,24 @@ const inputs_types_1 = require("../types/inputs.types");
32
32
  const processor_utils_1 = require("../processor/utils/processor.utils");
33
33
  const productsBuilder_types_1 = require("../types/productsBuilder.types");
34
34
  const date_fns_1 = require("date-fns");
35
+ const functions_1 = require("../functions");
36
+ const context_1 = require("../context");
37
+ class UnresolvedInputReferenceError extends Error {
38
+ constructor(reference, required) {
39
+ super(`Runtime input reference ${reference} is absent${required ? ' but required by the feature contract' : ''}.`);
40
+ this.reference = reference;
41
+ this.required = required;
42
+ this.code = 'UNRESOLVED_INPUT_REFERENCE';
43
+ this.name = 'UnresolvedInputReferenceError';
44
+ }
45
+ }
46
+ exports.UnresolvedInputReferenceError = UnresolvedInputReferenceError;
35
47
  /** Only log when DUCTAPE_DEBUG is set to avoid sync I/O and serialization cost in hot path */
36
48
  const debugLog = typeof process !== 'undefined' && (((_a = process.env) === null || _a === void 0 ? void 0 : _a.DUCTAPE_DEBUG) === 'true' || ((_b = process.env) === null || _b === void 0 ? void 0 : _b.DUCTAPE_DEBUG) === '1')
37
49
  ? (...args) => console.log(...args)
38
50
  : () => { };
39
- /** Always-on step/summary logging for visibility in tests and support */
40
- const stepLog = typeof process !== 'undefined'
51
+ /** Detailed step logging is opt-in; synchronous stdout is expensive in server runtimes. */
52
+ const stepLog = typeof process !== 'undefined' && (((_c = process.env) === null || _c === void 0 ? void 0 : _c.DUCTAPE_DEBUG) === 'true' || ((_d = process.env) === null || _d === void 0 ? void 0 : _d.DUCTAPE_DEBUG) === '1')
41
53
  ? (msg, data) => {
42
54
  if (data != null && Object.keys(data).length > 0) {
43
55
  console.log(`[FeatureExecutor] ${msg}`, data);
@@ -47,6 +59,39 @@ const stepLog = typeof process !== 'undefined'
47
59
  }
48
60
  }
49
61
  : () => { };
62
+ const SENSITIVE_LOG_KEY = /(?:authorization|cookie|password|passwd|secret|token|api[_-]?key|access[_-]?key|private[_-]?key|client[_-]?secret|session)/i;
63
+ const MAX_LOG_STRING_LENGTH = 2000;
64
+ /** Produce useful payload traces without leaking credentials or exploding logs. */
65
+ const sanitizeForLog = (value, key = '', seen = new WeakSet()) => {
66
+ if (key !== 'has_session' && SENSITIVE_LOG_KEY.test(key))
67
+ return '[REDACTED]';
68
+ if (value == null || typeof value === 'number' || typeof value === 'boolean')
69
+ return value;
70
+ if (typeof value === 'bigint')
71
+ return value.toString();
72
+ if (typeof value === 'string') {
73
+ return value.length > MAX_LOG_STRING_LENGTH
74
+ ? `${value.slice(0, MAX_LOG_STRING_LENGTH)}...[truncated ${value.length - MAX_LOG_STRING_LENGTH} chars]`
75
+ : value;
76
+ }
77
+ if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {
78
+ return `[Buffer ${value.length} bytes]`;
79
+ }
80
+ if (value instanceof Error)
81
+ return { name: value.name, message: value.message, stack: value.stack };
82
+ if (Array.isArray(value))
83
+ return value.map((item) => sanitizeForLog(item, key, seen));
84
+ if (typeof value === 'object') {
85
+ if (seen.has(value))
86
+ return '[Circular]';
87
+ seen.add(value);
88
+ return Object.fromEntries(Object.entries(value).map(([nestedKey, nestedValue]) => [
89
+ nestedKey,
90
+ sanitizeForLog(nestedValue, nestedKey, seen),
91
+ ]));
92
+ }
93
+ return String(value);
94
+ };
50
95
  /** Observability must not disappear because a successful result contains BigInt or cycles. */
51
96
  const stringifyProcessorValue = (value) => {
52
97
  const seen = new WeakSet();
@@ -115,6 +160,9 @@ class FeatureExecutor {
115
160
  this.graphConnectionUsed = false;
116
161
  /** Pre-fetched bootstrap data per step tag (feature batch prefetch). */
117
162
  this.stepBootstrapCache = new Map();
163
+ this.functionInvocations = new Map();
164
+ /** Step telemetry is accumulated and persisted with the terminal feature record in one round trip. */
165
+ this.pendingProcessorResults = [];
118
166
  debugLog('[FeatureExecutor] constructor ENTRY', {
119
167
  feature_tag: feature.tag,
120
168
  product: options.product,
@@ -123,6 +171,7 @@ class FeatureExecutor {
123
171
  });
124
172
  this.config = config;
125
173
  this.feature = feature;
174
+ this.inheritedSession = options.session;
126
175
  this.sessionLogFields = sessionLogFields || {};
127
176
  this._privateKey = private_key || '';
128
177
  this.preInitializedProduct = Boolean(preInitializedBuilder);
@@ -158,8 +207,26 @@ class FeatureExecutor {
158
207
  feature_id: this.state.feature_id,
159
208
  feature_tag: this.state.feature_tag,
160
209
  steps_count: (_b = (_a = feature.steps) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0,
210
+ has_session: Boolean(this.inheritedSession),
211
+ input: sanitizeForLog(options.input),
161
212
  });
162
213
  }
214
+ /** Debug-only structured execution trace. Payloads are recursively redacted. */
215
+ trace(stage, payload = {}) {
216
+ debugLog('[FeatureExecutor][trace]', sanitizeForLog(Object.assign({ stage, feature_id: this.state.feature_id, feature_tag: this.state.feature_tag, product: this.state.product, env: this.state.env, has_session: Boolean(this.inheritedSession) }, payload)));
217
+ }
218
+ /** Brokers expose the same session as separate tag/token fields. */
219
+ inheritedBrokerSession() {
220
+ if (!this.inheritedSession)
221
+ return undefined;
222
+ const separator = this.inheritedSession.indexOf(':');
223
+ if (separator < 1)
224
+ return undefined;
225
+ return {
226
+ tag: this.inheritedSession.slice(0, separator),
227
+ token: this.inheritedSession.slice(separator + 1),
228
+ };
229
+ }
163
230
  /**
164
231
  * Get auth payload for API calls
165
232
  */
@@ -190,6 +257,10 @@ class FeatureExecutor {
190
257
  access_key: this.config.access_key,
191
258
  preInitializedProductBuilder: this.productBuilder,
192
259
  });
260
+ this._processorService.setFeatureExecutionContext({
261
+ feature_id: this.state.feature_id,
262
+ feature_tag: this.state.feature_tag,
263
+ });
193
264
  }
194
265
  return this._processorService;
195
266
  }
@@ -204,6 +275,7 @@ class FeatureExecutor {
204
275
  env_type: this.config.env_type,
205
276
  private_key: this._privateKey,
206
277
  access_key: this.config.access_key,
278
+ redis_client: this.config.redis_client,
207
279
  });
208
280
  }
209
281
  return this._graphService;
@@ -219,6 +291,8 @@ class FeatureExecutor {
219
291
  env_type: this.config.env_type,
220
292
  private_key: this._privateKey,
221
293
  access_key: this.config.access_key,
294
+ redis_client: this.config.redis_client,
295
+ preInitializedProductBuilder: this.productBuilder,
222
296
  });
223
297
  }
224
298
  return this._databaseService;
@@ -322,23 +396,10 @@ class FeatureExecutor {
322
396
  const resultStr = stringifyProcessorValue(resultData);
323
397
  const inputStr = stringifyProcessorValue(inputPayload);
324
398
  const encKey = this.getProcessorResultEncryptionKey();
325
- const processorResult = {
326
- process_id: this.state.feature_id,
327
- product_id: this.productId || null,
328
- env: this.state.env,
329
- component: types_1.LogEventTypes.FEATURE,
330
- status,
331
- start: this.state.started_at,
332
- end: Date.now(),
333
- retryable: true,
334
- result: encKey ? (0, processor_utils_1.encrypt)(resultStr, encKey) : resultStr,
335
- input: encKey ? (0, processor_utils_1.encrypt)(inputStr, encKey) : inputStr,
336
- feature_id: this.state.feature_id,
337
- feature_tag: this.state.feature_tag,
338
- product_tag: this.state.product,
339
- workspace_id: this.config.workspace_id,
340
- };
341
- await this.processorApiService.saveResult(processorResult, this.getAuthPayload());
399
+ const processorResult = Object.assign(Object.assign({}, (0, context_1.executionContextFields)()), { process_id: this.state.feature_id, product_id: this.productId || null, env: this.state.env, component: types_1.LogEventTypes.FEATURE, status, start: this.state.started_at, end: Date.now(), retryable: true, result: encKey ? (0, processor_utils_1.encrypt)(resultStr, encKey) : resultStr, input: encKey ? (0, processor_utils_1.encrypt)(inputStr, encKey) : inputStr, feature_id: this.state.feature_id, feature_tag: this.state.feature_tag, product_tag: this.state.product, workspace_id: this.config.workspace_id });
400
+ this.trace('execution.persistence.sent', { status, payload: resultData });
401
+ await this.processorApiService.saveResultsBatch([...this.pendingProcessorResults.splice(0), processorResult], this.getAuthPayload());
402
+ this.trace('execution.persistence.acknowledged', { status, process_id: this.state.feature_id });
342
403
  debugLog('[FeatureExecutor] persistExecutionResult SUCCESS', { feature_id: this.state.feature_id, status });
343
404
  }
344
405
  catch (err) {
@@ -374,6 +435,7 @@ class FeatureExecutor {
374
435
  * Processor results for steps always have component: 'feature_step' and step_type set to the step kind.
375
436
  */
376
437
  async persistStepResult(step, status, output, error, duration, input) {
438
+ var _a;
377
439
  const resolvedInput = input !== null && input !== void 0 ? input : step.input;
378
440
  const stepType = this.getStepTypeForResult(step);
379
441
  debugLog('[FeatureExecutor] persistStepResult ENTRY', {
@@ -394,27 +456,17 @@ class FeatureExecutor {
394
456
  const resultStr = stringifyProcessorValue(value);
395
457
  const inputStr = stringifyProcessorValue(resolvedInput);
396
458
  const encKey = this.getProcessorResultEncryptionKey();
397
- const stepResult = {
398
- process_id: stepProcessId,
399
- product_id: this.productId || null,
400
- env: this.state.env,
401
- component: types_1.LogEventTypes.FEATURE_STEP, // step results always feature_step
402
- status,
403
- start: Date.now() - (duration || 0),
404
- end: Date.now(),
405
- retryable: true,
406
- result: encKey ? (0, processor_utils_1.encrypt)(resultStr, encKey) : resultStr,
407
- input: encKey ? (0, processor_utils_1.encrypt)(inputStr, encKey) : inputStr,
408
- feature_id: this.state.feature_id,
409
- feature_tag: this.state.feature_tag,
410
- product_tag: this.state.product,
411
- workspace_id: this.config.workspace_id,
459
+ const stepResult = Object.assign(Object.assign(Object.assign({}, (0, context_1.executionContextFields)()), { process_id: stepProcessId, product_id: this.productId || null, env: this.state.env, component: types_1.LogEventTypes.FEATURE_STEP, // step results always feature_step
460
+ status, start: Date.now() - (duration || 0), end: Date.now(), retryable: true, result: encKey ? (0, processor_utils_1.encrypt)(resultStr, encKey) : resultStr, input: encKey ? (0, processor_utils_1.encrypt)(inputStr, encKey) : inputStr, feature_id: this.state.feature_id, feature_tag: this.state.feature_tag, product_tag: this.state.product, workspace_id: this.config.workspace_id, step_tag: step.tag, step_type: stepType, step_error: error, step_duration_ms: duration }), ((_a = this.functionInvocations.get(step.tag)) !== null && _a !== void 0 ? _a : {}));
461
+ this.trace('step.persistence.sent', {
412
462
  step_tag: step.tag,
413
- step_type: stepType, // action | notification | storage | produce | database_action | graph | vector | quota | fallback | child_feature | sleep | wait_for_signal | checkpoint
414
- step_error: error,
415
- step_duration_ms: duration,
416
- };
417
- await this.processorApiService.saveResult(stepResult, this.getAuthPayload());
463
+ step_type: stepType,
464
+ status,
465
+ input: resolvedInput,
466
+ output: value,
467
+ });
468
+ this.pendingProcessorResults.push(stepResult);
469
+ this.trace('step.persistence.queued', { step_tag: step.tag, status, process_id: stepProcessId });
418
470
  debugLog('[FeatureExecutor] persistStepResult SUCCESS', { feature_id: this.state.feature_id, step_tag: step.tag });
419
471
  }
420
472
  catch (err) {
@@ -467,7 +519,26 @@ class FeatureExecutor {
467
519
  * Execute the feature
468
520
  */
469
521
  async execute() {
470
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
522
+ var _a;
523
+ const context = (0, context_1.createExecutionContext)({
524
+ kind: 'feature',
525
+ workspace_id: this.config.workspace_id,
526
+ product_id: (_a = this.productId) !== null && _a !== void 0 ? _a : undefined,
527
+ product_tag: this.state.product,
528
+ env: this.state.env,
529
+ feature_id: this.state.feature_id,
530
+ feature_tag: this.state.feature_tag,
531
+ feature_run_id: this.state.feature_id,
532
+ process_id: this.state.feature_id,
533
+ session: this.inheritedSession,
534
+ session_id: this.sessionLogFields.session_id,
535
+ session_tag: this.sessionLogFields.session_tag,
536
+ session_user_id: this.sessionLogFields.session_user_id,
537
+ });
538
+ return (0, context_1.runWithExecutionContext)(context, () => this.executeInContext());
539
+ }
540
+ async executeInContext() {
541
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
471
542
  const startTime = Date.now();
472
543
  debugLog('[FeatureExecutor] execute ENTRY', {
473
544
  feature_id: this.state.feature_id,
@@ -475,6 +546,10 @@ class FeatureExecutor {
475
546
  product: this.state.product,
476
547
  env: this.state.env,
477
548
  });
549
+ this.trace('execution.started', {
550
+ input: this.state.input,
551
+ configured_steps: (_a = this.feature.steps) === null || _a === void 0 ? void 0 : _a.map((step) => ({ tag: step.tag, type: step.type, input: step.input })),
552
+ });
478
553
  try {
479
554
  // Track phase so failures before first step are reported as (initialization)
480
555
  this.state.current_step = '(initialization)';
@@ -487,6 +562,7 @@ class FeatureExecutor {
487
562
  this.productId = this.productBuilder.fetchProductId();
488
563
  }
489
564
  debugLog('[FeatureExecutor] execute product initialized', { feature_id: this.state.feature_id, product_id: this.productId });
565
+ this.trace('product.fetched', { product_id: this.productId, preinitialized: this.preInitializedProduct });
490
566
  // Initialize logging after we have product ID
491
567
  this.initializeLogging();
492
568
  this.state.status = types_1.FeatureStatus.RUNNING;
@@ -507,14 +583,17 @@ class FeatureExecutor {
507
583
  }
508
584
  catch (prefetchErr) {
509
585
  stepLog('Bootstrap prefetch failed (continuing without prefetch)', {
510
- error: (_a = prefetchErr === null || prefetchErr === void 0 ? void 0 : prefetchErr.message) !== null && _a !== void 0 ? _a : String(prefetchErr),
586
+ error: (_b = prefetchErr === null || prefetchErr === void 0 ? void 0 : prefetchErr.message) !== null && _b !== void 0 ? _b : String(prefetchErr),
511
587
  });
512
588
  this.stepBootstrapCache.clear();
513
589
  }
514
590
  // Execute each step
515
591
  for (const step of orderedSteps) {
516
592
  // Check step condition if present
517
- if (step.condition && !(await this.evaluateCondition(step.condition))) {
593
+ const conditionMet = step.condition ? await this.evaluateCondition(step.condition) : true;
594
+ if (step.condition)
595
+ this.trace('step.condition.evaluated', { step_tag: step.tag, condition: step.condition, result: conditionMet });
596
+ if (!conditionMet) {
518
597
  debugLog('[FeatureExecutor] execute step SKIPPED (condition)', { feature_id: this.state.feature_id, step_tag: step.tag });
519
598
  this.logStepEvent(step, `Step ${step.tag} - skipped (condition not met)`, logs_types_1.LogEventStatus.SUCCESS, { skipped: true });
520
599
  continue; // Skip step if condition not met
@@ -535,8 +614,8 @@ class FeatureExecutor {
535
614
  success: result.success,
536
615
  durationMs: result.duration,
537
616
  hasError: !!result.error,
538
- input: result.input,
539
- output: result.output,
617
+ input: sanitizeForLog(result.input),
618
+ output: sanitizeForLog(result.output),
540
619
  });
541
620
  // Log step completion
542
621
  this.logStepEvent(step, result.success ? `Step ${step.tag} - completed` : `Step ${step.tag} - failed`, result.success ? logs_types_1.LogEventStatus.SUCCESS : logs_types_1.LogEventStatus.FAIL, { duration: result.duration, error: result.error });
@@ -548,14 +627,14 @@ class FeatureExecutor {
548
627
  try {
549
628
  stepReturnValue = await this.resolveInput(outputSchema, result.output);
550
629
  }
551
- catch (_p) {
630
+ catch (_q) {
552
631
  stepReturnValue = result.output;
553
632
  }
554
633
  }
555
634
  else if (!result.success) {
556
635
  stepReturnValue = undefined;
557
636
  }
558
- console.warn(`[FeatureExecutor] Step "${step.tag}" output:`, stepReturnValue);
637
+ debugLog(`[FeatureExecutor] Step "${step.tag}" output:`, stepReturnValue);
559
638
  // Persist step result to backend (use step return shape, not raw primitive)
560
639
  await this.persistStepResult(step, result.success ? types_1.LogEventStatus.SUCCESS : types_1.LogEventStatus.FAIL, stepReturnValue, result.error, result.duration, result.input);
561
640
  this.state.step_timings.push({
@@ -565,22 +644,22 @@ class FeatureExecutor {
565
644
  });
566
645
  if (!result.success) {
567
646
  this.state.failed_step = step.tag;
568
- stepLog('Step failed', { tag: step.tag, error: result.error, allow_fail: (_b = step.options) === null || _b === void 0 ? void 0 : _b.allow_fail, optional: (_c = step.options) === null || _c === void 0 ? void 0 : _c.optional });
647
+ stepLog('Step failed', { tag: step.tag, error: result.error, allow_fail: (_c = step.options) === null || _c === void 0 ? void 0 : _c.allow_fail, optional: (_d = step.options) === null || _d === void 0 ? void 0 : _d.optional });
569
648
  debugLog('[FeatureExecutor] execute step FAILED', {
570
649
  feature_id: this.state.feature_id,
571
650
  step_tag: step.tag,
572
651
  error: result.error,
573
- allow_fail: (_d = step.options) === null || _d === void 0 ? void 0 : _d.allow_fail,
574
- optional: (_e = step.options) === null || _e === void 0 ? void 0 : _e.optional,
652
+ allow_fail: (_e = step.options) === null || _e === void 0 ? void 0 : _e.allow_fail,
653
+ optional: (_f = step.options) === null || _f === void 0 ? void 0 : _f.optional,
575
654
  });
576
655
  // Check if step allows failure
577
- if ((_f = step.options) === null || _f === void 0 ? void 0 : _f.allow_fail) {
656
+ if ((_g = step.options) === null || _g === void 0 ? void 0 : _g.allow_fail) {
578
657
  // Store error but continue
579
658
  this.state.steps[step.tag] = { error: result.error };
580
659
  continue;
581
660
  }
582
661
  // Check if step is optional
583
- if ((_g = step.options) === null || _g === void 0 ? void 0 : _g.optional) {
662
+ if ((_h = step.options) === null || _h === void 0 ? void 0 : _h.optional) {
584
663
  // Don't trigger rollback, just continue
585
664
  continue;
586
665
  }
@@ -592,7 +671,7 @@ class FeatureExecutor {
592
671
  feature_id: this.state.feature_id,
593
672
  success: rollbackResult.success,
594
673
  rolled_back_steps: rollbackResult.rolled_back_steps,
595
- failed_rollbacks: (_j = (_h = rollbackResult.failed_steps) === null || _h === void 0 ? void 0 : _h.length) !== null && _j !== void 0 ? _j : 0,
674
+ failed_rollbacks: (_k = (_j = rollbackResult.failed_steps) === null || _j === void 0 ? void 0 : _j.length) !== null && _k !== void 0 ? _k : 0,
596
675
  });
597
676
  // Persist failed feature result
598
677
  await this.persistExecutionResult(types_1.LogEventStatus.FAIL, undefined, result.error);
@@ -603,7 +682,7 @@ class FeatureExecutor {
603
682
  completed_steps: this.state.completed_steps,
604
683
  step_timings: this.state.step_timings,
605
684
  });
606
- await ((_k = this.logService) === null || _k === void 0 ? void 0 : _k.publish());
685
+ await ((_l = this.logService) === null || _l === void 0 ? void 0 : _l.publish());
607
686
  if (this.graphConnectionUsed) {
608
687
  await this.getGraphService().disconnect().catch(() => { });
609
688
  await this.getProcessorService().disconnectBrokerConnections().catch(() => { });
@@ -637,6 +716,7 @@ class FeatureExecutor {
637
716
  this.state.ended_at = Date.now();
638
717
  // Determine output - use last step output or aggregate (resolves $ operators)
639
718
  const output = await this.determineWorkflowOutput();
719
+ this.trace('execution.output.resolved', { output, completed_steps: this.state.completed_steps });
640
720
  stepLog('Run finished (completed)', {
641
721
  status: types_1.FeatureStatus.COMPLETED,
642
722
  completed_steps: this.state.completed_steps,
@@ -652,7 +732,7 @@ class FeatureExecutor {
652
732
  // Persist successful feature result
653
733
  await this.persistExecutionResult(types_1.LogEventStatus.SUCCESS, output);
654
734
  // Fire-and-forget log publish on success so we don't block on log upload
655
- (_l = this.logService) === null || _l === void 0 ? void 0 : _l.publish().catch(() => { });
735
+ (_m = this.logService) === null || _m === void 0 ? void 0 : _m.publish().catch(() => { });
656
736
  if (this.graphConnectionUsed) {
657
737
  await this.getGraphService().disconnect().catch(() => { });
658
738
  await this.getProcessorService().disconnectBrokerConnections().catch(() => { });
@@ -675,8 +755,9 @@ class FeatureExecutor {
675
755
  this.state.status = types_1.FeatureStatus.FAILED;
676
756
  this.state.ended_at = Date.now();
677
757
  const details = formatErrorDetails(error);
678
- const failedAt = (_m = this.state.current_step) !== null && _m !== void 0 ? _m : '(unknown)';
679
- debugLog('[FeatureExecutor] execute FAILED (exception)', Object.assign(Object.assign({ feature_id: this.state.feature_id, feature_tag: this.state.feature_tag, failed_at: failedAt, completed_steps: this.state.completed_steps, error: details.message }, (details.status != null && { http_status: details.status })), (details.responseBody != null && { response_body: details.responseBody })));
758
+ const failedAt = (_o = this.state.current_step) !== null && _o !== void 0 ? _o : '(unknown)';
759
+ debugLog('[FeatureExecutor] execute FAILED (exception)', Object.assign(Object.assign({ feature_id: this.state.feature_id, feature_tag: this.state.feature_tag, failed_at: failedAt, completed_steps: this.state.completed_steps, error: details.message }, (details.status != null && { http_status: details.status })), (details.responseBody != null && { response_body: sanitizeForLog(details.responseBody) })));
760
+ this.trace('execution.failed', { failed_at: failedAt, error: details, completed_steps: this.state.completed_steps });
680
761
  if (details.stack) {
681
762
  debugLog('[FeatureExecutor] execute FAILED stack', details.stack);
682
763
  }
@@ -686,7 +767,7 @@ class FeatureExecutor {
686
767
  : `Failed at ${failedAt}: ${details.message}`;
687
768
  stepLog('Run finished (failed - exception)', Object.assign({ status: types_1.FeatureStatus.FAILED, failed_step: failedAt, error: errorSummary, completed_steps: this.state.completed_steps, step_timings: this.state.step_timings }, (details.reason && { reason: details.reason })));
688
769
  await this.persistExecutionResult(types_1.LogEventStatus.FAIL, undefined, errorSummary);
689
- await ((_o = this.logService) === null || _o === void 0 ? void 0 : _o.publish());
770
+ await ((_p = this.logService) === null || _p === void 0 ? void 0 : _p.publish());
690
771
  if (this.graphConnectionUsed) {
691
772
  await this.getGraphService().disconnect().catch(() => { });
692
773
  await this.getProcessorService().disconnectBrokerConnections().catch(() => { });
@@ -711,6 +792,19 @@ class FeatureExecutor {
711
792
  * Execute a single step
712
793
  */
713
794
  async executeStep(step) {
795
+ const parent = (0, context_1.currentExecutionContext)();
796
+ const stepRunId = (0, crypto_1.randomUUID)();
797
+ const context = (0, context_1.createExecutionContext)({
798
+ kind: 'step',
799
+ step_tag: step.tag,
800
+ step_run_id: stepRunId,
801
+ step_attempt: 1,
802
+ process_id: `${this.state.feature_id}:${step.tag}`,
803
+ parent_process_id: this.state.feature_id,
804
+ }, parent);
805
+ return (0, context_1.runWithExecutionContext)(context, () => this.executeStepInContext(step));
806
+ }
807
+ async executeStepInContext(step) {
714
808
  const startTime = Date.now();
715
809
  debugLog('[FeatureExecutor] executeStep ENTRY', {
716
810
  feature_id: this.state.feature_id,
@@ -719,12 +813,14 @@ class FeatureExecutor {
719
813
  });
720
814
  let resolvedInput = {};
721
815
  try {
816
+ this.trace('step.input.supplied', { step_tag: step.tag, step_type: step.type, payload: step.input || {} });
722
817
  // Resolve input with data references and all $ operators
723
818
  resolvedInput = (await this.resolveInput(step.input || {}));
819
+ this.trace('step.input.resolved', { step_tag: step.tag, step_type: step.type, payload: resolvedInput });
724
820
  debugLog('[FeatureExecutor] executeStep input', {
725
821
  feature_id: this.state.feature_id,
726
822
  step_tag: step.tag,
727
- input: resolvedInput,
823
+ input: sanitizeForLog(resolvedInput),
728
824
  });
729
825
  let output;
730
826
  // Route steps to dedicated services; only action and notification use ProcessorService.
@@ -751,6 +847,9 @@ class FeatureExecutor {
751
847
  case types_1.FeatureStepType.VECTOR:
752
848
  output = await this.executeVectorStep(step, resolvedInput);
753
849
  break;
850
+ case types_1.FeatureStepType.FUNCTION:
851
+ output = await this.executeFunctionStep(step, resolvedInput);
852
+ break;
754
853
  case types_1.FeatureStepType.QUOTA:
755
854
  output = await this.executeQuotaStep(step, resolvedInput);
756
855
  break;
@@ -792,15 +891,16 @@ class FeatureExecutor {
792
891
  }
793
892
  }
794
893
  const duration = Date.now() - startTime;
894
+ this.trace('step.response.received', { step_tag: step.tag, step_type: step.type, payload: output, duration_ms: duration });
795
895
  debugLog('[FeatureExecutor] executeStep output', {
796
896
  feature_id: this.state.feature_id,
797
897
  step_tag: step.tag,
798
- output,
898
+ output: sanitizeForLog(output),
799
899
  durationMs: duration,
800
900
  });
801
901
  return {
802
902
  success: true,
803
- input: resolvedInput,
903
+ input: sanitizeForLog(resolvedInput),
804
904
  output,
805
905
  duration,
806
906
  };
@@ -812,7 +912,14 @@ class FeatureExecutor {
812
912
  const stepErrorMsg = details.reason != null
813
913
  ? (details.status != null ? `${details.reason} (HTTP ${details.status})` : details.reason)
814
914
  : (details.status != null ? `${details.message} (HTTP ${details.status})` : details.message);
815
- debugLog('[FeatureExecutor] executeStep FAILED', Object.assign(Object.assign({ feature_id: this.state.feature_id, step_tag: step.tag, step_type: step.type, durationMs: duration, input: resolvedInput, error: stepErrorMsg }, (details.status != null && { http_status: details.status })), (details.responseBody != null && { response_body: details.responseBody })));
915
+ debugLog('[FeatureExecutor] executeStep FAILED', Object.assign(Object.assign({ feature_id: this.state.feature_id, step_tag: step.tag, step_type: step.type, durationMs: duration, input: resolvedInput, error: stepErrorMsg }, (details.status != null && { http_status: details.status })), (details.responseBody != null && { response_body: sanitizeForLog(details.responseBody) })));
916
+ this.trace('step.failed', {
917
+ step_tag: step.tag,
918
+ step_type: step.type,
919
+ payload: resolvedInput,
920
+ error: details,
921
+ duration_ms: duration,
922
+ });
816
923
  return {
817
924
  success: false,
818
925
  input: resolvedInput,
@@ -829,9 +936,10 @@ class FeatureExecutor {
829
936
  debugLog('[FeatureExecutor] executeActionStep', { feature_id: this.state.feature_id, step_tag: step.tag, app: step.app });
830
937
  // Step that only returns (no component call): no app → passthrough, output = resolved input
831
938
  if (!step.app) {
939
+ this.trace('action.passthrough', { step_tag: step.tag, payload: input });
832
940
  return input;
833
941
  }
834
- return this.getProcessorService().processAction({
942
+ const payload = {
835
943
  product: this.state.product,
836
944
  env: this.state.env,
837
945
  app: step.app,
@@ -839,25 +947,30 @@ class FeatureExecutor {
839
947
  input: input,
840
948
  retries: ((_a = step.options) === null || _a === void 0 ? void 0 : _a.retries) || 0,
841
949
  cache: (_b = step.options) === null || _b === void 0 ? void 0 : _b.cache,
950
+ session: this.inheritedSession,
842
951
  preloadedBootstrap: this.stepBootstrapCache.get(step.tag),
843
- });
952
+ };
953
+ this.trace('action.request.sent', { step_tag: step.tag, payload });
954
+ return this.getProcessorService().processAction(payload);
844
955
  }
845
956
  /**
846
957
  * Execute a database step: use query/insert/update/delete/upsert for DB operations, execute() for named actions.
847
958
  */
848
959
  async executeDatabaseStep(step, input) {
960
+ var _a;
849
961
  const database = step.database;
850
962
  const product = this.state.product;
851
963
  const env = this.state.env;
852
964
  const event = (step.event || '').toLowerCase();
853
965
  const db = this.getDatabaseService();
854
- const opts = Object.assign({ product, env, database }, input);
966
+ const opts = Object.assign(Object.assign({ product, env, database }, input), { session: (_a = input.session) !== null && _a !== void 0 ? _a : this.inheritedSession });
855
967
  debugLog('[FeatureExecutor] executeDatabaseStep', {
856
968
  feature_id: this.state.feature_id,
857
969
  step_tag: step.tag,
858
970
  database,
859
971
  event,
860
972
  });
973
+ this.trace('database.request.sent', { step_tag: step.tag, event, payload: opts });
861
974
  switch (event) {
862
975
  case 'query':
863
976
  return db.query(opts);
@@ -876,6 +989,7 @@ class FeatureExecutor {
876
989
  database,
877
990
  action: step.event,
878
991
  input: input,
992
+ session: this.inheritedSession,
879
993
  });
880
994
  }
881
995
  }
@@ -886,14 +1000,17 @@ class FeatureExecutor {
886
1000
  var _a;
887
1001
  const event = `${step.notification}:${step.event}`;
888
1002
  debugLog('[FeatureExecutor] executeNotificationStep', { feature_id: this.state.feature_id, step_tag: step.tag, event });
889
- return this.getProcessorService().processNotification({
1003
+ const payload = {
890
1004
  product: this.state.product,
891
1005
  env: this.state.env,
892
1006
  event,
893
1007
  input: input,
894
1008
  retries: ((_a = step.options) === null || _a === void 0 ? void 0 : _a.retries) || 0,
1009
+ session: this.inheritedSession,
895
1010
  preloadedBootstrap: this.stepBootstrapCache.get(step.tag),
896
- });
1011
+ };
1012
+ this.trace('notification.request.sent', { step_tag: step.tag, payload });
1013
+ return this.getProcessorService().processNotification(payload);
897
1014
  }
898
1015
  /**
899
1016
  * Execute a storage step using StorageService (upload, download, or delete by input shape).
@@ -903,9 +1020,10 @@ class FeatureExecutor {
903
1020
  const product = this.state.product;
904
1021
  const env = this.state.env;
905
1022
  const storage = step.storage;
906
- const opts = { product, env, storage };
1023
+ const opts = { product, env, storage, session: this.inheritedSession };
907
1024
  debugLog('[FeatureExecutor] executeStorageStep', { feature_id: this.state.feature_id, step_tag: step.tag, storage, event: step.event });
908
1025
  const inp = input;
1026
+ this.trace('storage.request.sent', { step_tag: step.tag, event: step.event, payload: Object.assign(Object.assign({}, opts), inp) });
909
1027
  if (inp.buffer != null && inp.fileName != null) {
910
1028
  return this.getStorageService().upload(Object.assign(Object.assign({}, opts), { fileName: String(inp.fileName), buffer: inp.buffer, mimeType: inp.mimeType != null ? String(inp.mimeType) : undefined }));
911
1029
  }
@@ -922,12 +1040,15 @@ class FeatureExecutor {
922
1040
  const event = `${step.broker}:${step.event}`;
923
1041
  const message = (input && input.message != null) ? input.message : (input || {});
924
1042
  debugLog('[FeatureExecutor] executeProduceStep', { feature_id: this.state.feature_id, step_tag: step.tag, event });
925
- return this.getBrokersService().publish({
1043
+ const payload = {
926
1044
  product: this.state.product,
927
1045
  env: this.state.env,
928
1046
  event,
929
1047
  message: message,
930
- });
1048
+ session: this.inheritedBrokerSession(),
1049
+ };
1050
+ this.trace('event.request.sent', { step_tag: step.tag, payload });
1051
+ return this.getBrokersService().publish(payload);
931
1052
  }
932
1053
  /**
933
1054
  * Execute a graph step
@@ -938,6 +1059,7 @@ class FeatureExecutor {
938
1059
  const graphTag = step.graph;
939
1060
  const action = step.event;
940
1061
  debugLog('[FeatureExecutor] executeGraphStep', { feature_id: this.state.feature_id, step_tag: step.tag, graph: graphTag, action });
1062
+ this.trace('graph.request.preparing', { step_tag: step.tag, action, graph: graphTag, payload: input });
941
1063
  this.graphConnectionUsed = true;
942
1064
  await this.getGraphService().connect({
943
1065
  env: this.state.env,
@@ -951,6 +1073,7 @@ class FeatureExecutor {
951
1073
  const result = await this.getGraphService().createNode({
952
1074
  labels: input.labels,
953
1075
  properties: input.properties,
1076
+ session: this.inheritedSession,
954
1077
  });
955
1078
  return result;
956
1079
  }
@@ -958,6 +1081,7 @@ class FeatureExecutor {
958
1081
  const result = await this.getGraphService().updateNode({
959
1082
  id: input.id,
960
1083
  properties: input.properties,
1084
+ session: this.inheritedSession,
961
1085
  });
962
1086
  return result;
963
1087
  }
@@ -965,6 +1089,7 @@ class FeatureExecutor {
965
1089
  const result = await this.getGraphService().deleteNode({
966
1090
  id: input.id,
967
1091
  detach: input.detach,
1092
+ session: this.inheritedSession,
968
1093
  });
969
1094
  return result;
970
1095
  }
@@ -974,17 +1099,19 @@ class FeatureExecutor {
974
1099
  endNodeId: input.endNodeId || input.to,
975
1100
  type: input.type,
976
1101
  properties: input.properties,
1102
+ session: this.inheritedSession,
977
1103
  });
978
1104
  return result;
979
1105
  }
980
1106
  case 'deleteRelationship': {
981
1107
  const result = await this.getGraphService().deleteRelationship({
982
1108
  id: input.id,
1109
+ session: this.inheritedSession,
983
1110
  });
984
1111
  return result;
985
1112
  }
986
1113
  case 'query': {
987
- const result = await this.getGraphService().query(input.query, input.params);
1114
+ const result = await this.getGraphService().query(input.query, input.params, undefined, this.inheritedSession);
988
1115
  return result;
989
1116
  }
990
1117
  case 'findNodes': {
@@ -993,6 +1120,7 @@ class FeatureExecutor {
993
1120
  where: input.where,
994
1121
  limit: input.limit,
995
1122
  skip: input.skip,
1123
+ session: this.inheritedSession,
996
1124
  });
997
1125
  return result;
998
1126
  }
@@ -1002,6 +1130,7 @@ class FeatureExecutor {
1002
1130
  relationshipTypes: input.relationshipTypes,
1003
1131
  direction: input.direction,
1004
1132
  maxDepth: input.maxDepth,
1133
+ session: this.inheritedSession,
1005
1134
  });
1006
1135
  return result;
1007
1136
  }
@@ -1013,6 +1142,7 @@ class FeatureExecutor {
1013
1142
  graph: graphTag,
1014
1143
  action,
1015
1144
  input,
1145
+ session: this.inheritedSession,
1016
1146
  });
1017
1147
  return result;
1018
1148
  }
@@ -1026,7 +1156,7 @@ class FeatureExecutor {
1026
1156
  * Execute a vector step (query, upsert, deleteVectors, or custom action via execute).
1027
1157
  */
1028
1158
  async executeVectorStep(step, input) {
1029
- var _a;
1159
+ var _a, _b, _c, _d, _e;
1030
1160
  const vectorTag = step.vector;
1031
1161
  if (!vectorTag) {
1032
1162
  throw new Error(`Vector step "${step.tag}" has no vector tag`);
@@ -1041,21 +1171,22 @@ class FeatureExecutor {
1041
1171
  vector: vectorTag,
1042
1172
  event,
1043
1173
  });
1174
+ this.trace('vector.request.preparing', { step_tag: step.tag, event, vector: vectorTag, payload: input });
1044
1175
  switch (event) {
1045
1176
  case 'query': {
1046
- const result = await svc.query(Object.assign({ product,
1047
- env, tag: vectorTag }, input));
1177
+ const result = await svc.query(Object.assign(Object.assign({ product,
1178
+ env, tag: vectorTag }, input), { session: (_a = input.session) !== null && _a !== void 0 ? _a : this.inheritedSession }));
1048
1179
  return result;
1049
1180
  }
1050
1181
  case 'upsert': {
1051
- const result = await svc.upsert(Object.assign({ product,
1052
- env, tag: vectorTag }, input));
1182
+ const result = await svc.upsert(Object.assign(Object.assign({ product,
1183
+ env, tag: vectorTag }, input), { session: (_b = input.session) !== null && _b !== void 0 ? _b : this.inheritedSession }));
1053
1184
  return result;
1054
1185
  }
1055
1186
  case 'delete':
1056
1187
  case 'deletevectors': {
1057
- const result = await svc.deleteVectors(Object.assign({ product,
1058
- env, tag: vectorTag }, input));
1188
+ const result = await svc.deleteVectors(Object.assign(Object.assign({ product,
1189
+ env, tag: vectorTag }, input), { session: (_c = input.session) !== null && _c !== void 0 ? _c : this.inheritedSession }));
1059
1190
  return result;
1060
1191
  }
1061
1192
  default: {
@@ -1066,9 +1197,10 @@ class FeatureExecutor {
1066
1197
  vector: vectorTag,
1067
1198
  action: step.event,
1068
1199
  input: input,
1200
+ session: this.inheritedSession,
1069
1201
  });
1070
- const op = (_a = resolved === null || resolved === void 0 ? void 0 : resolved._operation) !== null && _a !== void 0 ? _a : resolved === null || resolved === void 0 ? void 0 : resolved.operation;
1071
- const opts = Object.assign({ product, env, tag: vectorTag }, resolved);
1202
+ const op = (_d = resolved === null || resolved === void 0 ? void 0 : resolved._operation) !== null && _d !== void 0 ? _d : resolved === null || resolved === void 0 ? void 0 : resolved.operation;
1203
+ const opts = Object.assign(Object.assign({ product, env, tag: vectorTag }, resolved), { session: (_e = resolved.session) !== null && _e !== void 0 ? _e : this.inheritedSession });
1072
1204
  if (op === 'query' || !op) {
1073
1205
  return svc.query(opts);
1074
1206
  }
@@ -1082,6 +1214,54 @@ class FeatureExecutor {
1082
1214
  }
1083
1215
  }
1084
1216
  }
1217
+ /** Execute a portable application function through a local handler or signed HTTP transport. */
1218
+ async executeFunctionStep(step, input) {
1219
+ var _a, _b, _c;
1220
+ const reference = step.function_ref;
1221
+ if (!reference) {
1222
+ throw new Error(`Function step "${step.tag}" has no function_ref. Recompile the feature with ctx.functions.`);
1223
+ }
1224
+ const invocationId = (0, crypto_1.randomUUID)();
1225
+ this.functionInvocations.set(step.tag, {
1226
+ function_invocation_id: invocationId,
1227
+ function_namespace: reference.namespace,
1228
+ function_operation: reference.operation,
1229
+ function_version: reference.version,
1230
+ });
1231
+ const deadlineAt = reference.timeout_ms ? Date.now() + reference.timeout_ms : undefined;
1232
+ this.trace('function.resolution.started', {
1233
+ step_tag: step.tag,
1234
+ invocation_id: invocationId,
1235
+ function: `${reference.namespace}.${reference.operation}@${reference.version}`,
1236
+ transports: reference.transports.map(transport => transport.type),
1237
+ payload: input,
1238
+ });
1239
+ const output = await functions_1.portableFunctions.invoke(reference, input, {
1240
+ product: this.state.product,
1241
+ env: this.state.env,
1242
+ workspace_id: this.config.workspace_id,
1243
+ feature_id: this.state.feature_id,
1244
+ feature_tag: this.state.feature_tag,
1245
+ feature_run_id: this.state.feature_id,
1246
+ step_tag: step.tag,
1247
+ step_run_id: (_a = (0, context_1.currentExecutionContext)()) === null || _a === void 0 ? void 0 : _a.step_run_id,
1248
+ invocation_id: invocationId,
1249
+ trace_id: (_b = (0, context_1.currentExecutionContext)()) === null || _b === void 0 ? void 0 : _b.trace_id,
1250
+ parent_span_id: (_c = (0, context_1.currentExecutionContext)()) === null || _c === void 0 ? void 0 : _c.span_id,
1251
+ session: this.inheritedSession,
1252
+ session_id: this.sessionLogFields.session_id,
1253
+ session_tag: this.sessionLogFields.session_tag,
1254
+ session_user_id: this.sessionLogFields.session_user_id,
1255
+ deadline_at: deadlineAt,
1256
+ }, this.config.access_key);
1257
+ this.trace('function.response.received', {
1258
+ step_tag: step.tag,
1259
+ invocation_id: invocationId,
1260
+ function: `${reference.namespace}.${reference.operation}@${reference.version}`,
1261
+ payload: output,
1262
+ });
1263
+ return output;
1264
+ }
1085
1265
  /**
1086
1266
  * Execute a quota step
1087
1267
  */
@@ -1092,6 +1272,7 @@ class FeatureExecutor {
1092
1272
  env: this.state.env,
1093
1273
  tag: step.quota,
1094
1274
  input,
1275
+ session: this.inheritedSession,
1095
1276
  });
1096
1277
  }
1097
1278
  /**
@@ -1104,6 +1285,7 @@ class FeatureExecutor {
1104
1285
  env: this.state.env,
1105
1286
  tag: step.fallback,
1106
1287
  input,
1288
+ session: this.inheritedSession,
1107
1289
  });
1108
1290
  }
1109
1291
  /**
@@ -1130,7 +1312,8 @@ class FeatureExecutor {
1130
1312
  env: this.state.env,
1131
1313
  tag: childTag,
1132
1314
  input,
1133
- }, this._privateKey);
1315
+ session: this.inheritedSession,
1316
+ }, this._privateKey, this.sessionLogFields, this.productBuilder);
1134
1317
  const result = await childExecutor.execute();
1135
1318
  debugLog('[FeatureExecutor] executeChildWorkflowStep result', {
1136
1319
  feature_id: this.state.feature_id,
@@ -1405,12 +1588,13 @@ class FeatureExecutor {
1405
1588
  * Execute a rollback handler
1406
1589
  */
1407
1590
  async executeRollbackHandler(rollback, stepOutput) {
1408
- var _a, _b, _c, _d, _e, _f, _g;
1591
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
1409
1592
  if (!rollback)
1410
1593
  return;
1411
1594
  debugLog('[FeatureExecutor] executeRollbackHandler', { feature_id: this.state.feature_id, rollback_type: rollback.type });
1412
1595
  // Resolve rollback input, including step output references
1413
1596
  const resolvedInput = await this.resolveInput(rollback.input || {}, stepOutput);
1597
+ this.trace('rollback.input.resolved', { rollback_type: rollback.type, payload: resolvedInput });
1414
1598
  switch (rollback.type) {
1415
1599
  case types_1.FeatureStepType.ACTION:
1416
1600
  await this.getProcessorService().processAction({
@@ -1420,6 +1604,7 @@ class FeatureExecutor {
1420
1604
  action: rollback.event,
1421
1605
  input: resolvedInput,
1422
1606
  retries: (_a = rollback.retries) !== null && _a !== void 0 ? _a : 0,
1607
+ session: this.inheritedSession,
1423
1608
  });
1424
1609
  break;
1425
1610
  case types_1.FeatureStepType.DATABASE_ACTION: {
@@ -1428,7 +1613,7 @@ class FeatureExecutor {
1428
1613
  const env = this.state.env;
1429
1614
  const database = rollback.database;
1430
1615
  const event = ((_b = rollback.event) !== null && _b !== void 0 ? _b : '').toLowerCase();
1431
- const opts = Object.assign({ product, env, database }, resolvedInput);
1616
+ const opts = Object.assign(Object.assign({ product, env, database }, resolvedInput), { session: (_c = resolvedInput.session) !== null && _c !== void 0 ? _c : this.inheritedSession });
1432
1617
  if (event === 'query')
1433
1618
  await db.query(opts);
1434
1619
  else if (event === 'insert')
@@ -1446,6 +1631,7 @@ class FeatureExecutor {
1446
1631
  database,
1447
1632
  action: rollback.event,
1448
1633
  input: resolvedInput,
1634
+ session: this.inheritedSession,
1449
1635
  });
1450
1636
  break;
1451
1637
  }
@@ -1456,14 +1642,15 @@ class FeatureExecutor {
1456
1642
  env: this.state.env,
1457
1643
  event: notifEvent,
1458
1644
  input: resolvedInput,
1459
- retries: (_c = rollback.retries) !== null && _c !== void 0 ? _c : 0,
1645
+ retries: (_d = rollback.retries) !== null && _d !== void 0 ? _d : 0,
1646
+ session: this.inheritedSession,
1460
1647
  });
1461
1648
  break;
1462
1649
  case types_1.FeatureStepType.STORAGE: {
1463
1650
  const product = this.state.product;
1464
1651
  const env = this.state.env;
1465
1652
  const storage = rollback.storage;
1466
- const opts = { product, env, storage };
1653
+ const opts = { product, env, storage, session: this.inheritedSession };
1467
1654
  const inp = resolvedInput;
1468
1655
  if (inp.buffer != null && inp.fileName != null) {
1469
1656
  await this.getStorageService().upload(Object.assign(Object.assign({}, opts), { fileName: String(inp.fileName), buffer: inp.buffer, mimeType: inp.mimeType != null ? String(inp.mimeType) : undefined }));
@@ -1472,7 +1659,7 @@ class FeatureExecutor {
1472
1659
  await this.getStorageService().delete(Object.assign(Object.assign({}, opts), { fileName: String(inp.file_key) }));
1473
1660
  }
1474
1661
  else {
1475
- await this.getStorageService().download(Object.assign(Object.assign({}, opts), { fileName: String((_e = (_d = inp.fileName) !== null && _d !== void 0 ? _d : inp.file_key) !== null && _e !== void 0 ? _e : '') }));
1662
+ await this.getStorageService().download(Object.assign(Object.assign({}, opts), { fileName: String((_f = (_e = inp.fileName) !== null && _e !== void 0 ? _e : inp.file_key) !== null && _f !== void 0 ? _f : '') }));
1476
1663
  }
1477
1664
  break;
1478
1665
  }
@@ -1481,16 +1668,16 @@ class FeatureExecutor {
1481
1668
  const env = this.state.env;
1482
1669
  const vectorTag = rollback.vector;
1483
1670
  const inp = resolvedInput;
1484
- const event = ((_f = rollback.event) !== null && _f !== void 0 ? _f : '').toLowerCase();
1671
+ const event = ((_g = rollback.event) !== null && _g !== void 0 ? _g : '').toLowerCase();
1485
1672
  const svc = this.getVectorService();
1486
1673
  if (event === 'query') {
1487
- await svc.query(Object.assign({ product, env, tag: vectorTag }, inp));
1674
+ await svc.query(Object.assign(Object.assign({ product, env, tag: vectorTag }, inp), { session: (_h = inp.session) !== null && _h !== void 0 ? _h : this.inheritedSession }));
1488
1675
  }
1489
1676
  else if (event === 'upsert') {
1490
- await svc.upsert(Object.assign({ product, env, tag: vectorTag }, inp));
1677
+ await svc.upsert(Object.assign(Object.assign({ product, env, tag: vectorTag }, inp), { session: (_j = inp.session) !== null && _j !== void 0 ? _j : this.inheritedSession }));
1491
1678
  }
1492
1679
  else if (event === 'delete' || event === 'deletevectors') {
1493
- await svc.deleteVectors(Object.assign({ product, env, tag: vectorTag }, inp));
1680
+ await svc.deleteVectors(Object.assign(Object.assign({ product, env, tag: vectorTag }, inp), { session: (_k = inp.session) !== null && _k !== void 0 ? _k : this.inheritedSession }));
1494
1681
  }
1495
1682
  else {
1496
1683
  const resolved = await svc.actions.resolve({
@@ -1499,9 +1686,10 @@ class FeatureExecutor {
1499
1686
  vector: vectorTag,
1500
1687
  action: rollback.event,
1501
1688
  input: inp,
1689
+ session: this.inheritedSession,
1502
1690
  });
1503
- const op = (_g = resolved === null || resolved === void 0 ? void 0 : resolved._operation) !== null && _g !== void 0 ? _g : resolved === null || resolved === void 0 ? void 0 : resolved.operation;
1504
- const opts = Object.assign({ product, env, tag: vectorTag }, resolved);
1691
+ const op = (_l = resolved === null || resolved === void 0 ? void 0 : resolved._operation) !== null && _l !== void 0 ? _l : resolved === null || resolved === void 0 ? void 0 : resolved.operation;
1692
+ const opts = Object.assign(Object.assign({ product, env, tag: vectorTag }, resolved), { session: (_m = resolved.session) !== null && _m !== void 0 ? _m : this.inheritedSession });
1505
1693
  if (op === 'upsert')
1506
1694
  await svc.upsert(opts);
1507
1695
  else if (op === 'delete' || op === 'deleteVectors')
@@ -1519,6 +1707,7 @@ class FeatureExecutor {
1519
1707
  env: this.state.env,
1520
1708
  event: pubEvent,
1521
1709
  input: resolvedInput,
1710
+ session: this.inheritedSession,
1522
1711
  });
1523
1712
  break;
1524
1713
  }
@@ -1532,7 +1721,10 @@ class FeatureExecutor {
1532
1721
  const resolvedValues = await Promise.all(entries.map(([, value]) => this.resolveValue(value, stepOutput)));
1533
1722
  const resolved = {};
1534
1723
  entries.forEach(([key], i) => {
1535
- resolved[key] = resolvedValues[i];
1724
+ // An absent optional input is absence, not an unresolved operator string
1725
+ // and not an explicit null. Omitting it preserves defaulting semantics.
1726
+ if (resolvedValues[i] !== undefined)
1727
+ resolved[key] = resolvedValues[i];
1536
1728
  });
1537
1729
  return resolved;
1538
1730
  }
@@ -1566,14 +1758,23 @@ class FeatureExecutor {
1566
1758
  * $Uppercase, $Lowercase, $Dateformat, $Replace, $Filter, $Find, $Size, $Length
1567
1759
  */
1568
1760
  async resolveStringValue(value, stepOutput) {
1569
- var _a;
1761
+ var _a, _b;
1570
1762
  const v = value.trim();
1571
1763
  // --- Data references (exact match) ---
1572
1764
  if (v === '$Now' || v === '$Now()')
1573
1765
  return Date.now();
1574
1766
  const inputMatch = v.match(/^\$Input\{(.+)\}$/);
1575
- if (inputMatch)
1576
- return this.getNestedValue(this.state.input, inputMatch[1]);
1767
+ if (inputMatch) {
1768
+ const path = inputMatch[1];
1769
+ const resolved = this.getNestedValue(this.state.input, path);
1770
+ if (resolved !== undefined)
1771
+ return resolved;
1772
+ const root = path.split('.')[0];
1773
+ const inputDefinition = (_a = this.feature.input) === null || _a === void 0 ? void 0 : _a[root];
1774
+ if (inputDefinition === null || inputDefinition === void 0 ? void 0 : inputDefinition.required)
1775
+ throw new UnresolvedInputReferenceError(v, true);
1776
+ return undefined;
1777
+ }
1577
1778
  const sequenceMatch = v.match(/^\$Sequence\{main\}\{([^}]+)\}\{(.+)\}$/);
1578
1779
  if (sequenceMatch) {
1579
1780
  const stepData = this.state.steps[sequenceMatch[1]];
@@ -1634,6 +1835,10 @@ class FeatureExecutor {
1634
1835
  const separator = concatMatch[2];
1635
1836
  const parts = arrContent.split(',').map((s) => this.unquote(s.trim()));
1636
1837
  const resolvedParts = await Promise.all(parts.map((p) => this.resolveValue(p, stepOutput)));
1838
+ const missingIndex = resolvedParts.findIndex((part) => part === undefined);
1839
+ if (missingIndex >= 0) {
1840
+ throw new UnresolvedInputReferenceError(parts[missingIndex], false);
1841
+ }
1637
1842
  return resolvedParts.map((x) => (x == null ? '' : String(x))).join(separator);
1638
1843
  }
1639
1844
  // $Substring(str, start, end)
@@ -1688,7 +1893,7 @@ class FeatureExecutor {
1688
1893
  const val = await this.resolveValue(this.unquote(pickMatch[1]), stepOutput);
1689
1894
  const idx = Number(pickMatch[2]);
1690
1895
  if (typeof val === 'string' || Array.isArray(val))
1691
- return (_a = val[idx]) !== null && _a !== void 0 ? _a : '';
1896
+ return (_b = val[idx]) !== null && _b !== void 0 ? _b : '';
1692
1897
  return '';
1693
1898
  }
1694
1899
  // $Filter(array, "op", value)
@@ -1835,6 +2040,24 @@ class FeatureExecutor {
1835
2040
  storage_tag: step.storage,
1836
2041
  });
1837
2042
  }
2043
+ else if (step.type === types_1.FeatureStepType.DATABASE_ACTION && step.database) {
2044
+ workflowSteps.push({ step_tag: step.tag, type: 'database', database_tag: step.database });
2045
+ }
2046
+ else if ((step.type === types_1.FeatureStepType.PUBLISH || step.type === types_1.FeatureStepType.PRODUCE) && step.broker) {
2047
+ workflowSteps.push({ step_tag: step.tag, type: 'broker', broker_tag: step.broker, topic_tag: step.event });
2048
+ }
2049
+ else if (step.type === types_1.FeatureStepType.GRAPH && step.graph) {
2050
+ workflowSteps.push({ step_tag: step.tag, type: 'graph', graph_tag: step.graph });
2051
+ }
2052
+ else if (step.type === types_1.FeatureStepType.VECTOR && step.vector) {
2053
+ workflowSteps.push({ step_tag: step.tag, type: 'vector', vector_tag: step.vector });
2054
+ }
2055
+ else if (step.type === types_1.FeatureStepType.QUOTA && step.quota) {
2056
+ workflowSteps.push({ step_tag: step.tag, type: 'quota', component_tag: step.quota });
2057
+ }
2058
+ else if (step.type === types_1.FeatureStepType.FALLBACK && step.fallback) {
2059
+ workflowSteps.push({ step_tag: step.tag, type: 'fallback', component_tag: step.fallback });
2060
+ }
1838
2061
  }
1839
2062
  if (workflowSteps.length > 0) {
1840
2063
  stepLog('Bootstrap feature API call (single request)', {
@@ -1848,6 +2071,7 @@ class FeatureExecutor {
1848
2071
  env_slug: env,
1849
2072
  steps: workflowSteps,
1850
2073
  });
2074
+ this.trace('bootstrap.response.fetched', { requested_steps: workflowSteps, payload: bootstraps });
1851
2075
  stepLog('Bootstrap feature API response', {
1852
2076
  received_keys: Object.keys(bootstraps),
1853
2077
  keys_count: Object.keys(bootstraps).length,
@@ -1858,6 +2082,9 @@ class FeatureExecutor {
1858
2082
  }
1859
2083
  }
1860
2084
  }
2085
+ // All primitive metadata above is fetched by the single feature bootstrap call.
2086
+ return;
2087
+ // Legacy fallback retained below for older servers; unreachable with the unified bootstrap.
1861
2088
  // Pre-fetch message brokers for produce steps so runBrokerPublish has broker/topic in cache
1862
2089
  const brokerTags = new Set();
1863
2090
  for (const step of steps) {
@@ -1871,6 +2098,7 @@ class FeatureExecutor {
1871
2098
  var _a;
1872
2099
  try {
1873
2100
  const broker = await this.productBuilder.fetchMessageBroker(tag);
2101
+ this.trace('resource.fetched', { resource_type: 'event', resource_tag: tag, payload: broker });
1874
2102
  return { tag, found: !!broker };
1875
2103
  }
1876
2104
  catch (e) {
@@ -1892,6 +2120,7 @@ class FeatureExecutor {
1892
2120
  var _a;
1893
2121
  try {
1894
2122
  const db = await this.productBuilder.fetchDatabase(tag);
2123
+ this.trace('resource.fetched', { resource_type: 'database', resource_tag: tag, payload: db });
1895
2124
  return { tag, found: !!db };
1896
2125
  }
1897
2126
  catch (e) {
@@ -1913,6 +2142,7 @@ class FeatureExecutor {
1913
2142
  var _a;
1914
2143
  try {
1915
2144
  const graph = await this.productBuilder.fetchGraph(tag);
2145
+ this.trace('resource.fetched', { resource_type: 'graph', resource_tag: tag, payload: graph });
1916
2146
  return { tag, found: !!graph };
1917
2147
  }
1918
2148
  catch (e) {
@@ -1933,6 +2163,7 @@ class FeatureExecutor {
1933
2163
  var _a;
1934
2164
  try {
1935
2165
  const vec = await this.productBuilder.fetchVector(tag);
2166
+ this.trace('resource.fetched', { resource_type: 'vector', resource_tag: tag, payload: vec });
1936
2167
  return { tag, found: !!vec };
1937
2168
  }
1938
2169
  catch (e) {
@@ -1954,6 +2185,7 @@ class FeatureExecutor {
1954
2185
  var _a;
1955
2186
  try {
1956
2187
  const quota = await this.productBuilder.fetchQuota(tag);
2188
+ this.trace('resource.fetched', { resource_type: 'quota', resource_tag: tag, payload: quota });
1957
2189
  return { tag, found: !!quota };
1958
2190
  }
1959
2191
  catch (e) {