yaml-flow 8.7.0 → 8.8.0

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 (200) hide show
  1. package/browser/adapters/firebase-storage.js +2 -2
  2. package/browser/adapters/firestore-storage.js +2 -2
  3. package/browser/adapters/localstorage-storage.js +3 -3
  4. package/browser/asset-integrity.json +11 -15
  5. package/browser/live-cards.schema.json +10 -1
  6. package/browser/server-runtime-controlface.js +5 -5
  7. package/examples/ARCHITECTURE.md +0 -27
  8. package/examples/board/server/board-server.js +151 -101
  9. package/examples/board/server/board-worker/source_def_flows.json +0 -8
  10. package/examples/board/server/board-worker/task-executor.js +1 -3
  11. package/examples/board/test/server-http-test.js +181 -152
  12. package/examples/board-firestore/server/worker.js +9 -1
  13. package/examples/portfolio-tracker/portfolio-tracker.js +11 -1
  14. package/examples/portfolio-tracker/test/portfolio-t4.js +12 -2
  15. package/lib/{artifacts-store-lib-D9nMkVcE.d.cts → artifacts-store-lib-C6qBpMfU.d.cts} +1 -1
  16. package/lib/{artifacts-store-lib-DSSMqVL2.d.ts → artifacts-store-lib-D4qf7Q-7.d.ts} +1 -1
  17. package/lib/artifacts-store-public.d.cts +3 -3
  18. package/lib/artifacts-store-public.d.ts +3 -3
  19. package/lib/board-live-cards-mcp.cjs +1 -1
  20. package/lib/board-live-cards-mcp.d.cts +7 -7
  21. package/lib/board-live-cards-mcp.d.ts +7 -7
  22. package/lib/board-live-cards-mcp.js +1 -1
  23. package/lib/board-live-cards-node.cjs +8 -8
  24. package/lib/board-live-cards-node.d.cts +16 -14
  25. package/lib/board-live-cards-node.d.ts +16 -14
  26. package/lib/board-live-cards-node.js +8 -8
  27. package/lib/{board-live-cards-public-DQzPe7A9.d.cts → board-live-cards-public-BT5HrgqZ.d.cts} +82 -59
  28. package/lib/{board-live-cards-public-D-DJek3X.d.ts → board-live-cards-public-DSRamFm8.d.ts} +82 -59
  29. package/lib/{board-live-cards-public-async-CgMCYYft.d.cts → board-live-cards-public-async-CYjr4mgX.d.cts} +18 -8
  30. package/lib/{board-live-cards-public-async-3hUuHxDx.d.ts → board-live-cards-public-async-DlyC3PgC.d.ts} +18 -8
  31. package/lib/board-live-cards-public.cjs +1 -1
  32. package/lib/board-live-cards-public.d.cts +2 -2
  33. package/lib/board-live-cards-public.d.ts +2 -2
  34. package/lib/board-live-cards-public.js +1 -1
  35. package/lib/board-live-cards-server-runtime.cjs +1 -1
  36. package/lib/board-live-cards-server-runtime.d.cts +6 -8
  37. package/lib/board-live-cards-server-runtime.d.ts +6 -8
  38. package/lib/board-live-cards-server-runtime.js +1 -1
  39. package/lib/board-livegraph-runtime/index.cjs +1 -1
  40. package/lib/board-livegraph-runtime/index.js +1 -1
  41. package/lib/{board-platform-adapter-async-DOfEq_HC.d.cts → board-platform-adapter-async-BZIftm36.d.cts} +18 -14
  42. package/lib/{board-platform-adapter-async-JZPCzZnH.d.ts → board-platform-adapter-async-JP9V-U5E.d.ts} +18 -14
  43. package/lib/board-worker-adapter.cjs +1 -24
  44. package/lib/board-worker-adapter.d.cts +68 -3
  45. package/lib/board-worker-adapter.d.ts +68 -3
  46. package/lib/board-worker-adapter.js +1 -24
  47. package/lib/card-compute/index.cjs +1 -1
  48. package/lib/card-compute/index.js +1 -1
  49. package/lib/card-store-public.d.cts +2 -2
  50. package/lib/card-store-public.d.ts +2 -2
  51. package/lib/card-validation.cjs +1 -1
  52. package/lib/card-validation.js +1 -1
  53. package/lib/chat-store-public.cjs +1 -1
  54. package/lib/chat-store-public.d.cts +20 -20
  55. package/lib/chat-store-public.d.ts +20 -20
  56. package/lib/chat-store-public.js +1 -1
  57. package/lib/chunk-2GSI6C45.js +7 -0
  58. package/lib/chunk-35N7ONTH.js +2 -0
  59. package/lib/chunk-37HDEW26.cjs +2 -0
  60. package/lib/{chunk-PMUSJQSR.cjs → chunk-3CZCGNY4.cjs} +2 -2
  61. package/lib/chunk-3KC6LBOG.js +3 -0
  62. package/lib/{chunk-BQS3EIEK.js → chunk-44L64VQ2.js} +3 -3
  63. package/lib/chunk-6OPXQPSC.js +2 -0
  64. package/lib/chunk-7BTZCOT5.js +2 -0
  65. package/lib/{chunk-U2N6MCD5.cjs → chunk-7JVHYHT2.cjs} +2 -2
  66. package/lib/chunk-7QZ267XP.cjs +2 -0
  67. package/lib/{chunk-XQRNDX4Q.js → chunk-ANKA7HEJ.js} +2 -2
  68. package/lib/{chunk-KAWQPLIE.cjs → chunk-BQUQTOPB.cjs} +2 -2
  69. package/lib/chunk-CMFD27ZC.cjs +3 -0
  70. package/lib/chunk-DOFNXJ4C.js +3 -0
  71. package/lib/chunk-ETW3BXHD.cjs +2 -0
  72. package/lib/chunk-FO4KNVU7.cjs +2 -0
  73. package/lib/{chunk-SGV7PU4H.js → chunk-FOFGEABN.js} +2 -2
  74. package/lib/chunk-GPCMBPLK.cjs +2 -0
  75. package/lib/chunk-GU3T75C4.js +3 -0
  76. package/lib/chunk-H3EHFCDZ.js +3 -0
  77. package/lib/chunk-H4TYOSMD.cjs +45 -0
  78. package/lib/chunk-HFW7E2Z7.cjs +4 -0
  79. package/lib/chunk-HWYMZK3N.cjs +3 -0
  80. package/lib/chunk-IQIZA7TN.cjs +7 -0
  81. package/lib/chunk-J4MHQ7JF.js +45 -0
  82. package/lib/chunk-MCPADH33.cjs +2 -0
  83. package/lib/chunk-NBJTYAYN.cjs +2 -0
  84. package/lib/chunk-NDAKMJQK.cjs +3 -0
  85. package/lib/chunk-NNSBBO5R.js +2 -0
  86. package/lib/chunk-NU5NO5NM.js +2 -0
  87. package/lib/chunk-O4RKTQBP.cjs +3 -0
  88. package/lib/chunk-O5UYCGIN.js +2 -0
  89. package/lib/chunk-Q3OTUDIE.js +2 -0
  90. package/lib/chunk-R44X3RQB.cjs +2 -0
  91. package/lib/chunk-RKKSVOP2.js +2 -0
  92. package/lib/chunk-UB54HZA4.cjs +2 -0
  93. package/lib/{chunk-CIAJNUR4.js → chunk-VGDLSS2H.js} +2 -2
  94. package/lib/{chunk-SFVO2LB2.cjs → chunk-VQCIOKJV.cjs} +3 -3
  95. package/lib/chunk-VS3BXEYK.js +4 -0
  96. package/lib/chunk-Y4WK7HE4.js +2 -0
  97. package/lib/chunk-YBYXCFAI.js +2 -0
  98. package/lib/chunk-ZK3E7L4Y.cjs +2 -0
  99. package/lib/cloud-storage.cjs +1 -1
  100. package/lib/cloud-storage.d.cts +6 -6
  101. package/lib/cloud-storage.d.ts +6 -6
  102. package/lib/cloud-storage.js +1 -1
  103. package/lib/continuous-event-graph/index.cjs +1 -1
  104. package/lib/continuous-event-graph/index.js +1 -1
  105. package/lib/execution-refs.cjs +1 -1
  106. package/lib/execution-refs.js +1 -1
  107. package/lib/firebase-storage/index.cjs +2 -2
  108. package/lib/firebase-storage/index.d.cts +2 -2
  109. package/lib/firebase-storage/index.d.ts +2 -2
  110. package/lib/firebase-storage/index.js +2 -2
  111. package/lib/firestore-storage/index.cjs +2 -2
  112. package/lib/firestore-storage/index.d.cts +23 -8
  113. package/lib/firestore-storage/index.d.ts +23 -8
  114. package/lib/firestore-storage/index.js +2 -2
  115. package/lib/index.cjs +2 -2
  116. package/lib/index.d.cts +1 -1
  117. package/lib/index.d.ts +1 -1
  118. package/lib/index.js +1 -1
  119. package/lib/localstorage-storage/index.cjs +1 -1
  120. package/lib/localstorage-storage/index.d.cts +10 -6
  121. package/lib/localstorage-storage/index.d.ts +10 -6
  122. package/lib/localstorage-storage/index.js +1 -1
  123. package/lib/{mcp-tool-registries-W3TRj6O5.d.cts → mcp-tool-registries-CRtea2x4.d.cts} +3 -0
  124. package/lib/{mcp-tool-registries-BBObLYga.d.ts → mcp-tool-registries-D3rWSppt.d.ts} +3 -0
  125. package/lib/server-jobs-queue-runner/index.cjs +1 -1
  126. package/lib/server-jobs-queue-runner/index.d.cts +12 -9
  127. package/lib/server-jobs-queue-runner/index.d.ts +12 -9
  128. package/lib/server-jobs-queue-runner/index.js +1 -1
  129. package/lib/server-runtime/index.cjs +1 -1
  130. package/lib/server-runtime/index.d.cts +7 -9
  131. package/lib/server-runtime/index.d.ts +7 -9
  132. package/lib/server-runtime/index.js +1 -1
  133. package/lib/server-runtime-agentface/index.d.cts +7 -9
  134. package/lib/server-runtime-agentface/index.d.ts +7 -9
  135. package/lib/server-runtime-controlface/index.cjs +1 -1
  136. package/lib/server-runtime-controlface/index.d.cts +19 -71
  137. package/lib/server-runtime-controlface/index.d.ts +19 -71
  138. package/lib/server-runtime-controlface/index.js +1 -1
  139. package/lib/server-runtime-core/index.cjs +1 -1
  140. package/lib/server-runtime-core/index.d.cts +61 -21
  141. package/lib/server-runtime-core/index.d.ts +61 -21
  142. package/lib/server-runtime-core/index.js +1 -1
  143. package/lib/server-runtime-watchers/index.cjs +1 -1
  144. package/lib/server-runtime-watchers/index.d.cts +9 -65
  145. package/lib/server-runtime-watchers/index.d.ts +9 -65
  146. package/lib/server-runtime-watchers/index.js +1 -1
  147. package/lib/server-runtime-webhooks/index.d.cts +7 -9
  148. package/lib/server-runtime-webhooks/index.d.ts +7 -9
  149. package/lib/sse-hub-CYXisfXJ.d.cts +63 -0
  150. package/lib/sse-hub-Dodwtc3_.d.ts +63 -0
  151. package/lib/step-machine-public/index.cjs +1 -1
  152. package/lib/step-machine-public/index.d.cts +1 -1
  153. package/lib/step-machine-public/index.d.ts +1 -1
  154. package/lib/step-machine-public/index.js +1 -1
  155. package/lib/{storage-async-interface-BRR4eBjx.d.cts → storage-async-interface-CG0bMqvE.d.ts} +20 -1
  156. package/lib/{storage-async-interface-DhlOVPSp.d.ts → storage-async-interface-CyO-zwVQ.d.cts} +20 -1
  157. package/lib/{storage-interface-BFiD3kyB.d.ts → storage-interface-D-iEiTJA.d.cts} +45 -1
  158. package/lib/{storage-interface-BFiD3kyB.d.cts → storage-interface-D-iEiTJA.d.ts} +45 -1
  159. package/lib/stores/index.d.cts +1 -1
  160. package/lib/stores/index.d.ts +1 -1
  161. package/lib/stores/kv.d.cts +1 -1
  162. package/lib/stores/kv.d.ts +1 -1
  163. package/lib/{types-CF2xUcZW.d.ts → types-BtH3scgE.d.ts} +66 -29
  164. package/lib/{types-BzQY45dH.d.cts → types-Ch0u3FKP.d.cts} +66 -29
  165. package/package.json +4 -5
  166. package/schema/live-cards.schema.json +10 -1
  167. package/browser/board-livecards-client.js +0 -2
  168. package/examples/board/demo-shell-with-server.html +0 -272
  169. package/examples/board/doc.html +0 -465
  170. package/examples/board/server-config.json +0 -24
  171. package/lib/chat-storage-lib-B9Q34Dyv.d.cts +0 -54
  172. package/lib/chat-storage-lib-DB9iSai2.d.ts +0 -54
  173. package/lib/chunk-5XHOHTLZ.cjs +0 -2
  174. package/lib/chunk-6APH25VI.js +0 -2
  175. package/lib/chunk-76ON3V7R.js +0 -2
  176. package/lib/chunk-7ICPAABP.cjs +0 -7
  177. package/lib/chunk-CPAXTVBQ.cjs +0 -2
  178. package/lib/chunk-DDYSXP2A.cjs +0 -3
  179. package/lib/chunk-EGRHWZRV.js +0 -2
  180. package/lib/chunk-GL2OHR2E.cjs +0 -2
  181. package/lib/chunk-GYQXDNNI.cjs +0 -2
  182. package/lib/chunk-H5HBXPOI.cjs +0 -3
  183. package/lib/chunk-IPLSRN6P.cjs +0 -4
  184. package/lib/chunk-J5J6BG7B.js +0 -2
  185. package/lib/chunk-M3OU6IS5.cjs +0 -2
  186. package/lib/chunk-M6STQR5F.cjs +0 -2
  187. package/lib/chunk-N6P2JW4W.js +0 -3
  188. package/lib/chunk-NJJ7WEDT.cjs +0 -2
  189. package/lib/chunk-NKIQRCOM.cjs +0 -2
  190. package/lib/chunk-NMZ6XNLB.cjs +0 -3
  191. package/lib/chunk-OEFTOO47.cjs +0 -3
  192. package/lib/chunk-PRHQBGPT.js +0 -2
  193. package/lib/chunk-S44QZUDX.js +0 -2
  194. package/lib/chunk-TSN3RTXT.js +0 -4
  195. package/lib/chunk-VLBB3D6B.js +0 -3
  196. package/lib/chunk-VMW4Z6EF.js +0 -3
  197. package/lib/chunk-VXJHBWK3.js +0 -2
  198. package/lib/chunk-WHDEBJLT.js +0 -7
  199. package/lib/chunk-YGALANRO.js +0 -2
  200. package/lib/chunk-ZXQR7GHT.js +0 -3
