yaml-flow 8.6.4 → 8.7.1

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 (173) hide show
  1. package/browser/adapters/firebase-storage.js +3 -0
  2. package/browser/adapters/firestore-storage.js +3 -0
  3. package/browser/adapters/localstorage-storage.js +4 -0
  4. package/browser/asset-integrity.json +22 -6
  5. package/browser/live-cards.schema.json +10 -1
  6. package/browser/server-runtime-controlface.js +8 -0
  7. package/examples/ARCHITECTURE.md +5 -32
  8. package/examples/board/demo-shell-with-server.html +2 -2
  9. package/examples/board/doc.html +2 -2
  10. package/examples/board/server/board-server.js +4 -2
  11. package/examples/board/test/server-http-test.js +73 -79
  12. package/examples/board-firestore/README.md +81 -0
  13. package/examples/board-firestore/browser/board-runtime.js +263 -0
  14. package/examples/board-firestore/firestore.indexes.json +29 -0
  15. package/examples/board-firestore/package.json +14 -0
  16. package/examples/board-firestore/server/adapters/firestore-archive-factory.js +59 -0
  17. package/examples/board-firestore/server/adapters/firestore-blob-storage.js +82 -0
  18. package/examples/board-firestore/server/adapters/firestore-board-adapter.js +127 -0
  19. package/examples/board-firestore/server/adapters/firestore-journal-storage.js +54 -0
  20. package/examples/board-firestore/server/adapters/firestore-kv-storage.js +47 -0
  21. package/examples/board-firestore/server/adapters/firestore-lock.js +62 -0
  22. package/examples/board-firestore/server/adapters/firestore-queue-storage.js +186 -0
  23. package/examples/board-firestore/server/adapters/firestore-scratch-storage.js +50 -0
  24. package/examples/board-firestore/server/worker.js +146 -0
  25. package/lib/{artifacts-store-lib-BR-Samty.d.cts → artifacts-store-lib-D9nMkVcE.d.cts} +1 -1
  26. package/lib/{artifacts-store-lib-DT7XlWUL.d.ts → artifacts-store-lib-DSSMqVL2.d.ts} +1 -1
  27. package/lib/artifacts-store-public.d.cts +2 -2
  28. package/lib/artifacts-store-public.d.ts +2 -2
  29. package/lib/board-live-cards-mcp.cjs +1 -1
  30. package/lib/board-live-cards-mcp.d.cts +51 -3
  31. package/lib/board-live-cards-mcp.d.ts +51 -3
  32. package/lib/board-live-cards-mcp.js +1 -1
  33. package/lib/board-live-cards-node.cjs +5 -5
  34. package/lib/board-live-cards-node.d.cts +16 -11
  35. package/lib/board-live-cards-node.d.ts +16 -11
  36. package/lib/board-live-cards-node.js +5 -5
  37. package/lib/{board-live-cards-public-BMUIPOrc.d.ts → board-live-cards-public-JNRKfBZy.d.ts} +1 -1
  38. package/lib/{board-live-cards-public-wkNmBIRC.d.cts → board-live-cards-public-LlVUQPL2.d.cts} +1 -1
  39. package/lib/board-live-cards-public-async-Di9QB141.d.cts +55 -0
  40. package/lib/board-live-cards-public-async-fwd1QI82.d.ts +55 -0
  41. package/lib/board-live-cards-public.cjs +1 -1
  42. package/lib/board-live-cards-public.d.cts +1 -1
  43. package/lib/board-live-cards-public.d.ts +1 -1
  44. package/lib/board-live-cards-public.js +1 -1
  45. package/lib/board-live-cards-server-runtime.cjs +1 -1
  46. package/lib/board-live-cards-server-runtime.d.cts +10 -6
  47. package/lib/board-live-cards-server-runtime.d.ts +10 -6
  48. package/lib/board-live-cards-server-runtime.js +1 -1
  49. package/lib/board-livegraph-runtime/index.cjs +1 -1
  50. package/lib/board-livegraph-runtime/index.js +1 -1
  51. package/lib/board-platform-adapter-async-BfHmHdx2.d.cts +129 -0
  52. package/lib/board-platform-adapter-async-DYahVzIK.d.ts +129 -0
  53. package/lib/board-worker-adapter.cjs +3 -3
  54. package/lib/board-worker-adapter.js +3 -3
  55. package/lib/card-compute/index.cjs +1 -1
  56. package/lib/card-compute/index.js +1 -1
  57. package/lib/card-store-public.d.cts +1 -1
  58. package/lib/card-store-public.d.ts +1 -1
  59. package/lib/card-validation.cjs +1 -1
  60. package/lib/card-validation.js +1 -1
  61. package/lib/{chat-storage-lib-BIUbE-fM.d.cts → chat-storage-lib-B9Q34Dyv.d.cts} +1 -1
  62. package/lib/{chat-storage-lib-BlG-sobS.d.ts → chat-storage-lib-DB9iSai2.d.ts} +1 -1
  63. package/lib/chat-store-public.d.cts +2 -2
  64. package/lib/chat-store-public.d.ts +2 -2
  65. package/lib/chunk-272IYUKT.cjs +2 -0
  66. package/lib/chunk-3KC6LBOG.js +3 -0
  67. package/lib/chunk-5XHOHTLZ.cjs +2 -0
  68. package/lib/chunk-6APH25VI.js +2 -0
  69. package/lib/chunk-76C7N4YT.js +3 -0
  70. package/lib/chunk-7FGPOGRV.cjs +2 -0
  71. package/lib/chunk-7ICPAABP.cjs +7 -0
  72. package/lib/chunk-ASR44K7H.cjs +3 -0
  73. package/lib/chunk-CPAXTVBQ.cjs +2 -0
  74. package/lib/chunk-EGRHWZRV.js +2 -0
  75. package/lib/chunk-EZENHAVZ.cjs +2 -0
  76. package/lib/chunk-FO4KNVU7.cjs +2 -0
  77. package/lib/chunk-GL2OHR2E.cjs +2 -0
  78. package/lib/chunk-HWYMZK3N.cjs +3 -0
  79. package/lib/chunk-IPLSRN6P.cjs +4 -0
  80. package/lib/{chunk-H5HBXPOI.cjs → chunk-J6EGN6S4.cjs} +3 -3
  81. package/lib/chunk-JH37NJGP.js +3 -0
  82. package/lib/chunk-JJL5VOQZ.cjs +3 -0
  83. package/lib/chunk-KAWQPLIE.cjs +2 -0
  84. package/lib/chunk-LPXVVMQT.cjs +2 -0
  85. package/lib/chunk-NJJ7WEDT.cjs +2 -0
  86. package/lib/chunk-NKIQRCOM.cjs +2 -0
  87. package/lib/chunk-NM6O35RY.cjs +2 -0
  88. package/lib/chunk-NTICU4OK.js +2 -0
  89. package/lib/chunk-O7NOHKVR.js +2 -0
  90. package/lib/chunk-PBOQ4HYB.cjs +2 -0
  91. package/lib/{chunk-VMW4Z6EF.js → chunk-PRKRXAVN.js} +3 -3
  92. package/lib/chunk-QJVR3FWQ.js +2 -0
  93. package/lib/chunk-S44QZUDX.js +2 -0
  94. package/lib/chunk-SGV7PU4H.js +2 -0
  95. package/lib/chunk-TSN3RTXT.js +4 -0
  96. package/lib/chunk-VXJHBWK3.js +2 -0
  97. package/lib/chunk-WHDEBJLT.js +7 -0
  98. package/lib/chunk-XYN5D3GL.js +2 -0
  99. package/lib/chunk-YBYXCFAI.js +2 -0
  100. package/lib/chunk-YGALANRO.js +2 -0
  101. package/lib/chunk-ZCNN6XPV.js +2 -0
  102. package/lib/chunk-ZJ5M5COT.js +2 -0
  103. package/lib/cloud-storage.cjs +1 -1
  104. package/lib/cloud-storage.d.cts +5 -3
  105. package/lib/cloud-storage.d.ts +5 -3
  106. package/lib/cloud-storage.js +1 -1
  107. package/lib/continuous-event-graph/index.cjs +1 -1
  108. package/lib/continuous-event-graph/index.js +1 -1
  109. package/lib/firebase-storage/index.cjs +3 -0
  110. package/lib/firebase-storage/index.d.cts +57 -0
  111. package/lib/firebase-storage/index.d.ts +57 -0
  112. package/lib/firebase-storage/index.js +3 -0
  113. package/lib/firestore-storage/index.cjs +3 -0
  114. package/lib/firestore-storage/index.d.cts +111 -0
  115. package/lib/firestore-storage/index.d.ts +111 -0
  116. package/lib/firestore-storage/index.js +3 -0
  117. package/lib/index.cjs +2 -2
  118. package/lib/index.js +1 -1
  119. package/lib/localstorage-storage/index.cjs +2 -0
  120. package/lib/localstorage-storage/index.d.cts +39 -0
  121. package/lib/localstorage-storage/index.d.ts +39 -0
  122. package/lib/localstorage-storage/index.js +2 -0
  123. package/lib/mcp-tool-registries-BBObLYga.d.ts +41 -0
  124. package/lib/mcp-tool-registries-W3TRj6O5.d.cts +41 -0
  125. package/lib/queue-lane-registry-PaZuFpwp.d.cts +30 -0
  126. package/lib/queue-lane-registry-PaZuFpwp.d.ts +30 -0
  127. package/lib/server-jobs-queue-runner/index.cjs +2 -0
  128. package/lib/server-jobs-queue-runner/index.d.cts +22 -0
  129. package/lib/server-jobs-queue-runner/index.d.ts +22 -0
  130. package/lib/server-jobs-queue-runner/index.js +2 -0
  131. package/lib/server-runtime/index.cjs +1 -1
  132. package/lib/server-runtime/index.d.cts +11 -17
  133. package/lib/server-runtime/index.d.ts +11 -17
  134. package/lib/server-runtime/index.js +1 -1
  135. package/lib/server-runtime-agentface/index.cjs +2 -0
  136. package/lib/server-runtime-agentface/index.d.cts +53 -0
  137. package/lib/server-runtime-agentface/index.d.ts +53 -0
  138. package/lib/server-runtime-agentface/index.js +2 -0
  139. package/lib/server-runtime-controlface/index.cjs +2 -0
  140. package/lib/server-runtime-controlface/index.d.cts +29 -0
  141. package/lib/server-runtime-controlface/index.d.ts +29 -0
  142. package/lib/server-runtime-controlface/index.js +2 -0
  143. package/lib/server-runtime-core/index.cjs +2 -0
  144. package/lib/server-runtime-core/index.d.cts +378 -0
  145. package/lib/server-runtime-core/index.d.ts +378 -0
  146. package/lib/server-runtime-core/index.js +2 -0
  147. package/lib/server-runtime-watchers/index.cjs +2 -0
  148. package/lib/server-runtime-watchers/index.d.cts +127 -0
  149. package/lib/server-runtime-watchers/index.d.ts +127 -0
  150. package/lib/server-runtime-watchers/index.js +2 -0
  151. package/lib/server-runtime-webhooks/index.cjs +2 -0
  152. package/lib/server-runtime-webhooks/index.d.cts +34 -0
  153. package/lib/server-runtime-webhooks/index.d.ts +34 -0
  154. package/lib/server-runtime-webhooks/index.js +2 -0
  155. package/lib/storage-async-interface-BRR4eBjx.d.cts +81 -0
  156. package/lib/storage-async-interface-DhlOVPSp.d.ts +81 -0
  157. package/lib/{queue-lane-registry-BPKWWgd4.d.cts → types-Ba8H5_Wo.d.cts} +10 -34
  158. package/lib/{queue-lane-registry-Be6c0ftj.d.ts → types-SO5OZm4s.d.ts} +10 -34
  159. package/package.json +46 -2
  160. package/schema/live-cards.schema.json +10 -1
  161. package/examples/board-local/demo-shell-localstorage.html +0 -843
  162. package/lib/board-live-cards-public-async-DKZqbJVU.d.ts +0 -256
  163. package/lib/board-live-cards-public-async-dMWNbWq6.d.cts +0 -256
  164. package/lib/chunk-KXWT3CY6.cjs +0 -8
  165. package/lib/chunk-MLVTJASJ.js +0 -2
  166. package/lib/chunk-N6P2JW4W.js +0 -3
  167. package/lib/chunk-NMZ6XNLB.cjs +0 -3
  168. package/lib/chunk-OEFTOO47.cjs +0 -3
  169. package/lib/chunk-OJLA6NLU.js +0 -8
  170. package/lib/chunk-R5L5WUKN.js +0 -2
  171. package/lib/chunk-VLBB3D6B.js +0 -3
  172. package/lib/chunk-WOALA3V5.cjs +0 -2
  173. package/lib/chunk-YEB5QHGE.cjs +0 -2
