Skip to content

Commit cceaf37

Browse files
Pigbibicodex
andcommitted
feat(console): distinguish monitoring from execution proof
Co-Authored-By: Codex <noreply@openai.com>
1 parent d9e67a3 commit cceaf37

6 files changed

Lines changed: 124 additions & 8 deletions

File tree

docs/qsl_lifecycle_truth_v1.zh-CN.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,19 @@ catalog 名称、默认值或 inventory 状态推导 live 权限。
5353
运行、升级或下单依据;缺少新鲜快照时应显示 `unavailable` / `stale`,不能回退到该
5454
历史矩阵填充“正常”状态。
5555

56+
## 运行监测与成交证据的边界
57+
58+
`/api/runtime-target-lifecycle` 的来源记录固定为 `no_order=true`。它回答的是“目标
59+
是否按配置启用,以及运行守卫和执行心跳是否可用”,不读取账户、订单、成交、持仓或
60+
资金。控制台因此把每条记录明确标为以下之一:`not_due``monitoring_only`
61+
`not_applicable``attention``unavailable`,并固定显示
62+
`order_or_fill_evidence=not_collected`
63+
64+
其中 `monitoring_only` 只表示守卫和心跳通过,**不能**解释为订单已提交、券商已接受
65+
或已经成交;`not_due` 只表示当前没有到策略的应交易窗口。未来如需在控制台展示
66+
成交事实,必须由独立的、最小化且可核验的 execution-receipt 来源提供,不能把本
67+
生命周期快照或 GitHub workflow 成功状态升级为成交证据。
68+
5669
## 一次性迁移规则
5770

5871
- `platform-config.json`、设置网站 fallback 和生成的 profile asset 只写规范状态。

python/tests/test_runtime_target_lifecycle.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ def test_console_runtime_status_board_uses_only_the_read_only_central_api(self)
3939
self.assertIn('requestJson("/api/runtime-target-lifecycle")', console)
4040
self.assertIn("function renderRuntimeTargetLifecycle()", console)
4141
self.assertIn('runtimeTargetLifecycleNoOrder', console)
42+
self.assertIn('runtimeTargetLifecycleObservation', console)
43+
self.assertIn('runtimeTargetLifecycleOrderEvidenceNotCollected', console)
4244

4345
def test_disabled_target_remains_in_no_order_validation_lane(self) -> None:
4446
snapshot = _snapshot()

