yaml-flow 8.5.0 → 8.5.2

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 (44) hide show
  1. package/browser/asset-integrity.json +3 -3
  2. package/browser/board-livecards-client.js +1 -1
  3. package/cli/browser-api/board-live-cards-browser-adapter.d.ts +1 -1
  4. package/cli/bundled/board-live-cards-cli.mjs +14 -14
  5. package/cli/bundled/chat-store-cli.mjs +13 -13
  6. package/cli/{types-D2s3VzyY.d.ts → types-H3EMBPY2.d.ts} +7 -2
  7. package/examples/board/demo-shell-with-server.html +2 -2
  8. package/examples/board/doc.html +2 -2
  9. package/examples/board/server/README-mcp-api.md +680 -0
  10. package/examples/board/test/server-http-mcp-test.js +406 -34
  11. package/examples/board-local/demo-shell-localstorage.html +3 -3
  12. package/lib/artifacts-store-public.d.cts +1 -1
  13. package/lib/artifacts-store-public.d.ts +1 -1
  14. package/lib/board-live-cards-mcp.cjs +1 -1
  15. package/lib/board-live-cards-mcp.d.cts +42 -5
  16. package/lib/board-live-cards-mcp.d.ts +42 -5
  17. package/lib/board-live-cards-mcp.js +1 -1
  18. package/lib/board-live-cards-node.cjs +14 -14
  19. package/lib/board-live-cards-node.d.cts +4 -4
  20. package/lib/board-live-cards-node.d.ts +4 -4
  21. package/lib/board-live-cards-node.js +14 -14
  22. package/lib/{board-live-cards-public-CvkDfZQ7.d.cts → board-live-cards-public-B13InXhC.d.cts} +7 -2
  23. package/lib/{board-live-cards-public-DdVhH4M-.d.ts → board-live-cards-public-BGS22cMb.d.ts} +7 -2
  24. package/lib/board-live-cards-public.cjs +2 -2
  25. package/lib/board-live-cards-public.d.cts +1 -1
  26. package/lib/board-live-cards-public.d.ts +1 -1
  27. package/lib/board-live-cards-public.js +2 -2
  28. package/lib/board-live-cards-server-runtime.cjs +4 -4
  29. package/lib/board-live-cards-server-runtime.d.cts +2 -2
  30. package/lib/board-live-cards-server-runtime.d.ts +2 -2
  31. package/lib/board-live-cards-server-runtime.js +4 -4
  32. package/lib/card-store-public.d.cts +1 -1
  33. package/lib/card-store-public.d.ts +1 -1
  34. package/lib/chat-store-public.cjs +1 -1
  35. package/lib/chat-store-public.d.cts +6 -1
  36. package/lib/chat-store-public.d.ts +6 -1
  37. package/lib/chat-store-public.js +1 -1
  38. package/lib/server-runtime/index.cjs +4 -4
  39. package/lib/server-runtime/index.d.cts +3 -3
  40. package/lib/server-runtime/index.d.ts +3 -3
  41. package/lib/server-runtime/index.js +4 -4
  42. package/lib/{types-QNI__eAf.d.ts → types-30R357js.d.ts} +1 -1
  43. package/lib/{types-NM_d_1oZ.d.cts → types-CIgsh56O.d.cts} +1 -1
  44. package/package.json +1 -1
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * server-http-mcp-test.js
4
- *
3
+ name: 'portfolio variant B',
4
+ card: portfolioVariantB,
5
5
  * Smoke test for demo-board/server/board-server.js over HTTP + SSE.
6
6
  * Targets the 'live' board with --cards-pattern cardT* to load only the 3
7
- * test cards (cardT-portfolio, cardT-market-prices, cardT-portfolio-value).
8
- *
7
+ assert(Array.isArray(body.provides_outputs?.holdings) && body.provides_outputs.holdings.length === baseHoldings.length + 1, 'T4 run-cycle portfolio variant B provides mismatch');
8
+ assert(body.rendered_view?.elements?.[0]?.kind === 'editable-table', 'T4 run-cycle portfolio variant B rendered_view mismatch');
9
9
  * T0: init-board -> SSE initial payload -> wait for all cards to complete
10
10
  * T1: mutate holdings in memory -> manage.upsert-card over MCP -> verify recomputation
11
11
  *
@@ -34,6 +34,7 @@ const cliPort = portArg !== -1 ? parseInt(cliArgs[portArg + 1], 10) : NaN;
34
34
  const skipT1 = cliArgs.includes('--skip-t1');
35
35
  const skipT2 = cliArgs.includes('--skip-t2');
36
36
  const skipT3 = cliArgs.includes('--skip-t3');
37
+ const skipT4 = cliArgs.includes('--skip-t4');
37
38
  function isCopilotAvailable() {
38
39
  try {
39
40
  const r = spawnSync('copilot', ['--version'], { timeout: 5_000, stdio: 'ignore', windowsHide: true });
@@ -378,6 +379,16 @@ function httpUploadChatFile(url, fileName, content, contentType = 'text/plain; c
378
379
  });
379
380
  }
380
381
 