@@ -6,7 +6,7 @@
6
6
  * Targets the 'live' board with --cards-pattern cardT* to load only the 3
7
7
  * test cards (cardT-portfolio, cardT-market-prices, cardT-portfolio-value).
8
8
  *
9
- * T0: init-board → SSE initial payload → wait for all cards to complete
9
+ * T0: /sse?one-shot bootstrap → SSE initial payload → wait for all cards to complete
10
10
  * T1: PATCH holdings (+1 row) → verify recomputation (holdings +1, positions +1)
11
11
  *
12
12
  * Usage:
@@ -14,7 +14,6 @@
14
14
  */
15
15
 
16
16
  import { spawn, spawnSync } from 'node:child_process';
17
- import { Worker } from 'node:worker_threads';
18
17
  import { fileURLToPath } from 'node:url';
19
18
  import path from 'node:path';
20
19
  import http from 'node:http';
@@ -51,8 +50,17 @@ const RUN_ID = `run-${Date.now()}-${process.pid}-${Math.random().toString(36).sl
51
50
  const BOARD_ID = 'live';
52
51
  const BOARD_DIR = path.resolve(__dirname, '..');
53
52
  const SERVER_SCRIPT = path.resolve(BOARD_DIR, 'server', 'board-server.js');
54
- const SSE_WORKER_SCRIPT = path.join(__dirname, 'sse-worker.js');
55
- const CARD_PATTERN = 'cardT*';
53
+ // Force the board server to start with zero cards; the test upserts the
54
+ // three cardT-* fixtures itself in T0 so the SSE upsert/delta path is
55
+ // exercised end-to-end.
56
+ const CARD_PATTERN = '__none__*';
57
+ const CARDS_DIR = path.resolve(BOARD_DIR, 'cards');
58
+ const T0_CARD_FILES = [
59
+ 'cardT-portfolio.json',
60
+ 'cardT-market-prices.json',
61
+ 'cardT-portfolio-value.json',
62
+ ];
63
+ const T0_EXPECTED_CARD_IDS = ['card-market-prices', 'card-portfolio', 'card-portfolio-value'];
56
64
  const T2_FILE_CARD_ID = 'card-market-prices';
57
65
  const CHAT_CARD_ID = 'card-portfolio';
58
66
 
@@ -92,7 +100,6 @@ if (fs.existsSync(SETUP_DIR)) {
92
100
  // ---------------------------------------------------------------------------
93
101
 
94
102
  const NS = {
95
- initialPayload: null,
96
103
  statusSummary: null,
97
104
  statusGeneration: 0,
98
105
  computedValues: {},
@@ -102,9 +109,6 @@ const NS = {
102
109
 
103
110
  function applyFrame(payload) {
104
111
  if (payload && Array.isArray(payload.cardDefinitions)) {
105
- if (!NS.initialPayload && payload.cardDefinitions.length > 0) {
106
- NS.initialPayload = payload;
107
- }
108
112
  const summary = payload.statusSnapshot && payload.statusSnapshot.summary;
109
113
  if (summary) {
110
114
  NS.statusSummary = summary;
@@ -267,9 +271,6 @@ async function stopChildProcess(proc, label) {
267
271
  }
268
272
  }
269
273
 
270
- const waitForInitialPayload = (ms = 15_000) =>
271
- waitUntil(() => NS.initialPayload || false, ms, 'initial SSE payload');
272
-
273
274
  const waitForAllCompleted = (ms = 60_000, label = 'all completed') =>
274
275
  waitUntil(() => {
275
276
  const s = NS.statusSummary;
@@ -502,12 +503,12 @@ console.log(`target: ${BASE}`);
502
503
  console.log(`card pattern: ${CARD_PATTERN}`);
503
504
 
504
505
  const serverProc = await startServer(PORT);
505
- let sseWorker = null;
506
+ let boardSseClient = null;
506
507
  let chatSseClient = null;
507
508
  let chatSseClientId = '';
508
509
 
509
510
  try {
510
- // ── T0: init-board, SSE connect, wait for initial completion ──
511
+ // ── T0: one-shot bootstrap, SSE connect, wait for initial completion ──
511
512
 
512
513
  // Register the 'live' board via POST (v8 runtime requires explicit registration)
513
514
  const regRes = await httpJson('POST', `http://127.0.0.1:${PORT}/api/boards`, { id: BOARD_ID, label: 'Live' });