@@ -544,22 +544,13 @@ try {
544
544
  console.log(`[T0.3] completed: ${JSON.stringify(t0Summary)}`);
545
545
 
546
546
  console.log('\n=== T0 Step 4: board-status cross-check ===');
547
- const statusRes = await httpGet(`${BASE}/board-status`);
548
- assert(statusRes.status === 200, `board-status returned ${statusRes.status}`);
549
- const httpSummary = statusRes.data?.statusSnapshot?.summary;
550
- assert(httpSummary, 'statusSnapshot.summary missing from board-status');
551
547
  const statusMcpRes = await httpMcp('inspect.board-runtime-status', {});
552
548
  assert(statusMcpRes.status === 200, `inspect.board-runtime-status returned ${statusMcpRes.status}`);
553
549
  assert(statusMcpRes.data?.status === 'success', `inspect.board-runtime-status failed: ${JSON.stringify(statusMcpRes.data)}`);
554
550
  const mcpSummary = statusMcpRes.data?.data?.summary;
555
551
  assert(mcpSummary, 'summary missing from inspect.board-runtime-status');
556
- const comparableStatusKeys = ['card_count', 'completed', 'eligible', 'pending', 'blocked', 'in_progress', 'failed', 'unresolved'];
557
- const httpComparableSummary = Object.fromEntries(comparableStatusKeys.map((key) => [key, httpSummary[key]]));
558
- const mcpComparableSummary = Object.fromEntries(comparableStatusKeys.map((key) => [key, mcpSummary[key]]));
559
- assert(JSON.stringify(httpComparableSummary) === JSON.stringify(mcpComparableSummary),
560
- `HTTP board-status summary mismatch vs MCP summary: http=${JSON.stringify(httpComparableSummary)} mcp=${JSON.stringify(mcpComparableSummary)}`);
561
- assert(httpSummary.completed === httpSummary.card_count, `not all complete: ${JSON.stringify(httpSummary)}`);
562
- console.log(`[T0.4] board-status: ${JSON.stringify(httpSummary)}`);
552
+ assert(mcpSummary.completed === mcpSummary.card_count, `not all complete: ${JSON.stringify(mcpSummary)}`);
553
+ console.log(`[T0.4] board-status: ${JSON.stringify(mcpSummary)}`);
563
554
 
564
555
  // Verify computed_values arrived for portfolio-value card
565
556
  const t0Positions = NS.computedValues['card-portfolio-value']?.positions;
@@ -634,30 +625,32 @@ try {
634
625
  if (skipT2) {
635
626
  console.log('\n=== T2: skipped (--skip-t2) ===');
636
627
  } else {
637
- console.log('\n=== T2: plain file upload -> card_data.files -> download ===');
638
- const t2CardBefore = await httpGet(`${BASE}/cards/${T2_FILE_CARD_ID}`);
628
+ console.log('\n=== T2: MCP file upload -> card_data.files -> download ===');
629
+ const t2CardBefore = await httpMcp('manage.read-card', { card_id: T2_FILE_CARD_ID });
639
630
  assert(t2CardBefore.status === 200, `T2 pre card read returned ${t2CardBefore.status}`);
640
- const t2FilesBefore = Array.isArray(t2CardBefore.data?.card_data?.files)
641
- ? t2CardBefore.data.card_data.files
631
+ const t2FilesBefore = Array.isArray(t2CardBefore.data?.data?.[0]?.card_data?.files)
632
+ ? t2CardBefore.data.data[0].card_data.files
642
633
  : [];
643
634
  const t2BeforeCount = t2FilesBefore.length;
644
635
 
645
636
  const t2UploadText = `plain-file-upload-${Date.now()}`;
646
637
  const t2UploadName = 't2-upload.txt';
647
- const t2UploadRes = await httpUploadChatFile(
648
- `${BASE}/cards/${T2_FILE_CARD_ID}/files`,
649
- t2UploadName,
650
- t2UploadText,
651
- );
638
+ const t2UploadRes = await httpMcpControlplane('manage.upload-card-file', {
639
+ board_id: BOARD_ID,
640
+ card_id: T2_FILE_CARD_ID,
641
+ file_name: t2UploadName,
642
+ content_type: 'text/plain; charset=utf-8',
643
+ base64: Buffer.from(t2UploadText, 'utf-8').toString('base64'),
644
+ });
652
645
  assert(t2UploadRes.status === 200, `T2 file upload returned ${t2UploadRes.status}`);
653
- const t2UploadedFile = t2UploadRes.data?.file;
646
+ const t2UploadedFile = t2UploadRes.data?.data?.file;
654
647
  assert(t2UploadedFile && typeof t2UploadedFile === 'object', 'T2 upload response missing file metadata');
655
648
  assert(String(t2UploadedFile?.name || '') === t2UploadName, 'T2 uploaded file name mismatch');
656
649
 
657
- const t2CardAfter = await httpGet(`${BASE}/cards/${T2_FILE_CARD_ID}`);
650
+ const t2CardAfter = await httpMcp('manage.read-card', { card_id: T2_FILE_CARD_ID });
658
651
  assert(t2CardAfter.status === 200, `T2 post card read returned ${t2CardAfter.status}`);
659
- const t2FilesAfter = Array.isArray(t2CardAfter.data?.card_data?.files)
660
- ? t2CardAfter.data.card_data.files
652
+ const t2FilesAfter = Array.isArray(t2CardAfter.data?.data?.[0]?.card_data?.files)
653
+ ? t2CardAfter.data.data[0].card_data.files
661
654
  : [];
662
655
  assert(t2FilesAfter.length === t2BeforeCount + 1, `T2 expected files +1 (before=${t2BeforeCount}, after=${t2FilesAfter.length})`);
663
656
 
@@ -736,10 +729,10 @@ try {
736
729
  assert(subRes.status === 200, `chat subscribe returned ${subRes.status}`);
737
730
 
738
731
  t3Dbg('step 3: fetching pre-chat transcript');
739
- const t2Before = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats?all-turns=true`);
732
+ const t2Before = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
740
733
  t3Dbg(`step 3: pre-chat fetch returned status=${t2Before.status}`);
741
734
  assert(t2Before.status === 200, `T3 pre chats returned ${t2Before.status}`);
742
- const t2BeforeMessages = Array.isArray(t2Before.data?.messages) ? t2Before.data.messages : [];
735
+ const t2BeforeMessages = Array.isArray(t2Before.data?.data?.messages) ? t2Before.data.data.messages : [];
743
736
  const t2BeforeCount = t2BeforeMessages.length;
744
737
  const t2EventStart = NS.chatEvents.length;
745
738
  const t2ProbePrompt = `Probe protocol validation ${Date.now()}`;
@@ -770,10 +763,10 @@ try {
770
763
  assert(!!t2Lifecycle, 'T3 ordered lifecycle not observed');
771
764
 
772
765
  t3Dbg('step 6: fetching post-chat transcript');
773
- const t2After = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats?all-turns=true`);
766
+ const t2After = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
774
767
  t3Dbg(`step 6: post-chat fetch returned status=${t2After.status}`);
775
768
  assert(t2After.status === 200, `T3 post chats returned ${t2After.status}`);
776
- const t2AfterMessages = Array.isArray(t2After.data?.messages) ? t2After.data.messages : [];
769
+ const t2AfterMessages = Array.isArray(t2After.data?.data?.messages) ? t2After.data.data.messages : [];
777
770
  const t2NewMessages = t2AfterMessages.slice(t2BeforeCount);
778
771
  t3Dbg(`step 6: validating ${t2NewMessages.length} new messages`);
779
772
  assert(t2NewMessages.length >= 3, `T3 expected at least 3 new chat messages, got ${t2NewMessages.length}`);
@@ -815,10 +808,10 @@ try {
815
808
  console.log('\n=== T3a: non-probe chat protocol (expect paris) ===');
816
809
  const t3aDbg = (msg) => console.log(`[T3a.DBG ${new Date().toISOString()}] ${msg}`);
817
810
  t3aDbg('step 1: fetching pre-chat transcript');
818
- const t2aBefore = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats?all-turns=true`);
811
+ const t2aBefore = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
819
812
  t3aDbg(`step 1: pre-chat fetch returned status=${t2aBefore.status}`);
820
813
  assert(t2aBefore.status === 200, `T3a pre chats returned ${t2aBefore.status}`);
821
- const t2aBeforeMessages = Array.isArray(t2aBefore.data?.messages) ? t2aBefore.data.messages : [];
814
+ const t2aBeforeMessages = Array.isArray(t2aBefore.data?.data?.messages) ? t2aBefore.data.data.messages : [];
822
815
  const t2aBeforeCount = t2aBeforeMessages.length;
823
816
  const t2aPrompt = 'Just answer what is the capital of France. No Fluff. No COmmentary. No Markup Respond in lower case in one word.';
824
817
  t3aDbg(`step 1: beforeCount=${t2aBeforeCount}`);
@@ -854,10 +847,10 @@ try {
854
847
  t3aDbg(`step 3: assistant SSE text=${JSON.stringify(String(t2aSseLast?.text || '').slice(0, 400))}`);
855
848
 
856
849
  t3aDbg('step 4: fetching post-chat transcript');
857
- const t2aAfter = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats?all-turns=true`);
850
+ const t2aAfter = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
858
851
  t3aDbg(`step 4: post-chat fetch returned status=${t2aAfter.status}`);
859
852
  assert(t2aAfter.status === 200, `T3a post chats returned ${t2aAfter.status}`);
860
- const t2aAfterMessages = Array.isArray(t2aAfter.data?.messages) ? t2aAfter.data.messages : [];
853
+ const t2aAfterMessages = Array.isArray(t2aAfter.data?.data?.messages) ? t2aAfter.data.data.messages : [];
861
854
  const t2aNewMessages = t2aAfterMessages.slice(t2aBeforeCount);
862
855
  t3aDbg(`step 4: validating ${t2aNewMessages.length} new messages`);
863
856
  assert(t2aNewMessages.length >= 2, `T3a expected at least 2 new chat messages, got ${t2aNewMessages.length}`);
@@ -873,33 +866,36 @@ try {
873
866
  console.log('\n=== T3b: skipped (--skip-t3b) ===');
874
867
  } else {
875
868
  console.log('\n=== T3b: probe-echo chat with file upload protocol ===');
876
- const t2bBefore = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats?all-turns=true`);
869
+ const t2bBefore = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
877
870
  assert(t2bBefore.status === 200, `T3b pre chats returned ${t2bBefore.status}`);
878
- const t2bBeforeMessages = Array.isArray(t2bBefore.data?.messages) ? t2bBefore.data.messages : [];
871
+ const t2bBeforeMessages = Array.isArray(t2bBefore.data?.data?.messages) ? t2bBefore.data.data.messages : [];
879
872
  const t2bBeforeCount = t2bBeforeMessages.length;
880
873
 
881
874
  const t3bTurnId = randomTurnId();
882
- const t2bUploadRes = await httpUploadChatFile(
883
- `${BASE}/cards/${CHAT_CARD_ID}/files?inChat=true&turn-id=${encodeURIComponent(t3bTurnId)}`,
884
- 'q1.txt',
885
- 'tokyo',
886
- );
875
+ const t2bUploadRes = await httpMcpControlplane('manage.add-chat-attachment', {
876
+ board_id: BOARD_ID,
877
+ card_id: CHAT_CARD_ID,
878
+ turn_id: t3bTurnId,
879
+ file_name: 'q1.txt',
880
+ content_type: 'text/plain; charset=utf-8',
881
+ base64: Buffer.from('tokyo', 'utf-8').toString('base64'),
882
+ });
887
883
  assert(t2bUploadRes.status === 200, `T3b file upload returned ${t2bUploadRes.status}`);
888
- const uploadedFile = t2bUploadRes.data?.file;
884
+ const uploadedFile = t2bUploadRes.data?.data?.files?.[0];
889
885
  assert(uploadedFile && typeof uploadedFile === 'object', 'T3b upload response missing file metadata');
890
886
 
891
- const t2bAfterUpload = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats?all-turns=true`);
887
+ const t2bAfterUpload = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
892
888
  assert(t2bAfterUpload.status === 200, `T3b chats after upload returned ${t2bAfterUpload.status}`);
893
- const t2bUploadMessages = Array.isArray(t2bAfterUpload.data?.messages) ? t2bAfterUpload.data.messages : [];
889
+ const t2bUploadMessages = Array.isArray(t2bAfterUpload.data?.data?.messages) ? t2bAfterUpload.data.data.messages : [];
894
890
  const t2bUploadNewMessages = t2bUploadMessages.slice(t2bBeforeCount);
895
891
  const t2bUploadSystem = t2bUploadNewMessages.find((m) => m?.role === 'system');
896
892
  assert(!!t2bUploadSystem, 'T3b upload protocol missing system chat file');
897
893
  assert(String(t2bUploadSystem?.text || '').toLowerCase().includes('file uploaded:'), 'T3b upload system message does not describe uploaded file');
898
894
 
899
- const t2bCardAfterUpload = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}`);
895
+ const t2bCardAfterUpload = await httpMcp('manage.read-card', { card_id: CHAT_CARD_ID });
900
896
  assert(t2bCardAfterUpload.status === 200, `T3b card read after upload returned ${t2bCardAfterUpload.status}`);
901
- const t2bFilesAfterUpload = Array.isArray(t2bCardAfterUpload.data?.card_data?.files)
902
- ? t2bCardAfterUpload.data.card_data.files
897
+ const t2bFilesAfterUpload = Array.isArray(t2bCardAfterUpload.data?.data?.[0]?.card_data?.files)
898
+ ? t2bCardAfterUpload.data.data[0].card_data.files
903
899
  : [];
904
900
  const t2bFileIndex = t2bFilesAfterUpload.findIndex((f) => String(f?.stored_name || '') === String(uploadedFile?.stored_name || ''));
905
901
  assert(t2bFileIndex >= 0, 'T3b uploaded file metadata not found in card_data.files');
@@ -935,9 +931,9 @@ try {
935
931
  }, 60_000, 'T3b ordered lifecycle');
936
932
  assert(!!t2bLifecycle, 'T3b ordered lifecycle not observed');
937
933
 
938
- const t2bAfter = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats?all-turns=true`);
934
+ const t2bAfter = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
939
935
  assert(t2bAfter.status === 200, `T3b post chats returned ${t2bAfter.status}`);
940
- const t2bAfterMessages = Array.isArray(t2bAfter.data?.messages) ? t2bAfter.data.messages : [];
936
+ const t2bAfterMessages = Array.isArray(t2bAfter.data?.data?.messages) ? t2bAfter.data.data.messages : [];
941
937
  const t2bNewMessages = t2bAfterMessages.slice(t2bSendBaseline);
942
938
  assert(t2bNewMessages.length >= 3, `T3b expected at least 3 chat messages after send, got ${t2bNewMessages.length}`);
943
939
 
@@ -972,15 +968,15 @@ try {
972
968
  t3dOwnedSseClient = true;
973
969
  }
974
970
 
975
- const t2dBeforeChats = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats?all-turns=true`);
971
+ const t2dBeforeChats = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
976
972
  assert(t2dBeforeChats.status === 200, `T3d pre chats returned ${t2dBeforeChats.status}`);
977
- const t2dBeforeMessages = Array.isArray(t2dBeforeChats.data?.messages) ? t2dBeforeChats.data.messages : [];
973
+ const t2dBeforeMessages = Array.isArray(t2dBeforeChats.data?.data?.messages) ? t2dBeforeChats.data.data.messages : [];
978
974
  const t2dBeforeCount = t2dBeforeMessages.length;
979
975
 
980
- const t2dBeforeCard = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}`);
976
+ const t2dBeforeCard = await httpMcp('manage.read-card', { card_id: CHAT_CARD_ID });
981
977
  assert(t2dBeforeCard.status === 200, `T3d pre card returned ${t2dBeforeCard.status}`);
