@tradejs/infra 3.1.0 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,220 @@
1
+ import {
2
+ getData,
3
+ getKeys,
4
+ incrementKey,
5
+ redisKeys,
6
+ setData,
7
+ setDataIfAbsent
8
+ } from "./chunk-RZTVC4ZL.mjs";
9
+
10
+ // src/runtimeStrategyReleases.ts
11
+ import { createHash, randomUUID } from "crypto";
12
+ import {
13
+ RUNTIME_STRATEGY_DRAFT_SCHEMA,
14
+ RUNTIME_STRATEGY_RELEASE_SCHEMA
15
+ } from "@tradejs/types";
16
+ var SHA256_RE = /^[a-f0-9]{64}$/;
17
+ var FORBIDDEN_RELEASE_CONFIG_KEYS = /* @__PURE__ */ new Set([
18
+ "ACCOUNT_ID",
19
+ "AI_REPLAY_ANALYSES",
20
+ "DEPLOYMENT_ID",
21
+ "ENABLE",
22
+ "ENV",
23
+ "MAKE_ORDERS",
24
+ "RECORD_RUNTIME_TRADES",
25
+ "configId"
26
+ ]);
27
+ var SECRET_CONFIG_KEY_RE = /(?:^|_)(?:API_KEY|API_SECRET|TOKEN|PASSWORD|PRIVATE_KEY)$/i;
28
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
29
+ var canonicalJson = (value) => {
30
+ const normalize = (current) => {
31
+ if (Array.isArray(current)) return current.map(normalize);
32
+ const record = asRecord(current);
33
+ if (!record) return current;
34
+ return Object.fromEntries(
35
+ Object.entries(record).filter(([, entry]) => entry !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, normalize(entry)])
36
+ );
37
+ };
38
+ return JSON.stringify(normalize(value));
39
+ };
40
+ var releaseContentSha256 = (release) => createHash("sha256").update(canonicalJson(release)).digest("hex");
41
+ var assertStrategyName = (strategyName) => {
42
+ if (!/^[a-zA-Z0-9_-]+$/.test(strategyName)) {
43
+ throw new Error(`Invalid strategy name: ${strategyName}`);
44
+ }
45
+ };
46
+ var assertRuntimeStrategyReleaseConfig = (config) => {
47
+ const record = asRecord(config);
48
+ if (!record) throw new Error("Strategy release config must be an object");
49
+ for (const key of FORBIDDEN_RELEASE_CONFIG_KEYS) {
50
+ if (record[key] !== void 0) {
51
+ throw new Error(`${key} is a deployment binding, not release config`);
52
+ }
53
+ }
54
+ const inspect = (value, parentPath = "") => {
55
+ if (Array.isArray(value)) {
56
+ value.forEach(
57
+ (entry, index) => inspect(entry, `${parentPath}[${index}]`)
58
+ );
59
+ return;
60
+ }
61
+ const current = asRecord(value);
62
+ if (!current) return;
63
+ for (const [key, entry] of Object.entries(current)) {
64
+ const keyPath = parentPath ? `${parentPath}.${key}` : key;
65
+ if (SECRET_CONFIG_KEY_RE.test(key)) {
66
+ throw new Error(`${keyPath} is secret material, not release config`);
67
+ }
68
+ inspect(entry, keyPath);
69
+ }
70
+ };
71
+ inspect(record);
72
+ if (typeof record.INTERVAL !== "string" || !record.INTERVAL.trim()) {
73
+ throw new Error("Published strategy config must define INTERVAL");
74
+ }
75
+ if (record.UNIVERSE !== "crypto" && record.UNIVERSE !== "tradfi") {
76
+ throw new Error("Published strategy config must define a valid UNIVERSE");
77
+ }
78
+ };
79
+ var isRelease = (value) => {
80
+ const release = asRecord(value);
81
+ return Boolean(
82
+ release?.schema === RUNTIME_STRATEGY_RELEASE_SCHEMA && typeof release.strategyName === "string" && Number.isSafeInteger(release.releaseVersion) && Number(release.releaseVersion) > 0 && asRecord(release.config) && typeof release.createdAt === "number" && typeof release.createdBy === "string" && typeof release.contentSha256 === "string" && SHA256_RE.test(release.contentSha256)
83
+ );
84
+ };
85
+ var verifyRuntimeStrategyRelease = (value) => {
86
+ if (!isRelease(value)) throw new Error("Invalid runtime strategy release");
87
+ const { contentSha256, ...content } = value;
88
+ if (releaseContentSha256(content) !== contentSha256) {
89
+ throw new Error(
90
+ `${value.strategyName} v${value.releaseVersion} content checksum mismatch`
91
+ );
92
+ }
93
+ assertRuntimeStrategyReleaseConfig(value.config);
94
+ return value;
95
+ };
96
+ var getRuntimeStrategyDraft = async (userName, strategyName) => {
97
+ const value = await getData(
98
+ redisKeys.runtimeStrategyDraft(userName, strategyName),
99
+ null
100
+ );
101
+ const draft = asRecord(value);
102
+ return draft?.schema === RUNTIME_STRATEGY_DRAFT_SCHEMA && draft.strategyName === strategyName && asRecord(draft.config) ? value : null;
103
+ };
104
+ var saveRuntimeStrategyDraft = async ({
105
+ userName,
106
+ strategyName,
107
+ config,
108
+ baseReleaseVersion = null,
109
+ updatedBy
110
+ }) => {
111
+ assertStrategyName(strategyName);
112
+ const draft = {
113
+ schema: RUNTIME_STRATEGY_DRAFT_SCHEMA,
114
+ strategyName,
115
+ baseReleaseVersion,
116
+ config,
117
+ updatedAt: Date.now(),
118
+ updatedBy
119
+ };
120
+ await setData(redisKeys.runtimeStrategyDraft(userName, strategyName), draft, {
121
+ expire: 0
122
+ });
123
+ return draft;
124
+ };
125
+ var getRuntimeStrategyRelease = async (userName, strategyName, releaseVersion) => {
126
+ const value = await getData(
127
+ redisKeys.runtimeStrategyRelease(userName, strategyName, releaseVersion),
128
+ null
129
+ );
130
+ return value == null ? null : verifyRuntimeStrategyRelease(value);
131
+ };
132
+ var listRuntimeStrategyReleases = async (userName, strategyName) => {
133
+ const keys = await getKeys(
134
+ redisKeys.runtimeStrategyReleases(userName, strategyName)
135
+ );
136
+ const releases = await Promise.all(
137
+ keys.map(async (key) => {
138
+ const value = await getData(key, null);
139
+ return value == null ? null : verifyRuntimeStrategyRelease(value);
140
+ })
141
+ );
142
+ return releases.filter((release) => release != null).sort((left, right) => right.releaseVersion - left.releaseVersion);
143
+ };
144
+ var publishRuntimeStrategyRelease = async ({
145
+ userName,
146
+ strategyName,
147
+ config,
148
+ strategyPackage = null,
149
+ strategyPackageVersion = null,
150
+ runtimePackageVersion = null,
151
+ createdBy
152
+ }) => {
153
+ assertStrategyName(strategyName);
154
+ assertRuntimeStrategyReleaseConfig(config);
155
+ const releaseVersion = await incrementKey(
156
+ redisKeys.runtimeStrategyReleaseSequence(userName, strategyName)
157
+ );
158
+ const content = {
159
+ schema: RUNTIME_STRATEGY_RELEASE_SCHEMA,
160
+ strategyName,
161
+ releaseVersion,
162
+ config,
163
+ strategyPackage,
164
+ strategyPackageVersion,
165
+ runtimePackageVersion,
166
+ createdAt: Date.now(),
167
+ createdBy
168
+ };
169
+ const release = {
170
+ ...content,
171
+ contentSha256: releaseContentSha256(content)
172
+ };
173
+ const stored = await setDataIfAbsent(
174
+ redisKeys.runtimeStrategyRelease(userName, strategyName, releaseVersion),
175
+ release
176
+ );
177
+ if (!stored) {
178
+ throw new Error(`Refused to overwrite ${strategyName} v${releaseVersion}`);
179
+ }
180
+ return release;
181
+ };
182
+ var recordRuntimeStrategyControlEvent = async ({
183
+ userName,
184
+ deploymentId,
185
+ strategyName,
186
+ releaseVersion,
187
+ previousState,
188
+ nextState,
189
+ createdBy
190
+ }) => {
191
+ const action = nextState === "entries_paused" ? "pause_entries" : "resume";
192
+ const createdAt = Date.now();
193
+ const event = {
194
+ eventId: `${createdAt}-${randomUUID()}`,
195
+ deploymentId,
196
+ strategyName,
197
+ releaseVersion,
198
+ action,
199
+ previousState,
200
+ nextState,
201
+ createdAt,
202
+ createdBy
203
+ };
204
+ await setData(
205
+ redisKeys.runtimeStrategyControlEvent(userName, event.eventId),
206
+ event,
207
+ { expire: 0 }
208
+ );
209
+ return event;
210
+ };
211
+ export {
212
+ assertRuntimeStrategyReleaseConfig,
213
+ getRuntimeStrategyDraft,
214
+ getRuntimeStrategyRelease,
215
+ listRuntimeStrategyReleases,
216
+ publishRuntimeStrategyRelease,
217
+ recordRuntimeStrategyControlEvent,
218
+ saveRuntimeStrategyDraft,
219
+ verifyRuntimeStrategyRelease
220
+ };
@@ -106,7 +106,7 @@ var verifyStrategyEvidenceMarkerEnvelope = (value) => {
106
106
  for (const value2 of payload.markers) {
107
107
  const marker = asRecord(value2);
108
108
  const coverage = asRecord(marker?.coverage);
109
- if (!marker || !isString(marker.id) || !isString(marker.type) || !MARKER_TYPES.has(marker.type) || !isNumber(marker.timestamp) || !isString(marker.label) || !isString(marker.summary) || !isString(marker.artifactId) || !isString(marker.artifactSha256) || !SHA256_RE.test(marker.artifactSha256) || marker.gitSha !== void 0 && !isString(marker.gitSha) || marker.gateFingerprint !== void 0 && (!isString(marker.gateFingerprint) || !LINEAGE_FINGERPRINT_RE.test(marker.gateFingerprint)) || marker.configFingerprint !== void 0 && (!isString(marker.configFingerprint) || !LINEAGE_FINGERPRINT_RE.test(marker.configFingerprint)) || marker.contextFingerprint !== void 0 && (!isString(marker.contextFingerprint) || !LINEAGE_FINGERPRINT_RE.test(marker.contextFingerprint)) || marker.maxLossValue !== void 0 && !isNumber(marker.maxLossValue) || marker.coverage !== void 0 && (!coverage || !isNumber(coverage.startTime) || !isNumber(coverage.endTime) || coverage.startTime > coverage.endTime)) {
109
+ if (!marker || !isString(marker.id) || !isString(marker.type) || !MARKER_TYPES.has(marker.type) || !isNumber(marker.timestamp) || !isString(marker.label) || !isString(marker.summary) || !isString(marker.artifactId) || !isString(marker.artifactSha256) || !SHA256_RE.test(marker.artifactSha256) || marker.releaseVersion !== void 0 && (!Number.isSafeInteger(marker.releaseVersion) || Number(marker.releaseVersion) <= 0) || marker.gitSha !== void 0 && !isString(marker.gitSha) || marker.gateFingerprint !== void 0 && (!isString(marker.gateFingerprint) || !LINEAGE_FINGERPRINT_RE.test(marker.gateFingerprint)) || marker.configFingerprint !== void 0 && (!isString(marker.configFingerprint) || !LINEAGE_FINGERPRINT_RE.test(marker.configFingerprint)) || marker.contextFingerprint !== void 0 && (!isString(marker.contextFingerprint) || !LINEAGE_FINGERPRINT_RE.test(marker.contextFingerprint)) || marker.maxLossValue !== void 0 && !isNumber(marker.maxLossValue) || marker.coverage !== void 0 && (!coverage || !isNumber(coverage.startTime) || !isNumber(coverage.endTime) || coverage.startTime > coverage.endTime)) {
110
110
  throw new Error("Invalid strategy evidence marker");
111
111
  }
112
112
  }
@@ -76,7 +76,7 @@ var verifyStrategyEvidenceMarkerEnvelope = (value) => {
76
76
  for (const value2 of payload.markers) {
77
77
  const marker = asRecord(value2);
78
78
  const coverage = asRecord(marker?.coverage);
79
- if (!marker || !isString(marker.id) || !isString(marker.type) || !MARKER_TYPES.has(marker.type) || !isNumber(marker.timestamp) || !isString(marker.label) || !isString(marker.summary) || !isString(marker.artifactId) || !isString(marker.artifactSha256) || !SHA256_RE.test(marker.artifactSha256) || marker.gitSha !== void 0 && !isString(marker.gitSha) || marker.gateFingerprint !== void 0 && (!isString(marker.gateFingerprint) || !LINEAGE_FINGERPRINT_RE.test(marker.gateFingerprint)) || marker.configFingerprint !== void 0 && (!isString(marker.configFingerprint) || !LINEAGE_FINGERPRINT_RE.test(marker.configFingerprint)) || marker.contextFingerprint !== void 0 && (!isString(marker.contextFingerprint) || !LINEAGE_FINGERPRINT_RE.test(marker.contextFingerprint)) || marker.maxLossValue !== void 0 && !isNumber(marker.maxLossValue) || marker.coverage !== void 0 && (!coverage || !isNumber(coverage.startTime) || !isNumber(coverage.endTime) || coverage.startTime > coverage.endTime)) {
79
+ if (!marker || !isString(marker.id) || !isString(marker.type) || !MARKER_TYPES.has(marker.type) || !isNumber(marker.timestamp) || !isString(marker.label) || !isString(marker.summary) || !isString(marker.artifactId) || !isString(marker.artifactSha256) || !SHA256_RE.test(marker.artifactSha256) || marker.releaseVersion !== void 0 && (!Number.isSafeInteger(marker.releaseVersion) || Number(marker.releaseVersion) <= 0) || marker.gitSha !== void 0 && !isString(marker.gitSha) || marker.gateFingerprint !== void 0 && (!isString(marker.gateFingerprint) || !LINEAGE_FINGERPRINT_RE.test(marker.gateFingerprint)) || marker.configFingerprint !== void 0 && (!isString(marker.configFingerprint) || !LINEAGE_FINGERPRINT_RE.test(marker.configFingerprint)) || marker.contextFingerprint !== void 0 && (!isString(marker.contextFingerprint) || !LINEAGE_FINGERPRINT_RE.test(marker.contextFingerprint)) || marker.maxLossValue !== void 0 && !isNumber(marker.maxLossValue) || marker.coverage !== void 0 && (!coverage || !isNumber(coverage.startTime) || !isNumber(coverage.endTime) || coverage.startTime > coverage.endTime)) {
80
80
  throw new Error("Invalid strategy evidence marker");
81
81
  }
82
82
  }
@@ -314,6 +314,12 @@ var redisKeys = {
314
314
  backtestConfig: (userName, config) => `users:${userName}:backtests:configs:${config}`,
315
315
  strategies: (userName) => `users:${userName}:strategies`,
316
316
  strategyConfig: (userName, strategyName, configId = "config") => `users:${userName}:strategies:${strategyName}:${configId}`,
317
+ runtimeStrategyDraft: (userName, strategyName) => `users:${userName}:strategies:${strategyName}:draft`,
318
+ runtimeStrategyReleases: (userName, strategyName) => `users:${userName}:strategies:${strategyName}:releases:`,
319
+ runtimeStrategyRelease: (userName, strategyName, releaseVersion) => `users:${userName}:strategies:${strategyName}:releases:${releaseVersion}`,
320
+ runtimeStrategyReleaseSequence: (userName, strategyName) => `users:${userName}:strategies:${strategyName}:release-seq`,
321
+ runtimeStrategyControlEvents: (userName) => `users:${userName}:runtime:strategy-control-events:`,
322
+ runtimeStrategyControlEvent: (userName, eventId) => `users:${userName}:runtime:strategy-control-events:${eventId}`,
317
323
  strategyResults: (userName, strategyName) => `users:${userName}:strategies:${strategyName}:results`,
318
324
  strategyCharts: (userName, mode) => `users:${userName}:strategies:charts:${mode}`,
319
325
  strategyChartCards: (userName, mode) => `users:${userName}:strategies:charts:${mode}:cards:`,
@@ -4,7 +4,7 @@ import {
4
4
  getKeys,
5
5
  redisKeys,
6
6
  setData
7
- } from "./chunk-6ZKAUA4Q.mjs";
7
+ } from "./chunk-RZTVC4ZL.mjs";
8
8
 
9
9
  // src/tradingAccounts.ts
10
10
  var normalizeId = (value, label) => {
@@ -247,6 +247,12 @@ var redisKeys = {
247
247
  backtestConfig: (userName, config) => `users:${userName}:backtests:configs:${config}`,
248
248
  strategies: (userName) => `users:${userName}:strategies`,
249
249
  strategyConfig: (userName, strategyName, configId = "config") => `users:${userName}:strategies:${strategyName}:${configId}`,
250
+ runtimeStrategyDraft: (userName, strategyName) => `users:${userName}:strategies:${strategyName}:draft`,
251
+ runtimeStrategyReleases: (userName, strategyName) => `users:${userName}:strategies:${strategyName}:releases:`,
252
+ runtimeStrategyRelease: (userName, strategyName, releaseVersion) => `users:${userName}:strategies:${strategyName}:releases:${releaseVersion}`,
253
+ runtimeStrategyReleaseSequence: (userName, strategyName) => `users:${userName}:strategies:${strategyName}:release-seq`,
254
+ runtimeStrategyControlEvents: (userName) => `users:${userName}:runtime:strategy-control-events:`,
255
+ runtimeStrategyControlEvent: (userName, eventId) => `users:${userName}:runtime:strategy-control-events:${eventId}`,
250
256
  strategyResults: (userName, strategyName) => `users:${userName}:strategies:${strategyName}:results`,
251
257
  strategyCharts: (userName, mode) => `users:${userName}:strategies:charts:${mode}`,
252
258
  strategyChartCards: (userName, mode) => `users:${userName}:strategies:charts:${mode}:cards:`,
@@ -2,7 +2,7 @@ import {
2
2
  getData,
3
3
  redisKeys,
4
4
  setData
5
- } from "./chunk-6ZKAUA4Q.mjs";
5
+ } from "./chunk-RZTVC4ZL.mjs";
6
6
 
7
7
  // src/userSettings.ts
8
8
  var readString = (value) => typeof value === "string" ? value.trim() : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/infra",
3
- "version": "3.1.0",
3
+ "version": "3.1.2",
4
4
  "description": "MIT-licensed server infrastructure adapters for TradeJS: Redis, Timescale, ML, logging, and IO.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -9,7 +9,10 @@
9
9
  "infrastructure",
10
10
  "redis",
11
11
  "timescale",
12
- "ml"
12
+ "ml",
13
+ "ai",
14
+ "claude",
15
+ "codex"
13
16
  ],
14
17
  "repository": {
15
18
  "type": "git",
@@ -64,6 +67,11 @@
64
67
  "import": "./dist/runtimeStrategyConfigs.mjs",
65
68
  "require": "./dist/runtimeStrategyConfigs.js"
66
69
  },
70
+ "./runtimeStrategyReleases": {
71
+ "types": "./dist/runtimeStrategyReleases.d.ts",
72
+ "import": "./dist/runtimeStrategyReleases.mjs",
73
+ "require": "./dist/runtimeStrategyReleases.js"
74
+ },
67
75
  "./runtimeDeployments": {
68
76
  "types": "./dist/runtimeDeployments.d.ts",
69
77
  "import": "./dist/runtimeDeployments.mjs",
@@ -118,7 +126,7 @@
118
126
  "dependencies": {
119
127
  "@grpc/grpc-js": "^1.14.4",
120
128
  "@grpc/proto-loader": "^0.8.1",
121
- "@tradejs/types": "^3.1.0",
129
+ "@tradejs/types": "^3.1.2",
122
130
  "chalk": "4.1.2",
123
131
  "date-fns": "^3.6.0",
124
132
  "ioredis": "5.11.1",