@resolveio/server-lib 22.3.199 → 22.3.201

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.
@@ -41,6 +41,8 @@ export interface ResolveIOAICoderV6LaneMemory {
41
41
  export interface ResolveIOAICoderV6WorkflowQaRow {
42
42
  index?: number;
43
43
  workflowId?: string;
44
+ workflowStepId?: string;
45
+ workflowStepIds?: string[];
44
46
  stepId?: string;
45
47
  route?: string;
46
48
  action?: string;
@@ -267,11 +267,111 @@ function normalizeWorkflowStepPosition(step, index, total) {
267
267
  }
268
268
  return 'next';
269
269
  }
270
+ function normalizeJourneyReferenceKey(value) {
271
+ return cleanText(value, 160).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
272
+ }
273
+ function normalizeJourneyRouteKey(value) {
274
+ var raw = cleanText(value, 240);
275
+ if (!raw) {
276
+ return '';
277
+ }
278
+ var route = raw.split(/[?#]/)[0].replace(/\/+$/g, '') || '/';
279
+ return route.toLowerCase();
280
+ }
281
+ function normalizeJourneyActionText(value) {
282
+ return cleanText(value, 1000).toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
283
+ }
270
284
  function buildWorkflowStepId(step, index) {
271
285
  return cleanField(step, ['id', 'step_id', 'key'], 120)
272
286
  || cleanField(step, ['label', 'title', 'visible_cta'], 120).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')
273
287
  || "step-".concat(index + 1);
274
288
  }
289
+ function workflowStepVisibleAction(step) {
290
+ return cleanField(step, ['visible_cta', 'cta', 'label', 'action', 'embedded_hub_action', 'hub_action'], 500);
291
+ }
292
+ function journeySourceVisibleAction(source) {
293
+ return concatFields(source, [
294
+ 'visible_cta',
295
+ 'cta',
296
+ 'label',
297
+ 'action',
298
+ 'browser_steps',
299
+ 'expected_dom_or_data_proof',
300
+ 'proof',
301
+ 'assertion',
302
+ 'expected_result'
303
+ ], 700);
304
+ }
305
+ function actionTextReferencesLabel(actionText, label) {
306
+ var action = normalizeJourneyActionText(actionText);
307
+ var expected = normalizeJourneyActionText(label);
308
+ if (!action || !expected || expected.length < 4) {
309
+ return false;
310
+ }
311
+ if (action === expected || action.includes(expected) || expected.includes(action)) {
312
+ return true;
313
+ }
314
+ var actionTokens = new Set(action.split(/\s+/).filter(function (token) { return token.length > 2; }));
315
+ var expectedTokens = expected.split(/\s+/).filter(function (token) { return token.length > 2; });
316
+ if (!expectedTokens.length) {
317
+ return false;
318
+ }
319
+ var matched = expectedTokens.filter(function (token) { return actionTokens.has(token); }).length;
320
+ return matched >= Math.min(expectedTokens.length, Math.max(2, Math.ceil(expectedTokens.length * 0.67)));
321
+ }
322
+ function collectJourneyReferenceKeys(source) {
323
+ var e_3, _a;
324
+ if (!source || typeof source !== 'object') {
325
+ return [];
326
+ }
327
+ var values = [];
328
+ try {
329
+ for (var _b = __values([
330
+ 'workflow_step_id',
331
+ 'workflowStepId',
332
+ 'workflow_step_ids',
333
+ 'workflowStepIds',
334
+ 'covers_workflow_step_id',
335
+ 'coversWorkflowStepId',
336
+ 'covers_workflow_step_ids',
337
+ 'coversWorkflowStepIds',
338
+ 'north_star_step_id',
339
+ 'northStarStepId',
340
+ 'step_ref',
341
+ 'stepRef'
342
+ ]), _c = _b.next(); !_c.done; _c = _b.next()) {
343
+ var key = _c.value;
344
+ var value = source[key];
345
+ if (Array.isArray(value)) {
346
+ values.push.apply(values, __spreadArray([], __read(value), false));
347
+ }
348
+ else if (value !== undefined && value !== null) {
349
+ values.push(value);
350
+ }
351
+ }
352
+ }
353
+ catch (e_3_1) { e_3 = { error: e_3_1 }; }
354
+ finally {
355
+ try {
356
+ if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
357
+ }
358
+ finally { if (e_3) throw e_3.error; }
359
+ }
360
+ return Array.from(new Set(values.map(normalizeJourneyReferenceKey).filter(Boolean))).slice(0, 20);
361
+ }
362
+ function journeySourceMapsToWorkflowStep(source, step, index) {
363
+ var stepId = normalizeJourneyReferenceKey(buildWorkflowStepId(step, index));
364
+ var refs = collectJourneyReferenceKeys(source);
365
+ if (stepId && refs.includes(stepId)) {
366
+ return true;
367
+ }
368
+ var sourceRoute = normalizeJourneyRouteKey(cleanField(source, ['route', 'screen_route', 'path'], 240));
369
+ var stepRoute = normalizeJourneyRouteKey(cleanField(step, ['route'], 240));
370
+ if (!sourceRoute || !stepRoute || sourceRoute !== stepRoute) {
371
+ return false;
372
+ }
373
+ return actionTextReferencesLabel(journeySourceVisibleAction(source), workflowStepVisibleAction(step));
374
+ }
275
375
  function normalizeWorkflowProofKind(source, fallbackText) {
276
376
  if (fallbackText === void 0) { fallbackText = ''; }
277
377
  var explicit = cleanField(source, ['proof_kind', 'proofKind', 'qa_proof_kind', 'qaProofKind', 'type', 'kind'], 120).toLowerCase().replace(/[\s-]+/g, '_');
@@ -348,7 +448,7 @@ function normalizeWorkflowQaCoverageTag(value) {
348
448
  return aliases[normalized] || normalized;
349
449
  }
350
450
  function appendWorkflowQaCoverageValues(target, value) {
351
- var e_3, _a, e_4, _b;
451
+ var e_4, _a, e_5, _b;
352
452
  if (Array.isArray(value)) {
353
453
  value.forEach(function (entry) { return appendWorkflowQaCoverageValues(target, entry); });
354
454
  return;
@@ -365,12 +465,12 @@ function appendWorkflowQaCoverageValues(target, value) {
365
465
  }
366
466
  }
367
467
  }
368
- catch (e_3_1) { e_3 = { error: e_3_1 }; }
468
+ catch (e_4_1) { e_4 = { error: e_4_1 }; }
369
469
  finally {
370
470
  try {
371
471
  if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
372
472
  }
373
- finally { if (e_3) throw e_3.error; }
473
+ finally { if (e_4) throw e_4.error; }
374
474
  }
375
475
  return;
376
476
  }
@@ -384,16 +484,16 @@ function appendWorkflowQaCoverageValues(target, value) {
384
484
  target.push(normalizeWorkflowQaCoverageTag(entry));
385
485
  }
386
486
  }
387
- catch (e_4_1) { e_4 = { error: e_4_1 }; }
487
+ catch (e_5_1) { e_5 = { error: e_5_1 }; }
388
488
  finally {
389
489
  try {
390
490
  if (_g && !_g.done && (_b = _f.return)) _b.call(_f);
391
491
  }
392
- finally { if (e_4) throw e_4.error; }
492
+ finally { if (e_5) throw e_5.error; }
393
493
  }
394
494
  }
395
495
  function normalizeWorkflowQaCoverageTags(source, options) {
396
- var e_5, _a, e_6, _b;
496
+ var e_6, _a, e_7, _b;
397
497
  if (options === void 0) { options = {}; }
398
498
  var tags = [];
399
499
  try {
@@ -412,12 +512,12 @@ function normalizeWorkflowQaCoverageTags(source, options) {
412
512
  appendWorkflowQaCoverageValues(tags, readField(source, [key]));
413
513
  }
414
514
  }
415
- catch (e_5_1) { e_5 = { error: e_5_1 }; }
515
+ catch (e_6_1) { e_6 = { error: e_6_1 }; }
416
516
  finally {
417
517
  try {
418
518
  if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
419
519
  }
420
- finally { if (e_5) throw e_5.error; }
520
+ finally { if (e_6) throw e_6.error; }
421
521
  }
422
522
  if (options.includeProofKind !== false) {
423
523
  try {
@@ -426,12 +526,12 @@ function normalizeWorkflowQaCoverageTags(source, options) {
426
526
  appendWorkflowQaCoverageValues(tags, readField(source, [key]));
427
527
  }
428
528
  }
429
- catch (e_6_1) { e_6 = { error: e_6_1 }; }
529
+ catch (e_7_1) { e_7 = { error: e_7_1 }; }
430
530
  finally {
431
531
  try {
432
532
  if (_f && !_f.done && (_b = _e.return)) _b.call(_e);
433
533
  }
434
- finally { if (e_6) throw e_6.error; }
534
+ finally { if (e_7) throw e_7.error; }
435
535
  }
436
536
  }
437
537
  return Array.from(new Set(tags.filter(Boolean))).slice(0, 20);
@@ -488,7 +588,14 @@ function buildResolveIOAICoderWorkflowQaRowsFromJourneyContract(input) {
488
588
  assertion: assertionText || undefined,
489
589
  expectedState: expectedState || undefined
490
590
  };
491
- rows.push(__assign(__assign({ index: cleanNumber(readField(assertion, ['index', 'order'])) || index + 1, workflowId: cleanField(assertion, ['workflow_id', 'workflowId'], 160) || workflowId, stepId: cleanField(assertion, ['step_id', 'stepId', 'id'], 160) || "qa-".concat(index + 1) }, rowBase), { expectedOutput: cleanField(assertion, ['expected_output', 'expectedOutput', 'saved_output', 'calculated_output', 'report_output'], 700) || expectedState || assertionText || undefined, proofKind: proofKind, coverageTags: coverageTags, acceptanceGate: 'aiqa_business_assertion', requiredArtifacts: buildWorkflowQaRequiredArtifacts(assertion, proofKind), stateTransition: buildWorkflowQaStateTransition(assertion, rowBase), status: cleanField(assertion, ['status'], 120) || undefined, artifactPaths: cleanList(readField(assertion, ['artifact_paths', 'artifactPaths']), 20, 500) }));
591
+ rows.push(__assign(__assign({ index: cleanNumber(readField(assertion, ['index', 'order'])) || index + 1, workflowId: cleanField(assertion, ['workflow_id', 'workflowId'], 160) || workflowId, workflowStepId: cleanField(assertion, [
592
+ 'workflow_step_id',
593
+ 'workflowStepId',
594
+ 'covers_workflow_step_id',
595
+ 'coversWorkflowStepId',
596
+ 'north_star_step_id',
597
+ 'northStarStepId'
598
+ ], 160) || undefined, workflowStepIds: collectJourneyReferenceKeys(assertion), stepId: cleanField(assertion, ['step_id', 'stepId', 'id'], 160) || "qa-".concat(index + 1) }, rowBase), { expectedOutput: cleanField(assertion, ['expected_output', 'expectedOutput', 'saved_output', 'calculated_output', 'report_output'], 700) || expectedState || assertionText || undefined, proofKind: proofKind, coverageTags: coverageTags, acceptanceGate: 'aiqa_business_assertion', requiredArtifacts: buildWorkflowQaRequiredArtifacts(assertion, proofKind), stateTransition: buildWorkflowQaStateTransition(assertion, rowBase), status: cleanField(assertion, ['status'], 120) || undefined, artifactPaths: cleanList(readField(assertion, ['artifact_paths', 'artifactPaths']), 20, 500) }));
492
599
  });