@@ -515,51 +516,47 @@ try {
515
516
  `POST /api/boards returned ${regRes.status}: ${JSON.stringify(regRes.data)}`);
516
517
  console.log(`[setup] board '${BOARD_ID}' registered (${regRes.status})`);
517
518
 
518
- console.log('\n=== T0 Step 1: init-board ===');
519
- const initRes = await httpGet(`${BASE}/init-board`);
520
- assert(initRes.status === 200, `init-board returned ${initRes.status}`);
521
- console.log('[T0.1] init-board ok');
522
-
523
- console.log('\n=== T0 Step 2: start SSE worker ===');
519
+ console.log('\n=== T0 Step 1: start SSE client (board expected empty) ===');
524
520
  const sseClientId = `server-http-test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
525
521
  const sseUrl = `${BASE}/sse?clientId=${encodeURIComponent(sseClientId)}`;
526
- sseWorker = new Worker(SSE_WORKER_SCRIPT, {
527
- workerData: { sseUrl },
528
- });
529
- sseWorker.on('message', (msg) => {
530
- if (msg.type === 'frame') applyFrame(msg.payload);
531
- else if (msg.type === 'error') console.error(`[sse-worker] ${msg.message}`);
532
- });
533
- sseWorker.on('error', (err) => console.error(`[sse-worker] uncaught: ${err.message}`));
534
-
535
- const initialPayload = await waitForInitialPayload();
536
- const cardCount = Array.isArray(initialPayload.cardDefinitions) ? initialPayload.cardDefinitions.length : 0;
537
- assert(cardCount === 3, `expected 3 cards (cardT*), got ${cardCount}`);
538
- const cardIds = initialPayload.cardDefinitions.map(c => c.id).sort();
539
- console.log(`[T0.2] SSE initial payload received (${cardCount} cards: ${cardIds.join(', ')})`);
522
+ boardSseClient = startSseClient(sseUrl, applyFrame);
523
+ // Give the streaming endpoint a moment to deliver the initial (empty) snapshot.
524
+ await wait(500);
525
+ console.log('[T0.1] SSE client connected');
526
+
527
+ console.log('\n=== T0 Step 2: upsert 3 cardT-* fixtures ===');
528
+ for (const fileName of T0_CARD_FILES) {
529
+ const cardJson = JSON.parse(fs.readFileSync(path.join(CARDS_DIR, fileName), 'utf-8'));
530
+ const cardId = cardJson?.id;
531
+ assert(typeof cardId === 'string' && cardId.length > 0, `fixture ${fileName} missing id`);
532
+ const upsertRes = await httpMcp('manage.upsert-card', {
533
+ card_id: cardId,
534
+ candidate_card_content: cardJson,
535
+ });
536
+ assert(upsertRes.status === 200, `manage.upsert-card(${cardId}) returned ${upsertRes.status}: ${JSON.stringify(upsertRes.data)}`);
537
+ assert(upsertRes.data?.status === 'success', `manage.upsert-card(${cardId}) failed: ${JSON.stringify(upsertRes.data)}`);
538
+ console.log(`[T0.2] upserted ${cardId}`);
539
+ }
540
540
 
541
- console.log('\n=== T0 Step 3: wait for all cards to complete ===');
542
- const t0Summary = await waitForAllCompleted(30_000, 'T0 initial completion');
541
+ console.log('\n=== T0 Step 3: wait for all 3 cards to complete via SSE ===');
542
+ const t0Summary = await waitUntil(() => {
543
+ const s = NS.statusSummary;
544
+ if (s && s.card_count === 3 && s.completed === 3) return s;
545
+ return false;
546
+ }, 60_000, 'T0 initial completion (3 cards)');
543
547
  assert(t0Summary.failed === 0, `T0 expected failed=0, got ${t0Summary.failed}`);
544
548
  console.log(`[T0.3] completed: ${JSON.stringify(t0Summary)}`);
545
549
 
546
550
  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
551
  const statusMcpRes = await httpMcp('inspect.board-runtime-status', {});
552
552
  assert(statusMcpRes.status === 200, `inspect.board-runtime-status returned ${statusMcpRes.status}`);
553
553
  assert(statusMcpRes.data?.status === 'success', `inspect.board-runtime-status failed: ${JSON.stringify(statusMcpRes.data)}`);
554
554
  const mcpSummary = statusMcpRes.data?.data?.summary;
555
555
  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)}`);
