@tradejs/app 3.1.29-beta.275 → 3.1.29-beta.277

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/app",
3
- "version": "3.1.29-beta.275",
3
+ "version": "3.1.29-beta.277",
4
4
  "description": "Installable Next.js UI for the TradeJS TypeScript framework: dashboards, backtests, charts, and runtime data.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -59,11 +59,11 @@
59
59
  "@emotion/react": "^11.14.0",
60
60
  "@modelcontextprotocol/sdk": "^1.32.1",
61
61
  "@node-oauth/oauth2-server": "^5.3.0",
62
- "@tradejs/core": "^3.1.29-beta.275",
63
- "@tradejs/indicators": "^3.1.29-beta.275",
64
- "@tradejs/infra": "^3.1.29-beta.275",
65
- "@tradejs/node": "^3.1.29-beta.275",
66
- "@tradejs/types": "^3.1.29-beta.275",
62
+ "@tradejs/core": "^3.1.29-beta.277",
63
+ "@tradejs/indicators": "^3.1.29-beta.277",
64
+ "@tradejs/infra": "^3.1.29-beta.277",
65
+ "@tradejs/node": "^3.1.29-beta.277",
66
+ "@tradejs/types": "^3.1.29-beta.277",
67
67
  "@types/bcryptjs": "2.4.6",
68
68
  "@types/lodash": "4.17.24",
69
69
  "@types/node": "24.13.3",
@@ -1,3 +1,4 @@
1
+ import { readDeploymentVerification } from './verification';
1
2
  import { randomUUID } from 'node:crypto';
2
3
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
4
  import { z } from 'zod';
@@ -275,6 +276,27 @@ export const createTradejsMcpServer = (principal: McpPrincipal) => {
275
276
  {},
276
277
  async () => readMcpDeployments(principal.userName),
277
278
  );