493
600
  if (rows.length) {
494
601
  return rows.slice(0, 80);
@@ -504,13 +611,13 @@ function buildResolveIOAICoderWorkflowQaRowsFromJourneyContract(input) {
504
611
  assertion: cleanField(step, ['success_confirmation', 'expected_state_transition', 'final_result'], 700) || undefined,
505
612
  expectedState: cleanField(step, ['expected_state_transition', 'success_confirmation', 'final_result'], 500) || undefined
506
613
  };
507
- rows.push(__assign(__assign({ index: index + 1, workflowId: workflowId, stepId: buildWorkflowStepId(step, index) }, rowBase), { expectedOutput: cleanField(step, ['final_result', 'saved_exported_reported_result', 'completion_result', 'expected_state_transition'], 700) || rowBase.expectedState || rowBase.assertion, proofKind: proofKind, coverageTags: Array.from(new Set([proofKind, normalizeWorkflowStepPosition(step, index, workflow.length)].map(normalizeWorkflowQaCoverageTag).filter(Boolean))), acceptanceGate: 'aiqa_business_assertion', requiredArtifacts: buildWorkflowQaRequiredArtifacts(step, proofKind), stateTransition: buildWorkflowQaStateTransition(step, rowBase), status: undefined, artifactPaths: [] }));
614
+ rows.push(__assign(__assign({ index: index + 1, workflowId: workflowId, workflowStepId: buildWorkflowStepId(step, index), workflowStepIds: [buildWorkflowStepId(step, index)], stepId: buildWorkflowStepId(step, index) }, rowBase), { expectedOutput: cleanField(step, ['final_result', 'saved_exported_reported_result', 'completion_result', 'expected_state_transition'], 700) || rowBase.expectedState || rowBase.assertion, proofKind: proofKind, coverageTags: Array.from(new Set([proofKind, normalizeWorkflowStepPosition(step, index, workflow.length)].map(normalizeWorkflowQaCoverageTag).filter(Boolean))), acceptanceGate: 'aiqa_business_assertion', requiredArtifacts: buildWorkflowQaRequiredArtifacts(step, proofKind), stateTransition: buildWorkflowQaStateTransition(step, rowBase), status: undefined, artifactPaths: [] }));
508
615
  void workflow;
509
616
  });
