yaml-flow 5.4.0 → 6.0.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/board-live-cards-cli.js +2 -2
  2. package/board-livecards-server-runtime.js +488 -551
  3. package/browser/asset-integrity.json +10 -0
  4. package/browser/board-livecards-runtime-client.js +0 -6
  5. package/browser/board-livegraph-engine.js +2 -1676
  6. package/browser/board-livegraph-engine.js.map +1 -1
  7. package/browser/live-cards.js +347 -26
  8. package/browser/live-cards.schema.json +418 -132
  9. package/card-store.js +37 -0
  10. package/dist/batch/index.cjs +1 -108
  11. package/dist/batch/index.cjs.map +1 -1
  12. package/dist/batch/index.js +1 -106
  13. package/dist/batch/index.js.map +1 -1
  14. package/dist/board-live-cards-lib-Bg6EvCo5.d.cts +136 -0
  15. package/dist/board-live-cards-lib-jM2uYG1v.d.ts +136 -0
  16. package/dist/board-live-cards-public-CltXYgaY.d.cts +314 -0
  17. package/dist/board-live-cards-public-f-E-FAyp.d.ts +314 -0
  18. package/dist/board-livegraph-runtime/index.cjs +2 -1671
  19. package/dist/board-livegraph-runtime/index.cjs.map +1 -1
  20. package/dist/board-livegraph-runtime/index.d.cts +1 -2
  21. package/dist/board-livegraph-runtime/index.d.ts +1 -2
  22. package/dist/board-livegraph-runtime/index.js +2 -1662
  23. package/dist/board-livegraph-runtime/index.js.map +1 -1
  24. package/dist/board-livegraph-runtime/jsonata-sync.cjs +7587 -0
  25. package/dist/card-compute/index.cjs +9 -7159
  26. package/dist/card-compute/index.cjs.map +1 -1
  27. package/dist/card-compute/index.d.cts +22 -0
  28. package/dist/card-compute/index.d.ts +22 -0
  29. package/dist/card-compute/index.js +9 -7145
  30. package/dist/card-compute/index.js.map +1 -1
  31. package/dist/card-compute/jsonata-sync.cjs +7587 -0
  32. package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs +2 -0
  33. package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs.map +1 -0
  34. package/dist/cli/browser-api/board-live-cards-browser-adapter.d.cts +24 -0
  35. package/dist/cli/browser-api/board-live-cards-browser-adapter.d.ts +24 -0
  36. package/dist/cli/browser-api/board-live-cards-browser-adapter.js +2 -0
  37. package/dist/cli/browser-api/board-live-cards-browser-adapter.js.map +1 -0
  38. package/dist/cli/browser-api/card-store-browser-api.cjs +2 -0
  39. package/dist/cli/browser-api/card-store-browser-api.cjs.map +1 -0
  40. package/dist/cli/browser-api/card-store-browser-api.d.cts +26 -0
  41. package/dist/cli/browser-api/card-store-browser-api.d.ts +26 -0
  42. package/dist/cli/browser-api/card-store-browser-api.js +2 -0
  43. package/dist/cli/browser-api/card-store-browser-api.js.map +1 -0
  44. package/dist/cli/browser-api/jsonata-sync.cjs +7587 -0
  45. package/dist/cli/node/artifacts-store-cli.cjs +11 -0
  46. package/dist/cli/node/artifacts-store-cli.cjs.map +1 -0
  47. package/dist/cli/node/artifacts-store-cli.d.cts +8 -0
  48. package/dist/cli/node/artifacts-store-cli.d.ts +8 -0
  49. package/dist/cli/node/artifacts-store-cli.js +11 -0
  50. package/dist/cli/node/artifacts-store-cli.js.map +1 -0
  51. package/dist/cli/node/board-live-cards-cli.cjs +15 -0
  52. package/dist/cli/node/board-live-cards-cli.cjs.map +1 -0
  53. package/dist/cli/node/board-live-cards-cli.d.cts +20 -0
  54. package/dist/cli/node/board-live-cards-cli.d.ts +20 -0
  55. package/dist/cli/node/board-live-cards-cli.js +15 -0
  56. package/dist/cli/node/board-live-cards-cli.js.map +1 -0
  57. package/dist/cli/node/card-store-cli.cjs +8 -0
  58. package/dist/cli/node/card-store-cli.cjs.map +1 -0
  59. package/dist/cli/node/card-store-cli.d.cts +15 -0
  60. package/dist/cli/node/card-store-cli.d.ts +15 -0
  61. package/dist/cli/node/card-store-cli.js +8 -0
  62. package/dist/cli/node/card-store-cli.js.map +1 -0
  63. package/dist/cli/node/fs-board-adapter.cjs +14 -0
  64. package/dist/cli/node/fs-board-adapter.cjs.map +1 -0
  65. package/dist/cli/node/fs-board-adapter.d.cts +204 -0
  66. package/dist/cli/node/fs-board-adapter.d.ts +204 -0
  67. package/dist/cli/node/fs-board-adapter.js +14 -0
  68. package/dist/cli/node/fs-board-adapter.js.map +1 -0
  69. package/dist/cli/node/jsonata-sync.cjs +7587 -0
  70. package/dist/cli/node/source-cli-task-executor.cjs +11 -0
  71. package/dist/cli/node/source-cli-task-executor.cjs.map +1 -0
  72. package/dist/cli/node/source-cli-task-executor.d.cts +1 -0
  73. package/dist/cli/node/source-cli-task-executor.d.ts +1 -0
  74. package/dist/cli/node/source-cli-task-executor.js +11 -0
  75. package/dist/cli/node/source-cli-task-executor.js.map +1 -0
  76. package/dist/config/index.cjs +1 -79
  77. package/dist/config/index.cjs.map +1 -1
  78. package/dist/config/index.js +1 -76
  79. package/dist/config/index.js.map +1 -1
  80. package/dist/continuous-event-graph/index.cjs +2 -2129
  81. package/dist/continuous-event-graph/index.cjs.map +1 -1
  82. package/dist/continuous-event-graph/index.d.cts +81 -5
  83. package/dist/continuous-event-graph/index.d.ts +81 -5
  84. package/dist/continuous-event-graph/index.js +2 -2088
  85. package/dist/continuous-event-graph/index.js.map +1 -1
  86. package/dist/continuous-event-graph/jsonata-sync.cjs +7587 -0
  87. package/dist/event-graph/index.cjs +22 -8292
  88. package/dist/event-graph/index.cjs.map +1 -1
  89. package/dist/event-graph/index.js +22 -8237
  90. package/dist/event-graph/index.js.map +1 -1
  91. package/dist/execution-refs.cjs +2 -0
  92. package/dist/execution-refs.cjs.map +1 -0
  93. package/dist/execution-refs.d.cts +222 -0
  94. package/dist/execution-refs.d.ts +222 -0
  95. package/dist/execution-refs.js +2 -0
  96. package/dist/execution-refs.js.map +1 -0
  97. package/dist/index.cjs +29 -13221
  98. package/dist/index.cjs.map +1 -1
  99. package/dist/index.d.cts +2 -4
  100. package/dist/index.d.ts +2 -4
  101. package/dist/index.js +29 -13112
  102. package/dist/index.js.map +1 -1
  103. package/dist/inference/index.cjs +5 -617
  104. package/dist/inference/index.cjs.map +1 -1
  105. package/dist/inference/index.js +5 -610
  106. package/dist/inference/index.js.map +1 -1
  107. package/dist/jsonata-sync.cjs +7587 -0
  108. package/dist/{live-cards-bridge-x5XREkXm.d.cts → live-cards-bridge-BXbVTsna.d.cts} +27 -4
  109. package/dist/{live-cards-bridge-EQjytzI_.d.ts → live-cards-bridge-Ds28XR15.d.ts} +27 -4
  110. package/dist/pycli/quickjs-board-runtime.global.js +9 -0
  111. package/dist/pycli/quickjs-board-runtime.global.js.map +1 -0
  112. package/dist/pycli/quickjs-step-machine-runtime.global.js +5 -0
  113. package/dist/pycli/quickjs-step-machine-runtime.global.js.map +1 -0
  114. package/dist/step-machine/index.cjs +11 -7129
  115. package/dist/step-machine/index.cjs.map +1 -1
  116. package/dist/step-machine/index.js +11 -7113
  117. package/dist/step-machine/index.js.map +1 -1
  118. package/dist/storage-refs.cjs +10 -0
  119. package/dist/storage-refs.cjs.map +1 -0
  120. package/dist/storage-refs.d.cts +92 -0
  121. package/dist/storage-refs.d.ts +92 -0
  122. package/dist/storage-refs.js +10 -0
  123. package/dist/storage-refs.js.map +1 -0
  124. package/dist/stores/file.cjs +1 -114
  125. package/dist/stores/file.cjs.map +1 -1
  126. package/dist/stores/file.js +1 -112
  127. package/dist/stores/file.js.map +1 -1
  128. package/dist/stores/index.cjs +1 -231
  129. package/dist/stores/index.cjs.map +1 -1
  130. package/dist/stores/index.js +1 -227
  131. package/dist/stores/index.js.map +1 -1
  132. package/dist/stores/localStorage.cjs +1 -76
  133. package/dist/stores/localStorage.cjs.map +1 -1
  134. package/dist/stores/localStorage.js +1 -74
  135. package/dist/stores/localStorage.js.map +1 -1
  136. package/dist/stores/memory.cjs +1 -47
  137. package/dist/stores/memory.cjs.map +1 -1
  138. package/dist/stores/memory.js +1 -45
  139. package/dist/stores/memory.js.map +1 -1
  140. package/examples/browser/boards/portfolio-tracker/portfolio-t4.js +292 -0
  141. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-fetch-prices.js +218 -0
  142. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-fetch-prices.py +201 -0
  143. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-inference-adapter.js +25 -16
  144. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-public.js +553 -0
  145. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.py +365 -0
  146. package/examples/cli/step-machine-cli/portfolio-tracker/--base-ref/.runtime-out +1 -0
  147. package/examples/cli/step-machine-cli/portfolio-tracker/--base-ref/board-graph.json +32 -0
  148. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +53 -1
  149. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +15 -6
  150. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/init-board-cli.js +6 -1
  151. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/poll-status-cli.js +57 -0
  152. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/retrigger-cli.js +1 -1
  153. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/status-cli.js +1 -1
  154. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +7 -2
  155. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/wait-completed-cli.js +6 -2
  156. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/_board_pycli.py +97 -0
  157. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/add-cards.py +50 -0
  158. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/init-board.py +44 -0
  159. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/poll-status.py +70 -0
  160. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/reset-board-dir.py +36 -0
  161. package/examples/cli/step-machine-cli/portfolio-tracker/inline-python-demo.flow.yaml +26 -0
  162. package/examples/cli/step-machine-cli/portfolio-tracker/inline-python-handlers.py +39 -0
  163. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker-pycli.flow.yaml +80 -0
  164. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +25 -172
  165. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.input.json +40 -34
  166. package/examples/cli/step-machine-cli/portfolio-tracker/run-inline-python-demo-pycli.py +46 -0
  167. package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker-pycli.py +77 -0
  168. package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +1 -2
  169. package/examples/example-board/agent-instructions.md +11 -5
  170. package/examples/example-board/demo-chat-handler.js +14 -4
  171. package/examples/example-board/demo-server-config.json +1 -0
  172. package/examples/example-board/demo-server.js +19 -34
  173. package/examples/example-board/demo-shell-browser.html +5 -4
  174. package/examples/example-board/demo-shell-with-server.html +10 -6
  175. package/examples/example-board/demo-task-executor.js +81 -35
  176. package/examples/index.html +0 -14
  177. package/examples/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +0 -1
  178. package/examples/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +1 -2
  179. package/package.json +39 -3
  180. package/schema/live-cards.schema.json +418 -132
  181. package/dist/cli/board-live-cards-cli.cjs +0 -10644
  182. package/dist/cli/board-live-cards-cli.cjs.map +0 -1
  183. package/dist/cli/board-live-cards-cli.d.cts +0 -179
  184. package/dist/cli/board-live-cards-cli.d.ts +0 -179
  185. package/dist/cli/board-live-cards-cli.js +0 -10592
  186. package/dist/cli/board-live-cards-cli.js.map +0 -1
  187. package/dist/journal-9HEgs7dU.d.ts +0 -28
  188. package/dist/journal-B-JCfQnh.d.cts +0 -28
  189. package/dist/schedule-Cszq9LYY.d.ts +0 -21
  190. package/dist/schedule-qWNL0RQh.d.cts +0 -21
  191. package/examples/browser/boards/portfolio-tracker/cards/holdings-table.json +0 -22
  192. package/examples/browser/boards/portfolio-tracker/cards/portfolio-form.json +0 -16
  193. package/examples/browser/boards/portfolio-tracker/cards/portfolio-risk-assessment.json +0 -28
  194. package/examples/browser/boards/portfolio-tracker/cards/portfolio-value.json +0 -15
  195. package/examples/browser/boards/portfolio-tracker/cards/price-fetch.json +0 -15
  196. package/examples/browser/boards/portfolio-tracker/cards/rebalancing-strategy.json +0 -28
  197. package/examples/browser/boards/portfolio-tracker/fetch-prices.js +0 -43
  198. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-task-executor.cjs +0 -96
  199. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.bat +0 -7
  200. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +0 -351
