@tradejs/node 3.1.29-beta.282 → 3.1.29-beta.284
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/dist/backtest.d.mts +13 -2
- package/dist/backtest.d.ts +13 -2
- package/dist/backtest.js +161 -7
- package/dist/backtest.mjs +28 -6
- package/dist/{chunk-SKY2HNZU.mjs → chunk-K56EW2BB.mjs} +140 -1
- package/dist/diagnostics.d.mts +54 -1
- package/dist/diagnostics.d.ts +54 -1
- package/dist/diagnostics.js +329 -33
- package/dist/diagnostics.mjs +168 -5
- package/dist/mcp.d.mts +2 -2
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +134 -2
- package/dist/mcp.mjs +5 -2
- package/dist/registry.js +130 -1
- package/dist/registry.mjs +1 -1
- package/dist/runtimeDashboard.js +130 -1
- package/dist/runtimeDashboard.mjs +1 -1
- package/dist/runtimeStrategies.js +130 -1
- package/dist/runtimeStrategies.mjs +1 -1
- package/dist/strategies.js +130 -1
- package/dist/strategies.mjs +1 -1
- package/package.json +5 -5
package/dist/diagnostics.d.ts
CHANGED
|
@@ -179,4 +179,57 @@ declare const verifyRuntimeFeedbackReplaySource: ({ bundleDir, runtimeEvidenceBu
|
|
|
179
179
|
runtimeEvidence: VerifiedRuntimeEvidenceBundle;
|
|
180
180
|
}>;
|
|
181
181
|
|
|
182
|
-
|
|
182
|
+
/** Version of the complete feedback read contract, independent of package versions. */
|
|
183
|
+
declare const MCP_FEEDBACK_CONTRACT: {
|
|
184
|
+
readonly version: 1;
|
|
185
|
+
readonly hostProtocolVersion: 2;
|
|
186
|
+
readonly replayQueue: {
|
|
187
|
+
readonly waitingStates: readonly ["waiting_resources", "waiting_executor"];
|
|
188
|
+
readonly automaticFailedRetries: false;
|
|
189
|
+
};
|
|
190
|
+
readonly artifactEncoding: "base64";
|
|
191
|
+
readonly maximumChunkBytes: 48000;
|
|
192
|
+
readonly artifactFiles: readonly ["payload", "manifest.json", ".complete", "replay.log"];
|
|
193
|
+
readonly requiredTools: readonly ["tradejs_info", "diagnostics_list_reports", "diagnostics_get_report", "artifact_get", "job_get", "job_get_logs"];
|
|
194
|
+
};
|
|
195
|
+
type McpToolDescription = {
|
|
196
|
+
name: string;
|
|
197
|
+
inputSchema: {
|
|
198
|
+
properties?: Record<string, unknown>;
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
/** Check the actual client's tools/list, not only what the server advertises. */
|
|
202
|
+
declare const assertMcpFeedbackContract: (version: unknown, tools: McpToolDescription[]) => void;
|
|
203
|
+
|
|
204
|
+
type McpArtifactFile = 'payload' | 'manifest.json' | '.complete' | 'replay.log';
|
|
205
|
+
type McpBundleDescriptor = {
|
|
206
|
+
id: string;
|
|
207
|
+
kind: 'runtime-evidence' | 'runtime-feedback-replay';
|
|
208
|
+
files: {
|
|
209
|
+
file: McpArtifactFile;
|
|
210
|
+
bytes: number;
|
|
211
|
+
sha256: string;
|
|
212
|
+
available: boolean;
|
|
213
|
+
}[];
|
|
214
|
+
};
|
|
215
|
+
declare class McpReadTransportError extends Error {
|
|
216
|
+
readonly retryAfterMs?: number | undefined;
|
|
217
|
+
constructor(message: string, retryAfterMs?: number | undefined);
|
|
218
|
+
}
|
|
219
|
+
type ChunkRequest = {
|
|
220
|
+
artifactId: string;
|
|
221
|
+
file: McpArtifactFile;
|
|
222
|
+
offset: number;
|
|
223
|
+
length: number;
|
|
224
|
+
};
|
|
225
|
+
/** The caller owns the authenticated MCP connection. This function only reads artifacts. */
|
|
226
|
+
declare const downloadMcpBundle: ({ descriptor, root, contractVersion, tools, readChunk, wait, }: {
|
|
227
|
+
descriptor: McpBundleDescriptor;
|
|
228
|
+
root: string;
|
|
229
|
+
contractVersion: unknown;
|
|
230
|
+
tools: McpToolDescription[];
|
|
231
|
+
readChunk: (request: ChunkRequest) => Promise<unknown>;
|
|
232
|
+
wait?: (ms: number) => Promise<unknown>;
|
|
233
|
+
}) => Promise<VerifiedRuntimeEvidenceBundle | VerifiedRuntimeFeedbackReplayBundle>;
|
|
234
|
+
|
|
235
|
+
export { MCP_FEEDBACK_CONTRACT, type McpArtifactFile, type McpBundleDescriptor, McpReadTransportError, type McpToolDescription, RUNTIME_EVIDENCE_COMPLETE_FILE, RUNTIME_EVIDENCE_MANIFEST_FILE, RUNTIME_EVIDENCE_PAYLOAD_FILE, RUNTIME_FEEDBACK_COMPLETE_FILE, RUNTIME_FEEDBACK_LOG_FILE, RUNTIME_FEEDBACK_MANIFEST_FILE, RUNTIME_FEEDBACK_REPLAY_FILE, type RuntimeEvidenceArtifactManifest, type RuntimeEvidenceDeploymentSnapshot, type RuntimeEvidenceProducer, type RuntimeEvidenceStrategySnapshot, type RuntimeFeedbackReplayManifest, type VerifiedRuntimeEvidenceBundle, type VerifiedRuntimeFeedbackReplayBundle, activeRuntimeEvidenceStrategies, assertCurrentRuntimeEvidenceArtifact, assertMcpFeedbackContract, discoverRuntimeEvidenceBundles, discoverRuntimeFeedbackReplayBundles, downloadMcpBundle, parseRuntimeEvidenceDeploymentSnapshot, parseRuntimeEvidenceProducer, publishRuntimeEvidenceBundle, resolveRuntimeEvidenceDeploymentSnapshot, resolveRuntimeEvidenceProducer, resolveRuntimeEvidenceTickerUniverse, runtimeDeploymentFromEvidence, runtimeLineageMatchesStrategySnapshot, sealRuntimeFeedbackReplayBundle, verifyRuntimeEvidenceBundle, verifyRuntimeFeedbackReplayBundle, verifyRuntimeFeedbackReplaySource };
|
package/dist/diagnostics.js
CHANGED
|
@@ -43,7 +43,7 @@ var init_jevInput = __esm({
|
|
|
43
43
|
import_data = require("@tradejs/core/data");
|
|
44
44
|
import_jev = require("@tradejs/core/jev");
|
|
45
45
|
object = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
46
|
-
at = (value, ...
|
|
46
|
+
at = (value, ...path8) => path8.reduce((current, key) => object(current)[key], value);
|
|
47
47
|
finite = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
48
48
|
fact = (value) => {
|
|
49
49
|
if (value == null || typeof value === "boolean") return value ?? null;
|
|
@@ -453,25 +453,25 @@ var init_jevTraining = __esm({
|
|
|
453
453
|
"Jev training is bounded to 20000 rows; select a frozen sample first"
|
|
454
454
|
);
|
|
455
455
|
for (const row of rows) {
|
|
456
|
-
const
|
|
457
|
-
if (row?.schema !== "tradejs-jev-study/v4" || !row.signalId ||
|
|
458
|
-
inputHash:
|
|
459
|
-
questionsHash:
|
|
460
|
-
provider:
|
|
456
|
+
const record2 = row?.record;
|
|
457
|
+
if (row?.schema !== "tradejs-jev-study/v4" || !row.signalId || record2?.schema !== "tradejs-jev-record/v4" || record2.input?.schema !== "tradejs-jev-input/v4" || record2.input.questionSet !== import_jev2.JEV_QUESTION_SET || !Number.isFinite(record2.input.timestamp) || record2.inputHash !== (0, import_jev3.jevHash)(record2.input) || record2.questionsHash !== (0, import_jev3.jevHash)(questionsForJevInput(record2.input)) || record2.id !== (0, import_jev3.jevHash)({
|
|
458
|
+
inputHash: record2.inputHash,
|
|
459
|
+
questionsHash: record2.questionsHash,
|
|
460
|
+
provider: record2.provider
|
|
461
461
|
}))
|
|
462
462
|
throw new Error("Invalid Jev study provenance");
|
|
463
|
-
(0, import_jev3.validateJevResponse)(
|
|
463
|
+
(0, import_jev3.validateJevResponse)(record2.response, record2.input.questions);
|
|
464
464
|
for (const dimension of import_jev2.JEV_DIMENSIONS) {
|
|
465
|
-
const expected =
|
|
466
|
-
if (
|
|
465
|
+
const expected = record2.input.questions.includes(dimension) ? record2.response.answers[dimension].score / (0, import_jev3.jevScoreMaximum)(dimension) : null;
|
|
466
|
+
if (record2.scores[dimension] !== expected)
|
|
467
467
|
throw new Error("Jev study scores do not match the teacher response");
|
|
468
468
|
}
|
|
469
469
|
if (row.profit != null && !Number.isFinite(row.profit))
|
|
470
470
|
throw new Error("Invalid study outcome");
|
|
471
|
-
const prior = unique.get(
|
|
472
|
-
if (prior && (prior.record.id !==
|
|
471
|
+
const prior = unique.get(record2.inputHash);
|
|
472
|
+
if (prior && (prior.record.id !== record2.id || prior.profit !== row.profit || (0, import_jev3.jevHash)(prior.record.response) !== (0, import_jev3.jevHash)(record2.response)))
|
|
473
473
|
throw new Error("Conflicting duplicated Jev sample");
|
|
474
|
-
unique.set(
|
|
474
|
+
unique.set(record2.inputHash, row);
|
|
475
475
|
}
|
|
476
476
|
const ordered = [...unique.values()].sort(
|
|
477
477
|
(a, b) => a.record.input.timestamp - b.record.input.timestamp || a.record.id.localeCompare(b.record.id)
|
|
@@ -777,6 +777,8 @@ var init_jevTraining = __esm({
|
|
|
777
777
|
// src/diagnostics.ts
|
|
778
778
|
var diagnostics_exports = {};
|
|
779
779
|
__export(diagnostics_exports, {
|
|
780
|
+
MCP_FEEDBACK_CONTRACT: () => MCP_FEEDBACK_CONTRACT,
|
|
781
|
+
McpReadTransportError: () => McpReadTransportError,
|
|
780
782
|
RUNTIME_EVIDENCE_COMPLETE_FILE: () => RUNTIME_EVIDENCE_COMPLETE_FILE,
|
|
781
783
|
RUNTIME_EVIDENCE_MANIFEST_FILE: () => RUNTIME_EVIDENCE_MANIFEST_FILE,
|
|
782
784
|
RUNTIME_EVIDENCE_PAYLOAD_FILE: () => RUNTIME_EVIDENCE_PAYLOAD_FILE,
|
|
@@ -786,8 +788,10 @@ __export(diagnostics_exports, {
|
|
|
786
788
|
RUNTIME_FEEDBACK_REPLAY_FILE: () => RUNTIME_FEEDBACK_REPLAY_FILE,
|
|
787
789
|
activeRuntimeEvidenceStrategies: () => activeRuntimeEvidenceStrategies,
|
|
788
790
|
assertCurrentRuntimeEvidenceArtifact: () => assertCurrentRuntimeEvidenceArtifact,
|
|
791
|
+
assertMcpFeedbackContract: () => assertMcpFeedbackContract,
|
|
789
792
|
discoverRuntimeEvidenceBundles: () => discoverRuntimeEvidenceBundles,
|
|
790
793
|
discoverRuntimeFeedbackReplayBundles: () => discoverRuntimeFeedbackReplayBundles,
|
|
794
|
+
downloadMcpBundle: () => downloadMcpBundle,
|
|
791
795
|
parseRuntimeEvidenceDeploymentSnapshot: () => parseRuntimeEvidenceDeploymentSnapshot,
|
|
792
796
|
parseRuntimeEvidenceProducer: () => parseRuntimeEvidenceProducer,
|
|
793
797
|
publishRuntimeEvidenceBundle: () => publishRuntimeEvidenceBundle,
|
|
@@ -982,24 +986,24 @@ var evaluateJevInput = async ({
|
|
|
982
986
|
promise = getRecord();
|
|
983
987
|
pending.set(pendingKey, promise);
|
|
984
988
|
}
|
|
985
|
-
let
|
|
989
|
+
let record2;
|
|
986
990
|
try {
|
|
987
|
-
|
|
991
|
+
record2 = await promise;
|
|
988
992
|
} finally {
|
|
989
993
|
pending.delete(pendingKey);
|
|
990
994
|
}
|
|
991
995
|
return {
|
|
992
|
-
record,
|
|
996
|
+
record: record2,
|
|
993
997
|
assessment: {
|
|
994
998
|
...base,
|
|
995
999
|
status: "available",
|
|
996
1000
|
recordId: id,
|
|
997
|
-
model:
|
|
998
|
-
scores:
|
|
1001
|
+
model: record2.response.model,
|
|
1002
|
+
scores: record2.scores,
|
|
999
1003
|
confidence: Object.fromEntries(
|
|
1000
1004
|
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
1001
1005
|
key,
|
|
1002
|
-
|
|
1006
|
+
record2.response.answers[key]?.confidence ?? null
|
|
1003
1007
|
])
|
|
1004
1008
|
)
|
|
1005
1009
|
}
|
|
@@ -2922,7 +2926,7 @@ var recordRuntimeTradeOpen = async (params) => {
|
|
|
2922
2926
|
if (!userName) {
|
|
2923
2927
|
return null;
|
|
2924
2928
|
}
|
|
2925
|
-
const
|
|
2929
|
+
const record2 = {
|
|
2926
2930
|
...params,
|
|
2927
2931
|
entryCount: 1,
|
|
2928
2932
|
lastEntryPrice: params.entryPrice,
|
|
@@ -2936,33 +2940,33 @@ var recordRuntimeTradeOpen = async (params) => {
|
|
|
2936
2940
|
exitTimestamp: null,
|
|
2937
2941
|
lastSyncedAt: now()
|
|
2938
2942
|
};
|
|
2939
|
-
const dayKey = (0, import_time.getRuntimeStorageDayKey)(
|
|
2940
|
-
const runtimeScopeId =
|
|
2943
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(record2.entryTimestamp);
|
|
2944
|
+
const runtimeScopeId = record2.deploymentId ?? record2.accountId;
|
|
2941
2945
|
try {
|
|
2942
2946
|
await Promise.all([
|
|
2943
|
-
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName,
|
|
2947
|
+
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, record2.orderId), record2, {
|
|
2944
2948
|
expire: 0
|
|
2945
2949
|
}),
|
|
2946
2950
|
(0, import_redis2.setHashJsonField)(
|
|
2947
2951
|
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
2948
|
-
|
|
2949
|
-
|
|
2952
|
+
record2.orderId,
|
|
2953
|
+
record2,
|
|
2950
2954
|
{ expire: 0 }
|
|
2951
2955
|
),
|
|
2952
2956
|
(0, import_redis2.setData)(
|
|
2953
|
-
import_redis2.redisKeys.runtimeActiveTrade(userName,
|
|
2954
|
-
{ orderId:
|
|
2957
|
+
import_redis2.redisKeys.runtimeActiveTrade(userName, record2.symbol, runtimeScopeId),
|
|
2958
|
+
{ orderId: record2.orderId },
|
|
2955
2959
|
{ expire: 0 }
|
|
2956
2960
|
)
|
|
2957
2961
|
]);
|
|
2958
2962
|
} catch (error) {
|
|
2959
2963
|
import_logger2.logger.error(
|
|
2960
2964
|
"runtime trade open journal failed: %s %s",
|
|
2961
|
-
|
|
2965
|
+
record2.symbol,
|
|
2962
2966
|
error?.message || String(error)
|
|
2963
2967
|
);
|
|
2964
2968
|
}
|
|
2965
|
-
return
|
|
2969
|
+
return record2;
|
|
2966
2970
|
};
|
|
2967
2971
|
var recordRuntimeTradeIncrease = async (params) => {
|
|
2968
2972
|
const {
|
|
@@ -6213,6 +6217,112 @@ var buildAiHookContext = ({
|
|
|
6213
6217
|
};
|
|
6214
6218
|
};
|
|
6215
6219
|
|
|
6220
|
+
// src/researchEntryPolicy.ts
|
|
6221
|
+
var forbidden = /(?:^|\.)(?:__proto__|prototype|constructor|profit|pnl|outcome|tradeResult|backtestExecution|quality|deterministicQuality|approvalAllowedNow|timestamp|knownAt|source|coverage|points|rows|length)(?:\.|$)/i;
|
|
6222
|
+
var buildResearchEntryPayload = (signal, payload = buildAiPayload(signal)) => {
|
|
6223
|
+
const additional = payload.additionalIndicators && typeof payload.additionalIndicators === "object" ? payload.additionalIndicators : {};
|
|
6224
|
+
const raw = signal.additionalIndicators?.baseContext;
|
|
6225
|
+
const adapted = additional.baseContext;
|
|
6226
|
+
return {
|
|
6227
|
+
...payload,
|
|
6228
|
+
additionalIndicators: {
|
|
6229
|
+
...additional,
|
|
6230
|
+
...signal.additionalIndicators?.jev && typeof signal.additionalIndicators.jev === "object" ? { jev: signal.additionalIndicators.jev } : {},
|
|
6231
|
+
baseContext: {
|
|
6232
|
+
...raw && typeof raw === "object" ? raw : {},
|
|
6233
|
+
...adapted && typeof adapted === "object" ? adapted : {}
|
|
6234
|
+
}
|
|
6235
|
+
}
|
|
6236
|
+
};
|
|
6237
|
+
};
|
|
6238
|
+
var parseResearchEntryPolicy = (value) => {
|
|
6239
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
6240
|
+
throw new Error("Invalid research entry policy");
|
|
6241
|
+
const policy = value;
|
|
6242
|
+
if (policy.schema !== "tradejs-research-entry-policy/v1" || typeof policy.strategy !== "string" || !policy.strategy.trim() || !["current-gate", "rules"].includes(policy.mode) || !Number.isInteger(policy.minQuality) || policy.minQuality < 1 || policy.minQuality > 5 || !policy.rules || typeof policy.rules !== "object")
|
|
6243
|
+
throw new Error("Invalid research entry policy contract");
|
|
6244
|
+
if (policy.directions !== void 0 && (!Array.isArray(policy.directions) || policy.directions.length > 2 || new Set(policy.directions).size !== policy.directions.length || policy.directions.some((side) => !["LONG", "SHORT"].includes(side))))
|
|
6245
|
+
throw new Error("Invalid research policy directions");
|
|
6246
|
+
for (const side of ["LONG", "SHORT"]) {
|
|
6247
|
+
const predicates = policy.rules[side];
|
|
6248
|
+
if (predicates === null) continue;
|
|
6249
|
+
if (!Array.isArray(predicates) || predicates.length > 4)
|
|
6250
|
+
throw new Error(
|
|
6251
|
+
"Research policy needs bounded rules for both directions"
|
|
6252
|
+
);
|
|
6253
|
+
for (const predicate of predicates) {
|
|
6254
|
+
if (!predicate || typeof predicate.path !== "string" || !/^additionalIndicators\.[a-zA-Z0-9_.]+$/.test(predicate.path) || forbidden.test(predicate.path) || !["<=", ">=", "=="].includes(predicate.op) || (predicate.op === "==" ? !["string", "boolean"].includes(typeof predicate.value) : typeof predicate.value !== "number" || !Number.isFinite(predicate.value)))
|
|
6255
|
+
throw new Error(
|
|
6256
|
+
"Research policy contains an unsupported or non-causal predicate"
|
|
6257
|
+
);
|
|
6258
|
+
}
|
|
6259
|
+
}
|
|
6260
|
+
return policy;
|
|
6261
|
+
};
|
|
6262
|
+
var matchesResearchEntryRules = (payload, predicates) => predicates.every((predicate) => {
|
|
6263
|
+
const value = predicate.path.split(".").reduce(
|
|
6264
|
+
(current, key) => current != null && typeof current === "object" && Object.hasOwn(current, key) ? Reflect.get(current, key) : void 0,
|
|
6265
|
+
payload
|
|
6266
|
+
);
|
|
6267
|
+
if (value == null) return false;
|
|
6268
|
+
if (predicate.op === "==") return value === predicate.value;
|
|
6269
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return false;
|
|
6270
|
+
return predicate.op === "<=" ? value <= predicate.value : value >= predicate.value;
|
|
6271
|
+
});
|
|
6272
|
+
var evaluateResearchEntryPolicy = async (policy, signal, onDecision) => {
|
|
6273
|
+
if (signal.strategy !== policy.strategy)
|
|
6274
|
+
throw new Error("Research policy strategy mismatch");
|
|
6275
|
+
if (policy.directions && !policy.directions.includes(signal.direction) || policy.mode === "rules" && policy.rules[signal.direction] === null) {
|
|
6276
|
+
onDecision?.({
|
|
6277
|
+
mode: policy.mode,
|
|
6278
|
+
allowed: false,
|
|
6279
|
+
reason: "DIRECTION_BLOCKED"
|
|
6280
|
+
});
|
|
6281
|
+
return false;
|
|
6282
|
+
}
|
|
6283
|
+
if (policy.mode === "current-gate") {
|
|
6284
|
+
const analysis = await runAiPromptLocal(signal);
|
|
6285
|
+
const allowed2 = analysis.direction === signal.direction && typeof analysis.quality === "number" && analysis.quality >= policy.minQuality;
|
|
6286
|
+
onDecision?.({
|
|
6287
|
+
mode: policy.mode,
|
|
6288
|
+
allowed: allowed2,
|
|
6289
|
+
quality: analysis.quality ?? null,
|
|
6290
|
+
reason: allowed2 ? "CURRENT_GATE_APPROVED" : "CURRENT_GATE_REJECTED"
|
|
6291
|
+
});
|
|
6292
|
+
return allowed2;
|
|
6293
|
+
}
|
|
6294
|
+
const original = buildAiPayload(signal);
|
|
6295
|
+
const gateContext = getDeterministicAiGateContext(original);
|
|
6296
|
+
if (Array.isArray(gateContext?.structuralHardBlockReasons) && gateContext.structuralHardBlockReasons.length) {
|
|
6297
|
+
onDecision?.({
|
|
6298
|
+
mode: policy.mode,
|
|
6299
|
+
allowed: false,
|
|
6300
|
+
reason: "STRUCTURAL_HARD_BLOCK"
|
|
6301
|
+
});
|
|
6302
|
+
return false;
|
|
6303
|
+
}
|
|
6304
|
+
const rules = policy.rules[signal.direction];
|
|
6305
|
+
const payload = buildResearchEntryPayload(signal, original);
|
|
6306
|
+
const allowed = rules !== null && matchesResearchEntryRules(payload, rules);
|
|
6307
|
+
onDecision?.({
|
|
6308
|
+
mode: policy.mode,
|
|
6309
|
+
allowed,
|
|
6310
|
+
reason: allowed ? "RULES_APPROVED" : "PREDICATE_REJECTED",
|
|
6311
|
+
predicates: rules?.map((rule) => {
|
|
6312
|
+
const value = rule.path.split(".").reduce(
|
|
6313
|
+
(current, key) => current && typeof current === "object" && Object.hasOwn(current, key) ? Reflect.get(current, key) : void 0,
|
|
6314
|
+
payload
|
|
6315
|
+
);
|
|
6316
|
+
return {
|
|
6317
|
+
path: rule.path,
|
|
6318
|
+
actual: typeof value === "number" && Number.isFinite(value) || typeof value === "string" || typeof value === "boolean" ? value : null,
|
|
6319
|
+
passed: matchesResearchEntryRules(payload, [rule])
|
|
6320
|
+
};
|
|
6321
|
+
})
|
|
6322
|
+
});
|
|
6323
|
+
return allowed;
|
|
6324
|
+
};
|
|
6325
|
+
|
|
6216
6326
|
// src/strategy/runtimeDecisionTelemetry.ts
|
|
6217
6327
|
var finitePositive = (value) => {
|
|
6218
6328
|
const parsed = Number(value);
|
|
@@ -6751,6 +6861,12 @@ var createStrategyRuntime = ({
|
|
|
6751
6861
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
6752
6862
|
const projectHooks = projectConfig.hooks;
|
|
6753
6863
|
const env = String(config.ENV ?? "BACKTEST");
|
|
6864
|
+
const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
|
|
6865
|
+
const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
|
|
6866
|
+
if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
|
|
6867
|
+
throw new Error(
|
|
6868
|
+
"Research entry policies require a BACKTEST simulation connector"
|
|
6869
|
+
);
|
|
6754
6870
|
const jev = (0, import_jev6.parseJevConfig)(config.JEV);
|
|
6755
6871
|
if (jev && env !== "BACKTEST" && jev.source === "provider" && !jev.provider)
|
|
6756
6872
|
throw new Error(
|
|
@@ -7461,7 +7577,8 @@ var createStrategyRuntime = ({
|
|
|
7461
7577
|
await enrichSignalWithMarketContextStages({
|
|
7462
7578
|
signal,
|
|
7463
7579
|
env,
|
|
7464
|
-
includeHyperliquidWhales:
|
|
7580
|
+
includeHyperliquidWhales: Boolean(researchEntryPolicy),
|
|
7581
|
+
...researchEntryPolicy ? { coinMarketCapEnabled: true } : {}
|
|
7465
7582
|
});
|
|
7466
7583
|
if (jev) {
|
|
7467
7584
|
await assessSignalWithJev({
|
|
@@ -7559,6 +7676,22 @@ var createStrategyRuntime = ({
|
|
|
7559
7676
|
}
|
|
7560
7677
|
return signal ?? decision.code;
|
|
7561
7678
|
}
|
|
7679
|
+
if (researchEntryPolicy && (!signal || !await evaluateResearchEntryPolicy(
|
|
7680
|
+
researchEntryPolicy,
|
|
7681
|
+
signal,
|
|
7682
|
+
(decision2) => {
|
|
7683
|
+
signal.additionalIndicators = {
|
|
7684
|
+
...signal.additionalIndicators,
|
|
7685
|
+
researchEntryDecision: decision2
|
|
7686
|
+
};
|
|
7687
|
+
}
|
|
7688
|
+
))) {
|
|
7689
|
+
if (signal) {
|
|
7690
|
+
signal.orderStatus = "skipped";
|
|
7691
|
+
signal.orderSkipReason = "RESEARCH_ENTRY_POLICY_REJECTED";
|
|
7692
|
+
}
|
|
7693
|
+
return signal ?? "RESEARCH_ENTRY_POLICY_REJECTED";
|
|
7694
|
+
}
|
|
7562
7695
|
const entryGate = await invokeGateHooks(
|
|
7563
7696
|
"beforeEntryGate",
|
|
7564
7697
|
decisionManifest?.hooks?.beforeEntryGate,
|
|
@@ -8287,10 +8420,10 @@ var parseRuntimeEvidenceDeploymentSnapshot = (value) => {
|
|
|
8287
8420
|
};
|
|
8288
8421
|
var activeRuntimeEvidenceStrategies = (deployment) => deployment.enabled ? deployment.strategies.filter((strategy) => strategy.enabled) : [];
|
|
8289
8422
|
var unwrapRuntimeEvidenceRows = (value, field) => (Array.isArray(value) ? value : []).map((candidate) => {
|
|
8290
|
-
const
|
|
8291
|
-
const nested =
|
|
8292
|
-
return nested ??
|
|
8293
|
-
}).filter((
|
|
8423
|
+
const record2 = isRecord2(candidate) ? candidate : null;
|
|
8424
|
+
const nested = record2 && isRecord2(record2[field]) ? record2[field] : null;
|
|
8425
|
+
return nested ?? record2;
|
|
8426
|
+
}).filter((record2) => record2 != null);
|
|
8294
8427
|
var assertCurrentRuntimeEvidenceArtifact = (artifact) => {
|
|
8295
8428
|
if (!isRecord2(artifact) || artifact.reportType !== "runtime-evidence") {
|
|
8296
8429
|
throw new Error("Invalid runtime evidence artifact");
|
|
@@ -9032,8 +9165,169 @@ var verifyRuntimeFeedbackReplaySource = async ({
|
|
|
9032
9165
|
});
|
|
9033
9166
|
return { feedback, runtimeEvidence };
|
|
9034
9167
|
};
|
|
9168
|
+
|
|
9169
|
+
// src/diagnostics/mcpFeedbackContract.ts
|
|
9170
|
+
var MCP_FEEDBACK_CONTRACT = {
|
|
9171
|
+
version: 1,
|
|
9172
|
+
hostProtocolVersion: 2,
|
|
9173
|
+
replayQueue: {
|
|
9174
|
+
waitingStates: ["waiting_resources", "waiting_executor"],
|
|
9175
|
+
automaticFailedRetries: false
|
|
9176
|
+
},
|
|
9177
|
+
artifactEncoding: "base64",
|
|
9178
|
+
maximumChunkBytes: 48e3,
|
|
9179
|
+
artifactFiles: ["payload", "manifest.json", ".complete", "replay.log"],
|
|
9180
|
+
requiredTools: [
|
|
9181
|
+
"tradejs_info",
|
|
9182
|
+
"diagnostics_list_reports",
|
|
9183
|
+
"diagnostics_get_report",
|
|
9184
|
+
"artifact_get",
|
|
9185
|
+
"job_get",
|
|
9186
|
+
"job_get_logs"
|
|
9187
|
+
]
|
|
9188
|
+
};
|
|
9189
|
+
var assertMcpFeedbackContract = (version, tools) => {
|
|
9190
|
+
if (version !== MCP_FEEDBACK_CONTRACT.version)
|
|
9191
|
+
throw new Error("MCP feedback contract version mismatch");
|
|
9192
|
+
for (const name of MCP_FEEDBACK_CONTRACT.requiredTools)
|
|
9193
|
+
if (!tools.some((tool) => tool.name === name))
|
|
9194
|
+
throw new Error(`MCP feedback requires tool ${name}`);
|
|
9195
|
+
const properties = tools.find((tool) => tool.name === "artifact_get")?.inputSchema.properties;
|
|
9196
|
+
if (!properties || !("file" in properties))
|
|
9197
|
+
throw new Error(
|
|
9198
|
+
"MCP feedback requires artifact_get.file; refresh the client tool schema"
|
|
9199
|
+
);
|
|
9200
|
+
};
|
|
9201
|
+
|
|
9202
|
+
// src/diagnostics/mcpBundleDownload.ts
|
|
9203
|
+
var import_node_crypto8 = require("crypto");
|
|
9204
|
+
var import_promises6 = __toESM(require("fs/promises"));
|
|
9205
|
+
var import_node_path6 = __toESM(require("path"));
|
|
9206
|
+
var McpReadTransportError = class extends Error {
|
|
9207
|
+
constructor(message, retryAfterMs) {
|
|
9208
|
+
super(message);
|
|
9209
|
+
this.retryAfterMs = retryAfterMs;
|
|
9210
|
+
}
|
|
9211
|
+
};
|
|
9212
|
+
var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
9213
|
+
var digest = (bytes) => (0, import_node_crypto8.createHash)("sha256").update(bytes).digest("hex");
|
|
9214
|
+
var exists = async (file) => import_promises6.default.lstat(file).catch((error) => {
|
|
9215
|
+
if (error.code === "ENOENT") return void 0;
|
|
9216
|
+
throw error;
|
|
9217
|
+
});
|
|
9218
|
+
var checkFile = async (file) => {
|
|
9219
|
+
const stat = await exists(file);
|
|
9220
|
+
if (stat && (!stat.isFile() || stat.isSymbolicLink()))
|
|
9221
|
+
throw new Error("Invalid local bundle file");
|
|
9222
|
+
return stat;
|
|
9223
|
+
};
|
|
9224
|
+
var downloadMcpBundle = async ({
|
|
9225
|
+
descriptor,
|
|
9226
|
+
root,
|
|
9227
|
+
contractVersion,
|
|
9228
|
+
tools,
|
|
9229
|
+
readChunk,
|
|
9230
|
+
wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
9231
|
+
}) => {
|
|
9232
|
+
assertMcpFeedbackContract(contractVersion, tools);
|
|
9233
|
+
if (!/^[a-f0-9]{64}$/.test(descriptor.id))
|
|
9234
|
+
throw new Error("Invalid artifact identity");
|
|
9235
|
+
const required = ["payload", "manifest.json", ".complete"];
|
|
9236
|
+
if (descriptor.kind === "runtime-feedback-replay")
|
|
9237
|
+
required.push("replay.log");
|
|
9238
|
+
for (const name of required) {
|
|
9239
|
+
const matches = descriptor.files.filter((f) => f.file === name);
|
|
9240
|
+
if (matches.length !== 1 || !matches[0].available || !Number.isSafeInteger(matches[0].bytes) || matches[0].bytes < 0 || !/^[a-f0-9]{64}$/.test(matches[0].sha256))
|
|
9241
|
+
throw new Error(`Incomplete bundle descriptor: ${name}`);
|
|
9242
|
+
}
|
|
9243
|
+
const bundleDir = import_node_path6.default.join(root, descriptor.id);
|
|
9244
|
+
await import_promises6.default.mkdir(bundleDir, { recursive: true });
|
|
9245
|
+
const dir = await import_promises6.default.lstat(bundleDir);
|
|
9246
|
+
if (dir.isSymbolicLink()) throw new Error("Invalid bundle directory");
|
|
9247
|
+
const lock = import_node_path6.default.join(bundleDir, ".download-lock");
|
|
9248
|
+
await import_promises6.default.mkdir(lock);
|
|
9249
|
+
try {
|
|
9250
|
+
for (const name of [
|
|
9251
|
+
...required.filter((f) => f !== ".complete"),
|
|
9252
|
+
".complete"
|
|
9253
|
+
]) {
|
|
9254
|
+
const expected = descriptor.files.find((f) => f.file === name);
|
|
9255
|
+
if (!expected) throw new Error("Missing bundle file");
|
|
9256
|
+
const filename = name === "payload" ? descriptor.kind === "runtime-evidence" ? "runtime-evidence.json" : "replay-runtime-evidence.json" : name;
|
|
9257
|
+
const target = import_node_path6.default.join(bundleDir, filename);
|
|
9258
|
+
const verified = await checkFile(target);
|
|
9259
|
+
if (verified) {
|
|
9260
|
+
if (verified.size !== expected.bytes || digest(await import_promises6.default.readFile(target)) !== expected.sha256)
|
|
9261
|
+
throw new Error("Local bundle checksum mismatch");
|
|
9262
|
+
continue;
|
|
9263
|
+
}
|
|
9264
|
+
const partial = target + ".part";
|
|
9265
|
+
const checkpoint = partial + ".json";
|
|
9266
|
+
const identity = JSON.stringify({
|
|
9267
|
+
artifactId: descriptor.id,
|
|
9268
|
+
file: name,
|
|
9269
|
+
bytes: expected.bytes,
|
|
9270
|
+
sha256: expected.sha256
|
|
9271
|
+
});
|
|
9272
|
+
await checkFile(checkpoint);
|
|
9273
|
+
const stat = await checkFile(partial);
|
|
9274
|
+
if (stat) {
|
|
9275
|
+
if (await import_promises6.default.readFile(checkpoint, "utf8") !== identity || stat.size > expected.bytes)
|
|
9276
|
+
throw new Error("Artifact changed during download");
|
|
9277
|
+
} else {
|
|
9278
|
+
if (await checkFile(checkpoint)) {
|
|
9279
|
+
if (await import_promises6.default.readFile(checkpoint, "utf8") !== identity)
|
|
9280
|
+
throw new Error("Artifact changed during download");
|
|
9281
|
+
} else await import_promises6.default.writeFile(checkpoint, identity, { flag: "wx" });
|
|
9282
|
+
}
|
|
9283
|
+
let offset = stat?.size ?? 0;
|
|
9284
|
+
if (!stat) await import_promises6.default.writeFile(partial, Buffer.alloc(0), { flag: "wx" });
|
|
9285
|
+
while (offset < expected.bytes) {
|
|
9286
|
+
const request = {
|
|
9287
|
+
artifactId: descriptor.id,
|
|
9288
|
+
file: name,
|
|
9289
|
+
offset,
|
|
9290
|
+
length: MCP_FEEDBACK_CONTRACT.maximumChunkBytes
|
|
9291
|
+
};
|
|
9292
|
+
let response;
|
|
9293
|
+
for (let attempt = 0; ; attempt++) {
|
|
9294
|
+
try {
|
|
9295
|
+
response = await readChunk(request);
|
|
9296
|
+
break;
|
|
9297
|
+
} catch (error) {
|
|
9298
|
+
if (!(error instanceof McpReadTransportError) || attempt >= 3)
|
|
9299
|
+
throw error;
|
|
9300
|
+
await wait(
|
|
9301
|
+
Math.max([2e3, 5e3, 1e4][attempt], error.retryAfterMs ?? 0)
|
|
9302
|
+
);
|
|
9303
|
+
}
|
|
9304
|
+
}
|
|
9305
|
+
if (!record(response) || response.id !== descriptor.id || response.file !== name || response.offset !== offset || response.bytes !== expected.bytes || response.sha256 !== expected.sha256 || response.encoding !== "base64" || typeof response.data !== "string" || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(
|
|
9306
|
+
response.data
|
|
9307
|
+
))
|
|
9308
|
+
throw new Error("Invalid artifact chunk identity");
|
|
9309
|
+
const bytes = Buffer.from(response.data, "base64");
|
|
9310
|
+
const next = offset + bytes.length;
|
|
9311
|
+
if (!bytes.length || bytes.length > request.length || next > expected.bytes || response.nextOffset !== (next === expected.bytes ? null : next))
|
|
9312
|
+
throw new Error("Invalid artifact chunk range");
|
|
9313
|
+
await import_promises6.default.appendFile(partial, bytes);
|
|
9314
|
+
offset = next;
|
|
9315
|
+
}
|
|
9316
|
+
if (digest(await import_promises6.default.readFile(partial)) !== expected.sha256)
|
|
9317
|
+
throw new Error("Downloaded bundle checksum mismatch");
|
|
9318
|
+
await import_promises6.default.rename(partial, target);
|
|
9319
|
+
await import_promises6.default.unlink(checkpoint);
|
|
9320
|
+
}
|
|
9321
|
+
const bundle = descriptor.kind === "runtime-evidence" ? await verifyRuntimeEvidenceBundle(bundleDir) : await verifyRuntimeFeedbackReplayBundle(bundleDir);
|
|
9322
|
+
return bundle;
|
|
9323
|
+
} finally {
|
|
9324
|
+
await import_promises6.default.rmdir(lock);
|
|
9325
|
+
}
|
|
9326
|
+
};
|
|
9035
9327
|
// Annotate the CommonJS export names for ESM import in node:
|
|
9036
9328
|
0 && (module.exports = {
|
|
9329
|
+
MCP_FEEDBACK_CONTRACT,
|
|
9330
|
+
McpReadTransportError,
|
|
9037
9331
|
RUNTIME_EVIDENCE_COMPLETE_FILE,
|
|
9038
9332
|
RUNTIME_EVIDENCE_MANIFEST_FILE,
|
|
9039
9333
|
RUNTIME_EVIDENCE_PAYLOAD_FILE,
|
|
@@ -9043,8 +9337,10 @@ var verifyRuntimeFeedbackReplaySource = async ({
|
|
|
9043
9337
|
RUNTIME_FEEDBACK_REPLAY_FILE,
|
|
9044
9338
|
activeRuntimeEvidenceStrategies,
|
|
9045
9339
|
assertCurrentRuntimeEvidenceArtifact,
|
|
9340
|
+
assertMcpFeedbackContract,
|
|
9046
9341
|
discoverRuntimeEvidenceBundles,
|
|
9047
9342
|
discoverRuntimeFeedbackReplayBundles,
|
|
9343
|
+
downloadMcpBundle,
|
|
9048
9344
|
parseRuntimeEvidenceDeploymentSnapshot,
|
|
9049
9345
|
parseRuntimeEvidenceProducer,
|
|
9050
9346
|
publishRuntimeEvidenceBundle,
|