510
617
  return rows.slice(0, 80);
511
618
  }
512
619
  function validateResolveIOAICoderJourneyContract(input, options) {
513
- var e_7, _a, e_8, _b, e_9, _c, e_10, _d;
620
+ var e_8, _a, e_9, _b, e_10, _c, e_11, _d;
514
621
  if (options === void 0) { options = {}; }
515
622
  var pathLabel = cleanText(options.pathLabel || 'docs/APP_JOURNEY_CONTRACT.md', 240) || 'docs/APP_JOURNEY_CONTRACT.md';
516
623
  var issues = [];
@@ -559,12 +666,12 @@ function validateResolveIOAICoderJourneyContract(input, options) {
559
666
  }
560
667
  }
561
668
  }
562
- catch (e_7_1) { e_7 = { error: e_7_1 }; }
669
+ catch (e_8_1) { e_8 = { error: e_8_1 }; }
563
670
  finally {
564
671
  try {
565
672
  if (requiredKeys_1_1 && !requiredKeys_1_1.done && (_a = requiredKeys_1.return)) _a.call(requiredKeys_1);
566
673
  }
567
- finally { if (e_7) throw e_7.error; }
674
+ finally { if (e_8) throw e_8.error; }
568
675
  }
569
676
  try {
570
677
  for (var _e = __values(['app_archetype', 'primary_actor', 'primary_goal']), _f = _e.next(); !_f.done; _f = _e.next()) {
@@ -578,12 +685,12 @@ function validateResolveIOAICoderJourneyContract(input, options) {
578
685
  }
579
686
  }
580
687
  }
