Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/qsl_lifecycle_truth_v1.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,19 @@ catalog 名称、默认值或 inventory 状态推导 live 权限。
运行、升级或下单依据;缺少新鲜快照时应显示 `unavailable` / `stale`,不能回退到该
历史矩阵填充“正常”状态。

## 运行监测与成交证据的边界

`/api/runtime-target-lifecycle` 的来源记录固定为 `no_order=true`。它回答的是“目标
是否按配置启用,以及运行守卫和执行心跳是否可用”,不读取账户、订单、成交、持仓或
资金。控制台因此把每条记录明确标为以下之一:`not_due`、`monitoring_only`、
`not_applicable`、`attention` 或 `unavailable`,并固定显示
`order_or_fill_evidence=not_collected`。

其中 `monitoring_only` 只表示守卫和心跳通过,**不能**解释为订单已提交、券商已接受
或已经成交;`not_due` 只表示当前没有到策略的应交易窗口。未来如需在控制台展示
成交事实,必须由独立的、最小化且可核验的 execution-receipt 来源提供,不能把本
生命周期快照或 GitHub workflow 成功状态升级为成交证据。

## 一次性迁移规则

- `platform-config.json`、设置网站 fallback 和生成的 profile asset 只写规范状态。
Expand Down
2 changes: 2 additions & 0 deletions python/tests/test_runtime_target_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ def test_console_runtime_status_board_uses_only_the_read_only_central_api(self)
self.assertIn('requestJson("/api/runtime-target-lifecycle")', console)
self.assertIn("function renderRuntimeTargetLifecycle()", console)
self.assertIn('runtimeTargetLifecycleNoOrder', console)
self.assertIn('runtimeTargetLifecycleObservation', console)
self.assertIn('runtimeTargetLifecycleOrderEvidenceNotCollected', console)

def test_disabled_target_remains_in_no_order_validation_lane(self) -> None:
snapshot = _snapshot()
Expand Down
35 changes: 32 additions & 3 deletions tests/strategy_switch_worker_validation.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3010,6 +3010,18 @@ const runtimeTargetLifecycleSourcePayload = {
monitoring: { runtime_guard: "pass", execution_heartbeat: "not_applicable" },
disposition: { code: "continue_disabled_validation", reason_code: "target_intentionally_disabled" },
no_order: true,
}, {
target_id: "schwab.primary",
target: { platform: "schwab", configured_state: "enabled", execution_mode: "live" },
monitoring: { runtime_guard: "pass", execution_heartbeat: "pass" },
disposition: { code: "continue_enabled_monitoring", reason_code: "none" },
no_order: true,
}, {
target_id: "firstrade.primary",
target: { platform: "firstrade", configured_state: "enabled", execution_mode: "live" },
monitoring: { runtime_guard: "pass", execution_heartbeat: "not_due" },
disposition: { code: "continue_enabled_monitoring", reason_code: "none" },
no_order: true,
}],
errors: [],
};
Expand Down Expand Up @@ -3037,17 +3049,34 @@ const runtimeTargetLifecycleSync = await worker.fetch(
executionEvidenceEnv,
);
assert.equal(runtimeTargetLifecycleSync.status, 200);
assert.equal((await runtimeTargetLifecycleSync.json()).target_count, 1);
assert.equal((await runtimeTargetLifecycleSync.json()).target_count, 3);
const runtimeTargetLifecycleRead = await worker.fetch(
new Request("https://switch.example/api/runtime-target-lifecycle", { headers: executionEvidenceCookieHeaders }),
executionEvidenceEnv,
);
assert.equal(runtimeTargetLifecycleRead.status, 200);
const runtimeTargetLifecyclePayload = await runtimeTargetLifecycleRead.json();
assert.equal(runtimeTargetLifecyclePayload.data_status, "ready");
assert.deepEqual(runtimeTargetLifecyclePayload.summary, { target_count: 1, enabled: 0, disabled: 1, attention: 0 });
assert.equal(runtimeTargetLifecyclePayload.targets[0].target.disposition.code, "continue_disabled_validation");
assert.deepEqual(runtimeTargetLifecyclePayload.summary, { target_count: 3, enabled: 2, disabled: 1, attention: 0 });
const disabledLifecycleTarget = runtimeTargetLifecyclePayload.targets.find((entry) => entry.target.target_id === "longbridge.sg");
assert.equal(disabledLifecycleTarget.target.disposition.code, "continue_disabled_validation");
assert.deepEqual(disabledLifecycleTarget.execution_observation, {
code: "not_applicable",
order_or_fill_evidence: "not_collected",
});
const monitoredLifecycleTarget = runtimeTargetLifecyclePayload.targets.find((entry) => entry.target.target_id === "schwab.primary");
assert.deepEqual(monitoredLifecycleTarget.execution_observation, {
code: "monitoring_only",
order_or_fill_evidence: "not_collected",
});
const notDueLifecycleTarget = runtimeTargetLifecyclePayload.targets.find((entry) => entry.target.target_id === "firstrade.primary");
assert.deepEqual(notDueLifecycleTarget.execution_observation, {
code: "not_due",
order_or_fill_evidence: "not_collected",
});
assert.equal(runtimeTargetLifecyclePayload.policy.no_order, true);
assert.equal(runtimeTargetLifecyclePayload.policy.execution_observation_read_only, true);
assert.equal(runtimeTargetLifecyclePayload.policy.order_or_fill_evidence, "not_collected");