279
+ tool(
280
+ 'runtime_get_verification',
281
+ 'Read the checksum-verified Deploy-owned package manifest, actual image digest, deployment verification and health observation. fresh=false means the collector is stale; heartbeat is not a replacement. No job or runtime mutation is performed.',
282
+ ['runtime:read'],
283
+ { deploymentId: identifier },
284
+ async (args) => {
285
+ const deployment = await requireMcpDeployment(
286
+ principal.userName,
287
+ args.deploymentId,
288
+ );
289
+ return readDeploymentVerification(
290
+ String(process.env.PROJECT_CWD || process.cwd()),
291
+ {
292
+ userName: principal.userName,
293
+ deploymentId: deployment.id,
294
+ accountId: deployment.accountId,
295
+ deploymentCompositionId: deployment.deploymentCompositionId,
296
+ },
297
+ );
298
+ },
299
+ );
278
300
  tool(
279
301
  'runtime_list_strategies',
280
302
  'Read effective strategies, configs and immutable revisions for a deployment bound to this user account.',
@@ -0,0 +1,181 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFile, realpath, stat } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+
5
+ type Binding = {
6
+ userName: string;
7
+ deploymentId: string;
8
+ accountId: string;
9
+ deploymentCompositionId: string;
10
+ };
11
+ const hash = (bytes: Buffer) =>
12
+ createHash('sha256').update(bytes).digest('hex');
13
+
14
+ export const parseDeploymentVerification = (
15
+ bytes: Buffer,
16
+ pointer: { sha256: string; bytes: number },
17
+ binding: Binding,
18
+ manifestBytes: Buffer,
19
+ imageDigest: string,
20
+ now = Date.now(),
21
+ ) => {
22
+ if (bytes.length !== pointer.bytes || hash(bytes) !== pointer.sha256)
23
+ throw Error('MCP verification checksum mismatch');
24
+ const report = JSON.parse(bytes.toString('utf8'));
25
+ if (
26
+ report.schemaVersion !== 1 ||
27
+ report.reportType !== 'runtime-deployment-verification' ||
28
+ Object.entries(binding).some(([key, value]) => report[key] !== value)
29
+ )
30
+ throw Error('MCP verification binding mismatch');
31
+ if (
32
+ typeof report.ok !== 'boolean' ||
33
+ typeof report.verification?.ok !== 'boolean' ||
34
+ !Array.isArray(report.verification?.strategies) ||
35
+ !report.verification.strategies.length ||
36
+ typeof report.health?.status !== 'string' ||
37
+ report.verification.strategies.some(
38
+ (strategy: {
39
+ strategyName?: string;
40
+ strategyRevision?: string;
41
+ controlState?: string;
42
+ }) =>
43
+ !strategy.strategyName ||
44
+ !/^sr1:[a-f0-9]+$/.test(strategy.strategyRevision ?? '') ||
45
+ !['active', 'entries_paused'].includes(strategy.controlState ?? ''),
46
+ ) ||
47
+ report.ok !== (report.verification.ok && report.health.status === 'healthy')
48
+ )
49
+ throw Error('MCP verification result invalid');
50
+ const manifest = JSON.parse(manifestBytes.toString('utf8'));
51
+ if (
52
+ !/^sha256:[a-f0-9]{64}$/.test(imageDigest) ||
53
+ report.projectSha !== manifest.projectSha ||
54
+ report.imageDigest !== imageDigest ||
55
+ report.runtimePackageManifest?.sha256 !== hash(manifestBytes) ||
56
+ report.runtimePackageManifest?.bytes !== manifestBytes.length ||
57
+ JSON.stringify(report.runtimePackageManifest.manifest) !==
58
+ JSON.stringify(manifest)
59
+ )
60
+ throw Error('MCP verification image or manifest mismatch');
61
+ if (
62
+ !Number.isSafeInteger(report.observedAt) ||
63
+ report.observedAt > now + 30000 ||
64
+ (report.deploymentVerifiedAt !== null &&
65
+ (!Number.isSafeInteger(report.deploymentVerifiedAt) ||
66
+ report.deploymentVerifiedAt > report.observedAt))
67
+ )
68
+ throw Error('MCP verification timestamp invalid');
69
+ return {
70
+ report,
71
+ payload: { encoding: 'base64', data: bytes.toString('base64') },
72
+ sha256: pointer.sha256,
73
+ bytes: pointer.bytes,
74
+ observedAt: now,
75
+ fresh: now - report.observedAt <= 10 * 60000,
76
+ source: 'deploy_owned_verification_bundle',
77
+ };
78
+ };
79
+
80
+ const readVerification = async (projectRoot: string, binding: Binding) => {
81
+ const manifestBytes = await readFile(
82
+ path.join(projectRoot, 'runtime-package-manifest.json'),
83
+ );
84
+ const manifest = JSON.parse(manifestBytes.toString('utf8'));
85
+ if (
86
+ !/^[a-f0-9]{40}$/.test(manifest.projectSha) ||
87
+ !/^[\w-]+$/.test(binding.userName) ||
88
+ !/^[\w-]+$/.test(binding.deploymentId) ||
89
+ !/^dc1:[a-f0-9]+$/.test(binding.deploymentCompositionId)
90
+ )
91
+ throw Error('MCP verification identity invalid');
92
+ const root = await realpath(
93
+ path.join(projectRoot, 'data', 'runtime-verification'),
94
+ );
95
+ const dir = path.join(
96
+ root,
97
+ binding.userName,
98
+ binding.deploymentId,
99
+ manifest.projectSha,
100
+ binding.deploymentCompositionId,
101
+ );
102
+ const confined = async (name: string) => {
103
+ const resolved = await realpath(path.join(dir, name));
104
+ if (!resolved.startsWith(root + path.sep))
105
+ throw Error('MCP verification outside data root');
106
+ return resolved;
107
+ };
108
+ const pointerBytes = await readFile(await confined('latest.json'));
109
+ if (pointerBytes.length > 1024)
110
+ throw Error('MCP verification pointer too large');
111
+ const pointer = JSON.parse(pointerBytes.toString('utf8'));
112
+ if (
113
+ !/^[a-f0-9]{64}\.json$/.test(pointer.file) ||
114
+ pointer.file !== `${pointer.sha256}.json` ||
115
+ !Number.isSafeInteger(pointer.bytes) ||
116
+ pointer.bytes < 1 ||
117
+ pointer.bytes > 48000
118
+ )
119
+ throw Error('MCP verification pointer invalid');
120
+ const payloadPath = await confined(pointer.file);
121
+ if ((await stat(payloadPath)).size !== pointer.bytes)
122
+ throw Error('MCP verification size mismatch');
123
+ const bytes = await readFile(payloadPath);
124
+ const result = parseDeploymentVerification(
125
+ bytes,
126
+ pointer,
127
+ binding,
128
+ manifestBytes,
129
+ String(process.env.TRADEJS_PROJECT_IMAGE_DIGEST ?? ''),
130
+ );
131
+ const attemptPath = path.join(
132
+ root,
133
+ binding.userName,
134
+ binding.deploymentId,
135
+ 'latest-attempt.json',
136
+ );
137
+ try {
138
+ const resolved = await realpath(attemptPath);
139
+ if (
140
+ !resolved.startsWith(root + path.sep) ||
141
+ (await stat(resolved)).size > 2048
142
+ )
143
+ throw Error('MCP verification attempt invalid');
144
+ const attempt = JSON.parse(await readFile(resolved, 'utf8'));
145
+ if (
146
+ attempt.projectSha !== manifest.projectSha ||
147
+ attempt.imageDigest !== result.report.imageDigest ||
148
+ !Number.isSafeInteger(attempt.observedAt) ||
149
+ !['running', 'failed', 'succeeded'].includes(attempt.status)
150
+ )
151
+ throw Error('MCP verification attempt binding invalid');
152
+ return {
153
+ ...result,
154
+ fresh:
155
+ result.fresh &&
156
+ !(
157
+ attempt.status !== 'succeeded' &&
158
+ attempt.observedAt + 1000 >= result.report.observedAt
159
+ ),
160
+ collection: attempt,
161
+ };
162
+ } catch (error) {
163
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
164
+ return result;
165
+ }
166
+ };
167
+
168
+ export const readDeploymentVerification = async (
169
+ projectRoot: string,
170
+ binding: Binding,
171
+ ) => {
172
+ try {
173
+ return await readVerification(projectRoot, binding);
174
+ } catch (error) {
175
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT')
176
+ throw new Error(
177
+ 'MCP deployment verification is not published for the current image and composition',
178
+ );
179
+ throw error;
180
+ }
181
+ };