581
- catch (e_8_1) { e_8 = { error: e_8_1 }; }
688
+ catch (e_9_1) { e_9 = { error: e_9_1 }; }
582
689
  finally {
583
690
  try {
584
691
  if (_f && !_f.done && (_b = _e.return)) _b.call(_e);
585
692
  }
586
- finally { if (e_8) throw e_8.error; }
693
+ finally { if (e_9) throw e_9.error; }
587
694
  }
588
695
  var firstScreen = contract.first_screen;
589
696
  if (!firstScreen || typeof firstScreen !== 'object' || Array.isArray(firstScreen)) {
@@ -604,12 +711,12 @@ function validateResolveIOAICoderJourneyContract(input, options) {
604
711
  }
605
712
  }
606
713
  }
607
- catch (e_9_1) { e_9 = { error: e_9_1 }; }
714
+ catch (e_10_1) { e_10 = { error: e_10_1 }; }
608
715
  finally {
609
716
  try {
610
717
  if (_h && !_h.done && (_c = _g.return)) _c.call(_g);
611
718
  }
612
- finally { if (e_9) throw e_9.error; }
719
+ finally { if (e_10) throw e_10.error; }
613
720
  }
614
721
  if (route && !looksLikeAbsoluteRoute(route)) {
615
722
  addJourneyIssue(issues, 'relative_first_screen_route', "".concat(pathLabel, ".journey_contract.first_screen.route"), "".concat(pathLabel, ": first_screen.route must be an absolute app route."));
@@ -628,15 +735,15 @@ function validateResolveIOAICoderJourneyContract(input, options) {
628
735
  }
629
736
  }