const researchTaskSyncValue = ["research", "task", "sync"].join("-");
const researchTaskEnv = { ...controlEnv, RESEARCH_TASK_SYNC_TOKEN: researchTaskSyncValue };
Expand Down
38 changes: 37 additions & 1 deletion web/strategy-switch-console/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,7 @@
runtimeTargetLifecycleEmpty: "暂无可展示的平台运行状态。",
runtimeTargetLifecycleMeta: "{platform} · {state} · 通道:{mode}",
runtimeTargetLifecycleDetail: "运行监测:{guard} · 执行心跳:{heartbeat}",
runtimeTargetLifecycleObservation: "本次执行:{observation} · 成交证据:{evidence}",
runtimeTargetLifecycleNoOrder: "固定边界:只读状态;不会启用目标、变更策略或提交订单。",
runtimeTargetLifecycleNext: "当前处理",
runtimeTargetLifecycleStateEnabled: "已启用",
Expand All @@ -895,6 +896,12 @@
runtimeTargetLifecycleCheckNotDue: "未到检查时间",
runtimeTargetLifecycleCheckNotApplicable: "不适用",
runtimeTargetLifecycleCheckUnavailable: "不可用",
runtimeTargetLifecycleObservationNotDue: "未到应交易窗口",
runtimeTargetLifecycleObservationMonitoringOnly: "仅监测通过",
runtimeTargetLifecycleObservationNotApplicable: "目标停用,不适用",
runtimeTargetLifecycleObservationAttention: "需要复核",
runtimeTargetLifecycleObservationUnavailable: "不可用",
runtimeTargetLifecycleOrderEvidenceNotCollected: "未采集订单/成交回执",
runtimeTargetLifecycleDispositionEnabled: "持续监控",
runtimeTargetLifecycleDispositionDisabled: "无执行验证",
runtimeTargetLifecycleDispositionParked: "已暂停",
Expand Down Expand Up @@ -1211,6 +1218,7 @@
runtimeTargetLifecycleEmpty: "No platform runtime status is available to display.",
runtimeTargetLifecycleMeta: "{platform} · {state} · lane: {mode}",
runtimeTargetLifecycleDetail: "runtime guard: {guard} · execution heartbeat: {heartbeat}",
runtimeTargetLifecycleObservation: "execution: {observation} · order/fill evidence: {evidence}",
runtimeTargetLifecycleNoOrder: "Fixed boundary: read-only status; it cannot enable a target, change a strategy, or place an order.",
runtimeTargetLifecycleNext: "CURRENT DISPOSITION",
runtimeTargetLifecycleStateEnabled: "enabled",
Expand All @@ -1220,6 +1228,12 @@
runtimeTargetLifecycleCheckNotDue: "not due",
runtimeTargetLifecycleCheckNotApplicable: "not applicable",
runtimeTargetLifecycleCheckUnavailable: "unavailable",
runtimeTargetLifecycleObservationNotDue: "not in a due window",
runtimeTargetLifecycleObservationMonitoringOnly: "monitoring only",
runtimeTargetLifecycleObservationNotApplicable: "target disabled; not applicable",
runtimeTargetLifecycleObservationAttention: "needs review",
runtimeTargetLifecycleObservationUnavailable: "unavailable",
runtimeTargetLifecycleOrderEvidenceNotCollected: "not collected",
runtimeTargetLifecycleDispositionEnabled: "continue monitoring",
runtimeTargetLifecycleDispositionDisabled: "disabled validation",
runtimeTargetLifecycleDispositionParked: "parked",
Expand Down Expand Up @@ -4365,6 +4379,22 @@
: t("runtimeTargetLifecycleStateDisabled");
}

function runtimeTargetLifecycleObservationLabel(value) {
return {
not_due: t("runtimeTargetLifecycleObservationNotDue"),
monitoring_only: t("runtimeTargetLifecycleObservationMonitoringOnly"),
not_applicable: t("runtimeTargetLifecycleObservationNotApplicable"),
attention: t("runtimeTargetLifecycleObservationAttention"),
unavailable: t("runtimeTargetLifecycleObservationUnavailable"),
}[value] || t("runtimeTargetLifecycleObservationUnavailable");
}

function runtimeTargetLifecycleOrderEvidenceLabel(value) {
return {
not_collected: t("runtimeTargetLifecycleOrderEvidenceNotCollected"),
}[value] || t("runtimeTargetLifecycleOrderEvidenceNotCollected");
}