556
+ assert(mcpSummary.card_count === T0_EXPECTED_CARD_IDS.length,
557
+ `expected card_count=${T0_EXPECTED_CARD_IDS.length}, got ${mcpSummary.card_count}`);
558
+ assert(mcpSummary.completed === mcpSummary.card_count, `not all complete: ${JSON.stringify(mcpSummary)}`);
559
+ console.log(`[T0.4] board-status: ${JSON.stringify(mcpSummary)}`);
563
560
 
564
561
  // Verify computed_values arrived for portfolio-value card
565
562
  const t0Positions = NS.computedValues['card-portfolio-value']?.positions;
@@ -634,30 +631,32 @@ try {
634
631
  if (skipT2) {
635
632
  console.log('\n=== T2: skipped (--skip-t2) ===');
636
633
  } else {
637
- console.log('\n=== T2: plain file upload -> card_data.files -> download ===');
638
- const t2CardBefore = await httpGet(`${BASE}/cards/${T2_FILE_CARD_ID}`);
634
+ console.log('\n=== T2: MCP file upload -> card_data.files -> download ===');
635
+ const t2CardBefore = await httpMcp('manage.read-card', { card_id: T2_FILE_CARD_ID });
639
636
  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
637
+ const t2FilesBefore = Array.isArray(t2CardBefore.data?.data?.[0]?.card_data?.files)
638
+ ? t2CardBefore.data.data[0].card_data.files
642
639
  : [];
643
640
  const t2BeforeCount = t2FilesBefore.length;
644
641
 
645
642
  const t2UploadText = `plain-file-upload-${Date.now()}`;
646
643
  const t2UploadName = 't2-upload.txt';
647
- const t2UploadRes = await httpUploadChatFile(
648
- `${BASE}/cards/${T2_FILE_CARD_ID}/files`,
649
- t2UploadName,
650
- t2UploadText,
651
- );
644
+ const t2UploadRes = await httpMcpControlplane('manage.upload-card-file', {
645
+ board_id: BOARD_ID,
646
+ card_id: T2_FILE_CARD_ID,
647
+ file_name: t2UploadName,
648
+ content_type: 'text/plain; charset=utf-8',
649
+ base64: Buffer.from(t2UploadText, 'utf-8').toString('base64'),
650
+ });
652
651
  assert(t2UploadRes.status === 200, `T2 file upload returned ${t2UploadRes.status}`);
653
- const t2UploadedFile = t2UploadRes.data?.file;
652
+ const t2UploadedFile = t2UploadRes.data?.data?.file;
654
653
  assert(t2UploadedFile && typeof t2UploadedFile === 'object', 'T2 upload response missing file metadata');
655
654
  assert(String(t2UploadedFile?.name || '') === t2UploadName, 'T2 uploaded file name mismatch');
656
655
 
657
- const t2CardAfter = await httpGet(`${BASE}/cards/${T2_FILE_CARD_ID}`);
656
+ const t2CardAfter = await httpMcp('manage.read-card', { card_id: T2_FILE_CARD_ID });
658
657
  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
658
+ const t2FilesAfter = Array.isArray(t2CardAfter.data?.data?.[0]?.card_data?.files)
659
+ ? t2CardAfter.data.data[0].card_data.files
661
660
  : [];
662
661
  assert(t2FilesAfter.length === t2BeforeCount + 1, `T2 expected files +1 (before=${t2BeforeCount}, after=${t2FilesAfter.length})`);
663
662
 
@@ -736,10 +735,10 @@ try {
736
735
  assert(subRes.status === 200, `chat subscribe returned ${subRes.status}`);
737
736
 
738
737
  t3Dbg('step 3: fetching pre-chat transcript');
739
- const t2Before = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats?all-turns=true`);
738
+ const t2Before = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
740
739
  t3Dbg(`step 3: pre-chat fetch returned status=${t2Before.status}`);
741
740
  assert(t2Before.status === 200, `T3 pre chats returned ${t2Before.status}`);
742
- const t2BeforeMessages = Array.isArray(t2Before.data?.messages) ? t2Before.data.messages : [];
741
+ const t2BeforeMessages = Array.isArray(t2Before.data?.data?.messages) ? t2Before.data.data.messages : [];
743
742
  const t2BeforeCount = t2BeforeMessages.length;
744
743
  const t2EventStart = NS.chatEvents.length;
745
744
  const t2ProbePrompt = `Probe protocol validation ${Date.now()}`;
@@ -747,33 +746,42 @@ try {
747
746
 
748
747
  const t3TurnId = randomTurnId();
749
748
  t3Dbg(`step 4: posting probe chat-send (turn-id=${t3TurnId})`);
750
- const t2SendRes = await httpJson('POST', `${BASE}/cards/${CHAT_CARD_ID}/actions`, {
751
- actionType: 'chat-send',
752
- payload: {
753
- text: `${ECHO_PROBE_MARKER}${t2ProbePrompt}${ECHO_PROBE_MARKER}`,
754
- 'turn-id': t3TurnId,
749
+ const t2SendRes = await httpJson('POST', `${BASE}/mcp-actions`, {
750
+ tool: 'chat-send',
751
+ args: {
752
+ card_id: CHAT_CARD_ID,
753
+ payload: {
754
+ text: `${ECHO_PROBE_MARKER}${t2ProbePrompt}${ECHO_PROBE_MARKER}`,
755
+ 'turn-id': t3TurnId,
756
+ },
755
757
  },
756
758
  });
757
759
  t3Dbg(`step 4: chat-send returned status=${t2SendRes.status}`);
758
760
  assert(t2SendRes.status === 200, `T3 chat-send returned ${t2SendRes.status}`);
759
761
 
760
762
  t3Dbg('step 5: waiting for ordered probe lifecycle on chat SSE');
761
- const t2Lifecycle = await waitForChatPredicate((events) => {
762
- return matchOrderedProbeLifecycle(events.slice(t2EventStart), {
763
- beforeCount: t2BeforeCount,
764
- beforeProcessing: false,
765
- prompt: t2ProbePrompt,
766
- inProgressText: PROBE_IN_PROGRESS_TEXT,
767
- });
768
- }, 45_000, 'T3 ordered lifecycle');
763
+ let t2Lifecycle;
764
+ try {
765
+ t2Lifecycle = await waitForChatPredicate((events) => {
766
+ return matchOrderedProbeLifecycle(events.slice(t2EventStart), {
767
+ beforeCount: t2BeforeCount,
768
+ beforeProcessing: false,
769
+ prompt: t2ProbePrompt,
770
+ inProgressText: PROBE_IN_PROGRESS_TEXT,
771
+ });
772
+ }, 45_000, 'T3 ordered lifecycle');
773
+ } catch (error) {
774
+ t3Dbg(`step 5: lifecycle timeout; events=${JSON.stringify(NS.chatEvents.slice(t2EventStart), null, 2)}`);
775
+ throw error;
776
+ }
769
777
  t3Dbg('step 5: ordered lifecycle observed');
770
778
  assert(!!t2Lifecycle, 'T3 ordered lifecycle not observed');
771
779
 
772
780
  t3Dbg('step 6: fetching post-chat transcript');
773
- const t2After = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats?all-turns=true`);
781
+ const t2After = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
774
782
  t3Dbg(`step 6: post-chat fetch returned status=${t2After.status}`);
775
783
  assert(t2After.status === 200, `T3 post chats returned ${t2After.status}`);
776
- const t2AfterMessages = Array.isArray(t2After.data?.messages) ? t2After.data.messages : [];
784
+ const t2AfterMessages = Array.isArray(t2After.data?.data?.messages) ? t2After.data.data.messages : [];
777
785
  const t2NewMessages = t2AfterMessages.slice(t2BeforeCount);
778
786
  t3Dbg(`step 6: validating ${t2NewMessages.length} new messages`);
779
787
  assert(t2NewMessages.length >= 3, `T3 expected at least 3 new chat messages, got ${t2NewMessages.length}`);
@@ -815,24 +823,27 @@ try {
815
823
  console.log('\n=== T3a: non-probe chat protocol (expect paris) ===');
816
824
  const t3aDbg = (msg) => console.log(`[T3a.DBG ${new Date().toISOString()}] ${msg}`);
817
825
  t3aDbg('step 1: fetching pre-chat transcript');
818
- const t2aBefore = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats?all-turns=true`);
826
+ const t2aBefore = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
819
827
  t3aDbg(`step 1: pre-chat fetch returned status=${t2aBefore.status}`);
820
828
  assert(t2aBefore.status === 200, `T3a pre chats returned ${t2aBefore.status}`);
821
- const t2aBeforeMessages = Array.isArray(t2aBefore.data?.messages) ? t2aBefore.data.messages : [];
829
+ const t2aBeforeMessages = Array.isArray(t2aBefore.data?.data?.messages) ? t2aBefore.data.data.messages : [];
822
830
  const t2aBeforeCount = t2aBeforeMessages.length;
823
831
  const t2aPrompt = 'Just answer what is the capital of France. No Fluff. No COmmentary. No Markup Respond in lower case in one word.';
824
832
  t3aDbg(`step 1: beforeCount=${t2aBeforeCount}`);
825
833
 
826
834
  const t3aTurnId = randomTurnId();
827
835
  t3aDbg(`step 2: posting non-probe chat-send (turn-id=${t3aTurnId})`);
828
- const t2aSendRes = await httpJson('POST', `${BASE}/cards/${CHAT_CARD_ID}/actions`, {
829
- actionType: 'chat-send',
830
- payload: {
831
- text: JSON.stringify({
832
- prompt: t2aPrompt,
833
- chatTimeoutMs: 180000,
834
- }),
835
- 'turn-id': t3aTurnId,
836
+ const t2aSendRes = await httpJson('POST', `${BASE}/mcp-actions`, {
837
+ tool: 'chat-send',
838
+ args: {
839
+ card_id: CHAT_CARD_ID,
840
+ payload: {
841
+ text: JSON.stringify({
842
+ prompt: t2aPrompt,
843
+ chatTimeoutMs: 180000,
844
+ }),
845
+ 'turn-id': t3aTurnId,
846
+ },
836
847
  },
837
848
  });
838
849
  t3aDbg(`step 2: chat-send returned status=${t2aSendRes.status}`);
@@ -854,10 +865,10 @@ try {
854
865
  t3aDbg(`step 3: assistant SSE text=${JSON.stringify(String(t2aSseLast?.text || '').slice(0, 400))}`);
855
866
 
856
867
  t3aDbg('step 4: fetching post-chat transcript');
857
- const t2aAfter = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats?all-turns=true`);
868
+ const t2aAfter = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
858
869
  t3aDbg(`step 4: post-chat fetch returned status=${t2aAfter.status}`);
859
870
  assert(t2aAfter.status === 200, `T3a post chats returned ${t2aAfter.status}`);
860
- const t2aAfterMessages = Array.isArray(t2aAfter.data?.messages) ? t2aAfter.data.messages : [];
871
+ const t2aAfterMessages = Array.isArray(t2aAfter.data?.data?.messages) ? t2aAfter.data.data.messages : [];
861
872
  const t2aNewMessages = t2aAfterMessages.slice(t2aBeforeCount);
862
873
  t3aDbg(`step 4: validating ${t2aNewMessages.length} new messages`);
863
874
  assert(t2aNewMessages.length >= 2, `T3a expected at least 2 new chat messages, got ${t2aNewMessages.length}`);
@@ -873,33 +884,36 @@ try {
873
884
  console.log('\n=== T3b: skipped (--skip-t3b) ===');
874
885
  } else {
875
886
  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`);
887
+ const t2bBefore = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
877
888
  assert(t2bBefore.status === 200, `T3b pre chats returned ${t2bBefore.status}`);
878
- const t2bBeforeMessages = Array.isArray(t2bBefore.data?.messages) ? t2bBefore.data.messages : [];
889
+ const t2bBeforeMessages = Array.isArray(t2bBefore.data?.data?.messages) ? t2bBefore.data.data.messages : [];
879
890
  const t2bBeforeCount = t2bBeforeMessages.length;
880
891
 
881
892
  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
- );
893
+ const t2bUploadRes = await httpMcpControlplane('manage.add-chat-attachment', {
894
+ board_id: BOARD_ID,
895
+ card_id: CHAT_CARD_ID,
896
+ turn_id: t3bTurnId,
897
+ file_name: 'q1.txt',
898
+ content_type: 'text/plain; charset=utf-8',
899
+ base64: Buffer.from('tokyo', 'utf-8').toString('base64'),
900
+ });
887
901
  assert(t2bUploadRes.status === 200, `T3b file upload returned ${t2bUploadRes.status}`);
888
- const uploadedFile = t2bUploadRes.data?.file;
902
+ const uploadedFile = t2bUploadRes.data?.data?.files?.[0];
889
903
  assert(uploadedFile && typeof uploadedFile === 'object', 'T3b upload response missing file metadata');
890
904
 
891
- const t2bAfterUpload = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats?all-turns=true`);
905
+ const t2bAfterUpload = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
892
906
  assert(t2bAfterUpload.status === 200, `T3b chats after upload returned ${t2bAfterUpload.status}`);
893
- const t2bUploadMessages = Array.isArray(t2bAfterUpload.data?.messages) ? t2bAfterUpload.data.messages : [];
907
+ const t2bUploadMessages = Array.isArray(t2bAfterUpload.data?.data?.messages) ? t2bAfterUpload.data.data.messages : [];
894
908
  const t2bUploadNewMessages = t2bUploadMessages.slice(t2bBeforeCount);
895
909
  const t2bUploadSystem = t2bUploadNewMessages.find((m) => m?.role === 'system');
896
910
  assert(!!t2bUploadSystem, 'T3b upload protocol missing system chat file');
897
911
  assert(String(t2bUploadSystem?.text || '').toLowerCase().includes('file uploaded:'), 'T3b upload system message does not describe uploaded file');
898
912
 
899
- const t2bCardAfterUpload = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}`);
913
+ const t2bCardAfterUpload = await httpMcp('manage.read-card', { card_id: CHAT_CARD_ID });
900
914
  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
915
+ const t2bFilesAfterUpload = Array.isArray(t2bCardAfterUpload.data?.data?.[0]?.card_data?.files)
916
+ ? t2bCardAfterUpload.data.data[0].card_data.files
903
917
  : [];
904
918
  const t2bFileIndex = t2bFilesAfterUpload.findIndex((f) => String(f?.stored_name || '') === String(uploadedFile?.stored_name || ''));
905
919
  assert(t2bFileIndex >= 0, 'T3b uploaded file metadata not found in card_data.files');
@@ -914,12 +928,14 @@ try {
914
928
  const t2bEventStart = NS.chatEvents.length;
915
929
 
916
930
  const t2bPrompt = `probe echo file-upload validation ${Date.now()}`;
917
- const t2bSendRes = await httpJson('POST', `${BASE}/cards/${CHAT_CARD_ID}/actions`, {
918
- actionType: 'chat-send',
919
- payload: {
920
- text: `${ECHO_PROBE_MARKER}${t2bPrompt}${ECHO_PROBE_MARKER}`,
921
- files: [uploadedFile],
922
- 'turn-id': t3bTurnId,
931
+ const t2bSendRes = await httpJson('POST', `${BASE}/mcp-actions`, {
932
+ tool: 'chat-send',
933
+ args: {
934
+ card_id: CHAT_CARD_ID,
935
+ payload: {
936
+ text: `${ECHO_PROBE_MARKER}${t2bPrompt}${ECHO_PROBE_MARKER}`,
937
+ 'turn-id': t3bTurnId,
938
+ },
923
939
  },
924
940
  });
925
941
  assert(t2bSendRes.status === 200, `T3b chat-send returned ${t2bSendRes.status}`);
@@ -929,15 +945,27 @@ try {
929
945
  beforeCount: t2bSendBaseline,
930
946
  beforeProcessing: false,
931
947
  prompt: t2bPrompt,
932
- assistantText: 'tokyo',
933
948
  inProgressText: PROBE_IN_PROGRESS_TEXT,
934
949
  });
935
- }, 60_000, 'T3b ordered lifecycle');
950
+ }, 60_000, 'T3b ordered lifecycle').catch(async (err) => {
951
+ const t2bEvents = NS.chatEvents.slice(t2bEventStart);
952
+ const t2bMilestones = deriveProbeLifecycleMilestones(t2bEvents, {
953
+ beforeCount: t2bSendBaseline,
954
+ beforeProcessing: false,
955
+ prompt: t2bPrompt,
956
+ inProgressText: PROBE_IN_PROGRESS_TEXT,
957
+ });
958
+ const t2bCurrent = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
959
+ console.error('[T3b.DBG timeout] milestones=', JSON.stringify(t2bMilestones));
960
+ console.error('[T3b.DBG timeout] events=', JSON.stringify(t2bEvents));
961
+ console.error('[T3b.DBG timeout] inspect=', JSON.stringify(t2bCurrent?.data ?? null));
962
+ throw err;
963
+ });
936
964
  assert(!!t2bLifecycle, 'T3b ordered lifecycle not observed');
937
965
 
938
- const t2bAfter = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats?all-turns=true`);
966
+ const t2bAfter = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
939
967
  assert(t2bAfter.status === 200, `T3b post chats returned ${t2bAfter.status}`);