tests/strategy_switch_worker_validation.mjs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3010,6 +3010,18 @@ const runtimeTargetLifecycleSourcePayload = {
30103010
monitoring: { runtime_guard: "pass", execution_heartbeat: "not_applicable" },
30113011
disposition: { code: "continue_disabled_validation", reason_code: "target_intentionally_disabled" },
30123012
no_order: true,
3013+
}, {
3014+
target_id: "schwab.primary",
3015+
target: { platform: "schwab", configured_state: "enabled", execution_mode: "live" },
3016+
monitoring: { runtime_guard: "pass", execution_heartbeat: "pass" },
3017+
disposition: { code: "continue_enabled_monitoring", reason_code: "none" },
3018+
no_order: true,
3019+
}, {
3020+
target_id: "firstrade.primary",
3021+
target: { platform: "firstrade", configured_state: "enabled", execution_mode: "live" },
3022+
monitoring: { runtime_guard: "pass", execution_heartbeat: "not_due" },
3023+
disposition: { code: "continue_enabled_monitoring", reason_code: "none" },
3024+
no_order: true,
30133025
}],
30143026
errors: [],
30153027
};
@@ -3037,17 +3049,34 @@ const runtimeTargetLifecycleSync = await worker.fetch(
30373049
executionEvidenceEnv,
30383050
);
30393051
assert.equal(runtimeTargetLifecycleSync.status, 200);
3040-
assert.equal((await runtimeTargetLifecycleSync.json()).target_count, 1);
3052+
assert.equal((await runtimeTargetLifecycleSync.json()).target_count, 3);
30413053
const runtimeTargetLifecycleRead = await worker.fetch(
30423054
new Request("https://switch.example/api/runtime-target-lifecycle", { headers: executionEvidenceCookieHeaders }),
30433055
executionEvidenceEnv,
30443056
);
30453057
assert.equal(runtimeTargetLifecycleRead.status, 200);
30463058
const runtimeTargetLifecyclePayload = await runtimeTargetLifecycleRead.json();
30473059
assert.equal(runtimeTargetLifecyclePayload.data_status, "ready");
3048-
assert.deepEqual(runtimeTargetLifecyclePayload.summary, { target_count: 1, enabled: 0, disabled: 1, attention: 0 });
3049-
assert.equal(runtimeTargetLifecyclePayload.targets[0].target.disposition.code, "continue_disabled_validation");
3060+
assert.deepEqual(runtimeTargetLifecyclePayload.summary, { target_count: 3, enabled: 2, disabled: 1, attention: 0 });
3061+
const disabledLifecycleTarget = runtimeTargetLifecyclePayload.targets.find((entry) => entry.target.target_id === "longbridge.sg");
3062+
assert.equal(disabledLifecycleTarget.target.disposition.code, "continue_disabled_validation");
3063+
assert.deepEqual(disabledLifecycleTarget.execution_observation, {
3064+
code: "not_applicable",
3065+
order_or_fill_evidence: "not_collected",
3066+
});
3067+
const monitoredLifecycleTarget = runtimeTargetLifecyclePayload.targets.find((entry) => entry.target.target_id === "schwab.primary");
3068+
assert.deepEqual(monitoredLifecycleTarget.execution_observation, {
3069+
code: "monitoring_only",
3070+
order_or_fill_evidence: "not_collected",
3071+
});
3072+
const notDueLifecycleTarget = runtimeTargetLifecyclePayload.targets.find((entry) => entry.target.target_id === "firstrade.primary");
3073+
assert.deepEqual(notDueLifecycleTarget.execution_observation, {
3074+
code: "not_due",
3075+
order_or_fill_evidence: "not_collected",
3076+
});
30503077
assert.equal(runtimeTargetLifecyclePayload.policy.no_order, true);
3078+
assert.equal(runtimeTargetLifecyclePayload.policy.execution_observation_read_only, true);
3079+
assert.equal(runtimeTargetLifecyclePayload.policy.order_or_fill_evidence, "not_collected");
30513080

30523081
const researchTaskSyncValue = ["research", "task", "sync"].join("-");
30533082
const researchTaskEnv = { ...controlEnv, RESEARCH_TASK_SYNC_TOKEN: researchTaskSyncValue };

web/strategy-switch-console/app.js

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,7 @@
886886
runtimeTargetLifecycleEmpty: "暂无可展示的平台运行状态。",
887887
runtimeTargetLifecycleMeta: "{platform} · {state} · 通道:{mode}",
888888
runtimeTargetLifecycleDetail: "运行监测:{guard} · 执行心跳:{heartbeat}",
889+
runtimeTargetLifecycleObservation: "本次执行:{observation} · 成交证据:{evidence}",
889890
runtimeTargetLifecycleNoOrder: "固定边界:只读状态;不会启用目标、变更策略或提交订单。",
890891
runtimeTargetLifecycleNext: "当前处理",
891892
runtimeTargetLifecycleStateEnabled: "已启用",
@@ -895,6 +896,12 @@
895896
runtimeTargetLifecycleCheckNotDue: "未到检查时间",
896897
runtimeTargetLifecycleCheckNotApplicable: "不适用",
897898
runtimeTargetLifecycleCheckUnavailable: "不可用",
899+
runtimeTargetLifecycleObservationNotDue: "未到应交易窗口",
900+
runtimeTargetLifecycleObservationMonitoringOnly: "仅监测通过",
901+
runtimeTargetLifecycleObservationNotApplicable: "目标停用,不适用",
902+
runtimeTargetLifecycleObservationAttention: "需要复核",
903+
runtimeTargetLifecycleObservationUnavailable: "不可用",
904+
runtimeTargetLifecycleOrderEvidenceNotCollected: "未采集订单/成交回执",
898905
runtimeTargetLifecycleDispositionEnabled: "持续监控",
899906
runtimeTargetLifecycleDispositionDisabled: "无执行验证",
900907
runtimeTargetLifecycleDispositionParked: "已暂停",
@@ -1211,6 +1218,7 @@
12111218
runtimeTargetLifecycleEmpty: "No platform runtime status is available to display.",
12121219
runtimeTargetLifecycleMeta: "{platform} · {state} · lane: {mode}",
12131220
runtimeTargetLifecycleDetail: "runtime guard: {guard} · execution heartbeat: {heartbeat}",
1221+
runtimeTargetLifecycleObservation: "execution: {observation} · order/fill evidence: {evidence}",
12141222
runtimeTargetLifecycleNoOrder: "Fixed boundary: read-only status; it cannot enable a target, change a strategy, or place an order.",
12151223
runtimeTargetLifecycleNext: "CURRENT DISPOSITION",
12161224
runtimeTargetLifecycleStateEnabled: "enabled",
@@ -1220,6 +1228,12 @@
12201228
runtimeTargetLifecycleCheckNotDue: "not due",
12211229
runtimeTargetLifecycleCheckNotApplicable: "not applicable",
12221230
runtimeTargetLifecycleCheckUnavailable: "unavailable",
1231+
runtimeTargetLifecycleObservationNotDue: "not in a due window",
1232+
runtimeTargetLifecycleObservationMonitoringOnly: "monitoring only",
1233+
runtimeTargetLifecycleObservationNotApplicable: "target disabled; not applicable",
1234+
runtimeTargetLifecycleObservationAttention: "needs review",
1235+
runtimeTargetLifecycleObservationUnavailable: "unavailable",
1236+
runtimeTargetLifecycleOrderEvidenceNotCollected: "not collected",
12231237
runtimeTargetLifecycleDispositionEnabled: "continue monitoring",
12241238
runtimeTargetLifecycleDispositionDisabled: "disabled validation",
12251239
runtimeTargetLifecycleDispositionParked: "parked",
@@ -4365,6 +4379,22 @@
43654379
: t("runtimeTargetLifecycleStateDisabled");
43664380
}
43674381

