@xtalpi/agentic-lab-skills 0.0.8 → 0.0.10

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.
@@ -356,6 +356,34 @@ function validateCompleteResult(result) {
356
356
  return errors;
357
357
  }
358
358
 
359
+ function validateRunResult(result) {
360
+ const errors = [];
361
+ if (typeof result !== 'object' || result === null) {
362
+ errors.push('run() must return an object, got ' + (result === null ? 'null' : typeof result));
363
+ return errors;
364
+ }
365
+ if (result.new_tickets !== undefined) {
366
+ if (!Array.isArray(result.new_tickets)) {
367
+ errors.push(`run().new_tickets must be array, got ${typeof result.new_tickets}`);
368
+ } else {
369
+ for (let i = 0; i < Math.min(result.new_tickets.length, 3); i++) {
370
+ const t = result.new_tickets[i];
371
+ if (typeof t.flow_id !== 'number') errors.push(`new_tickets[${i}].flow_id must be number, got ${typeof t.flow_id}`);
372
+ if (typeof t.pool_id !== 'number') errors.push(`new_tickets[${i}].pool_id must be number, got ${typeof t.pool_id}`);
373
+ if (typeof t.order_id !== 'number') errors.push(`new_tickets[${i}].order_id must be number, got ${typeof t.order_id}`);
374
+ if (typeof t.detail !== 'object' || t.detail === null) errors.push(`new_tickets[${i}].detail must be object`);
375
+ if (typeof t.status !== 'string') errors.push(`new_tickets[${i}].status must be string, got ${typeof t.status}`);
376
+ }
377
+ }
378
+ }
379
+ for (const key of Object.keys(result)) {
380
+ if (CAMEL_CASE_PATTERN.test(key)) {
381
+ errors.push(`run() return key "${key}" is camelCase — must be snake_case`);
382
+ }
383
+ }
384
+ return errors;
385
+ }
386
+
359
387
  // ─── Script loader via vm ──────────────────────────────────────────
360
388
 