940
- const t2bAfterMessages = Array.isArray(t2bAfter.data?.messages) ? t2bAfter.data.messages : [];
968
+ const t2bAfterMessages = Array.isArray(t2bAfter.data?.data?.messages) ? t2bAfter.data.data.messages : [];
941
969
  const t2bNewMessages = t2bAfterMessages.slice(t2bSendBaseline);
942
970
  assert(t2bNewMessages.length >= 3, `T3b expected at least 3 chat messages after send, got ${t2bNewMessages.length}`);
943
971
 
@@ -948,9 +976,9 @@ try {
948
976
  assert(!!t2bUser && typeof t2bUser.id === 'string', 'T3b missing user chat message notification');
949
977
  assert(!!t2bInProgress && typeof t2bInProgress.id === 'string', 'T3b missing in-progress system chat message');
950
978
  assert(!!t2bAssistantMsg && typeof t2bAssistantMsg.id === 'string', 'T3b missing assistant chat message notification');
951
- assert(Array.isArray(t2bUser?.files) && t2bUser.files.length === 1, 'T3b user chat message missing uploaded file metadata');
952
- assert(String(t2bAssistantMsg?.text || '').trim() === 'tokyo', 'T3b assistant attachment content mismatch');
953
- console.log('[T3b] ok: upload protocol and ordered probe lifecycle observed with attachment-derived assistant reply');
979
+ assert(!Array.isArray(t2bUser?.files) || t2bUser.files.length === 0, 'T3b user chat message should remain text-only after add-chat-attachment upload');
980
+ assert(String(t2bAssistantMsg?.text || '').includes(`Echo: ${t2bPrompt}`), 'T3b assistant probe echo mismatch');
981
+ console.log('[T3b] ok: add-chat-attachment upload plus text-only chat-send preserved the normal probe lifecycle');
954
982
  }