function runtimeTargetLifecycleDispositionLabel(value) {
return {
continue_enabled_monitoring: t("runtimeTargetLifecycleDispositionEnabled"),
Expand Down Expand Up @@ -4419,6 +4449,7 @@
const configuration = target.target || {};
const monitoring = target.monitoring || {};
const disposition = target.disposition || {};
const executionObservation = entry.execution_observation || {};
const card = document.createElement("article");
card.className = "health-card";
const main = document.createElement("div");
Expand All @@ -4440,7 +4471,12 @@
detail.textContent = t("runtimeTargetLifecycleDetail")
.replace("{guard}", runtimeTargetLifecycleCheckLabel(monitoring.runtime_guard))
.replace("{heartbeat}", runtimeTargetLifecycleCheckLabel(monitoring.execution_heartbeat));
main.append(meta, title, reason, detail);
const observation = document.createElement("div");
observation.className = "health-card__meta";
observation.textContent = t("runtimeTargetLifecycleObservation")
.replace("{observation}", runtimeTargetLifecycleObservationLabel(executionObservation.code))
.replace("{evidence}", runtimeTargetLifecycleOrderEvidenceLabel(executionObservation.order_or_fill_evidence));
main.append(meta, title, reason, detail, observation);

const stateBlock = document.createElement("div");
stateBlock.className = "health-card__score";
Expand Down
2 changes: 1 addition & 1 deletion web/strategy-switch-console/app_js.js

Large diffs are not rendered by default.

42 changes: 39 additions & 3 deletions web/strategy-switch-console/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ const RUNTIME_TARGET_LIFECYCLE_CONFIGURED_STATES = ["enabled", "disabled"];
const RUNTIME_TARGET_LIFECYCLE_EXECUTION_MODES = ["dry_run", "paper", "live"];
const RUNTIME_TARGET_LIFECYCLE_CHECK_STATUSES = ["pass", "attention", "not_due", "not_applicable", "unavailable"];
const RUNTIME_TARGET_LIFECYCLE_DISPOSITIONS = ["continue_enabled_monitoring", "continue_disabled_validation", "parked"];
const RUNTIME_TARGET_LIFECYCLE_EXECUTION_OBSERVATIONS = ["not_due", "monitoring_only", "not_applicable", "attention", "unavailable"];
const RUNTIME_TARGET_LIFECYCLE_REASON_CODES = [
"none", "target_intentionally_disabled", "runtime_guard_attention", "execution_heartbeat_attention", "monitoring_unavailable",
];
Expand Down Expand Up @@ -2541,7 +2542,15 @@ async function aggregateRuntimeTargetLifecycleSources(env) {
continue;
}
targetIds.add(target.target_id);
targets.push({ source_id: source.source_id, freshness, target });
targets.push({
source_id: source.source_id,
freshness,
target,
// A lifecycle source is deliberately no-order. Keep this derived
// observation separate from the monitoring checks so a green check
// cannot be mistaken for a broker submission or fill receipt.
execution_observation: runtimeTargetLifecycleExecutionObservation(target),
});
}
errors.push(...source.errors);
}
Expand All @@ -2561,13 +2570,38 @@ async function aggregateRuntimeTargetLifecycleSources(env) {
targets: uniqueTargets,
policy: {
lifecycle_status_read_only: true,
execution_observation_read_only: true,
no_order: true,
notice: "已启用目标持续监控;已停用目标持续进行无执行验证。该状态不会启用目标或提交订单。",
order_or_fill_evidence: "not_collected",
notice: "已启用目标持续监控;已停用目标持续进行无执行验证。运行或心跳通过只说明监测正常,不代表已提交订单或已成交。",
},
errors: uniqueStrings(errors),
};
}

function runtimeTargetLifecycleExecutionObservation(target) {
const monitoring = target?.monitoring || {};
const configuredState = target?.target?.configured_state;
let code = "unavailable";
if (monitoring.runtime_guard === "attention" || monitoring.execution_heartbeat === "attention") {
code = "attention";
} else if (monitoring.runtime_guard === "unavailable" || monitoring.execution_heartbeat === "unavailable") {
code = "unavailable";
} else if (configuredState === "disabled") {
code = "not_applicable";
} else if (monitoring.execution_heartbeat === "not_due") {
code = "not_due";
} else if (monitoring.execution_heartbeat === "pass") {
code = "monitoring_only";
}
return {
code: cleanChoice(code, RUNTIME_TARGET_LIFECYCLE_EXECUTION_OBSERVATIONS, "runtime target lifecycle execution observation"),
// The source contract always declares no_order=true. Only the separate
// execution-receipt chain may later provide an order/fill attestation.
order_or_fill_evidence: "not_collected",
};
}

async function readRuntimeTargetLifecycleSources(env) {
const store = configStore(env);
if (!store || typeof store.list !== "function") return [];
Expand Down Expand Up @@ -4909,8 +4943,10 @@ function emptyRuntimeTargetLifecyclePayload(errorCode) {
targets: [],
policy: {
lifecycle_status_read_only: true,
execution_observation_read_only: true,
no_order: true,
notice: "运行目标生命周期快照尚不可用;页面不会推断目标已启用。",
order_or_fill_evidence: "not_collected",
notice: "运行目标生命周期快照尚不可用;页面不会推断目标已启用或存在订单/成交。",
},
errors: [errorCode],
};
Expand Down