4382+
function runtimeTargetLifecycleObservationLabel(value) {
4383+
return {
4384+
not_due: t("runtimeTargetLifecycleObservationNotDue"),
4385+
monitoring_only: t("runtimeTargetLifecycleObservationMonitoringOnly"),
4386+
not_applicable: t("runtimeTargetLifecycleObservationNotApplicable"),
4387+
attention: t("runtimeTargetLifecycleObservationAttention"),
4388+
unavailable: t("runtimeTargetLifecycleObservationUnavailable"),
4389+
}[value] || t("runtimeTargetLifecycleObservationUnavailable");
4390+
}
4391+
4392+
function runtimeTargetLifecycleOrderEvidenceLabel(value) {
4393+
return {
4394+
not_collected: t("runtimeTargetLifecycleOrderEvidenceNotCollected"),
4395+
}[value] || t("runtimeTargetLifecycleOrderEvidenceNotCollected");
4396+
}
4397+
43684398
function runtimeTargetLifecycleDispositionLabel(value) {
43694399
return {
43704400
continue_enabled_monitoring: t("runtimeTargetLifecycleDispositionEnabled"),
@@ -4419,6 +4449,7 @@
44194449
const configuration = target.target || {};
44204450
const monitoring = target.monitoring || {};
44214451
const disposition = target.disposition || {};
4452+
const executionObservation = entry.execution_observation || {};
44224453
const card = document.createElement("article");
44234454
card.className = "health-card";
44244455
const main = document.createElement("div");
@@ -4440,7 +4471,12 @@
44404471
detail.textContent = t("runtimeTargetLifecycleDetail")
44414472
.replace("{guard}", runtimeTargetLifecycleCheckLabel(monitoring.runtime_guard))
44424473
.replace("{heartbeat}", runtimeTargetLifecycleCheckLabel(monitoring.execution_heartbeat));
4443-
main.append(meta, title, reason, detail);
4474+
const observation = document.createElement("div");
4475+
observation.className = "health-card__meta";
4476+
observation.textContent = t("runtimeTargetLifecycleObservation")
4477+
.replace("{observation}", runtimeTargetLifecycleObservationLabel(executionObservation.code))
4478+
.replace("{evidence}", runtimeTargetLifecycleOrderEvidenceLabel(executionObservation.order_or_fill_evidence));
4479+
main.append(meta, title, reason, detail, observation);
44444480

44454481
const stateBlock = document.createElement("div");
44464482
stateBlock.className = "health-card__score";

web/strategy-switch-console/app_js.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/strategy-switch-console/worker.js

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ const RUNTIME_TARGET_LIFECYCLE_CONFIGURED_STATES = ["enabled", "disabled"];
175175
const RUNTIME_TARGET_LIFECYCLE_EXECUTION_MODES = ["dry_run", "paper", "live"];
176176
const RUNTIME_TARGET_LIFECYCLE_CHECK_STATUSES = ["pass", "attention", "not_due", "not_applicable", "unavailable"];
177177
const RUNTIME_TARGET_LIFECYCLE_DISPOSITIONS = ["continue_enabled_monitoring", "continue_disabled_validation", "parked"];
178+
const RUNTIME_TARGET_LIFECYCLE_EXECUTION_OBSERVATIONS = ["not_due", "monitoring_only", "not_applicable", "attention", "unavailable"];
178179
const RUNTIME_TARGET_LIFECYCLE_REASON_CODES = [
179180
"none", "target_intentionally_disabled", "runtime_guard_attention", "execution_heartbeat_attention", "monitoring_unavailable",
180181
];
@@ -2541,7 +2542,15 @@ async function aggregateRuntimeTargetLifecycleSources(env) {
25412542
continue;
25422543
}
25432544
targetIds.add(target.target_id);
2544-
targets.push({ source_id: source.source_id, freshness, target });
2545+
targets.push({
2546+
source_id: source.source_id,
2547+
freshness,
2548+
target,
2549+
// A lifecycle source is deliberately no-order. Keep this derived
2550+
// observation separate from the monitoring checks so a green check
2551+
// cannot be mistaken for a broker submission or fill receipt.
2552+
execution_observation: runtimeTargetLifecycleExecutionObservation(target),
2553+
});
25452554
}
25462555
errors.push(...source.errors);
25472556
}
@@ -2561,13 +2570,38 @@ async function aggregateRuntimeTargetLifecycleSources(env) {
25612570
targets: uniqueTargets,
25622571
policy: {
25632572
lifecycle_status_read_only: true,
2573+
execution_observation_read_only: true,
25642574
no_order: true,
2565-
notice: "已启用目标持续监控;已停用目标持续进行无执行验证。该状态不会启用目标或提交订单。",
2575+
order_or_fill_evidence: "not_collected",
2576+
notice: "已启用目标持续监控;已停用目标持续进行无执行验证。运行或心跳通过只说明监测正常,不代表已提交订单或已成交。",
25662577
},
25672578
errors: uniqueStrings(errors),
25682579
};
25692580
}
25702581