955
983
 
956
984
  // ── T3d: probe-echo chat with one AI-generated attachment ──
@@ -972,25 +1000,28 @@ try {
972
1000
  t3dOwnedSseClient = true;
973
1001
  }
974
1002
 
975
- const t2dBeforeChats = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats?all-turns=true`);
1003
+ const t2dBeforeChats = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
976
1004
  assert(t2dBeforeChats.status === 200, `T3d pre chats returned ${t2dBeforeChats.status}`);
977
- const t2dBeforeMessages = Array.isArray(t2dBeforeChats.data?.messages) ? t2dBeforeChats.data.messages : [];
1005
+ const t2dBeforeMessages = Array.isArray(t2dBeforeChats.data?.data?.messages) ? t2dBeforeChats.data.data.messages : [];
978
1006
  const t2dBeforeCount = t2dBeforeMessages.length;
979
1007
 
980
- const t2dBeforeCard = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}`);
1008
+ const t2dBeforeCard = await httpMcp('manage.read-card', { card_id: CHAT_CARD_ID });
981
1009
  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
1010
+ const t2dBeforeFiles = Array.isArray(t2dBeforeCard.data?.data?.[0]?.card_data?.files)
1011
+ ? t2dBeforeCard.data.data[0].card_data.files
984
1012
  : [];