982
- const t2dBeforeFiles = Array.isArray(t2dBeforeCard.data?.card_data?.files)
983
- ? t2dBeforeCard.data.card_data.files
978
+ const t2dBeforeFiles = Array.isArray(t2dBeforeCard.data?.data?.[0]?.card_data?.files)
979
+ ? t2dBeforeCard.data.data[0].card_data.files
984
980
  : [];
985
981
 
986
982
  const t3dTurnId = randomTurnId();
@@ -1006,9 +1002,9 @@ try {
1006
1002
  }, 60_000, 'T3d ordered lifecycle');
1007
1003
  assert(!!t2dLifecycle, 'T3d ordered lifecycle not observed');
1008
1004
 
1009
- const t2dAfter = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats?all-turns=true`);
1005
+ const t2dAfter = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
1010
1006
  assert(t2dAfter.status === 200, `T3d post chats returned ${t2dAfter.status}`);
1011
- const t2dAfterMessages = Array.isArray(t2dAfter.data?.messages) ? t2dAfter.data.messages : [];
1007
+ const t2dAfterMessages = Array.isArray(t2dAfter.data?.data?.messages) ? t2dAfter.data.data.messages : [];
1012
1008
  const t2dNewMessages = t2dAfterMessages.slice(t2dBeforeCount);
1013
1009
  assert(t2dNewMessages.length >= 4, `T3d expected at least 4 chat messages after send, got ${t2dNewMessages.length}`);
1014
1010
 
@@ -1031,10 +1027,10 @@ try {
1031
1027
  const t2dFileIndex = Number.parseInt(t2dFileIndexMatch[1], 10);
1032
1028
  assert(Number.isInteger(t2dFileIndex) && t2dFileIndex >= 0, 'T3d AI-generated message file index should be non-negative');
1033
1029
 
1034
- const t2dAfterCard = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}`);
1030
+ const t2dAfterCard = await httpMcp('manage.read-card', { card_id: CHAT_CARD_ID });
1035
1031
  assert(t2dAfterCard.status === 200, `T3d post card returned ${t2dAfterCard.status}`);