2582+
function runtimeTargetLifecycleExecutionObservation(target) {
2583+
const monitoring = target?.monitoring || {};
2584+
const configuredState = target?.target?.configured_state;
2585+
let code = "unavailable";
2586+
if (monitoring.runtime_guard === "attention" || monitoring.execution_heartbeat === "attention") {
2587+
code = "attention";
2588+
} else if (monitoring.runtime_guard === "unavailable" || monitoring.execution_heartbeat === "unavailable") {
2589+
code = "unavailable";
2590+
} else if (configuredState === "disabled") {
2591+
code = "not_applicable";
2592+
} else if (monitoring.execution_heartbeat === "not_due") {
2593+
code = "not_due";
2594+
} else if (monitoring.execution_heartbeat === "pass") {
2595+
code = "monitoring_only";
2596+
}
2597+
return {
2598+
code: cleanChoice(code, RUNTIME_TARGET_LIFECYCLE_EXECUTION_OBSERVATIONS, "runtime target lifecycle execution observation"),
2599+
// The source contract always declares no_order=true. Only the separate
2600+
// execution-receipt chain may later provide an order/fill attestation.
2601+
order_or_fill_evidence: "not_collected",
2602+
};
2603+
}
2604+
25712605
async function readRuntimeTargetLifecycleSources(env) {
25722606
const store = configStore(env);
25732607
if (!store || typeof store.list !== "function") return [];
@@ -4909,8 +4943,10 @@ function emptyRuntimeTargetLifecyclePayload(errorCode) {
49094943
targets: [],
49104944
policy: {
49114945
lifecycle_status_read_only: true,
4946+
execution_observation_read_only: true,
49124947
no_order: true,
4913-
notice: "运行目标生命周期快照尚不可用;页面不会推断目标已启用。",
4948+
order_or_fill_evidence: "not_collected",
4949+
notice: "运行目标生命周期快照尚不可用;页面不会推断目标已启用或存在订单/成交。",
49144950
},
49154951
errors: [errorCode],
49164952
};

0 commit comments

Comments
 (0)