630
737
  }
631
- catch (e_10_1) { e_10 = { error: e_10_1 }; }
738
+ catch (e_11_1) { e_11 = { error: e_11_1 }; }
632
739
  finally {
633
740
  try {
634
741
  if (_l && !_l.done && (_d = _k.return)) _d.call(_k);
635
742
  }
636
- finally { if (e_10) throw e_10.error; }
743
+ finally { if (e_11) throw e_11.error; }
637
744
  }
638
745
  workflow.forEach(function (step, index) {
639
- var e_11, _a;
746
+ var e_12, _a;
640
747
  var stepPath = "".concat(pathLabel, ".journey_contract.north_star_workflow[").concat(index, "]");
641
748
  if (!step || typeof step !== 'object' || Array.isArray(step)) {
642
749
  addJourneyIssue(issues, 'invalid_workflow_step', stepPath, "".concat(pathLabel, ": north_star_workflow[").concat(index, "] must be an object."));
@@ -674,12 +781,12 @@ function validateResolveIOAICoderJourneyContract(input, options) {
674
781
  }
675
782
  }
676
783
  }
677
- catch (e_11_1) { e_11 = { error: e_11_1 }; }
784
+ catch (e_12_1) { e_12 = { error: e_12_1 }; }
678
785
  finally {
679
786
  try {
680
787
  if (requiredStepFields_1_1 && !requiredStepFields_1_1.done && (_a = requiredStepFields_1.return)) _a.call(requiredStepFields_1);
681
788
  }
682
- finally { if (e_11) throw e_11.error; }
789
+ finally { if (e_12) throw e_12.error; }
683
790
  }
684
791
  if (!route && !embeddedAction) {
685
792
  addJourneyIssue(issues, 'workflow_step_missing_route_or_hub_action', stepPath, "".concat(pathLabel, ": north_star_workflow[").concat(index, "] must specify a route or embedded hub action."));
@@ -706,7 +813,7 @@ function validateResolveIOAICoderJourneyContract(input, options) {
706
813
  addJourneyIssue(issues, 'screen_sequence_too_short', "".concat(pathLabel, ".journey_contract.screen_sequence"), "".concat(pathLabel, ": screen_sequence must map workflow steps to routes, CTAs, data, and next state."));
707
814
  }
708
815
  screenSequence.forEach(function (screen, index) {
709
- var e_12, _a;
816
+ var e_13, _a;
710
817
  var screenPath = "".concat(pathLabel, ".journey_contract.screen_sequence[").concat(index, "]");
711
818
  if (!screen || typeof screen !== 'object' || Array.isArray(screen)) {
712
819
  addJourneyIssue(issues, 'invalid_screen_sequence_row', screenPath, "".concat(pathLabel, ": screen_sequence[").concat(index, "] must be an object."));
@@ -726,12 +833,12 @@ function validateResolveIOAICoderJourneyContract(input, options) {
726
833
  }
727
834
  }
728
835
  }
729
- catch (e_12_1) { e_12 = { error: e_12_1 }; }
836
+ catch (e_13_1) { e_13 = { error: e_13_1 }; }
730
837
  finally {
731
838
  try {
732
839
  if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
733
840
  }
734
- finally { if (e_12) throw e_12.error; }
841
+ finally { if (e_13) throw e_13.error; }
735
842
  }
736
843
  var route = cleanField(screen, ['route'], 240);
737
844
  if (route && !looksLikeAbsoluteRoute(route)) {
@@ -740,6 +847,19 @@ function validateResolveIOAICoderJourneyContract(input, options) {
740
847
  if (isNavigationOnlyAction(screen)) {
741
848
  addJourneyIssue(issues, 'navigation_only_screen_action', screenPath, "".concat(pathLabel, ": screen_sequence[").concat(index, "] CTA appears to navigate without changing workflow state."));
742
849
  }
850
+ if (workflow.length && !workflow.some(function (step, workflowIndex) { return journeySourceMapsToWorkflowStep(screen, step, workflowIndex); })) {
851
+ addJourneyIssue(issues, 'screen_sequence_unmapped_to_workflow', screenPath, "".concat(pathLabel, ": screen_sequence[").concat(index, "] must reference a north_star_workflow step by workflow_step_id or match that step's route and CTA."));
852
+ }
853
+ });
854
+ workflow.forEach(function (step, index) {
855
+ if (!step || typeof step !== 'object' || Array.isArray(step)) {
856
+ return;
857
+ }
858
+ var stepPath = "".concat(pathLabel, ".journey_contract.north_star_workflow[").concat(index, "]");
859
+ var matchingScreens = screenSequence.filter(function (screen) { return journeySourceMapsToWorkflowStep(screen, step, index); });
860
+ if (!matchingScreens.length) {
861
+ addJourneyIssue(issues, 'workflow_step_missing_screen_mapping', stepPath, "".concat(pathLabel, ": north_star_workflow[").concat(index, "] must have a matching screen_sequence row with the same step id or route/CTA mapping."));
862
+ }
743
863
  });
744
864
  var firstScreenRoute = cleanField(firstScreen, ['route'], 240);
745
865
  var hubScreens = screenSequence.filter(function (screen) {
@@ -782,6 +902,7 @@ function validateResolveIOAICoderJourneyContract(input, options) {
782
902
  if (qaAssertions.length < 5) {
783
903
  addJourneyIssue(issues, 'qa_assertions_too_short', "".concat(pathLabel, ".journey_contract.qa_assertions"), "".concat(pathLabel, ": qa_assertions must include login, hub action, workflow completion, sample data, and mobile proof."));
784
904
  }
905
+ var workflowStepsWithQa = new Set();
785
906
  var hasMobileQa = false;
786
907
  var hasRecoveryQa = false;
787
908
  var hasCompletionQa = false;
@@ -789,7 +910,7 @@ function validateResolveIOAICoderJourneyContract(input, options) {
789
910
  var hasLoginQa = false;
790
911
  var hasHubActionQa = false;
791
912
  qaAssertions.forEach(function (assertion, index) {
792
- var e_13, _a;
913
+ var e_14, _a;
793
914
  var qaPath = "".concat(pathLabel, ".journey_contract.qa_assertions[").concat(index, "]");
794
915
  if (!assertion || typeof assertion !== 'object' || Array.isArray(assertion)) {
795
916
  addJourneyIssue(issues, 'invalid_qa_assertion', qaPath, "".concat(pathLabel, ": qa_assertions[").concat(index, "] must be an object."));
@@ -807,12 +928,12 @@ function validateResolveIOAICoderJourneyContract(input, options) {
807
928
  }
808
929
  }
809
930
  }
810
- catch (e_13_1) { e_13 = { error: e_13_1 }; }
931
+ catch (e_14_1) { e_14 = { error: e_14_1 }; }
811
932
  finally {
812
933
  try {
813
934
  if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
814
935
  }
815
- finally { if (e_13) throw e_13.error; }
936
+ finally { if (e_14) throw e_14.error; }
816
937
  }
817
938
  var route = cleanField(assertion, ['route', 'screen_route', 'path'], 240);
818
939
  if (route && !looksLikeAbsoluteRoute(route)) {
@@ -829,6 +950,13 @@ function validateResolveIOAICoderJourneyContract(input, options) {
829
950
  hasRecoveryQa = hasRecoveryQa || coverageTags.includes('recovery');
830
951
  hasCompletionQa = hasCompletionQa || coverageTags.includes('workflow_completion');
831
952
  hasNonEmptyQa = hasNonEmptyQa || coverageTags.includes('non_empty_data');
953
+ var mappedWorkflowStepIds = workflow
954
+ .map(function (step, workflowIndex) { return journeySourceMapsToWorkflowStep(assertion, step, workflowIndex) ? buildWorkflowStepId(step, workflowIndex) : ''; })
955
+ .filter(Boolean);
956
+ mappedWorkflowStepIds.forEach(function (stepId) { return workflowStepsWithQa.add(stepId); });
957
+ if (workflow.length && !mappedWorkflowStepIds.length) {
958
+ addJourneyIssue(issues, 'qa_assertion_unmapped_to_workflow', qaPath, "".concat(pathLabel, ": qa_assertions[").concat(index, "] must reference a north_star_workflow step by workflow_step_id/covers_workflow_step_ids or match that step's route and CTA/action."));
959
+ }
832
960
  });
833
961
  if (!hasLoginQa) {
834
962
  addJourneyIssue(issues, 'missing_login_qa', "".concat(pathLabel, ".journey_contract.qa_assertions"), "".concat(pathLabel, ": qa_assertions missing structured login coverage."));
@@ -848,6 +976,15 @@ function validateResolveIOAICoderJourneyContract(input, options) {
848
976
  if (!hasRecoveryQa) {
849
977
  addJourneyIssue(issues, 'missing_recovery_qa', "".concat(pathLabel, ".journey_contract.qa_assertions"), "".concat(pathLabel, ": qa_assertions missing empty/error recovery proof."));
850
978
  }
979
+ workflow.forEach(function (step, index) {
980
+ if (!step || typeof step !== 'object' || Array.isArray(step)) {
981
+ return;
982
+ }
983
+ var stepId = buildWorkflowStepId(step, index);
984
+ if (!workflowStepsWithQa.has(stepId)) {
985
+ addJourneyIssue(issues, 'workflow_step_missing_qa_assertion', "".concat(pathLabel, ".journey_contract.north_star_workflow[").concat(index, "]"), "".concat(pathLabel, ": north_star_workflow[").concat(index, "] must be covered by at least one qa_assertions row using workflow_step_id/covers_workflow_step_ids or matching route/CTA."));
986
+ }
987
+ });
851
988
  var workflowQaRows = buildResolveIOAICoderWorkflowQaRowsFromJourneyContract(contract);
852
989
  return {
853
990
  valid: !issues.some(function (issue) { return issue.severity === 'error'; }),
@@ -866,7 +1003,7 @@ function cleanNumber(value) {
866
1003
  return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 0;
867
1004
  }
868
1005
  function cleanWorkflowQaRows(values, limit) {
869
- var e_14, _a;
1006
+ var e_15, _a;
870
1007
  if (limit === void 0) { limit = 40; }
871
1008
  if (!Array.isArray(values)) {
872
1009
  return [];
@@ -881,6 +1018,8 @@ function cleanWorkflowQaRows(values, limit) {
881
1018
  var row = {
882
1019
  index: cleanNumber(value.index) || undefined,
883
1020
  workflowId: cleanText(value.workflowId || value.workflow_id, 160) || undefined,
1021
+ workflowStepId: cleanText(value.workflowStepId || value.workflow_step_id, 160) || undefined,
1022
+ workflowStepIds: cleanList(value.workflowStepIds || value.workflow_step_ids, 20, 160),
884
1023
  stepId: cleanText(value.stepId || value.step_id, 160) || undefined,
885
1024
  route: cleanText(value.route, 240) || undefined,
886
1025
  action: cleanText(value.action, 500) || undefined,
@@ -912,12 +1051,12 @@ function cleanWorkflowQaRows(values, limit) {
912
1051
  }
913
1052
  }
914
1053
  }
915
- catch (e_14_1) { e_14 = { error: e_14_1 }; }
1054
+ catch (e_15_1) { e_15 = { error: e_15_1 }; }
916
1055
  finally {
917
1056
  try {
918
1057
  if (values_2_1 && !values_2_1.done && (_a = values_2.return)) _a.call(values_2);
919
1058
  }
920
- finally { if (e_14) throw e_14.error; }
1059
+ finally { if (e_15) throw e_15.error; }
921
1060
  }
922
1061
  return rows;
923
1062
  }
@@ -1128,7 +1267,7 @@ function fingerprintResolveIOAICoderWorkflowProofPayload(value, prefix) {
1128
1267
  return "".concat(prefix, "-").concat(Math.abs(hash).toString(36));
1129
1268
  }
1130
1269
  function normalizeWorkflowProofFingerprintList() {
1131
- var e_15, _a, e_16, _b;
1270
+ var e_16, _a, e_17, _b;
1132
1271
  var values = [];
1133
1272
  for (var _i = 0; _i < arguments.length; _i++) {
1134
1273
  values[_i] = arguments[_i];
@@ -1139,7 +1278,7 @@ function normalizeWorkflowProofFingerprintList() {
1139
1278
  var value = values_3_1.value;
1140
1279
  var list = Array.isArray(value) ? value : value ? [value] : [];
1141
1280
  try {
1142
- for (var list_1 = (e_16 = void 0, __values(list)), list_1_1 = list_1.next(); !list_1_1.done; list_1_1 = list_1.next()) {
1281
+ for (var list_1 = (e_17 = void 0, __values(list)), list_1_1 = list_1.next(); !list_1_1.done; list_1_1 = list_1.next()) {
1143
1282
  var entry = list_1_1.value;
1144
1283
  var normalized = cleanText(entry, 200);
1145
1284
  if (normalized && !fingerprints.includes(normalized)) {
@@ -1147,21 +1286,21 @@ function normalizeWorkflowProofFingerprintList() {
1147
1286
  }
1148
1287
  }
1149
1288
  }
1150
- catch (e_16_1) { e_16 = { error: e_16_1 }; }
1289
+ catch (e_17_1) { e_17 = { error: e_17_1 }; }
1151
1290
  finally {
1152
1291
  try {
1153
1292
  if (list_1_1 && !list_1_1.done && (_b = list_1.return)) _b.call(list_1);
1154
1293
  }
1155
- finally { if (e_16) throw e_16.error; }
1294
+ finally { if (e_17) throw e_17.error; }
1156
1295
  }
1157
1296
  }
1158
1297
  }
1159
- catch (e_15_1) { e_15 = { error: e_15_1 }; }
1298
+ catch (e_16_1) { e_16 = { error: e_16_1 }; }
1160
1299
  finally {
1161
1300
  try {
1162
1301
  if (values_3_1 && !values_3_1.done && (_a = values_3.return)) _a.call(values_3);
1163
1302
  }
1164
- finally { if (e_15) throw e_15.error; }
1303
+ finally { if (e_16) throw e_16.error; }
1165
1304
  }
1166
1305
  return fingerprints.slice(0, 40);
1167
1306
  }