382
+ function deepCloneJson(value) {
383
+ return JSON.parse(JSON.stringify(value));
384
+ }
385
+
386
+ function expectMcpSuccess(httpResult, label) {
387
+ assert(httpResult.status === 200, `${label} returned ${httpResult.status}`);
388
+ assert(httpResult.data?.status === 'success', `${label} expected status=success, got ${JSON.stringify(httpResult.data)}`);
389
+ return httpResult.data.data;
390
+ }
391
+
381
392
  function startServer(port) {
382
393
  return new Promise((resolve, reject) => {
383
394
  const proc = spawn(process.execPath, [SERVER_SCRIPT], {
@@ -456,8 +467,8 @@ try {
456
467
 
457
468
  console.log('\n=== T0 Step 4: board-status cross-check ===');
458
469
  const statusRes = await httpMcp('inspect.board-runtime-status', {});
459
- assert(statusRes.status === 200, `inspect.board-runtime-status returned ${statusRes.status}`);
460
- const httpSummary = statusRes.data?.summary;
470
+ const statusData = expectMcpSuccess(statusRes, 'inspect.board-runtime-status');
471
+ const httpSummary = statusData?.summary;
461
472
  assert(httpSummary, 'statusSnapshot.summary missing from board-status');
462
473
  assert(httpSummary.completed === httpSummary.card_count, `not all complete: ${JSON.stringify(httpSummary)}`);
463
474
  console.log(`[T0.4] board-status: ${JSON.stringify(httpSummary)}`);
@@ -471,9 +482,8 @@ try {
471
482
  } else {
472
483
  console.log('\n=== T1: local mutation + manage.upsert-card (+1 row) ===');
473
484
 
474
- const portfolioCardRes = await httpMcp('manage.read-card', { card_id: 'card-portfolio' });
475
- assert(portfolioCardRes.status === 200, `manage.read-card returned ${portfolioCardRes.status}`);
476
- const existingCard = Array.isArray(portfolioCardRes.data) ? portfolioCardRes.data[0] : null;
485
+ const portfolioCardRes = await httpMcp('inspect.card-definition-and-runtime', { card_id: 'card-portfolio' });
486
+ const existingCard = expectMcpSuccess(portfolioCardRes, 'inspect.card-definition-and-runtime')?.card_definition_and_static_data ?? null;
477
487
  const existingHoldings = existingCard?.card_data?.holdings;
478
488
  assert(Array.isArray(existingHoldings), 'card-portfolio.card_data.holdings missing');
479
489
  const t0HoldingsCount = existingHoldings.length;
@@ -510,9 +520,8 @@ try {
510
520
  const t1Summary = await waitForAllCompleted(30_000, 'T1 holdings upsert');
511
521
  assert(t1Summary.failed === 0, `T1 failed=${t1Summary.failed}`);
512
522
 
513
- const t1PortfolioRes = await httpMcp('manage.read-card', { card_id: 'card-portfolio' });
514
- assert(t1PortfolioRes.status === 200, `manage.read-card after upsert returned ${t1PortfolioRes.status}`);
515
- const afterCard = Array.isArray(t1PortfolioRes.data) ? t1PortfolioRes.data[0] : null;
523
+ const t1PortfolioRes = await httpMcp('inspect.card-definition-and-runtime', { card_id: 'card-portfolio' });
524
+ const afterCard = expectMcpSuccess(t1PortfolioRes, 'inspect.card-definition-and-runtime after upsert')?.card_definition_and_static_data ?? null;
516
525
  const afterHoldings = afterCard?.card_data?.holdings;
517
526
  const afterHoldingsCount = Array.isArray(afterHoldings) ? afterHoldings.length : 0;
518
527
 
@@ -532,8 +541,8 @@ try {
532
541
  } else {
533
542
  console.log('\n=== T2: plain file upload -> card_data.files -> download ===');
534
543
  const t2CardBefore = await httpMcp('manage.read-card', { card_id: T2_FILE_CARD_ID });
535
- assert(t2CardBefore.status === 200, `T2 pre card read returned ${t2CardBefore.status}`);
536
- const t2CardBeforeObj = Array.isArray(t2CardBefore.data) ? t2CardBefore.data[0] : null;
544
+ const t2CardBeforeData = expectMcpSuccess(t2CardBefore, 'T2 pre card read');
545
+ const t2CardBeforeObj = Array.isArray(t2CardBeforeData) ? t2CardBeforeData[0] : null;
537
546
  const t2FilesBefore = Array.isArray(t2CardBeforeObj?.card_data?.files) ? t2CardBeforeObj.card_data.files : [];
538
547
  const t2BeforeCount = t2FilesBefore.length;
539
548
 
@@ -545,14 +554,14 @@ try {
545
554
  content_type: 'text/plain; charset=utf-8',
546
555
  text: t2UploadText,
547
556
  });
548
- assert(t2UploadRes.status === 200, `T2 file upload returned ${t2UploadRes.status}`);
549
- const t2UploadedFile = t2UploadRes.data?.file;
557
+ const t2UploadData = expectMcpSuccess(t2UploadRes, 'T2 file upload');
558
+ const t2UploadedFile = t2UploadData?.file;
550
559
  assert(t2UploadedFile && typeof t2UploadedFile === 'object', 'T2 upload response missing file metadata');
551
560
  assert(String(t2UploadedFile?.name || '') === t2UploadName, 'T2 uploaded file name mismatch');
552
561
 
553
562
  const t2CardAfter = await httpMcp('manage.read-card', { card_id: T2_FILE_CARD_ID });
554
- assert(t2CardAfter.status === 200, `T2 post card read returned ${t2CardAfter.status}`);
555
- const t2CardAfterObj = Array.isArray(t2CardAfter.data) ? t2CardAfter.data[0] : null;
563
+ const t2CardAfterData = expectMcpSuccess(t2CardAfter, 'T2 post card read');
564
+ const t2CardAfterObj = Array.isArray(t2CardAfterData) ? t2CardAfterData[0] : null;
556
565
  const t2FilesAfter = Array.isArray(t2CardAfterObj?.card_data?.files) ? t2CardAfterObj.card_data.files : [];
557
566
  assert(t2FilesAfter.length === t2BeforeCount + 1, `T2 expected files +1 (before=${t2BeforeCount}, after=${t2FilesAfter.length})`);
558
567
 
@@ -584,8 +593,8 @@ try {
584
593
  assert(subRes.status === 200, `chat subscribe returned ${subRes.status}`);
585
594
 
586
595
  const t3Before = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, 'all-turns': true });
587
- assert(t3Before.status === 200, `T3 MCP pre chats returned ${t3Before.status}`);
588
- const t3BeforeMessages = Array.isArray(t3Before.data?.messages) ? t3Before.data.messages : [];
596
+ const t3BeforeData = expectMcpSuccess(t3Before, 'T3 MCP pre chats');
597
+ const t3BeforeMessages = Array.isArray(t3BeforeData?.messages) ? t3BeforeData.messages : [];
589
598
  const t3BeforeCount = t3BeforeMessages.length;
590
599
  const t3EventStart = NS.chatEvents.length;
591
600
  const t3ProbePrompt = `Probe protocol validation ${Date.now()}`;
@@ -611,8 +620,8 @@ try {
611
620
  assert(!!t3Lifecycle, 'T3 ordered lifecycle not observed');
612
621
 
613
622
  const t3After = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, 'all-turns': true });
614
- assert(t3After.status === 200, `T3 MCP post chats returned ${t3After.status}`);
615
- const t3AfterMessages = Array.isArray(t3After.data?.messages) ? t3After.data.messages : [];
623
+ const t3AfterData = expectMcpSuccess(t3After, 'T3 MCP post chats');
624
+ const t3AfterMessages = Array.isArray(t3AfterData?.messages) ? t3AfterData.messages : [];
616
625
  const t3NewMessages = t3AfterMessages.slice(t3BeforeCount);
617
626
  assert(t3NewMessages.length >= 3, `T3 expected at least 3 new chat messages, got ${t3NewMessages.length}`);
618
627
  const t3User = t3NewMessages.find((m) => m?.role === 'user');
@@ -634,8 +643,8 @@ try {
634
643
  } else {
635
644
  console.log('\n=== T3a: non-probe chat protocol (expect paris) ===');
636
645
  const t3aBefore = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, 'all-turns': true });
637
- assert(t3aBefore.status === 200, `T3a MCP pre chats returned ${t3aBefore.status}`);
638
- const t3aBeforeMessages = Array.isArray(t3aBefore.data?.messages) ? t3aBefore.data.messages : [];
646
+ const t3aBeforeData = expectMcpSuccess(t3aBefore, 'T3a MCP pre chats');
647
+ const t3aBeforeMessages = Array.isArray(t3aBeforeData?.messages) ? t3aBeforeData.messages : [];
639
648
  const t3aBeforeCount = t3aBeforeMessages.length;
640
649
  const t3aPrompt = 'Just answer what is the capital of France. No Fluff. No COmmentary. No Markup Respond in lower case in one word.';
641
650
  const t3aTurnId = randomTurnId();
@@ -664,8 +673,8 @@ try {
664
673
  assert(!!t3aAssistant, 'T3a assistant response with paris not observed on SSE');
665
674
 
666
675
  const t3aAfter = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, 'all-turns': true });
667
- assert(t3aAfter.status === 200, `T3a MCP post chats returned ${t3aAfter.status}`);
668
- const t3aAfterMessages = Array.isArray(t3aAfter.data?.messages) ? t3aAfter.data.messages : [];
676
+ const t3aAfterData = expectMcpSuccess(t3aAfter, 'T3a MCP post chats');
677
+ const t3aAfterMessages = Array.isArray(t3aAfterData?.messages) ? t3aAfterData.messages : [];
669
678
  const t3aNewMessages = t3aAfterMessages.slice(t3aBeforeCount);
670
679
  assert(t3aNewMessages.length >= 2, `T3a expected at least 2 new chat messages, got ${t3aNewMessages.length}`);
671
680
  const t3aUser = t3aNewMessages.find((m) => m?.role === 'user');
@@ -686,8 +695,8 @@ try {
686
695
  } else {
687
696
  console.log('\n=== T3b: probe-echo chat with file upload protocol ===');
688
697
  const t3bBefore = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, 'all-turns': true });
689
- assert(t3bBefore.status === 200, `T3b MCP pre chats returned ${t3bBefore.status}`);
690
- const t3bBeforeMessages = Array.isArray(t3bBefore.data?.messages) ? t3bBefore.data.messages : [];
698
+ const t3bBeforeData = expectMcpSuccess(t3bBefore, 'T3b MCP pre chats');
699
+ const t3bBeforeMessages = Array.isArray(t3bBeforeData?.messages) ? t3bBeforeData.messages : [];
691
700
  const t3bBeforeCount = t3bBeforeMessages.length;
692
701
  const t3bTurnId = randomTurnId();
693
702
 
@@ -701,8 +710,8 @@ try {
701
710
  assert(uploadedFile && typeof uploadedFile === 'object', 'T3b upload response missing file metadata');
702
711
 
703
712
  const t3bAfterUpload = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, 'all-turns': true });
704
- assert(t3bAfterUpload.status === 200, `T3b MCP chats after upload returned ${t3bAfterUpload.status}`);
705
- const t3bUploadMessages = Array.isArray(t3bAfterUpload.data?.messages) ? t3bAfterUpload.data.messages : [];
713
+ const t3bAfterUploadData = expectMcpSuccess(t3bAfterUpload, 'T3b MCP chats after upload');
714
+ const t3bUploadMessages = Array.isArray(t3bAfterUploadData?.messages) ? t3bAfterUploadData.messages : [];
706
715
  const t3bUploadNewMessages = t3bUploadMessages.slice(t3bBeforeCount);
707
716
  const t3bUploadSystem = t3bUploadNewMessages.find((m) => m?.role === 'system');
708
717
  assert(!!t3bUploadSystem, 'T3b upload protocol missing system chat file');
@@ -710,8 +719,8 @@ try {
710
719
  assert(String(t3bUploadSystem?.turn || '') === t3bTurnId, 'T3b upload system turn id mismatch');
711
720
 
712
721
  const t3bCardAfterUpload = await httpMcp('manage.read-card', { card_id: CHAT_CARD_ID });
713
- assert(t3bCardAfterUpload.status === 200, `T3b manage.read-card after upload returned ${t3bCardAfterUpload.status}`);
714
- const t3bCardAfterUploadValue = Array.isArray(t3bCardAfterUpload.data) ? t3bCardAfterUpload.data[0] : null;
722
+ const t3bCardAfterUploadData = expectMcpSuccess(t3bCardAfterUpload, 'T3b manage.read-card after upload');
723
+ const t3bCardAfterUploadValue = Array.isArray(t3bCardAfterUploadData) ? t3bCardAfterUploadData[0] : null;
715
724
  const t3bFilesAfterUpload = Array.isArray(t3bCardAfterUploadValue?.card_data?.files)
716
725
  ? t3bCardAfterUploadValue.card_data.files
717
726
  : [];
@@ -751,8 +760,8 @@ try {
751
760
  assert(!!t3bLifecycle, 'T3b ordered lifecycle not observed');
752
761
 
753
762
  const t3bAfter = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, 'all-turns': true });
754
- assert(t3bAfter.status === 200, `T3b MCP post chats returned ${t3bAfter.status}`);
755
- const t3bAfterMessages = Array.isArray(t3bAfter.data?.messages) ? t3bAfter.data.messages : [];
763
+ const t3bAfterData = expectMcpSuccess(t3bAfter, 'T3b MCP post chats');
764
+ const t3bAfterMessages = Array.isArray(t3bAfterData?.messages) ? t3bAfterData.messages : [];
756
765
  const t3bNewMessages = t3bAfterMessages.slice(t3bSendBaseline);
757
766
  assert(t3bNewMessages.length >= 3, `T3b expected at least 3 chat messages after send, got ${t3bNewMessages.length}`);
758
767
 
@@ -772,6 +781,369 @@ try {
772
781
  }
773
782
  }
774
783
 
784
+ if (skipT4) {
785
+ console.log('\n=== T4: skipped (--skip-t4) ===');
786
+ } else {
787
+ console.log('\n=== T4: preflight MCP smoke checks ===');
788
+
789
+ const discoverSourceKindsData = expectMcpSuccess(
790
+ await httpMcp('discover.source-kinds', {}),
791
+ 'T4 discover.source-kinds',
792
+ );
793
+ assert(discoverSourceKindsData && typeof discoverSourceKindsData === 'object', 'T4 discover.source-kinds missing payload');
794
+ assert(discoverSourceKindsData.sourceKinds && typeof discoverSourceKindsData.sourceKinds === 'object', 'T4 discover.source-kinds missing sourceKinds');
795
+ const discoveredSourceKinds = Object.keys(discoverSourceKindsData.sourceKinds).sort();
796
+ assert(
797
+ JSON.stringify(discoveredSourceKinds) === JSON.stringify(['mock', 'sqlite', 'urls']),
798
+ `T4 discover.source-kinds mismatch: ${JSON.stringify(discoveredSourceKinds)}`,
799
+ );
800
+ console.log('[T4.discover] ok: source kinds match demo task executor');
801
+
802
+ const getCardDefinition = (fileName) => {
803
+ const filePath = path.join(BOARD_DIR, 'cards', fileName);
804
+ const raw = fs.readFileSync(filePath, 'utf-8');
805
+ return JSON.parse(raw);
806
+ };
807
+
808
+ const expectPreflightSuccess = (res, label) => {
809
+ assert(res.status === 200, `${label} returned ${res.status}`);
810
+ assert(res.data?.status === 'success', `${label} expected status=success, got ${JSON.stringify(res.data)}`);
811
+ assert(res.data?.data && typeof res.data.data === 'object', `${label} missing success data`);
812
+ return res.data.data;
813
+ };
814
+
815
+ const portfolioCard = getCardDefinition('cardT-portfolio.json');
816
+ const marketCard = getCardDefinition('cardT-market-prices.json');
817
+ const portfolioValueCard = getCardDefinition('cardT-portfolio-value.json');
818
+ const baseHoldings = Array.isArray(portfolioCard?.card_data?.holdings) ? deepCloneJson(portfolioCard.card_data.holdings) : [];
819
+
820
+ const mockQuotes = {
821
+ quoteResponse: {
822
+ result: [
823
+ { symbol: 'AAPL', shortName: 'Apple Inc.', regularMarketPrice: 198.15, regularMarketChange: 2.15, regularMarketChangePercent: 1.10 },
824
+ { symbol: 'MSFT', shortName: 'Microsoft Corp.', regularMarketPrice: 415.32, regularMarketChange: -1.23, regularMarketChangePercent: -0.30 },
825
+ { symbol: 'GOOGL', shortName: 'Alphabet Inc.', regularMarketPrice: 174.89, regularMarketChange: 0.89, regularMarketChangePercent: 0.51 },
826
+ { symbol: 'TSLA', shortName: 'Tesla Inc.', regularMarketPrice: 247.12, regularMarketChange: 5.43, regularMarketChangePercent: 2.25 },
827
+ ],
828
+ error: null,
829
+ },
830
+ };
831
+
832
+ const makePortfolioVariant = (id, extraHolding) => {
833
+ const card = deepCloneJson(portfolioCard);
834
+ card.id = id;
835
+ card.card_data.holdings = [...baseHoldings, extraHolding];
836
+ return card;
837
+ };
838
+
839
+ const makeMockSourceCard = ({ id, bindTo = 'quotes', secondBindTo = null, includeProjection = false, projectionExpr = '"ok"', missingMock = false }) => {
840
+ const card = deepCloneJson(marketCard);
841
+ card.id = id;
842
+ card.requires = [];
843
+ card.source_defs = [
844
+ { bindTo, mock: missingMock ? 'missing-mock-key' : 'quotes' },
845
+ ...(secondBindTo ? [{ bindTo: secondBindTo, mock: 'quotes' }] : []),
846
+ ];
847
+ if (includeProjection) {
848
+ card.source_defs[0].projections = { passthrough: projectionExpr };
849
+ } else {
850
+ delete card.source_defs[0].projections;
851
+ }
852
+ delete card.source_defs[0].urls;
853
+ if (card.source_defs[1]) delete card.source_defs[1].urls;
854
+ return card;
855
+ };
856
+
857
+ const portfolioVariantA = makePortfolioVariant('card-portfolio-preflight-a', { ticker: 'NVDA', quantity: 7, cost_basis: 121 });
858
+ const portfolioVariantB = makePortfolioVariant('card-portfolio-preflight-b', { ticker: 'AMD', quantity: 9, cost_basis: 143 });
859
+ const marketMockCycleCard = makeMockSourceCard({ id: 'card-market-prices-preflight-cycle' });
860
+ const marketMockSourceCardA = makeMockSourceCard({ id: 'card-market-prices-preflight-source-a' });
861
+ const marketMockSourceCardB = makeMockSourceCard({ id: 'card-market-prices-preflight-source-b', includeProjection: true });
862
+ const marketMockSourceCardC = makeMockSourceCard({ id: 'card-market-prices-preflight-source-c', secondBindTo: 'quotesBackup' });
863
+ const marketMockSourceCardD = makeMockSourceCard({ id: 'card-market-prices-preflight-source-d', bindTo: 'quotesPrimary' });
864
+ const marketMockSourceCardE = makeMockSourceCard({ id: 'card-market-prices-preflight-source-e', bindTo: 'quotesEcho' });
865
+ const marketMissingMockCard = makeMockSourceCard({ id: 'card-market-prices-preflight-missing', missingMock: true });
866
+ const invalidCard = {
867
+ ...deepCloneJson(marketCard),
868
+ id: '',
869
+ source_defs: [{ bindTo: '', mock: 'quotes' }],
870
+ view: { layout: { kind: 'stack' }, elements: [{ id: 'broken' }] },
871
+ };
872
+
873
+ const validateSuccessCases = [
874
+ { name: 'portfolio live', card: portfolioCard, expectCardId: 'card-portfolio' },
875
+ { name: 'market live', card: marketCard, expectCardId: 'card-market-prices' },
876
+ { name: 'portfolio-value live', card: portfolioValueCard, expectCardId: 'card-portfolio-value' },
877
+ { name: 'portfolio variant', card: portfolioVariantA, expectCardId: 'card-portfolio-preflight-a' },
878
+ { name: 'portfolio variant B', card: portfolioVariantB, expectCardId: 'card-portfolio-preflight-b' },
879
+ ];
880
+ for (const tc of validateSuccessCases) {
881
+ const body = expectPreflightSuccess(await httpMcp('preflight.validate-candidate-card-definition', {
882
+ candidate_card_content: tc.card,
883
+ }), `T4 validate success (${tc.name})`);
884
+ assert(body.cardId === tc.expectCardId, `T4 validate ${tc.name} cardId mismatch`);
885
+ assert(body.isValid === true, `T4 validate ${tc.name} expected isValid=true`);
886
+ assert(Array.isArray(body.issues) && body.issues.length === 0, `T4 validate ${tc.name} expected no issues`);
887
+ console.log(`[T4.validate] ok: ${tc.name}`);
888
+ }
889
+
890
+ const validateFailureBody = expectPreflightSuccess(await httpMcp('preflight.validate-candidate-card-definition', {
891
+ candidate_card_content: invalidCard,
892
+ }), 'T4 validate failure (invalid card)');
893
+ assert(validateFailureBody.isValid === false, 'T4 validate invalid card should be invalid');
894
+ assert(Array.isArray(validateFailureBody.issues) && validateFailureBody.issues.length > 0, 'T4 validate invalid card should report issues');
895
+ console.log('[T4.validate] ok: invalid card reports validation issues');
896
+
897
+ const materializeSuccessCases = [
898
+ {
899
+ name: 'portfolio live empty mocks',
900
+ card: portfolioCard,
901
+ mockRequires: {},
902
+ mockFetchedSources: {},
903
+ verify: (body) => {
904
+ assert(Array.isArray(body.provides_outputs?.holdings), 'T4 materialize portfolio holdings missing');
905
+ assert(body.provides_outputs.holdings.length === baseHoldings.length, 'T4 materialize portfolio holdings length mismatch');
906
+ assert(body.rendered_view?.elements?.[0]?.kind === 'editable-table', 'T4 materialize portfolio rendered_view mismatch');
907
+ },
908
+ },
909
+ {
910
+ name: 'portfolio variant with extra holding',
911
+ card: portfolioVariantA,
912
+ mockRequires: {},
913
+ mockFetchedSources: {},
914
+ verify: (body) => {
915
+ assert(Array.isArray(body.provides_outputs?.holdings), 'T4 materialize portfolio variant holdings missing');
916
+ assert(body.provides_outputs.holdings.length === baseHoldings.length + 1, 'T4 materialize portfolio variant holdings length mismatch');
917
+ },
918
+ },
919
+ {
920
+ name: 'portfolio variant B with extra holding',
921
+ card: portfolioVariantB,
922
+ mockRequires: {},
923
+ mockFetchedSources: {},
924
+ verify: (body) => {
925
+ assert(Array.isArray(body.provides_outputs?.holdings), 'T4 materialize portfolio variant B holdings missing');
926
+ assert(body.provides_outputs.holdings.length === baseHoldings.length + 1, 'T4 materialize portfolio variant B holdings length mismatch');
927
+ },
928
+ },
929
+ {
930
+ name: 'portfolio-value live with mock requires',
931
+ card: portfolioValueCard,
932
+ mockRequires: { holdings: baseHoldings, quotes: mockQuotes },
933
+ mockFetchedSources: {},
934
+ verify: (body) => {
935
+ assert(Array.isArray(body.computed_values?.positions) && body.computed_values.positions.length > 0, 'T4 materialize portfolio-value positions missing');
936
+ assert(Array.isArray(body.provides_outputs?.positions) && body.provides_outputs.positions.length > 0, 'T4 materialize portfolio-value provides missing');
937
+ assert(body.rendered_view?.elements?.length === 3, 'T4 materialize portfolio-value rendered_view length mismatch');
938
+ },
939
+ },
940
+ {
941
+ name: 'portfolio-value subset requires',
942
+ card: portfolioValueCard,
943
+ mockRequires: {
944
+ holdings: baseHoldings.slice(0, 2),
945
+ quotes: { quoteResponse: { result: mockQuotes.quoteResponse.result.slice(0, 2), error: null } },
946
+ },
947
+ mockFetchedSources: {},
948
+ verify: (body) => {
949
+ assert(Array.isArray(body.computed_values?.positions) && body.computed_values.positions.length === 2, 'T4 materialize portfolio-value subset positions mismatch');
950
+ assert(Array.isArray(body.provides_outputs?.positions) && body.provides_outputs.positions.length === 2, 'T4 materialize portfolio-value subset provides mismatch');
951
+ },
952
+ },
953
+ ];
954
+ for (const tc of materializeSuccessCases) {
955
+ const body = expectPreflightSuccess(await httpMcp('preflight.materialize-candidate-card', {
956
+ candidate_card_content: tc.card,
957
+ mock_requires: tc.mockRequires,
958
+ mock_fetched_sources: tc.mockFetchedSources,
959
+ }), `T4 materialize success (${tc.name})`);
960
+ assert(body.ok === true, `T4 materialize ${tc.name} expected ok=true`);
961
+ assert(Array.isArray(body.errors) && body.errors.length === 0, `T4 materialize ${tc.name} expected no errors`);
962
+ tc.verify(body);
963
+ console.log(`[T4.materialize] ok: ${tc.name}`);
964
+ }
965
+
966
+ const materializeFailureRes = await httpMcp('preflight.materialize-candidate-card', {
967
+ candidate_card_content: portfolioCard,
968
+ });
969
+ assert(materializeFailureRes.status === 400, `T4 materialize missing args expected 400, got ${materializeFailureRes.status}`);
970
+ assert(materializeFailureRes.data?.error === 'MCP tool requires mock_requires', 'T4 materialize missing args error mismatch');
971
+ console.log('[T4.materialize] ok: missing required mocks is rejected');
972
+
973
+ const probeSuccessCases = [
974
+ { name: 'single mock source base', card: marketMockSourceCardA, sourceIdx: 0, bindTo: 'quotes', mockProjections: {} },
975
+ { name: 'single mock source with projection payload', card: marketMockSourceCardB, sourceIdx: 0, bindTo: 'quotes', mockProjections: { passthrough: 'ok' } },
976
+ { name: 'two mock sources first entry', card: marketMockSourceCardC, sourceIdx: 0, bindTo: 'quotes', mockProjections: {} },
977
+ { name: 'two mock sources second entry', card: marketMockSourceCardC, sourceIdx: 1, bindTo: 'quotesBackup', mockProjections: {} },
978
+ { name: 'single mock source alternate bindTo', card: marketMockSourceCardD, sourceIdx: 0, bindTo: 'quotesPrimary', mockProjections: {} },
979
+ ];
980
+ for (const tc of probeSuccessCases) {
981
+ const body = expectPreflightSuccess(await httpMcp('preflight.probe-single-source-in-candidate-card', {
982
+ candidate_card_content: tc.card,
983
+ source_idx: tc.sourceIdx,
984
+ mock_projections: tc.mockProjections,
985
+ }), `T4 probe success (${tc.name})`);
986
+ assert(body.bindTo === tc.bindTo, `T4 probe ${tc.name} bindTo mismatch`);
987
+ assert(body.reachable === true, `T4 probe ${tc.name} expected reachable=true`);
988
+ assert(typeof body.latencyMs === 'number', `T4 probe ${tc.name} expected numeric latencyMs`);
989
+ console.log(`[T4.probe] ok: ${tc.name}`);
990
+ }
991
+
992
+ const probeFailureRes = await httpMcp('preflight.probe-single-source-in-candidate-card', {
993
+ candidate_card_content: marketMissingMockCard,
994
+ source_idx: 0,
995
+ mock_projections: {},
996
+ });
997
+ assert(probeFailureRes.status === 400, `T4 probe failure expected 400, got ${probeFailureRes.status}`);
998
+ assert(typeof probeFailureRes.data?.error === 'string' && probeFailureRes.data.error.length > 0, 'T4 probe failure expected error text');
999
+ console.log('[T4.probe] ok: missing mock source returns HTTP error');
1000
+
1001
+ const runSourceSuccessCases = [
1002
+ { name: 'single mock source base', card: marketMockSourceCardA, sourceIdx: 0, bindTo: 'quotes' },
1003
+ { name: 'single mock source with projection payload', card: marketMockSourceCardB, sourceIdx: 0, bindTo: 'quotes' },
1004
+ { name: 'two mock sources first entry', card: marketMockSourceCardC, sourceIdx: 0, bindTo: 'quotes' },
1005
+ { name: 'two mock sources second entry', card: marketMockSourceCardC, sourceIdx: 1, bindTo: 'quotesBackup' },
1006
+ { name: 'single mock source alternate bindTo', card: marketMockSourceCardE, sourceIdx: 0, bindTo: 'quotesEcho' },
1007
+ ];
1008
+ for (const tc of runSourceSuccessCases) {
1009
+ const body = expectPreflightSuccess(await httpMcp('preflight.run-single-source-in-candidate-card', {
1010
+ candidate_card_content: tc.card,
1011
+ source_idx: tc.sourceIdx,
1012
+ mock_projections: {},
1013
+ }), `T4 run-source success (${tc.name})`);
1014
+ assert(body.bindTo === tc.bindTo, `T4 run-source ${tc.name} bindTo mismatch`);
1015
+ assert(body.ok === true, `T4 run-source ${tc.name} expected ok=true`);
1016
+ assert(Array.isArray(body.issues) && body.issues.length === 0, `T4 run-source ${tc.name} expected no issues`);
1017
+ assert(Array.isArray(body.result?.quoteResponse?.result) && body.result.quoteResponse.result.length > 0, `T4 run-source ${tc.name} result shape mismatch`);
1018
+ console.log(`[T4.run-source] ok: ${tc.name}`);
1019
+ }
1020
+
1021
+ const runSourceFailureBody = expectPreflightSuccess(await httpMcp('preflight.run-single-source-in-candidate-card', {
1022
+ candidate_card_content: marketMissingMockCard,
1023
+ source_idx: 0,
1024
+ mock_projections: {},
1025
+ }), 'T4 run-source failure (missing mock source)');
1026
+ assert(runSourceFailureBody.ok === false, 'T4 run-source missing mock should set ok=false');
1027
+ assert(Array.isArray(runSourceFailureBody.issues) && runSourceFailureBody.issues.length > 0, 'T4 run-source missing mock should report issues');
1028
+ console.log('[T4.run-source] ok: missing mock source returns ok=false with issues');
1029
+
1030
+ const liveRunCardId = String(marketCard?.id || 'card-market-prices');
1031
+
1032
+ const liveRunSourceBody = expectPreflightSuccess(await httpMcp('preflight.run-single-source-in-live-card', {
1033
+ card_id: liveRunCardId,
1034
+ source_idx: 0,
1035
+ mock_requires: { holdings: baseHoldings },
1036
+ }), 'T4 run-source live card success');
1037
+ assert(liveRunSourceBody.bindTo === 'quotes', 'T4 run-source live card bindTo mismatch');
1038
+ assert(liveRunSourceBody.ok === true, 'T4 run-source live card expected ok=true');
1039
+ assert(Array.isArray(liveRunSourceBody.issues) && liveRunSourceBody.issues.length === 0, 'T4 run-source live card expected no issues');
1040
+ assert(Array.isArray(liveRunSourceBody.result) && liveRunSourceBody.result.length === baseHoldings.length, 'T4 run-source live card result shape mismatch');
1041
+ console.log('[T4.run-source-live] ok: live card source run returns candidate-compatible shape');
1042
+
1043
+ const liveRunRequiresBody = expectPreflightSuccess(await httpMcp('preflight.run-single-source-in-live-card', {
1044
+ card_id: liveRunCardId,
1045
+ source_idx: 0,
1046
+ mock_requires: { holdings: baseHoldings },
1047
+ }), 'T4 run-source live card uses mock_requires in projections');
1048
+ assert(liveRunRequiresBody.bindTo === 'quotes', 'T4 run-source live card requires bindTo mismatch');
1049
+ assert(liveRunRequiresBody.ok === true, 'T4 run-source live card requires expected ok=true');
1050
+ assert(Array.isArray(liveRunRequiresBody.issues) && liveRunRequiresBody.issues.length === 0, 'T4 run-source live card requires expected no issues');
1051
+ assert(Array.isArray(liveRunRequiresBody.result) && liveRunRequiresBody.result.length === baseHoldings.length, 'T4 run-source live card requires result shape mismatch');
1052
+ console.log('[T4.run-source-live] ok: non-empty mock_requires is consumed via source projections');
1053
+
1054
+ const liveRunOutOfRangeRes = await httpMcp('preflight.run-single-source-in-live-card', {
1055
+ card_id: liveRunCardId,
1056
+ source_idx: 9,
1057
+ mock_requires: {},
1058
+ });
1059
+ assert(liveRunOutOfRangeRes.status === 400, `T4 run-source live card out-of-range expected 400, got ${liveRunOutOfRangeRes.status}`);
1060
+ assert(typeof liveRunOutOfRangeRes.data?.error === 'string' && liveRunOutOfRangeRes.data.error.length > 0, 'T4 run-source live card out-of-range expected error text');
1061
+ console.log('[T4.run-source-live] ok: out-of-range source_idx is rejected with HTTP error');
1062
+
1063
+ const liveRunMissingMockRequiresRes = await httpMcp('preflight.run-single-source-in-live-card', {
1064
+ card_id: liveRunCardId,
1065
+ source_idx: 0,
1066
+ });
1067
+ assert(liveRunMissingMockRequiresRes.status === 400, `T4 run-source live card missing mock_requires expected 400, got ${liveRunMissingMockRequiresRes.status}`);
1068
+ assert(liveRunMissingMockRequiresRes.data?.error === 'MCP tool requires mock_requires', 'T4 run-source live card missing mock_requires error mismatch');
1069
+ console.log('[T4.run-source-live] ok: missing mock_requires is rejected');
1070
+
1071
+ const runCycleSuccessCases = [
1072
+ {
1073
+ name: 'portfolio live',
1074
+ card: portfolioCard,
1075
+ mockRequires: {},
1076
+ verify: (body) => {
1077
+ assert(Array.isArray(body.provides_outputs?.holdings) && body.provides_outputs.holdings.length === baseHoldings.length, 'T4 run-cycle portfolio provides mismatch');
1078
+ assert(body.rendered_view?.elements?.[0]?.kind === 'editable-table', 'T4 run-cycle portfolio rendered_view mismatch');
1079
+ },
1080
+ },
1081
+ {
1082
+ name: 'portfolio variant',
1083
+ card: portfolioVariantB,
1084
+ mockRequires: {},
1085
+ verify: (body) => {
1086
+ assert(Array.isArray(body.provides_outputs?.holdings) && body.provides_outputs.holdings.length === baseHoldings.length + 1, 'T4 run-cycle portfolio variant provides mismatch');
1087
+ },
1088
+ },
1089
+ {
1090
+ name: 'portfolio variant B',
1091
+ card: portfolioVariantB,
1092
+ mockRequires: {},
1093
+ verify: (body) => {
1094
+ assert(Array.isArray(body.provides_outputs?.holdings) && body.provides_outputs.holdings.length === baseHoldings.length + 1, 'T4 run-cycle portfolio variant B provides mismatch');
1095
+ assert(body.rendered_view?.elements?.[0]?.kind === 'editable-table', 'T4 run-cycle portfolio variant B rendered_view mismatch');
1096
+ },
1097
+ },
1098
+ {
1099
+ name: 'portfolio-value with full requires',
1100
+ card: portfolioValueCard,
1101
+ mockRequires: { holdings: baseHoldings, quotes: mockQuotes },
1102
+ verify: (body) => {
1103
+ assert(Array.isArray(body.provides_outputs?.positions) && body.provides_outputs.positions.length > 0, 'T4 run-cycle portfolio-value provides mismatch');
1104
+ assert(body.rendered_view?.elements?.length === 3, 'T4 run-cycle portfolio-value rendered_view mismatch');
1105
+ },
1106
+ },
1107
+ {
1108
+ name: 'portfolio-value subset requires',
1109
+ card: portfolioValueCard,
1110
+ mockRequires: {
1111
+ holdings: baseHoldings.slice(0, 2),
1112
+ quotes: { quoteResponse: { result: mockQuotes.quoteResponse.result.slice(0, 2), error: null } },
1113
+ },
1114
+ verify: (body) => {
1115
+ assert(Array.isArray(body.provides_outputs?.positions) && body.provides_outputs.positions.length === 2, 'T4 run-cycle portfolio-value subset length mismatch');
1116
+ },
1117
+ },
1118
+ {
1119
+ name: 'market-prices with live source simulation',
1120
+ card: marketCard,
1121
+ mockRequires: { holdings: baseHoldings.slice(0, 3) },
1122
+ verify: (body) => {
1123
+ const quoteRows = body.provides_outputs?.quotes?.quoteResponse?.result;
1124
+ assert(Array.isArray(quoteRows) && quoteRows.length === 3, 'T4 run-cycle market-prices provides result length mismatch');
1125
+ assert(typeof quoteRows[0]?.symbol === 'string' && quoteRows[0].symbol.length > 0, 'T4 run-cycle market-prices provides symbol missing');
1126
+
1127
+ const resolvedRows = body.rendered_view?.elements?.[0]?.resolved;
1128
+ assert(Array.isArray(resolvedRows) && resolvedRows.length === 3, 'T4 run-cycle market-prices rendered resolved length mismatch');
1129
+ assert(typeof resolvedRows[0]?.ticker === 'string' && resolvedRows[0].ticker.length > 0, 'T4 run-cycle market-prices rendered ticker missing');
1130
+ assert(typeof resolvedRows[0]?.price === 'number', 'T4 run-cycle market-prices rendered price missing');
1131
+ },
1132
+ },
1133
+ ];
1134
+ for (const tc of runCycleSuccessCases) {
1135
+ const body = expectPreflightSuccess(await httpMcp('preflight.run-one-cycle-with-candidate-card', {
1136
+ candidate_card_content: tc.card,
1137
+ mock_requires: tc.mockRequires,
1138
+ }), `T4 run-cycle success (${tc.name})`);
1139
+ assert(body.ok === true, `T4 run-cycle ${tc.name} expected ok=true`);
1140
+ assert(Array.isArray(body.issues) && body.issues.length === 0, `T4 run-cycle ${tc.name} expected no issues`);
1141
+ tc.verify(body);
1142
+ console.log(`[T4.run-cycle] ok: ${tc.name}`);
1143
+ }
1144
+
1145
+ }
1146
+
775
1147
  console.log('\n=== All smoke checks passed ===\n');
776
1148
  } finally {
777
1149
  if (chatSseClientId) {
@@ -6,13 +6,13 @@
6
6
  <title>Example Board Demo (LocalStorage Runtime)</title>
7
7
  <link rel="icon" type="image/svg+xml" href="../../browser/favicon.svg" />
8
8
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
9
- <script src="https://cdn.jsdelivr.net/npm/yaml-flow@8.5.0/browser/compute-jsonata.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow@8.5.2/browser/compute-jsonata.js"></script>
10
10
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
11
11
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
12
12
  <script src="https://cdn.jsdelivr.net/npm/dompurify/dist/purify.min.js"></script>
13
13
  <script src="https://cdn.jsdelivr.net/npm/leader-line/leader-line.min.js"></script>
14
- <script src="https://cdn.jsdelivr.net/npm/yaml-flow@8.5.0/browser/live-cards.js"></script>
15
- <script src="https://cdn.jsdelivr.net/npm/yaml-flow@8.5.0/browser/board-livecards-localstorage.js"></script>
14
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow@8.5.2/browser/live-cards.js"></script>
15
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow@8.5.2/browser/board-livecards-localstorage.js"></script>
16
16
  </head>
17
17
  <body class="bg-light">
18
18
  <div class="container-fluid py-3">
@@ -1,4 +1,4 @@
1
- import { a as CommandInput, C as CommandResult } from './board-live-cards-public-CvkDfZQ7.cjs';
1
+ import { a as CommandInput, C as CommandResult } from './board-live-cards-public-B13InXhC.cjs';
2
2
  import { A as ArtifactInfo, a as ArtifactsStore } from './artifacts-store-lib-CVgtQrNZ.cjs';
3
3
  import './storage-interface-B2WD9D5n.cjs';
4
4
  import './execution-refs.cjs';
@@ -1,4 +1,4 @@
1
- import { a as CommandInput, C as CommandResult } from './board-live-cards-public-DdVhH4M-.js';
1
+ import { a as CommandInput, C as CommandResult } from './board-live-cards-public-BGS22cMb.js';
2
2
  import { A as ArtifactInfo, a as ArtifactsStore } from './artifacts-store-lib-D-k-E8Vy.js';
3
3
  import './storage-interface-B2WD9D5n.js';
4
4
  import './execution-refs.js';