@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.
@@ -179,4 +179,57 @@ declare const verifyRuntimeFeedbackReplaySource: ({ bundleDir, runtimeEvidenceBu
179
179
  runtimeEvidence: VerifiedRuntimeEvidenceBundle;
180
180
  }>;
181
181
 
182
- export { 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, discoverRuntimeEvidenceBundles, discoverRuntimeFeedbackReplayBundles, parseRuntimeEvidenceDeploymentSnapshot, parseRuntimeEvidenceProducer, publishRuntimeEvidenceBundle, resolveRuntimeEvidenceDeploymentSnapshot, resolveRuntimeEvidenceProducer, resolveRuntimeEvidenceTickerUniverse, runtimeDeploymentFromEvidence, runtimeLineageMatchesStrategySnapshot, sealRuntimeFeedbackReplayBundle, verifyRuntimeEvidenceBundle, verifyRuntimeFeedbackReplayBundle, verifyRuntimeFeedbackReplaySource };
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 };
@@ -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, ...path7) => path7.reduce((current, key) => object(current)[key], 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 record = row?.record;
457
- if (row?.schema !== "tradejs-jev-study/v4" || !row.signalId || record?.schema !== "tradejs-jev-record/v4" || record.input?.schema !== "tradejs-jev-input/v4" || record.input.questionSet !== import_jev2.JEV_QUESTION_SET || !Number.isFinite(record.input.timestamp) || record.inputHash !== (0, import_jev3.jevHash)(record.input) || record.questionsHash !== (0, import_jev3.jevHash)(questionsForJevInput(record.input)) || record.id !== (0, import_jev3.jevHash)({
458
- inputHash: record.inputHash,
459
- questionsHash: record.questionsHash,
460
- provider: record.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)(record.response, record.input.questions);
463
+ (0, import_jev3.validateJevResponse)(record2.response, record2.input.questions);
464
464
  for (const dimension of import_jev2.JEV_DIMENSIONS) {
465
- const expected = record.input.questions.includes(dimension) ? record.response.answers[dimension].score / (0, import_jev3.jevScoreMaximum)(dimension) : null;
466
- if (record.scores[dimension] !== expected)
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(record.inputHash);
472
- if (prior && (prior.record.id !== record.id || prior.profit !== row.profit || (0, import_jev3.jevHash)(prior.record.response) !== (0, import_jev3.jevHash)(record.response)))
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(record.inputHash, row);
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 record;
989
+ let record2;
986
990
  try {
987
- record = await promise;
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: record.response.model,
998
- scores: record.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
- record.response.answers[key]?.confidence ?? null
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 record = {
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)(record.entryTimestamp);
2940
- const runtimeScopeId = record.deploymentId ?? record.accountId;
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, record.orderId), record, {
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
- record.orderId,
2949
- record,
2952
+ record2.orderId,
2953
+ record2,
2950
2954
  { expire: 0 }
2951
2955
  ),
2952
2956
  (0, import_redis2.setData)(
2953
- import_redis2.redisKeys.runtimeActiveTrade(userName, record.symbol, runtimeScopeId),
2954
- { orderId: record.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
- record.symbol,
2965
+ record2.symbol,
2962
2966
  error?.message || String(error)
2963
2967
  );
2964
2968
  }
2965
- return record;
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: false
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 record = isRecord2(candidate) ? candidate : null;
8291
- const nested = record && isRecord2(record[field]) ? record[field] : null;
8292
- return nested ?? record;
8293
- }).filter((record) => record != null);
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,