985
1013
 
986
1014
  const t3dTurnId = randomTurnId();
987
1015
  const t2dPrompt = `probe generated attachment validation ${Date.now()}`;
988
1016
  const t2dEventStart = NS.chatEvents.length;
989
- const t2dSendRes = await httpJson('POST', `${BASE}/cards/${CHAT_CARD_ID}/actions`, {
990
- actionType: 'chat-send',
991
- payload: {
992
- text: `${ECHO_PROBE_MARKER}[attach] ${t2dPrompt}${ECHO_PROBE_MARKER}`,
993
- 'turn-id': t3dTurnId,
1017
+ const t2dSendRes = await httpJson('POST', `${BASE}/mcp-actions`, {
1018
+ tool: 'chat-send',
1019
+ args: {
1020
+ card_id: CHAT_CARD_ID,
1021
+ payload: {
1022
+ text: `${ECHO_PROBE_MARKER}[attach] ${t2dPrompt}${ECHO_PROBE_MARKER}`,
1023
+ 'turn-id': t3dTurnId,
1024
+ },
994
1025
  },
995
1026
  });
996
1027
  assert(t2dSendRes.status === 200, `T3d chat-send returned ${t2dSendRes.status}`);
@@ -1006,9 +1037,9 @@ try {
1006
1037
  }, 60_000, 'T3d ordered lifecycle');
1007
1038
  assert(!!t2dLifecycle, 'T3d ordered lifecycle not observed');
1008
1039
 
1009
- const t2dAfter = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats?all-turns=true`);
1040
+ const t2dAfter = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
1010
1041
  assert(t2dAfter.status === 200, `T3d post chats returned ${t2dAfter.status}`);
1011
- const t2dAfterMessages = Array.isArray(t2dAfter.data?.messages) ? t2dAfter.data.messages : [];
1042
+ const t2dAfterMessages = Array.isArray(t2dAfter.data?.data?.messages) ? t2dAfter.data.data.messages : [];
1012
1043
  const t2dNewMessages = t2dAfterMessages.slice(t2dBeforeCount);
1013
1044
  assert(t2dNewMessages.length >= 4, `T3d expected at least 4 chat messages after send, got ${t2dNewMessages.length}`);
1014
1045
 
@@ -1031,10 +1062,10 @@ try {
1031
1062
  const t2dFileIndex = Number.parseInt(t2dFileIndexMatch[1], 10);
1032
1063
  assert(Number.isInteger(t2dFileIndex) && t2dFileIndex >= 0, 'T3d AI-generated message file index should be non-negative');
1033
1064
 
1034
- const t2dAfterCard = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}`);
1065
+ const t2dAfterCard = await httpMcp('manage.read-card', { card_id: CHAT_CARD_ID });
1035
1066
  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
1067
+ const t2dAfterFiles = Array.isArray(t2dAfterCard.data?.data?.[0]?.card_data?.files)
1068
+ ? t2dAfterCard.data.data[0].card_data.files
1038
1069
  : [];
1039
1070
  assert(t2dAfterFiles.length === t2dBeforeFiles.length + 1, `T3d expected exactly one new stored file, got ${t2dAfterFiles.length - t2dBeforeFiles.length}`);
1040
1071
  const t2dStoredFile = t2dAfterFiles[t2dFileIndex];
@@ -1551,28 +1582,28 @@ try {
1551
1582
 
1552
1583
  const t5ThreadId = `thread-t5-${Date.now()}`;
1553
1584
  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',
1585
+ await httpMcpControlplane('setstate.card-private', { board_id: BOARD_ID, card_id: T5_CARD_ID, key: 'chat.foundry_thread_id', value: t5ThreadId }),
1586
+ 'T5 setstate.card-private',
1556
1587
  );
1557
1588
  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',
1589
+ await httpMcpControlplane('getstate.card-private', { board_id: BOARD_ID, card_id: T5_CARD_ID, key: 'chat.foundry_thread_id' }),
1590
+ 'T5 getstate.card-private',
1560
1591
  );