1036
- const t2dAfterFiles = Array.isArray(t2dAfterCard.data?.card_data?.files)
1037
- ? t2dAfterCard.data.card_data.files
1032
+ const t2dAfterFiles = Array.isArray(t2dAfterCard.data?.data?.[0]?.card_data?.files)
1033
+ ? t2dAfterCard.data.data[0].card_data.files
1038
1034
  : [];
1039
1035
  assert(t2dAfterFiles.length === t2dBeforeFiles.length + 1, `T3d expected exactly one new stored file, got ${t2dAfterFiles.length - t2dBeforeFiles.length}`);
1040
1036
  const t2dStoredFile = t2dAfterFiles[t2dFileIndex];
@@ -1551,28 +1547,28 @@ try {
1551
1547
 
1552
1548
  const t5ThreadId = `thread-t5-${Date.now()}`;
1553
1549
  expectMcpSuccess(
1554
- await httpMcpControlplane('setstate.card-meta', { board_id: BOARD_ID, card_id: T5_CARD_ID, key: 'chat.foundry_thread_id', value: t5ThreadId }),
1555
- 'T5 setstate.card-meta',
1550
+ await httpMcpControlplane('setstate.card-private', { board_id: BOARD_ID, card_id: T5_CARD_ID, key: 'chat.foundry_thread_id', value: t5ThreadId }),
1551
+ 'T5 setstate.card-private',
1556
1552
  );
1557
1553
  const t5GetMeta = expectMcpSuccess(
1558
- await httpMcpControlplane('getstate.card-meta', { board_id: BOARD_ID, card_id: T5_CARD_ID, key: 'chat.foundry_thread_id' }),
1559
- 'T5 getstate.card-meta',
1554
+ await httpMcpControlplane('getstate.card-private', { board_id: BOARD_ID, card_id: T5_CARD_ID, key: 'chat.foundry_thread_id' }),
1555
+ 'T5 getstate.card-private',
1560
1556
  );
1561
- assert(t5GetMeta?.exists === true && t5GetMeta?.value === t5ThreadId, `T5 getstate.card-meta mismatch: ${JSON.stringify(t5GetMeta)}`);
1557
+ assert(t5GetMeta?.exists === true && t5GetMeta?.value === t5ThreadId, `T5 getstate.card-private mismatch: ${JSON.stringify(t5GetMeta)}`);
1562
1558
 
1563
1559
  const t5ReadCards = expectMcpSuccess(
1564
1560
  await httpMcp('manage.read-card', { card_id: T5_CARD_ID }),
1565
1561
  'T5 manage.read-card meta-redaction',
1566
1562
  );
1567
1563
  const t5ReadCard = Array.isArray(t5ReadCards) ? t5ReadCards[0] : null;
1568
- assert(t5ReadCard && t5ReadCard.meta === undefined, 'T5 expected manage.read-card to redact top-level meta');
1564
+ assert(t5ReadCard && t5ReadCard.__private === undefined, 'T5 expected manage.read-card to redact __private');
1569
1565
 
1570
1566
  const t5Inspect = expectMcpSuccess(
1571
1567
  await httpMcp('inspect.card-definition-and-runtime', { card_id: T5_CARD_ID }),
1572
1568
  'T5 inspect.card-definition-and-runtime meta-redaction',
1573
1569
  );
1574
- assert(t5Inspect?.card_definition_and_static_data?.meta === undefined, 'T5 expected inspect to redact card_definition_and_static_data.meta');
1575
- console.log('[T5] ok: regular /mcp surfaces redact card meta');
1570
+ assert(t5Inspect?.card_definition_and_static_data?.__private === undefined, 'T5 expected inspect to redact card_definition_and_static_data.__private');
1571
+ console.log('[T5] ok: regular /mcp surfaces redact __private');
1576
1572
 
1577
1573
  // ── T5: admin-only card round-trip ──────────────────────────────────────
1578
1574
  // 1. Read the existing card definition via the normal read-card path.
@@ -1610,34 +1606,32 @@ try {
1610
1606
  );
1611
1607
  const t5AdminCards = Array.isArray(t5AdminRead?.cards) ? t5AdminRead.cards : [];
1612
1608
  assert(t5AdminCards.length > 0, 'T5 expected admin-read-card to return the card');
1613
- assert(t5AdminCards[0]?.meta?.__visible_controlplane_only === true, `T5 expected meta.__visible_controlplane_only=true, got: ${JSON.stringify(t5AdminCards[0]?.meta)}`);
1614
- console.log('[T5] ok: manage.admin-read-card returns card with __visible_controlplane_only=true');
1609
+ assert(t5AdminCards[0]?.__private?.visible_controlplane_only === true, `T5 expected __private.visible_controlplane_only=true, got: ${JSON.stringify(t5AdminCards[0]?.__private)}`);
1610
+ console.log('[T5] ok: manage.admin-read-card returns card with __private.visible_controlplane_only=true');
1615
1611
 
1616
- // 5. Guard: setstate.card-meta must block changing the flag to a different value.
1617
- // key = 'chat.__visible_controlplane_only' passes the chat.* format check but contains
1618
- // the reserved segment, so it reaches the guard.
1619
- const t5MetaGuard = await httpMcpControlplane('setstate.card-meta', {
1612
+ // 5. Guard: setstate.card-private must block changing the flag to a different value.
1613
+ const t5MetaGuard = await httpMcpControlplane('setstate.card-private', {
1620
1614
  board_id: BOARD_ID,
1621
1615
  card_id: t5AdminCardId,
1622
- key: 'chat.__visible_controlplane_only',
1616
+ key: 'chat.visible_controlplane_only',
1623
1617
  value: false, // differs from current flag value (true) → must be rejected
1624
1618
  });
1625
1619
  assert(t5MetaGuard?.status !== 200,
1626
- `T5 expected setstate.card-meta to reject changing __visible_controlplane_only, got: ${JSON.stringify(t5MetaGuard)}`);
1627
- console.log('[T5] ok: setstate.card-meta blocked flag mutation (false != true)');
1620
+ `T5 expected setstate.card-private to reject changing visible_controlplane_only, got: ${JSON.stringify(t5MetaGuard)}`);
1621
+ console.log('[T5] ok: setstate.card-private blocked flag mutation (false != true)');
1628
1622
 
1629
1623
  // 6. Guard: same key with value matching the current flag (true) must pass (idempotent).
1630
1624
  const t5MetaIdempotent = expectMcpSuccess(
1631
- await httpMcpControlplane('setstate.card-meta', {
1625
+ await httpMcpControlplane('setstate.card-private', {
1632
1626
  board_id: BOARD_ID,
1633
1627
  card_id: t5AdminCardId,
1634
- key: 'chat.__visible_controlplane_only',
1628
+ key: 'chat.visible_controlplane_only',
1635
1629
  value: true, // matches current flag value → idempotent, allowed
1636
1630
  }),
1637
- 'T5 setstate.card-meta idempotent same-value',
1631
+ 'T5 setstate.card-private idempotent same-value',
1638
1632
  );
1639
- assert(t5MetaIdempotent, 'T5 expected setstate.card-meta to succeed with matching flag value');
1640
- console.log('[T5] ok: setstate.card-meta idempotent same-value allowed');
1633
+ assert(t5MetaIdempotent, 'T5 expected setstate.card-private to succeed with matching flag value');
1634
+ console.log('[T5] ok: setstate.card-private idempotent same-value allowed');
1641
1635
  }
1642
1636
  }
1643
1637
 
@@ -0,0 +1,81 @@
1
+ # board-firestore
2
+
3
+ Example of a [yaml-flow](https://www.npmjs.com/package/yaml-flow) board backed by **Cloud Firestore** (Firebase).
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ Browser SPA Node.js Worker (server/worker.js)
9
+ ────────────── ──────────────────────────────────
10
+ Firebase JS SDK ──> Firestore <── Firebase Admin SDK
11
+
12
+ board cards,
13
+ queue messages,
14
+ blob storage
15
+ ```
16
+
17
+ - **Worker** — runs queue lanes (board-worker, chat-agent, process-accumulated) and exposes an HTTP control-plane on port 7900 for the browser SPA.
18
+ - **Browser** — uses the `ServerRuntimeControlface` IIFE (`browser/server-runtime-controlface.js`) with a Firestore JS SDK adapter to read card state and trigger board operations.
19
+
20
+ ## Firestore data layout
21
+
22
+ All data lives under `boards/{boardId}/`:
23
+
24
+ | Subcollection | Purpose |
25
+ |---|---|
26
+ | `kv-{namespace}/` | `AsyncKVStorage` (card state, config, etc.) |
27
+ | `cards/` | Card store (KV, keyed by card ID) |
28
+ | `runtime-out/` | Computed outputs store |
29
+ | `journal/` | Append-only board journal |
30
+ | `worker-queue/` | Board worker task queue |
31
+ | `chat-queue/` | Chat agent dispatch queue |
32
+ | `process-queue/` | processAccumulated trigger queue |
33
+ | `blobs-{namespace}/` | Blob/artifact storage |
34
+ | `scratch/` | Ephemeral scratch storage |
35
+ | `archive-stream-{name}/` | Named archive streams |
36
+ | `archive-blob-{name}/` | Named archive blob collections |
37
+ | `locks/board-lock` | Distributed lock document |
38
+
39
+ > **Required Firestore composite index** for each queue collection:
40
+ > Fields: `dead` (ASC), `visibleAfter` (ASC)
41
+
42
+ ## Setup
43
+
44
+ 1. Create a Firebase project and enable Firestore.
45
+ 2. Generate a service account key and save it as `server/service-account.json` (or set `GOOGLE_APPLICATION_CREDENTIALS` env var).
46
+ 3. Install dependencies:
47
+
48
+ ```bash
49
+ npm install
50
+ ```
51
+
52
+ 4. Start the worker:
53
+
54
+ ```bash
55
+ FIREBASE_PROJECT_ID=your-project npm run worker
56
+ ```
57
+
58
+ The worker listens on `http://localhost:7900` by default.
59
+
60
+ ## Browser usage
61
+
62
+ Include the IIFE bundle in your HTML:
63
+
64
+ ```html
65
+ <script src="/browser/server-runtime-controlface.js"></script>
66
+ ```
67
+
68
+ Then use `browser/board-runtime.js` as a reference for constructing the runtime with the Firebase JS SDK.
69
+
70
+ ## Firestore security rules (development)
71
+
72
+ ```
73
+ rules_version = '2';
74
+ service cloud.firestore {
75
+ match /databases/{database}/documents {
76
+ match /boards/{boardId}/{document=**} {
77
+ allow read, write: if request.auth != null;
78
+ }
79
+ }
80
+ }
81
+ ```