@@ -0,0 +1,292 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * portfolio-t4.js — T4 rapid-fire test only.
4
+ *
5
+ * Runs T0 (init, card setup) then fires 5 portfolio-form upserts
6
+ * back-to-back (no delay) and waits for the board to converge.
7
+ * Asserts final prices contain the iter-5 tickers only (AAPL/MSFT/GOOG/TSLA)
8
+ * and AMZN is absent.
9
+ */
10
+
11
+ import path from 'node:path';
12
+ import fs from 'node:fs';
13
+ import os from 'node:os';
14
+ import { fileURLToPath, pathToFileURL } from 'node:url';
15
+
16
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
17
+ const _REPO_ROOT = path.resolve(__dirname, '..', '..', '..', '..');
18
+
19
+ // ── Library imports ────────────────────────────────────────────────────────────
20
+ const _adapterPath = path.join(_REPO_ROOT, 'dist', 'cli', 'node', 'fs-board-adapter.js');
21
+ const {
22
+ createBoardLiveCardsPublic,
23
+ createBoardLiveCardsNonCorePublic,
24
+ createFsBoardPlatformAdapter,
25
+ createFsBoardNonCorePlatformAdapter,
26
+ createCardStorePublic,
27
+ createCardStore,
28
+ parseRef,
29
+ } = await import(pathToFileURL(_adapterPath).href);
30
+
31
+ const FETCH_PRICES_JS = path.join(__dirname, 'portfolio-tracker-fetch-prices.js');
32
+
33
+ // ── Runtime directories ────────────────────────────────────────────────────────
34
+ const _TMP_BASE = path.join(os.tmpdir(), 'experiment-js-t4');
35
+ const CARDSTORE_DIR = path.join(_TMP_BASE, 'cardstore');
36
+ const BOARDRUNTIME_DIR = path.join(_TMP_BASE, 'boardruntime');
37
+ const OUTPUTS_DIR = path.join(_TMP_BASE, 'outputs');
38
+
39
+ const CARDSTORE_REF = `::fs-path::${CARDSTORE_DIR}`;
40
+ const BOARDRUNTIME_REF = `::fs-path::${BOARDRUNTIME_DIR}`;
41
+ const OUTPUTS_REF = `::fs-path::${OUTPUTS_DIR}`;
42
+
43
+ // ── Card definitions ───────────────────────────────────────────────────────────
44
+ const CARD_PORTFOLIO_FORM = {
45
+ id: 'portfolio-form',
46
+ meta: { title: 'Portfolio Holdings Form' },
47
+ provides: [{ bindTo: 'holdings', ref: 'card_data.holdings' }],
48
+ card_data: { holdings: [] },
49
+ view: { elements: [{ kind: 'table', label: 'Holdings', data: { bind: 'card_data.holdings', columns: ['symbol', 'qty'] } }] }
50
+ };
51
+
52
+ const CARD_PRICE_FETCH = {
53
+ id: 'price-fetch',
54
+ meta: { title: 'Fetch Market Prices' },
55
+ requires: ['holdings'],
56
+ provides: [{ bindTo: 'prices', ref: 'fetched_sources.prices' }],
57
+ card_data: {},
58
+ source_defs: [{
59
+ kind: 'mock-quotes',
60
+ bindTo: 'prices',
61
+ outputFile: 'prices.json',
62
+ projections: { tickers: '$append([], requires.holdings.symbol)' }
63
+ }],
64
+ view: { elements: [{ kind: 'table', label: 'Market Prices', data: { bind: 'fetched_sources.prices' } }] }
65
+ };
66
+
67
+ const CARD_HOLDINGS_TABLE = {
68
+ id: 'holdings-table',
69
+ meta: { title: 'Holdings Table' },
70
+ requires: ['holdings', 'prices'],
71
+ provides: [{ bindTo: 'table', ref: 'computed_values.table' }],
72
+ card_data: {},
73
+ compute: [{
74
+ bindTo: 'table',
75
+ expr: '{ "rows": $map(requires.holdings, function($h) { { "symbol": $h.symbol, "qty": $h.qty, "price": $lookup(requires.prices, $h.symbol), "value": $h.qty * $lookup(requires.prices, $h.symbol) } }) }'
76
+ }],
77
+ view: { elements: [{ kind: 'table', label: 'Portfolio Positions', data: { bind: 'computed_values.table.rows', columns: ['symbol', 'qty', 'price', 'value'] } }] }
78
+ };
79
+
80
+ const CARD_PORTFOLIO_VALUE = {
81
+ id: 'portfolio-value',
82
+ meta: { title: 'Portfolio Total Value' },
83
+ requires: ['table'],
84
+ provides: [{ bindTo: 'totalValue', ref: 'computed_values.totalValue' }],
85
+ card_data: {},
86
+ compute: [{ bindTo: 'totalValue', expr: '$sum(requires.table.rows.value)' }],
87
+ view: { elements: [{ kind: 'metric', label: 'Total Portfolio Value', data: { bind: 'computed_values.totalValue' } }] }
88
+ };
89
+
90
+ // ── Helpers ────────────────────────────────────────────────────────────────────
91
+ function setHoldings(card, holdings) {
92
+ return { ...card, card_data: { ...card.card_data, holdings: Object.entries(holdings).map(([symbol, qty]) => ({ symbol, qty })) } };
93
+ }
94
+
95
+ function assert(condition, message) {
96
+ if (!condition) { console.error(`[ASSERT FAILED] ${message}`); process.exit(1); }
97
+ }
98
+
99
+ function readJson(filePath) {
100
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
101
+ }
102
+
103
+ function makeBoard() {
104
+ const br = parseRef(BOARDRUNTIME_REF);
105
+ return createBoardLiveCardsPublic(br, createFsBoardPlatformAdapter(br, _REPO_ROOT, { onWarn: console.warn }));
106
+ }
107
+
108
+ function makeNonCoreBoard() {
109
+ const br = parseRef(BOARDRUNTIME_REF);
110
+ return createBoardLiveCardsNonCorePublic(br, createFsBoardNonCorePlatformAdapter(br, _REPO_ROOT, { onWarn: console.warn }));
111
+ }
112
+
113
+ function makeCardStore() {
114
+ const ref = parseRef(CARDSTORE_REF);
115
+ const adapter = createFsBoardPlatformAdapter(ref, _REPO_ROOT, { onWarn: console.warn });
116
+ const kv = adapter.kvStorageForRef(CARDSTORE_REF);
117
+ const cardAdapterObj = {
118
+ readIndex: () => kv.read('_index'),
119
+ writeIndex: (idx) => kv.write('_index', idx),
120
+ readCard: (id) => kv.read(id),
121
+ writeCard: (id, card) => { kv.write(id, card); return id; },
122
+ cardExists: (id) => kv.read(id) !== null,
123
+ defaultCardKey: (id) => id,
124
+ };
125
+ return createCardStorePublic(createCardStore(cardAdapterObj, console.warn));
126
+ }
127
+
128
+ function checkResult(result, label) {
129
+ if (result.status !== 'success') { console.error(`[ERROR] ${label}: ${result.status} — ${result.error}`); process.exit(1); }
130
+ return result.data;
131
+ }
132
+
133
+ async function waitForCompleted(label, timeoutMs = 90_000, pollMs = 500) {
134
+ const deadline = Date.now() + timeoutMs;
135
+ let pollCount = 0;
136
+ while (Date.now() < deadline) {
137
+ await new Promise(r => setTimeout(r, pollMs));
138
+ const result = makeBoard().status({});
139
+ pollCount++;
140
+ if (result.status === 'success') {
141
+ const { card_count, completed, in_progress, pending, failed } = result.data.summary;
142
+ if (card_count > 0 && completed === card_count) {
143
+ console.log(`[${label}] all ${card_count} card(s) completed.`);
144
+ return result.data;
145
+ }
146
+ if (pollCount % 4 === 0) {
147
+ const notDone = result.data.cards.filter(c => c.status !== 'completed').map(c => `${c.name}:${c.status}`);
148
+ console.log(`[${label}] poll#${pollCount}: completed=${completed}/${card_count}, in_progress=${in_progress}, pending=${pending}, failed=${failed} | ${notDone.join(', ')}`);
149
+ }
150
+ }
151
+ }
152
+ console.error(`[ERROR] ${label}: timed out waiting for all cards to complete.`);
153
+ process.exit(1);
154
+ }
155
+
156
+ const T = () => Date.now();
157
+
158
+ // ── T0 — Init ─────────────────────────────────────────────────────────────────
159
+ console.log('\n=== T0: Init ===');
160
+ if (fs.existsSync(_TMP_BASE)) fs.rmSync(_TMP_BASE, { recursive: true, force: true });
161
+ for (const d of [CARDSTORE_DIR, BOARDRUNTIME_DIR, OUTPUTS_DIR]) fs.mkdirSync(d, { recursive: true });
162
+ console.log(` runtime base: ${_TMP_BASE}`);
163
+
164
+ checkResult(
165
+ makeBoard().init({
166
+ params: { cardStoreRef: CARDSTORE_REF, outputsStoreRef: OUTPUTS_REF },
167
+ body: { 'task-executor-ref': { meta: 'task-executor', howToRun: 'local-node', whatToRun: `::fs-path::${FETCH_PRICES_JS}` } },
168
+ }),
169
+ 'init'
170
+ );
171
+ console.log(` [${T()}] init done`);
172
+
173
+ const cardStore = makeCardStore();
174
+ for (const card of [
175
+ setHoldings(CARD_PORTFOLIO_FORM, { AAPL: 10 }),
176
+ CARD_PRICE_FETCH,
177
+ CARD_HOLDINGS_TABLE,
178
+ CARD_PORTFOLIO_VALUE,
179
+ ]) {
180
+ const vr = makeNonCoreBoard().validateTmpCard({ body: card });
181
+ console.log(` [${T()}] validateTmpCard ${card.id} done`);
182
+ if (!vr.data?.isValid) { console.error(`[VALIDATE FAILED] ${card.id}:`, JSON.stringify(vr.data?.issues ?? vr.error)); process.exit(1); }
183
+ checkResult(cardStore.set({ body: card }), `card-store set ${card.id}`);
184
+ console.log(` [${T()}] cardStore.set ${card.id} done`);
185
+ }
186
+
187
+ for (const cardId of ['portfolio-form', 'price-fetch', 'holdings-table', 'portfolio-value']) {
188
+ checkResult(makeBoard().upsertCard({ params: { cardId } }), `upsertCard ${cardId}`);
189
+ console.log(` [${T()}] upsertCard ${cardId} done`);
190
+ }
191
+
192
+ await waitForCompleted('T0-settle');
193
+ console.log(`[${T()}] [T0] board settled with initial holdings.`);
194
+
195
+ // ── T4 — Rapid 5× portfolio-form updates (no delay) ───────────────────────────
196
+ console.log('\n=== T4: Rapid 5x portfolio-form updates (no delay) ===');
197
+
198
+ const T4_ITERS = [
199
+ { AAPL: 50 },
200
+ { AAPL: 45, MSFT: 30 },
201
+ { AAPL: 45, MSFT: 30, GOOG: 110 },
202
+ { AAPL: 40, MSFT: 35, GOOG: 120, TSLA: 70 },
203
+ { AAPL: 45, MSFT: 30, GOOG: 110, AMZN: 140, TSLA: 60 },
204
+ ];
205
+
206
+ // Expected final state: iter 5 holdings (AMZN present in iter 5 but test verifies what actually wins)
207
+ const T4_EXPECTED_FINAL = { AAPL: 45, MSFT: 30, GOOG: 110, AMZN: 140, TSLA: 60 };
208
+
209
+ for (let i = 0; i < T4_ITERS.length; i++) {
210
+ const holdings = T4_ITERS[i];
211
+ console.log(` iter ${i + 1}: ${JSON.stringify(holdings)}`);
212
+ checkResult(makeCardStore().set({ body: setHoldings(CARD_PORTFOLIO_FORM, holdings) }), `iter${i + 1} card-store set`);
213
+ console.log(` [${T()}] iter ${i + 1} cardStore.set done`);
214
+ checkResult(makeBoard().upsertCard({ params: { cardId: 'portfolio-form', restart: 'true' } }), `iter${i + 1} upsert`);
215
+ console.log(` [${T()}] iter ${i + 1} upsertCard done`);
216
+ // await waitForCompleted(`T4-iter${i + 1}`); // commented out: rapid-fire, no waits
217
+ }
218
+
219
+ console.log(`\n[${T()}] [T4] all 5 upserts fired — waiting for board to converge...`);
220
+ const t4Final = await waitForCompleted('T4');
221
+ console.log(`[${T()}] [T4] waitForCompleted done`);
222
+
223
+ // ── Assertions ────────────────────────────────────────────────────────────────
224
+ const holdingsPath = path.join(OUTPUTS_DIR, 'data-objects', 'holdings.json');
225
+ const holdings = readJson(holdingsPath);
226
+ console.log('\n[T4] holdings.json (data-object output):', JSON.stringify(holdings, null, 2));
227
+
228
+ const finalCard = readJson(path.join(CARDSTORE_DIR, 'portfolio-form.json'));
229
+ console.log('[T4] cardstore portfolio-form holdings:', JSON.stringify(finalCard.card_data?.holdings, null, 2));
230
+
231
+ // Assert final holdings match iter-5
232
+ const holdingsBySymbol = Object.fromEntries(holdings.map(h => [h.symbol, h.qty]));
233
+ const expectedSymbols = Object.keys(T4_EXPECTED_FINAL).sort();
234
+ const actualSymbols = Object.keys(holdingsBySymbol).sort();
235
+ assert(JSON.stringify(actualSymbols) === JSON.stringify(expectedSymbols),
236
+ `T4: expected symbols ${JSON.stringify(expectedSymbols)}, got ${JSON.stringify(actualSymbols)}`);
237
+ for (const [sym, qty] of Object.entries(T4_EXPECTED_FINAL)) {
238
+ assert(holdingsBySymbol[sym] === qty,
239
+ `T4: expected ${sym} qty=${qty}, got ${holdingsBySymbol[sym]}`);
240
+ }
241
+ console.log('[T4] holdings assertions passed: iter-5 symbols and quantities match.');
242
+
243
+ // Assert prices contain exactly the iter-5 tickers
244
+ const pricesPath = path.join(OUTPUTS_DIR, 'data-objects', 'prices.json');
245
+ const prices = readJson(pricesPath);
246
+ const priceKeys = Object.keys(prices).sort();
247
+ assert(JSON.stringify(priceKeys) === JSON.stringify(expectedSymbols),
248
+ `T4: expected price keys ${JSON.stringify(expectedSymbols)}, got ${JSON.stringify(priceKeys)}`);
249
+ assert(Object.values(prices).every(v => typeof v === 'number'),
250
+ 'T4: all price values must be numbers');
251
+ console.log('[T4] prices assertions passed:', JSON.stringify(prices));
252
+
253
+ // Assert holdings-table rows match
254
+ const htCvPath = path.join(OUTPUTS_DIR, 'cards', 'holdings-table', 'computed_values.json');
255
+ const htCv = readJson(htCvPath);
256
+ const rowsBySymbol = Object.fromEntries([].concat(htCv.table.rows).map(r => [r.symbol, r]));
257
+ for (const [sym, qty] of Object.entries(T4_EXPECTED_FINAL)) {
258
+ assert(rowsBySymbol[sym]?.qty === qty,
259
+ `T4: holdings-table expected ${sym} qty=${qty}, got ${rowsBySymbol[sym]?.qty}`);
260
+ const expectedValue = Math.round(qty * prices[sym] * 100) / 100;
261
+ assert(Math.round(rowsBySymbol[sym]?.value * 100) === Math.round(expectedValue * 100),
262
+ `T4: holdings-table expected ${sym} value=${expectedValue}, got ${rowsBySymbol[sym]?.value}`);
263
+ }
264
+ console.log('[T4] holdings-table assertions passed: rows match holdings × prices.');
265
+
266
+ // Assert portfolio-value totalValue = sum(holdings × prices)
267
+ const pvCv = readJson(path.join(OUTPUTS_DIR, 'cards', 'portfolio-value', 'computed_values.json'));
268
+ const expectedTotal = Object.entries(T4_EXPECTED_FINAL).reduce(
269
+ (sum, [sym, qty]) => sum + qty * prices[sym], 0
270
+ );
271
+ assert(Math.round(pvCv.totalValue * 100) === Math.round(expectedTotal * 100),
272
+ `T4: expected totalValue=${Math.round(expectedTotal * 100) / 100}, got ${pvCv.totalValue}`);
273
+ console.log(`[T4] portfolio-value assertion passed: totalValue=${pvCv.totalValue} matches sum(holdings × prices).`);
274
+
275
+ // Assert cardstore portfolio-form holdings match iter-5
276
+ const cardstoreHoldings = Object.fromEntries(
277
+ (finalCard.card_data?.holdings ?? []).map(h => [h.symbol, h.qty])
278
+ );
279
+ for (const [sym, qty] of Object.entries(T4_EXPECTED_FINAL)) {
280
+ assert(cardstoreHoldings[sym] === qty,
281
+ `T4: cardstore expected ${sym} qty=${qty}, got ${cardstoreHoldings[sym]}`);
282
+ }
283
+ assert(Object.keys(cardstoreHoldings).length === Object.keys(T4_EXPECTED_FINAL).length,
284
+ `T4: cardstore has ${Object.keys(cardstoreHoldings).length} symbols, expected ${Object.keys(T4_EXPECTED_FINAL).length}`);
285
+ console.log('[T4] cardstore holdings assertion passed: portfolio-form matches iter-5.');
286
+
287
+ console.log('\nFinal board status summary:');
288
+ const { summary } = t4Final;
289
+ console.log(` completed=${summary.completed}/${summary.card_count}, failed=${summary.failed}`);
290
+
291
+ console.log('\n=== portfolio-t4 completed ===');
292
+ console.log(' runtime base:', _TMP_BASE);
@@ -0,0 +1,218 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * portfolio-tracker-fetch-prices.js
4
+ *
5
+ * Task executor for the portfolio board demo.
6
+ * Handles run-source-fetch requests for source_defs with kind: "mock-quotes".
7
+ * Generates random prices (2dp, 10.00–999.99) for each projected ticker.
8
+ *
9
+ * Subcommands:
10
+ * run-source-fetch — fetch mock prices for tickers from _projections
11
+ * validate-source-def — validate source def structure; prints { ok, errors } JSON
12
+ * describe-capabilities — print executor capabilities JSON
13
+ *
14
+ * run-source-fetch protocol:
15
+ * node portfolio-tracker-fetch-prices.js run-source-fetch \
16
+ * --in-ref <::kind::value> \
17
+ * --out-ref <::kind::value> \
18
+ * --err-ref <::kind::value>
19
+ *
20
+ * validate-source-def protocol:
21
+ * node portfolio-tracker-fetch-prices.js validate-source-def --in <source.json>
22
+ */
23
+
24
+ import fs from 'node:fs';
25
+ import { parseRef, blobStorageForRef, reportComplete, reportFailed } from 'yaml-flow/storage-refs';
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // validate-source-def — structural validation of a source definition
29
+ // ---------------------------------------------------------------------------
30
+ function validateSourceDefSubcommand(argv) {
31
+ const inIdx = argv.indexOf('--in');
32
+ const inFile = inIdx !== -1 ? argv[inIdx + 1] : undefined;
33
+
34
+ if (!inFile) {
35
+ console.error('[portfolio-tracker-fetch-prices] Usage: validate-source-def --in <source.json>');
36
+ process.exit(1);
37
+ }
38
+
39
+ if (!fs.existsSync(inFile)) {
40
+ console.log(JSON.stringify({ ok: false, errors: [`Input file not found: ${inFile}`] }));
41
+ process.exit(1);
42
+ }
43
+
44
+ let sourceDef;
45
+ try {
46
+ sourceDef = JSON.parse(fs.readFileSync(inFile, 'utf-8'));
47
+ } catch (err) {
48
+ console.log(JSON.stringify({ ok: false, errors: [`Cannot parse source file: ${err && err.message || err}`] }));
49
+ process.exit(1);
50
+ }
51
+
52
+ const errors = [];
53
+
54
+ if (sourceDef.kind !== 'mock-quotes') {
55
+ errors.push(`kind must be "mock-quotes"; got "${sourceDef.kind}".`);
56
+ }
57
+ if (!sourceDef.bindTo || typeof sourceDef.bindTo !== 'string') {
58
+ errors.push('bindTo is required and must be a string.');
59
+ }
60
+ if (!sourceDef.outputFile || typeof sourceDef.outputFile !== 'string') {
61
+ errors.push('outputFile is required and must be a string.');
62
+ }
63
+ if (!sourceDef.projections || typeof sourceDef.projections.tickers !== 'string') {
64
+ errors.push('projections.tickers is required and must be a JSONata expression string.');
65
+ }
66
+
67
+ const result = { ok: errors.length === 0, errors };
68
+ console.log(JSON.stringify(result));
69
+ process.exit(errors.length === 0 ? 0 : 1);
70
+ }
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // describe-capabilities — introspection metadata for this executor
74
+ // ---------------------------------------------------------------------------
75
+ const CAPABILITIES = {
76
+ version: '1.0',
77
+ executor: 'portfolio-tracker-fetch-prices',
78
+ subcommands: ['run-source-fetch', 'validate-source-def', 'describe-capabilities'],
79
+ sourceKinds: {
80
+ 'mock-quotes': {
81
+ description: 'Generates random mock market prices (10.00–999.99) for each ticker in _projections.tickers.',
82
+ inputSchema: {
83
+ kind: { type: 'string', required: true, description: 'Must be "mock-quotes".' },
84
+ bindTo: { type: 'string', required: true, description: 'Token name for the output binding.' },
85
+ outputFile: { type: 'string', required: true, description: 'Relative path to write prices JSON.' },
86
+ projections: {
87
+ type: 'object', required: true,
88
+ properties: {
89
+ tickers: { type: 'string', required: true, description: 'JSONata expression resolving to a string[] of ticker symbols (e.g. "requires.holdings.symbol").' },
90
+ },
91
+ },
92
+ },
93
+ outputShape: '{ [ticker: string]: number } — map of ticker symbol to random price (2dp).',
94
+ example: {
95
+ input: { kind: 'mock-quotes', bindTo: 'prices', outputFile: 'prices.json', projections: { tickers: 'requires.holdings.symbol' } },
96
+ output: { AAPL: 152.34, MSFT: 310.45 },
97
+ },
98
+ },
99
+ },
100
+ };
101
+
102
+ function describeCapabilities() {
103
+ console.log(JSON.stringify(CAPABILITIES, null, 2));
104
+ }
105
+
106
+ // ---------------------------------------------------------------------------
107
+ // run-source-fetch — generate random prices and report back
108
+ // ---------------------------------------------------------------------------
109
+ async function runSourceFetchSubcommand(argv) {
110
+ let inRefStr = '';
111
+ let outRefStr = '';
112
+ let errRefStr = '';
113
+
114
+ for (let i = 0; i < argv.length; i++) {
115
+ if (argv[i] === '--in-ref' && i + 1 < argv.length) inRefStr = argv[++i];
116
+ else if (argv[i] === '--out-ref' && i + 1 < argv.length) outRefStr = argv[++i];
117
+ else if (argv[i] === '--err-ref' && i + 1 < argv.length) errRefStr = argv[++i];
118
+ }
119
+
120
+ if (!inRefStr || !outRefStr || !errRefStr) {
121
+ console.error(
122
+ 'Usage: portfolio-tracker-fetch-prices.js run-source-fetch' +
123
+ ' --in-ref <ref> --out-ref <ref> --err-ref <ref>',
124
+ );
125
+ process.exit(1);
126
+ }
127
+
128
+ const inRef = parseRef(inRefStr);
129
+ const outRef = parseRef(outRefStr);
130
+ const errRef = parseRef(errRefStr);
131
+
132
+ const inStorage = blobStorageForRef(inRef);
133
+ const outStorage = blobStorageForRef(outRef);
134
+ const errStorage = blobStorageForRef(errRef);
135
+
136
+ // Read and parse the envelope up front so `callback` is always available for
137
+ // reportFailed — even if a later validation step throws.
138
+ const rawIn = inStorage.read(inRef.value);
139
+ if (!rawIn) {
140
+ console.error(`[portfolio-tracker-fetch-prices] input envelope not found at: ${inRefStr}`);
141
+ process.exit(1);
142
+ }
143
+ const envelope = JSON.parse(rawIn);
144
+ const callback = envelope.source_def ? envelope.callback : undefined;
145
+
146
+ let didReport = false;
147
+ const safeReportFailed = (msg) => {
148
+ if (didReport) return;
149
+ didReport = true;
150
+ try { errStorage.write(errRef.value, msg); } catch { /* best-effort */ }
151
+ if (callback) { reportFailed(callback, msg); } else { process.exit(1); }
152
+ };
153
+
154
+ try {
155
+ const sourceDef = envelope.source_def ?? envelope;
156
+
157
+ if (sourceDef.kind !== 'mock-quotes') {
158
+ throw new Error(`Unsupported source kind: expected "mock-quotes", got "${sourceDef.kind}"`);
159
+ }
160
+
161
+ const tickers = sourceDef._projections?.tickers;
162
+ if (!Array.isArray(tickers)) {
163
+ throw new Error('sourceDef._projections.tickers is missing or not an array');
164
+ }
165
+
166
+ // Random 200–300 ms delay (simulates a real market data fetch)
167
+ await new Promise(resolve => setTimeout(resolve, 200 + Math.random() * 100));
168
+
169
+ // Generate random prices — 2dp, range 10.00–999.99
170
+ const prices = {};
171
+ for (const ticker of tickers) {
172
+ prices[ticker] = Math.round((10 + Math.random() * 989.99) * 100) / 100;
173
+ }
174
+
175
+ outStorage.write(outRef.value, JSON.stringify(prices));
176
+ console.log(`[portfolio-tracker-fetch-prices] wrote prices for: ${tickers.join(', ')}`);
177
+
178
+ didReport = true;
179
+ if (callback) {
180
+ reportComplete(callback, outRef);
181
+ } else {
182
+ process.exit(0);
183
+ }
184
+ } catch (error) {
185
+ const msg = error instanceof Error ? error.message : String(error);
186
+ console.error(`[portfolio-tracker-fetch-prices] error: ${msg}`);
187
+ safeReportFailed(msg);
188
+ }
189
+ }
190
+
191
+ // ---------------------------------------------------------------------------
192
+ // main — subcommand routing
193
+ // ---------------------------------------------------------------------------
194
+ async function main() {
195
+ const sub = process.argv[2];
196
+ if (sub === 'run-source-fetch') {
197
+ await runSourceFetchSubcommand(process.argv.slice(3));
198
+ return;
199
+ }
200
+ if (sub === 'validate-source-def') {
201
+ validateSourceDefSubcommand(process.argv.slice(3));
202
+ return;
203
+ }
204
+ if (sub === 'describe-capabilities') {
205
+ describeCapabilities();
206
+ return;
207
+ }
208
+ console.error(
209
+ 'Usage: portfolio-tracker-fetch-prices.js <subcommand> [...args]\n' +
210
+ 'Subcommands: run-source-fetch, validate-source-def, describe-capabilities',
211
+ );
212
+ process.exit(1);
213
+ }
214
+
215
+ main().catch(err => {
216
+ console.error(`[portfolio-tracker-fetch-prices] fatal: ${err && err.message || err}`);
217
+ process.exit(1);
218
+ });