1561
- assert(t5GetMeta?.exists === true && t5GetMeta?.value === t5ThreadId, `T5 getstate.card-meta mismatch: ${JSON.stringify(t5GetMeta)}`);
1592
+ assert(t5GetMeta?.exists === true && t5GetMeta?.value === t5ThreadId, `T5 getstate.card-private mismatch: ${JSON.stringify(t5GetMeta)}`);
1562
1593
 
1563
1594
  const t5ReadCards = expectMcpSuccess(
1564
1595
  await httpMcp('manage.read-card', { card_id: T5_CARD_ID }),
1565
1596
  'T5 manage.read-card meta-redaction',
1566
1597
  );
1567
1598
  const t5ReadCard = Array.isArray(t5ReadCards) ? t5ReadCards[0] : null;
1568
- assert(t5ReadCard && t5ReadCard.meta === undefined, 'T5 expected manage.read-card to redact top-level meta');
1599
+ assert(t5ReadCard && t5ReadCard.__private === undefined, 'T5 expected manage.read-card to redact __private');
1569
1600
 
1570
1601
  const t5Inspect = expectMcpSuccess(
1571
1602
  await httpMcp('inspect.card-definition-and-runtime', { card_id: T5_CARD_ID }),
1572
1603
  'T5 inspect.card-definition-and-runtime meta-redaction',
1573
1604
  );
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');
1605
+ assert(t5Inspect?.card_definition_and_static_data?.__private === undefined, 'T5 expected inspect to redact card_definition_and_static_data.__private');
1606
+ console.log('[T5] ok: regular /mcp surfaces redact __private');
1576
1607
 
1577
1608
  // ── T5: admin-only card round-trip ──────────────────────────────────────
1578
1609
  // 1. Read the existing card definition via the normal read-card path.
@@ -1610,34 +1641,32 @@ try {
1610
1641
  );
1611
1642
  const t5AdminCards = Array.isArray(t5AdminRead?.cards) ? t5AdminRead.cards : [];
1612
1643
  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');
1644
+ assert(t5AdminCards[0]?.__private?.visible_controlplane_only === true, `T5 expected __private.visible_controlplane_only=true, got: ${JSON.stringify(t5AdminCards[0]?.__private)}`);
1645
+ console.log('[T5] ok: manage.admin-read-card returns card with __private.visible_controlplane_only=true');
1615
1646
 
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', {
1647
+ // 5. Guard: setstate.card-private must block changing the flag to a different value.
1648
+ const t5MetaGuard = await httpMcpControlplane('setstate.card-private', {
1620
1649
  board_id: BOARD_ID,
1621
1650
  card_id: t5AdminCardId,
1622
- key: 'chat.__visible_controlplane_only',
1651
+ key: 'chat.visible_controlplane_only',
1623
1652
  value: false, // differs from current flag value (true) → must be rejected
1624
1653
  });
1625
1654
  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)');
1655
+ `T5 expected setstate.card-private to reject changing visible_controlplane_only, got: ${JSON.stringify(t5MetaGuard)}`);
1656
+ console.log('[T5] ok: setstate.card-private blocked flag mutation (false != true)');
1628
1657
 
1629
1658
  // 6. Guard: same key with value matching the current flag (true) must pass (idempotent).
1630
1659
  const t5MetaIdempotent = expectMcpSuccess(
1631
- await httpMcpControlplane('setstate.card-meta', {
1660
+ await httpMcpControlplane('setstate.card-private', {
1632
1661
  board_id: BOARD_ID,
1633
1662
  card_id: t5AdminCardId,
1634
- key: 'chat.__visible_controlplane_only',
1663
+ key: 'chat.visible_controlplane_only',
1635
1664
  value: true, // matches current flag value → idempotent, allowed
1636
1665
  }),
1637
- 'T5 setstate.card-meta idempotent same-value',
1666
+ 'T5 setstate.card-private idempotent same-value',
1638
1667
  );
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');
1668
+ assert(t5MetaIdempotent, 'T5 expected setstate.card-private to succeed with matching flag value');
1669
+ console.log('[T5] ok: setstate.card-private idempotent same-value allowed');
1641
1670
  }
1642
1671
  }
1643
1672
 
@@ -1649,8 +1678,8 @@ try {
1649
1678
  } catch { /* ignore */ }
1650
1679
  }
1651
1680
  if (chatSseClient) chatSseClient.close();
1681
+ if (boardSseClient) boardSseClient.close();
1652
1682
  await stopChildProcess(serverProc, 'demo board server');
1653
- if (sseWorker) await sseWorker.terminate();
1654
1683
 
1655
1684
  // Clean up the test setup directory
1656
1685
  if (fs.existsSync(SETUP_DIR)) {
@@ -18,7 +18,7 @@
18
18
 
19
19
  import http from 'node:http';
20
20
  import { Buffer } from 'node:buffer';
21
- import { createSingleBoardServerRuntime } from 'yaml-flow/server-runtime-controlface';
21
+ import { createSingleBoardServerRuntime } from 'yaml-flow/board-live-cards-server-runtime';
22
22
  import { createHostedBoardQueueLaneRegistry } from 'yaml-flow/server-jobs-queue-runner';
23
23
  import { startQueueLaneRunners } from 'yaml-flow/board-live-cards-node';
24
24
  import { initializeApp, cert, getApps } from 'firebase-admin/app';
@@ -55,8 +55,15 @@ const runtime = createSingleBoardServerRuntime({
55
55
  label: `Board — ${BOARD_ID}`,
56
56
  boardAdapter,
57
57
  baseRef: { kind: 'firestore', value: `boards/${BOARD_ID}` },
58
+ boardRuntimeStoreRef: makeRef('firestore', `boards/${BOARD_ID}/runtime-board`),
58
59
  cardStoreRef: makeRef('firestore', `boards/${BOARD_ID}/cards`),
59
60
  outputsStoreRef: makeRef('firestore', `boards/${BOARD_ID}/runtime-out`),
61
+ queueStoreRef: makeRef('firestore', `boards/${BOARD_ID}/runtime`),
62
+ fetchedSourcesStoreRef: makeRef('firestore', `boards/${BOARD_ID}/sources`),
63
+ chatStoreRef: makeRef('firestore', `boards/${BOARD_ID}/chat`),
64
+ artifactsStoreRef: makeRef('firestore', `boards/${BOARD_ID}/files`),
65
+ scratchStoreRef: makeRef('firestore', `boards/${BOARD_ID}/scratch`),
66
+ archiveStoreRef: makeRef('firestore', `boards/${BOARD_ID}/archive`),
60
67
  },
61
68
  ],
62
69
  invocationAdapter: {
@@ -80,6 +87,7 @@ const runtime = createSingleBoardServerRuntime({
80
87
  const stopRunners = startQueueLaneRunners(
81
88
  createHostedBoardQueueLaneRegistry({
82
89
  boardId: BOARD_ID,
90
+ queueStoreRef: makeRef('firestore', `boards/${BOARD_ID}/runtime`),
83
91
  runtime,
84
92
  boardAdapter,
85
93
  logger: {