361
389
  function loadProcesserClass(scriptContent, filename) {
@@ -621,8 +649,10 @@ function formatTicketsTable(tickets, maxDetailKeys) {
621
649
 
622
650
  function formatPreviewReport(trace) {
623
651
  const { scriptPath, valveName, valveConfig, pools, dataSource,
624
- inputTickets, startResult, startError, startTraceLog, startDetailDiffs,
625
- completeTickets, completeResult, completeError, completeTraceLog, completeDetailDiffs } = trace;
652
+ inputTickets, isAutoTrigger,
653
+ startResult, startError, startTraceLog, startDetailDiffs,
654
+ completeTickets, completeResult, completeError, completeTraceLog, completeDetailDiffs,
655
+ runResult, runError, runTraceLog, runDetailDiffs } = trace;
626
656
 
627
657
  const lines = [];
628
658
  const push = (...args) => lines.push(...args);
@@ -638,108 +668,176 @@ function formatPreviewReport(trace) {
638
668
  push(`- 入口池: ${inputPoolNames}`);
639
669
  push(`- 出口池: ${outputPoolNames}`);
640
670
  push(`- 数据来源: ${dataSource}`);
641
- push(`- tickets 数: ${inputTickets.length}`, '');
671
+ push(`- tickets 数: ${inputTickets.length}`);
672
+ push(`- 触发方式: ${isAutoTrigger ? '自动触发 (run)' : '人工触发 (start/complete)'}`, '');
673
+
674
+ if (isAutoTrigger) {
675
+ // run() trace
676
+ push('## run() 执行追踪', '');
677
+ push(`### 输入数据 (${inputTickets.length} 条 tickets)`, '');
678
+ push(formatTicketsTable(inputTickets));
679
+
680
+ push('### SDK 调用链', '');
681
+ for (const entry of (runTraceLog || [])) {
682
+ const paramStr = JSON.stringify(entry.params);
683
+ push(`${entry.seq + 1}. \`${entry.method}(${paramStr === '{}' ? '' : paramStr})\` → ${entry.returnSummary}`);
684
+ }
685
+ push('');
642
686
 
643
- // start() trace
644
- push('## start() 执行追踪', '');
645
- push(`### 输入数据 (${inputTickets.length} tickets)`, '');
646
- push(formatTicketsTable(inputTickets));
687
+ if (runDetailDiffs && runDetailDiffs.length > 0) {
688
+ push('### ticket.detail 变更 (run 阶段)', '');
689
+ push('| ticket | 字段 | 变更前 | 变更后 |');
690
+ push('|--------|------|--------|--------|');
691
+ for (const d of runDetailDiffs) {
692
+ for (const c of d.changes) {
693
+ push(`| #${d.ticketId} | ${c.key} | ${c.before} | ${c.after} |`);
694
+ }
695
+ }
696
+ push('');
697
+ }
647
698
 
648
- push('### SDK 调用链', '');
649
- for (const entry of startTraceLog) {
650
- const paramStr = JSON.stringify(entry.params);
651
- push(`${entry.seq + 1}. \`${entry.method}(${paramStr === '{}' ? '' : paramStr})\` → ${entry.returnSummary}`);
652
- }
653
- push('');
654
-
655
- if (startDetailDiffs.length > 0) {
656
- push('### ticket.detail 变更 (start 阶段)', '');
657
- push('| ticket | 字段 | 变更前 | 变更后 |');
658
- push('|--------|------|--------|--------|');
659
- for (const d of startDetailDiffs) {
660
- for (const c of d.changes) {
661
- push(`| #${d.ticketId} | ${c.key} | ${c.before} | ${c.after} |`);
699
+ if (runError) {
700
+ push(`### run() 错误`, '');
701
+ push(`\`\`\`\n${runError}\n\`\`\``, '');
702
+ } else if (runResult) {
703
+ push('### 数据路由', '');
704
+ if (runResult.new_tickets && runResult.new_tickets.length > 0) {
705
+ const routeMap = new Map();
706
+ for (const nt of runResult.new_tickets) {
707
+ const poolId = nt.pool_id;
708
+ if (!routeMap.has(poolId)) routeMap.set(poolId, []);
709
+ routeMap.get(poolId).push(nt);
710
+ }
711
+ push('| 目标池 | pool_id | 数量 | status |');
712
+ push('|--------|---------|------|--------|');
713
+ for (const [poolId, nts] of routeMap) {
714
+ const poolName = valveConfig.output_pools.find((_, i) => 100 + i === poolId)?.name || `pool_${poolId}`;
715
+ push(`| ${poolName} | ${poolId} | ${nts.length} | ${nts[0]?.status || '-'} |`);
716
+ }
717
+ push('');
718
+ } else {
719
+ push('(无 new_tickets 返回)', '');
662
720
  }
663
721
  }
664
- push('');
665
- }
666
722
 
667
- if (startError) {
668
- push(`### start() 错误`, '');
669
- push(`\`\`\`\n${startError}\n\`\`\``, '');
670
- } else if (startResult) {
671
- push('### 返回值', '');
672
- push(`- orbit_link: \`${startResult.orbit_link}\``);
673
- push(`- ticket_ids: [${(startResult.ticket_ids || []).join(', ')}]`);
674
- push('');
675
- }
723
+ // Summary
724
+ push('## 执行摘要', '');
725
+ const usedMethods = new Set((runTraceLog || []).map(c => c.method));
726
+ const allMethods = ['ticket.list', 'ticket.update', 'ticket.append', 'pool.getNext',
727
+ 'compound.getStockFromXfcSh', 'process.list', 'process.execute', 'station.list'];
728
+ const unused = allMethods.filter(m => !usedMethods.has(m));
676
729
 
677
- // complete() trace
678
- push('## complete() 执行追踪', '');
679
- push(`### 输入 tickets (${(completeTickets || []).length} )`, '');
730
+ const runStatus = runError
731
+ ? `✗ 失败: ${runError.split('\n')[0]}`
732
+ : `✓ 成功 (${(runTraceLog || []).length} SDK 调用, ${(runResult?.new_tickets || []).length} new_tickets)`;
680
733
 
681
- push('### SDK 调用链', '');
682
- for (const entry of completeTraceLog) {
683
- const paramStr = JSON.stringify(entry.params);
684
- push(`${entry.seq + 1}. \`${entry.method}(${paramStr === '{}' ? '' : paramStr})\` → ${entry.returnSummary}`);
685
- }
686
- push('');
687
-
688
- if (completeDetailDiffs.length > 0) {
689
- push('### ticket.detail 变更 (complete 阶段)', '');
690
- push('| ticket | 字段 | 变更前 | 变更后 |');
691
- push('|--------|------|--------|--------|');
692
- for (const d of completeDetailDiffs) {
693
- for (const c of d.changes) {
694
- push(`| #${d.ticketId} | ${c.key} | ${c.before} | ${c.after} |`);
695
- }
734
+ push(`- run: ${runStatus}`);
735
+ if (unused.length > 0) {
736
+ push(`- 未调用的 SDK 方法: ${unused.join(', ')}`);
737
+ }
738
+ push('');
739
+ } else {
740
+ // start() trace
741
+ push('## start() 执行追踪', '');
742
+ push(`### 输入数据 (${inputTickets.length} tickets)`, '');
743
+ push(formatTicketsTable(inputTickets));
744
+
745
+ push('### SDK 调用链', '');
746
+ for (const entry of startTraceLog) {
747
+ const paramStr = JSON.stringify(entry.params);
748
+ push(`${entry.seq + 1}. \`${entry.method}(${paramStr === '{}' ? '' : paramStr})\` → ${entry.returnSummary}`);
696
749
  }
697
750
  push('');
698
- }
699
751
 
700
- if (completeError) {
701
- push(`### complete() 错误`, '');
702
- push(`\`\`\`\n${completeError}\n\`\`\``, '');
703
- } else if (completeResult) {
704
- push('### 数据路由', '');
705
- if (completeResult.new_tickets && completeResult.new_tickets.length > 0) {
706
- const routeMap = new Map();
707
- for (const nt of completeResult.new_tickets) {
708
- const poolId = nt.pool_id;
709
- if (!routeMap.has(poolId)) routeMap.set(poolId, []);
710
- routeMap.get(poolId).push(nt);
752
+ if (startDetailDiffs.length > 0) {
753
+ push('### ticket.detail 变更 (start 阶段)', '');
754
+ push('| ticket | 字段 | 变更前 | 变更后 |');
755
+ push('|--------|------|--------|--------|');
756
+ for (const d of startDetailDiffs) {
757
+ for (const c of d.changes) {
758
+ push(`| #${d.ticketId} | ${c.key} | ${c.before} | ${c.after} |`);
759
+ }
711
760
  }
712
- push('| 目标池 | pool_id | 数量 | status |');
713
- push('|--------|---------|------|--------|');
714
- for (const [poolId, nts] of routeMap) {
715
- const poolName = valveConfig.output_pools.find((_, i) => 100 + i === poolId)?.name || `pool_${poolId}`;
716
- push(`| ${poolName} | ${poolId} | ${nts.length} | ${nts[0]?.status || '-'} |`);
761
+ push('');
762
+ }
763
+
764
+ if (startError) {
765
+ push(`### start() 错误`, '');
766
+ push(`\`\`\`\n${startError}\n\`\`\``, '');
767
+ } else if (startResult) {
768
+ push('### 返回值', '');
769
+ push(`- orbit_link: \`${startResult.orbit_link}\``);
770
+ push(`- ticket_ids: [${(startResult.ticket_ids || []).join(', ')}]`);
771
+ push('');
772
+ }
773
+
774
+ // complete() trace
775
+ push('## complete() 执行追踪', '');
776
+ push(`### 输入 tickets (${(completeTickets || []).length} 条)`, '');
777
+
778
+ push('### SDK 调用链', '');
779
+ for (const entry of completeTraceLog) {
780
+ const paramStr = JSON.stringify(entry.params);
781
+ push(`${entry.seq + 1}. \`${entry.method}(${paramStr === '{}' ? '' : paramStr})\` → ${entry.returnSummary}`);
782
+ }
783
+ push('');
784
+
785
+ if (completeDetailDiffs.length > 0) {
786
+ push('### ticket.detail 变更 (complete 阶段)', '');
787
+ push('| ticket | 字段 | 变更前 | 变更后 |');
788
+ push('|--------|------|--------|--------|');
789
+ for (const d of completeDetailDiffs) {
790
+ for (const c of d.changes) {
791
+ push(`| #${d.ticketId} | ${c.key} | ${c.before} | ${c.after} |`);
792
+ }
717
793
  }
718
794
  push('');
719
- } else {
720
- push('(无 new_tickets 返回)', '');
721
795
  }
722
- }
723
796
 
724
- // Summary
725
- push('## 执行摘要', '');
726
- const allCalls = [...startTraceLog, ...completeTraceLog];
727
- const usedMethods = new Set(allCalls.map(c => c.method));
728
- const allMethods = ['ticket.list', 'ticket.update', 'ticket.append', 'pool.getNext',
729
- 'compound.getStockFromXfcSh', 'process.list', 'process.execute', 'station.list'];
730
- const unused = allMethods.filter(m => !usedMethods.has(m));
731
-
732
- const startStatus = startError ? `✗ 失败: ${startError.split('\n')[0]}` : `✓ 成功 (${startTraceLog.length} SDK 调用, ${inputTickets.length} tickets)`;
733
- const completeStatus = completeError
734
- ? `✗ 失败: ${completeError.split('\n')[0]}`
735
- : `✓ 成功 (${completeTraceLog.length} SDK 调用, ${(completeResult?.new_tickets || []).length} new_tickets)`;
736
-
737
- push(`- start: ${startStatus}`);
738
- push(`- complete: ${completeStatus}`);
739
- if (unused.length > 0) {
740
- push(`- 未调用的 SDK 方法: ${unused.join(', ')}`);
797
+ if (completeError) {
798
+ push(`### complete() 错误`, '');
799
+ push(`\`\`\`\n${completeError}\n\`\`\``, '');
800
+ } else if (completeResult) {
801
+ push('### 数据路由', '');
802
+ if (completeResult.new_tickets && completeResult.new_tickets.length > 0) {
803
+ const routeMap = new Map();
804
+ for (const nt of completeResult.new_tickets) {
805
+ const poolId = nt.pool_id;
806
+ if (!routeMap.has(poolId)) routeMap.set(poolId, []);
807
+ routeMap.get(poolId).push(nt);
808
+ }
809
+ push('| 目标池 | pool_id | 数量 | status |');
810
+ push('|--------|---------|------|--------|');
811
+ for (const [poolId, nts] of routeMap) {
812
+ const poolName = valveConfig.output_pools.find((_, i) => 100 + i === poolId)?.name || `pool_${poolId}`;
813
+ push(`| ${poolName} | ${poolId} | ${nts.length} | ${nts[0]?.status || '-'} |`);
814
+ }
815
+ push('');
816
+ } else {
817
+ push('(无 new_tickets 返回)', '');
818
+ }
819
+ }
820
+
821
+ // Summary
822
+ push('## 执行摘要', '');
823
+ const allCalls = [...startTraceLog, ...completeTraceLog];
824
+ const usedMethods = new Set(allCalls.map(c => c.method));
825
+ const allMethods = ['ticket.list', 'ticket.update', 'ticket.append', 'pool.getNext',
826
+ 'compound.getStockFromXfcSh', 'process.list', 'process.execute', 'station.list'];
827
+ const unused = allMethods.filter(m => !usedMethods.has(m));
828
+
829
+ const startStatus = startError ? `✗ 失败: ${startError.split('\n')[0]}` : `✓ 成功 (${startTraceLog.length} SDK 调用, ${inputTickets.length} tickets)`;
830
+ const completeStatus = completeError
831
+ ? `✗ 失败: ${completeError.split('\n')[0]}`
832
+ : `✓ 成功 (${completeTraceLog.length} SDK 调用, ${(completeResult?.new_tickets || []).length} new_tickets)`;
833
+
834
+ push(`- start: ${startStatus}`);
835
+ push(`- complete: ${completeStatus}`);
836
+ if (unused.length > 0) {
837
+ push(`- 未调用的 SDK 方法: ${unused.join(', ')}`);
838
+ }
839
+ push('');
741
840
  }
742
- push('');
743
841
 
744
842
  return lines.join('\n');
745
843
  }
@@ -787,59 +885,85 @@ async function runPreview(args) {
787
885
  }
788
886
 
789
887
  const inputPoolIds = valveConfig.input_pools.map((_, i) => i + 1);
888
+ const proto = ProcesserClass.prototype;
889
+ const isAutoTrigger = typeof proto.run === 'function' && typeof proto.start !== 'function';
890
+
790
891
  const trace = {
791
- scriptPath, valveName, valveConfig, pools, dataSource, inputTickets,
892
+ scriptPath, valveName, valveConfig, pools, dataSource, inputTickets, isAutoTrigger,
792
893
  startResult: null, startError: null, startTraceLog: [], startDetailDiffs: [],
793
894
  completeTickets: null, completeResult: null, completeError: null, completeTraceLog: [], completeDetailDiffs: [],
895
+ runResult: null, runError: null, runTraceLog: [], runDetailDiffs: [],
794
896
  };
795
897
 
796
- // Run start()
797
- {
898
+ if (isAutoTrigger) {
899
+ // Auto-trigger: run()
798
900
  const tc = createTracingContext(pools, valveConfig, inputTickets);
799
901
  let processer;
800
902
  try {
801
903
  processer = new ProcesserClass(tc.context);
802
904
  } catch (e) {
803
- trace.startError = `constructor threw: ${e.message}\n${e.stack}`;
905
+ trace.runError = `constructor threw: ${e.message}\n${e.stack}`;
804
906
  console.log(formatPreviewReport(trace));
805
907
  process.exit(1);
806
908
  }
807
909
 
808
910
  try {
809
- trace.startResult = await processer.start({ valve_id: 1, pool_ids: inputPoolIds });
911
+ trace.runResult = await processer.run({ valve_id: 1, pool_ids: inputPoolIds });
810
912
  } catch (e) {
811
- trace.startError = `${e.constructor.name}: ${e.message}\n${e.stack}`;
913
+ trace.runError = `${e.constructor.name}: ${e.message}\n${e.stack}`;
812
914
  }
813
- trace.startTraceLog = tc.traceLog;
814
- trace.startDetailDiffs = tc.detailDiffs;
815
- }
816
-
817
- // Run complete()
818
- {
819
- const completeTickets = inputTickets.map(t => ({ ...t, status: 'created' }));
820
- trace.completeTickets = completeTickets;
915
+ trace.runTraceLog = tc.traceLog;
916
+ trace.runDetailDiffs = tc.detailDiffs;
917
+ } else {
918
+ // Manual-trigger: start() + complete()
919
+ // Run start()
920
+ {
921
+ const tc = createTracingContext(pools, valveConfig, inputTickets);
922
+ let processer;
923
+ try {
924
+ processer = new ProcesserClass(tc.context);
925
+ } catch (e) {
926
+ trace.startError = `constructor threw: ${e.message}\n${e.stack}`;
927
+ console.log(formatPreviewReport(trace));
928
+ process.exit(1);
929
+ }
821
930
 
822
- const tc = createTracingContext(pools, valveConfig, completeTickets);
823
- let processer;
824
- try {
825
- processer = new ProcesserClass(tc.context);
826
- } catch (e) {
827
- trace.completeError = `constructor threw: ${e.message}\n${e.stack}`;
828
- console.log(formatPreviewReport(trace));
829
- process.exit(1);
931
+ try {
932
+ trace.startResult = await processer.start({ valve_id: 1, pool_ids: inputPoolIds });
933
+ } catch (e) {
934
+ trace.startError = `${e.constructor.name}: ${e.message}\n${e.stack}`;
935
+ }
936
+ trace.startTraceLog = tc.traceLog;
937
+ trace.startDetailDiffs = tc.detailDiffs;
830
938
  }
831
939
 
832
- try {
833
- trace.completeResult = await processer.complete({ valve_id: 1, tickets: completeTickets });
834
- } catch (e) {
835
- trace.completeError = `${e.constructor.name}: ${e.message}\n${e.stack}`;
940
+ // Run complete()
941
+ {
942
+ const completeTickets = inputTickets.map(t => ({ ...t, status: 'created' }));
943
+ trace.completeTickets = completeTickets;
944
+
945
+ const tc = createTracingContext(pools, valveConfig, completeTickets);
946
+ let processer;
947
+ try {
948
+ processer = new ProcesserClass(tc.context);
949
+ } catch (e) {
950
+ trace.completeError = `constructor threw: ${e.message}\n${e.stack}`;
951
+ console.log(formatPreviewReport(trace));
952
+ process.exit(1);
953
+ }
954
+
955
+ try {
956
+ trace.completeResult = await processer.complete({ valve_id: 1, tickets: completeTickets });
957
+ } catch (e) {
958
+ trace.completeError = `${e.constructor.name}: ${e.message}\n${e.stack}`;
959
+ }
960
+ trace.completeTraceLog = tc.traceLog;
961
+ trace.completeDetailDiffs = tc.detailDiffs;
836
962
  }
837
- trace.completeTraceLog = tc.traceLog;
838
- trace.completeDetailDiffs = tc.detailDiffs;
839
963
  }
840
964
 
841
965
  console.log(formatPreviewReport(trace));
842
- const hasError = trace.startError || trace.completeError;
966
+ const hasError = isAutoTrigger ? trace.runError : (trace.startError || trace.completeError);
843
967
  process.exit(hasError ? 1 : 0);
844
968
  }
845
969
 
@@ -856,7 +980,7 @@ async function runTests(args) {
856
980
  script: scriptPath,
857
981
  phases: {
858
982
  syntax: { pass: false },
859
- structure: { pass: false, hasConstructor: false, hasStart: false, hasComplete: false },
983
+ structure: { pass: false, hasConstructor: false, hasStart: false, hasComplete: false, hasRun: false },
860
984
  normalRun: {
861
985
  start: { pass: false, returnValid: false, errors: [] },
862
986
  complete: { pass: false, returnValid: false, errors: [] },
@@ -914,18 +1038,23 @@ async function runTests(args) {
914
1038
  result.phases.structure.hasConstructor = typeof ProcesserClass === 'function';
915
1039
  result.phases.structure.hasStart = typeof proto.start === 'function';
916
1040
  result.phases.structure.hasComplete = typeof proto.complete === 'function';
1041
+ result.phases.structure.hasRun = typeof proto.run === 'function';
917
1042
  result.phases.structure.pass =
918
1043
  result.phases.structure.hasConstructor &&
919
- result.phases.structure.hasStart &&
920
- result.phases.structure.hasComplete;
1044
+ (
1045
+ (result.phases.structure.hasStart && result.phases.structure.hasComplete) ||
1046
+ result.phases.structure.hasRun
1047
+ );
921
1048
 
922
1049
  if (!result.phases.structure.pass) {
923
- if (!result.phases.structure.hasStart) result.errors.push('Processer missing start() method');
924
- if (!result.phases.structure.hasComplete) result.errors.push('Processer missing complete() method');
1050
+ if (!result.phases.structure.hasRun && !result.phases.structure.hasStart) result.errors.push('Processer missing start() method (manual-trigger) or run() method (auto-trigger)');
1051
+ if (!result.phases.structure.hasRun && !result.phases.structure.hasComplete) result.errors.push('Processer missing complete() method (manual-trigger) or run() method (auto-trigger)');
925
1052
  result.status = 'fail';
926
1053
  return result;
927
1054
  }
928
1055
 
1056
+ const isAutoTrigger = result.phases.structure.hasRun && !result.phases.structure.hasStart;
1057
+
929
1058
  // Load pool schemas and valve config
930
1059
  const pools = loadAllPools(poolsDir);
931
1060
  const valveConfig = loadValveConfig(valvesDir, valveName);
@@ -938,41 +1067,62 @@ async function runTests(args) {
938
1067
  try {
939
1068
  processer = new ProcesserClass(mock.context);
940
1069
  } catch (e) {
941
- result.phases.normalRun.start.errors.push(`constructor threw: ${e.message}`);
1070
+ const phaseKey = isAutoTrigger ? 'run' : 'start';
1071
+ if (!result.phases.normalRun[phaseKey]) result.phases.normalRun[phaseKey] = { pass: false, returnValid: false, errors: [] };
1072
+ result.phases.normalRun[phaseKey].errors.push(`constructor threw: ${e.message}`);
942
1073
  result.errors.push(`Normal run constructor error: ${e.message}`);
943
1074
  result.status = 'fail';
944
1075
  return result;
945
1076
  }
946
1077
 
947
- // start
948
- try {
949
- const startResult = await processer.start({ valve_id: 1, pool_ids: inputPoolIds });
950
- const startErrors = validateStartResult(startResult);
951
- result.phases.normalRun.start.returnValid = startErrors.length === 0;
952
- result.phases.normalRun.start.errors = startErrors;
953
- result.phases.normalRun.start.pass = startErrors.length === 0;
954
- if (startErrors.length > 0) {
955
- result.errors.push(...startErrors.map(e => `[normal start] ${e}`));
1078
+ if (isAutoTrigger) {
1079
+ // Auto-trigger: test run()
1080
+ if (!result.phases.normalRun.run) result.phases.normalRun.run = { pass: false, returnValid: false, errors: [] };
1081
+ try {
1082
+ const runResult = await processer.run({ valve_id: 1, pool_ids: inputPoolIds });
1083
+ const runErrors = validateRunResult(runResult);
1084
+ result.phases.normalRun.run.returnValid = runErrors.length === 0;
1085
+ result.phases.normalRun.run.errors = runErrors;
1086
+ result.phases.normalRun.run.pass = runErrors.length === 0;
1087
+ if (runErrors.length > 0) {
1088
+ result.errors.push(...runErrors.map(e => `[normal run] ${e}`));
1089
+ }
1090
+ } catch (e) {
1091
+ result.phases.normalRun.run.errors.push(e.message);
1092
+ result.errors.push(`[normal run] Runtime error: ${e.message}`);
1093
+ }
1094
+ } else {
1095
+ // Manual-trigger: test start() + complete()
1096
+ // start
1097
+ try {
1098
+ const startResult = await processer.start({ valve_id: 1, pool_ids: inputPoolIds });
1099
+ const startErrors = validateStartResult(startResult);
1100
+ result.phases.normalRun.start.returnValid = startErrors.length === 0;
1101
+ result.phases.normalRun.start.errors = startErrors;
1102
+ result.phases.normalRun.start.pass = startErrors.length === 0;
1103
+ if (startErrors.length > 0) {
1104
+ result.errors.push(...startErrors.map(e => `[normal start] ${e}`));
1105
+ }
1106
+ } catch (e) {
1107
+ result.phases.normalRun.start.errors.push(e.message);
1108
+ result.errors.push(`[normal start] Runtime error: ${e.message}`);
956
1109
  }
957
- } catch (e) {
958
- result.phases.normalRun.start.errors.push(e.message);
959
- result.errors.push(`[normal start] Runtime error: ${e.message}`);
960
- }
961
1110
 
962
- // complete — use mock tickets from the start phase (with updated details if any)
963
- try {
964
- const completeTickets = mock.mockTickets.map(t => ({ ...t, status: 'created' }));
965
- const completeResult = await processer.complete({ valve_id: 1, tickets: completeTickets });
966
- const completeErrors = validateCompleteResult(completeResult);
967
- result.phases.normalRun.complete.returnValid = completeErrors.length === 0;
968
- result.phases.normalRun.complete.errors = completeErrors;
969
- result.phases.normalRun.complete.pass = completeErrors.length === 0;
970
- if (completeErrors.length > 0) {
971
- result.errors.push(...completeErrors.map(e => `[normal complete] ${e}`));
1111
+ // complete — use mock tickets from the start phase (with updated details if any)
1112
+ try {
1113
+ const completeTickets = mock.mockTickets.map(t => ({ ...t, status: 'created' }));
1114
+ const completeResult = await processer.complete({ valve_id: 1, tickets: completeTickets });
1115
+ const completeErrors = validateCompleteResult(completeResult);
1116
+ result.phases.normalRun.complete.returnValid = completeErrors.length === 0;
1117
+ result.phases.normalRun.complete.errors = completeErrors;
1118
+ result.phases.normalRun.complete.pass = completeErrors.length === 0;
1119
+ if (completeErrors.length > 0) {
1120
+ result.errors.push(...completeErrors.map(e => `[normal complete] ${e}`));
1121
+ }
1122
+ } catch (e) {
1123
+ result.phases.normalRun.complete.errors.push(e.message);
1124
+ result.errors.push(`[normal complete] Runtime error: ${e.message}`);
972
1125
  }
973
- } catch (e) {
974
- result.phases.normalRun.complete.errors.push(e.message);
975
- result.errors.push(`[normal complete] Runtime error: ${e.message}`);
976
1126
  }
977
1127
 
978
1128
  result.sdkCalls = [...new Set(mock.callLog.map(c => c.method))];
@@ -998,7 +1148,9 @@ async function runTests(args) {
998
1148
  }
999
1149
  }
1000
1150
 
1001
- const normalPass = result.phases.normalRun.start.pass && result.phases.normalRun.complete.pass;
1151
+ const normalPass = isAutoTrigger
1152
+ ? result.phases.normalRun.run?.pass
1153
+ : result.phases.normalRun.start.pass && result.phases.normalRun.complete.pass;
1002
1154
  if (!normalPass) {
1003
1155
  result.status = 'fail';
1004
1156
  }
@@ -1010,26 +1162,39 @@ async function runTests(args) {
1010
1162
  try {
1011
1163
  processer = new ProcesserClass(mock.context);
1012
1164
  } catch (e) {
1013
- result.phases.adversarialRun.start.errors.push(`constructor threw: ${e.message}`);
1165
+ const phaseKey = isAutoTrigger ? 'run' : 'start';
1166
+ if (!result.phases.adversarialRun[phaseKey]) result.phases.adversarialRun[phaseKey] = { pass: false, errors: [] };
1167
+ result.phases.adversarialRun[phaseKey].errors.push(`constructor threw: ${e.message}`);
1014
1168
  result.warnings.push(`[adversarial] constructor error: ${e.message}`);
1015
1169
  }
1016
1170
 
1017
1171
  if (processer) {
1018
- try {
1019
- await processer.start({ valve_id: 1, pool_ids: inputPoolIds });
1020
- result.phases.adversarialRun.start.pass = true;
1021
- } catch (e) {
1022
- result.phases.adversarialRun.start.errors.push(e.message);
1023
- result.warnings.push(`[adversarial start] ${e.constructor.name}: ${e.message}`);
1024
- }
1172
+ if (isAutoTrigger) {
1173
+ if (!result.phases.adversarialRun.run) result.phases.adversarialRun.run = { pass: false, errors: [] };
1174
+ try {
1175
+ await processer.run({ valve_id: 1, pool_ids: inputPoolIds });
1176
+ result.phases.adversarialRun.run.pass = true;
1177
+ } catch (e) {
1178
+ result.phases.adversarialRun.run.errors.push(e.message);
1179
+ result.warnings.push(`[adversarial run] ${e.constructor.name}: ${e.message}`);
1180
+ }
1181
+ } else {
1182
+ try {
1183
+ await processer.start({ valve_id: 1, pool_ids: inputPoolIds });
1184
+ result.phases.adversarialRun.start.pass = true;
1185
+ } catch (e) {
1186
+ result.phases.adversarialRun.start.errors.push(e.message);
1187
+ result.warnings.push(`[adversarial start] ${e.constructor.name}: ${e.message}`);
1188
+ }
1025
1189
 
1026
- try {
1027
- const completeTickets = mock.mockTickets.map(t => ({ ...t, status: 'created' }));
1028
- await processer.complete({ valve_id: 1, tickets: completeTickets });
1029
- result.phases.adversarialRun.complete.pass = true;
1030
- } catch (e) {
1031
- result.phases.adversarialRun.complete.errors.push(e.message);
1032
- result.warnings.push(`[adversarial complete] ${e.constructor.name}: ${e.message}`);
1190
+ try {
1191
+ const completeTickets = mock.mockTickets.map(t => ({ ...t, status: 'created' }));
1192
+ await processer.complete({ valve_id: 1, tickets: completeTickets });
1193
+ result.phases.adversarialRun.complete.pass = true;
1194
+ } catch (e) {
1195
+ result.phases.adversarialRun.complete.errors.push(e.message);
1196
+ result.warnings.push(`[adversarial complete] ${e.constructor.name}: ${e.message}`);
1197
+ }
1033
1198
  }
1034
1199
 
1035
1200
  result.phases.compliance.warnings.push(