From ba064b5411aeed5c039ac55c64e1afa35bbe1ec8 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Mon, 31 Aug 2026 02:21:10 +0800 Subject: [PATCH] feat(console): add bounded legacy recovery confirmation Co-Authored-By: Codex --- .../deploy-strategy-switch-console.yml | 8 + .github/workflows/manual-strategy-switch.yml | 5 + tests/strategy_switch_worker_validation.mjs | 231 ++++++++ web/strategy-switch-console/README.md | 9 +- web/strategy-switch-console/README.zh-CN.md | 16 + web/strategy-switch-console/app.js | 218 +++++++ web/strategy-switch-console/app_js.js | 2 +- web/strategy-switch-console/index.html | 5 + web/strategy-switch-console/page_asset.js | 2 +- web/strategy-switch-console/worker.js | 560 ++++++++++++++++++ .../wrangler.toml.example | 1 + 11 files changed, 1054 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy-strategy-switch-console.yml b/.github/workflows/deploy-strategy-switch-console.yml index 5a722a4..35b8e1f 100644 --- a/.github/workflows/deploy-strategy-switch-console.yml +++ b/.github/workflows/deploy-strategy-switch-console.yml @@ -42,6 +42,7 @@ jobs: CONTROL_PLANE_SYNC_TOKEN: ${{ secrets.CONTROL_PLANE_SYNC_TOKEN }} RESEARCH_TASK_SYNC_TOKEN: ${{ secrets.RESEARCH_TASK_SYNC_TOKEN }} EXECUTION_EVIDENCE_SYNC_TOKEN: ${{ secrets.EXECUTION_EVIDENCE_SYNC_TOKEN }} + RECONCILIATION_RECOVERY_SYNC_TOKEN: ${{ secrets.RECONCILIATION_RECOVERY_SYNC_TOKEN }} steps: - name: Checkout uses: actions/checkout@v6 @@ -165,6 +166,13 @@ jobs: set -euo pipefail printf '%s' "$EXECUTION_EVIDENCE_SYNC_TOKEN" | npx wrangler@4.106.0 secret put EXECUTION_EVIDENCE_SYNC_TOKEN --config wrangler.toml + - name: Sync optional reconciliation-recovery ingress token + if: env.RECONCILIATION_RECOVERY_SYNC_TOKEN != '' + working-directory: web/strategy-switch-console + run: | + set -euo pipefail + printf '%s' "$RECONCILIATION_RECOVERY_SYNC_TOKEN" | npx wrangler@4.106.0 secret put RECONCILIATION_RECOVERY_SYNC_TOKEN --config wrangler.toml + - name: Sync M0 research-ledger ingress token env: M0_RESEARCH_SYNC_TOKEN: ${{ secrets.M0_RESEARCH_SYNC_TOKEN }} diff --git a/.github/workflows/manual-strategy-switch.yml b/.github/workflows/manual-strategy-switch.yml index d8196c7..18d8953 100644 --- a/.github/workflows/manual-strategy-switch.yml +++ b/.github/workflows/manual-strategy-switch.yml @@ -211,6 +211,11 @@ jobs: exit 2 fi + if [ "${LIVE_CONTINUITY_STATE}" != "NONE" ]; then + echo "Legacy continuity recovery is intentionally unavailable from this workflow. Use the private reconciliation recovery controller after fresh read-only evidence, bound dual review, and an owner confirmation." >&2 + exit 2 + fi + settings_activation="$( python3 python/scripts/runtime_settings.py settings-activation "${PLATFORM}" )" diff --git a/tests/strategy_switch_worker_validation.mjs b/tests/strategy_switch_worker_validation.mjs index a3d9462..c936825 100644 --- a/tests/strategy_switch_worker_validation.mjs +++ b/tests/strategy_switch_worker_validation.mjs @@ -80,6 +80,10 @@ assert.ok(indexHtml.includes('button.disabled = !supportedModes.includes(button. assert.ok(indexHtml.includes('function renderPlanReadiness()')); assert.ok(indexHtml.includes('id="execution-evidence-list"')); assert.ok(indexHtml.includes('id="execution-evidence-notice"')); +assert.ok(indexHtml.includes('id="reconciliation-recovery-list"')); +assert.ok(indexHtml.includes('id="reconciliation-recovery-notice"')); +assert.ok(indexHtml.includes('requestJson("/api/reconciliation-recovery")')); +assert.ok(indexHtml.includes('data-reconciliation-recovery-confirm')); assert.ok(indexHtml.includes('P0_CONTROL_PLANE_NOT_RUNTIME_WIRED')); assert.ok(indexHtml.includes('window.__QSL_RUNTIME_AUTHORITY_STATUS__')); assert.ok(indexHtml.includes('execution_metadata_is_runtime_authority')); @@ -827,6 +831,7 @@ const workflowYaml = readFileSync(resolve(root, ".github/workflows/manual-strate assert.ok(workflowYaml.includes('"live_continuity_state": continuity.get("state", "NONE")')); assert.ok(workflowYaml.includes('payload["live_continuity_baseline_id"]')); assert.ok(workflowYaml.includes('"cash_only_execution_mode": "current"')); +assert.ok(workflowYaml.includes('Legacy continuity recovery is intentionally unavailable from this workflow.')); const workflowInputs = [...workflowYaml.matchAll(/^ ([A-Za-z0-9_]+):\n description:/gm)].map((match) => match[1]); const dispatchInputs = __test.normalizeSwitchInputs({ platform: "ibkr", @@ -841,6 +846,40 @@ const dispatchInputs = __test.normalizeSwitchInputs({ for (const key of Object.keys(dispatchInputs)) { assert.ok(workflowInputs.includes(key), `workflow input missing for dispatch field: ${key}`); } +const directLegacyRecoveryEnv = { + SESSION_SECRET: "direct-legacy-recovery-session", + ALLOWED_GITHUB_LOGINS: "recovery-admin", + RUNTIME_SETTINGS_DISPATCH_TOKEN: "dispatch-token", +}; +const directLegacyRecoveryCookie = await __test.makeSession("recovery-admin", [], directLegacyRecoveryEnv); +const directLegacyRecoveryAttempt = await worker.fetch( + new Request("https://switch.example/api/switch", { + method: "POST", + headers: { + Cookie: `qsl_switch_session=${directLegacyRecoveryCookie}`, + Origin: "https://switch.example", + "Content-Type": "application/json", + }, + body: JSON.stringify({ + platform: "ibkr", + target_name: "legacy_ibkr_route", + strategy_profile: "soxl_soxx_trend_income", + execution_mode: "live", + live_continuity_state: "RECONCILE_ONLY", + live_continuity_baseline_id: "legacy-ibkr-lkg-20260830", + live_continuity_captured_at: "2026-08-30", + variable_scope: "default", + plugin_mode: "current", + option_overlay_mode: "current", + cash_only_execution_mode: "current", + apply: true, + trigger_platform_sync: true, + confirm_apply: "APPLY_AND_SYNC", + }), + }), + directLegacyRecoveryEnv, +); +assert.equal(directLegacyRecoveryAttempt.status, 409); const normalizedPluginInputs = __test.normalizeSwitchInputs({ platform: "ibkr", target_name: "ibkr-primary", @@ -2476,6 +2515,198 @@ const recordedOwnerDecisionQueue = await worker.fetch( const recordedOwnerDecisionPayload = await recordedOwnerDecisionQueue.json(); assert.equal(recordedOwnerDecisionPayload.candidates[0].intent.decision, "keep_parked"); +const recoveryStore = new Map(); +const recoveryKv = { + async get(key) { return recoveryStore.get(key) || null; }, + async put(key, value) { recoveryStore.set(key, value); }, + async list({ prefix = "", limit = 1000 } = {}) { + return { + keys: [...recoveryStore.keys()] + .filter((key) => key.startsWith(prefix)) + .slice(0, limit) + .map((name) => ({ name })), + }; + }, +}; +const recoverySyncValue = ["reconciliation", "recovery", "sync"].join("-"); +const recoveryEnv = { + SESSION_SECRET: "recovery-session-value", + ALLOWED_GITHUB_LOGINS: "recovery-admin,recovery-reader", + STRATEGY_SWITCH_ADMIN_LOGINS: "recovery-admin", + RECONCILIATION_RECOVERY_SYNC_TOKEN: recoverySyncValue, + STRATEGY_SWITCH_CONFIG: recoveryKv, +}; +const recoveryAdminCookie = await __test.makeSession("recovery-admin", [], recoveryEnv); +const recoveryReaderCookie = await __test.makeSession("recovery-reader", [], recoveryEnv); +const recoveryAdminHeaders = { Cookie: `qsl_switch_session=${recoveryAdminCookie}` }; +const recoveryReaderHeaders = { Cookie: `qsl_switch_session=${recoveryReaderCookie}` }; +const recoveryNow = new Date().toISOString().replace(/\.\d{3}Z$/, "Z"); +const recoveryFirstObservation = new Date(Date.parse(recoveryNow) - 60 * 1000).toISOString().replace(/\.\d{3}Z$/, "Z"); +const recoveryCandidateSha256 = "a".repeat(64); +const recoverySourcePayload = { + schema_version: "qsl_reconciliation_recovery_source_snapshot.v1", + source_id: "ibkr.legacy_recovery", + generated_at: recoveryNow, + computed_at: recoveryNow, + data_status: "ready", + recoveries: [{ + recovery_id: "ibkr_legacy_soxl_live", + platform: "ibkr", + strategy_profile: "soxl_soxx_trend_income", + environment: "live", + reconciliation_state: "RECONCILE_ONLY", + readiness: "awaiting_human_confirmation", + candidate_sha256: recoveryCandidateSha256, + evidence_sample_count: 2, + first_observed_at: recoveryFirstObservation, + last_observed_at: recoveryNow, + dual_review: { + outcome: "approved", + reviewer_count: 2, + evidence_binding_sha256: recoveryCandidateSha256, + }, + blocker_codes: [], + }], + errors: [], +}; +assert.throws( + () => __test.normalizeReconciliationRecoverySourceSnapshot({ + ...recoverySourcePayload, + recoveries: [{ ...recoverySourcePayload.recoveries[0], first_observed_at: recoveryNow }], + }), + /1-15 minute two-sample window/, +); +const unauthorizedRecoveryRead = await worker.fetch( + new Request("https://switch.example/api/reconciliation-recovery"), + recoveryEnv, +); +assert.equal(unauthorizedRecoveryRead.status, 401); +const wrongRecoverySync = await worker.fetch( + new Request("https://switch.example/api/internal/sync-reconciliation-recovery-source", { + method: "POST", + headers: { Authorization: "Bearer wrong-token", "Content-Type": "application/json" }, + body: JSON.stringify(recoverySourcePayload), + }), + recoveryEnv, +); +assert.equal(wrongRecoverySync.status, 401); +const invalidRecoverySync = await worker.fetch( + new Request("https://switch.example/api/internal/sync-reconciliation-recovery-source", { + method: "POST", + headers: { Authorization: `Bearer ${recoverySyncValue}`, "Content-Type": "application/json" }, + body: JSON.stringify({ + ...recoverySourcePayload, + recoveries: [{ + ...recoverySourcePayload.recoveries[0], + dual_review: { ...recoverySourcePayload.recoveries[0].dual_review, evidence_binding_sha256: "b".repeat(64) }, + }], + }), + }), + recoveryEnv, +); +assert.equal(invalidRecoverySync.status, 400); +const recoverySync = await worker.fetch( + new Request("https://switch.example/api/internal/sync-reconciliation-recovery-source", { + method: "POST", + headers: { Authorization: `Bearer ${recoverySyncValue}`, "Content-Type": "application/json" }, + body: JSON.stringify(recoverySourcePayload), + }), + recoveryEnv, +); +assert.equal(recoverySync.status, 200); +const recoveryRead = await worker.fetch( + new Request("https://switch.example/api/reconciliation-recovery", { headers: recoveryAdminHeaders }), + recoveryEnv, +); +assert.equal(recoveryRead.status, 200); +const recoveryDashboard = await recoveryRead.json(); +assert.equal(recoveryDashboard.data_status, "ready"); +assert.deepEqual(recoveryDashboard.summary, { + recovery_count: 1, awaiting_human_confirmation: 1, blocked: 0, confirmed: 0, +}); +assert.equal(recoveryDashboard.policy.no_order, true); +assert.equal(recoveryDashboard.policy.execution_authority_granted, false); +const recoveryConfirmationRequest = { + recovery_id: "ibkr_legacy_soxl_live", + candidate_sha256: recoveryCandidateSha256, + dual_review_binding_sha256: recoveryCandidateSha256, +}; +const staleRecoveryCandidateSha256 = "c".repeat(64); +const staleRecoverySourcePayload = { + ...recoverySourcePayload, + source_id: "ibkr.stale_recovery", + recoveries: [{ + ...recoverySourcePayload.recoveries[0], + recovery_id: "ibkr_legacy_stale", + candidate_sha256: staleRecoveryCandidateSha256, + first_observed_at: new Date(Date.parse(recoveryNow) - 41 * 60 * 1000).toISOString().replace(/\.\d{3}Z$/, "Z"), + last_observed_at: new Date(Date.parse(recoveryNow) - 40 * 60 * 1000).toISOString().replace(/\.\d{3}Z$/, "Z"), + dual_review: { + outcome: "approved", + reviewer_count: 2, + evidence_binding_sha256: staleRecoveryCandidateSha256, + }, + }], +}; +const staleRecoverySync = await worker.fetch( + new Request("https://switch.example/api/internal/sync-reconciliation-recovery-source", { + method: "POST", + headers: { Authorization: `Bearer ${recoverySyncValue}`, "Content-Type": "application/json" }, + body: JSON.stringify(staleRecoverySourcePayload), + }), + recoveryEnv, +); +assert.equal(staleRecoverySync.status, 200); +const staleRecoveryRead = await worker.fetch( + new Request("https://switch.example/api/reconciliation-recovery", { headers: recoveryAdminHeaders }), + recoveryEnv, +); +const staleRecoveryDashboard = await staleRecoveryRead.json(); +assert.equal(staleRecoveryDashboard.data_status, "stale"); +const staleDashboardConfirmation = await worker.fetch( + new Request("https://switch.example/api/reconciliation-recovery-confirmations", { + method: "POST", + headers: { ...recoveryAdminHeaders, Origin: "https://switch.example", "Content-Type": "application/json" }, + body: JSON.stringify(recoveryConfirmationRequest), + }), + recoveryEnv, +); +assert.equal(staleDashboardConfirmation.status, 409); +recoveryStore.delete("reconciliation_recovery_source:ibkr.stale_recovery"); +const readerRecoveryConfirmation = await worker.fetch( + new Request("https://switch.example/api/reconciliation-recovery-confirmations", { + method: "POST", + headers: { ...recoveryReaderHeaders, Origin: "https://switch.example", "Content-Type": "application/json" }, + body: JSON.stringify(recoveryConfirmationRequest), + }), + recoveryEnv, +); +assert.equal(readerRecoveryConfirmation.status, 403); +const recoveryConfirmation = await worker.fetch( + new Request("https://switch.example/api/reconciliation-recovery-confirmations", { + method: "POST", + headers: { ...recoveryAdminHeaders, Origin: "https://switch.example", "Content-Type": "application/json" }, + body: JSON.stringify(recoveryConfirmationRequest), + }), + recoveryEnv, +); +assert.equal(recoveryConfirmation.status, 200); +const recoveryConfirmationPayload = await recoveryConfirmation.json(); +assert.equal(recoveryConfirmationPayload.confirmation.no_order, true); +assert.equal(recoveryConfirmationPayload.confirmation.execution_authority_granted, false); +assert.ok(recoveryStore.has("reconciliation_recovery_current:ibkr_legacy_soxl_live")); +assert.ok(recoveryStore.has( + `reconciliation_recovery_confirmation:ibkr_legacy_soxl_live:${recoveryConfirmationPayload.confirmation.confirmation_sha256}`, +)); +const recordedRecoveryRead = await worker.fetch( + new Request("https://switch.example/api/reconciliation-recovery", { headers: recoveryAdminHeaders }), + recoveryEnv, +); +const recordedRecoveryPayload = await recordedRecoveryRead.json(); +assert.equal(recordedRecoveryPayload.summary.confirmed, 1); +assert.equal(recordedRecoveryPayload.summary.awaiting_human_confirmation, 0); +assert.equal(recordedRecoveryPayload.recoveries[0].confirmation.confirmed_by, "recovery-admin"); + const riskProfileStore = new Map(); const riskProfileKv = { async get(key) { return riskProfileStore.get(key) || null; }, diff --git a/web/strategy-switch-console/README.md b/web/strategy-switch-console/README.md index e16799e..22013a9 100644 --- a/web/strategy-switch-console/README.md +++ b/web/strategy-switch-console/README.md @@ -169,6 +169,12 @@ When adding or renaming a strategy profile: The console only allows live-enabled profiles whose `domain` is included in the selected account's `supported_domains`. If a profile is dynamically read from GitHub Variables but is missing from the catalog, add it to the catalog before switching to it. +## Existing Live Recovery Verification + +An already authorised target that entered `RECONCILE_ONLY` uses a separate, non-executable recovery path rather than the P6 new-strategy queue. A private runtime publishes a redacted `qsl_reconciliation_recovery_source_snapshot.v1` to `POST /api/internal/sync-reconciliation-recovery-source` with a dedicated `RECONCILIATION_RECOVERY_SYNC_TOKEN`; allowlisted users read `GET /api/reconciliation-recovery`, and an administrator may record `POST /api/reconciliation-recovery-confirmations`. + +An item can await confirmation only when it remains `RECONCILE_ONLY`, has two or more read-only samples separated by 1–15 minutes, has a current candidate digest, two approved reviews bound to that same digest, and no blocker. Source and candidate evidence expire after 30 minutes by default. The confirmation is an immutable `no_order=true`, `execution_authority_granted=false` intent only: it cannot dispatch a workflow, read broker credentials, change an account, restore runtime, or place an order. A private platform controller must re-verify raw receipts and the dual-review binding before it may restore the pre-existing runtime; `manual-strategy-switch.yml` rejects all legacy continuity states so it cannot bypass this boundary. + ## GitHub OAuth App Create a GitHub OAuth App: @@ -196,6 +202,7 @@ wrangler secret put SESSION_SECRET wrangler secret put RUNTIME_SETTINGS_DISPATCH_TOKEN wrangler secret put STRATEGY_SWITCH_SYNC_TOKEN # optional; defaults to RUNTIME_SETTINGS_DISPATCH_TOKEN wrangler secret put M0_RESEARCH_SYNC_TOKEN +wrangler secret put RECONCILIATION_RECOVERY_SYNC_TOKEN wrangler secret put ALLOWED_GITHUB_LOGINS wrangler secret put ALLOWED_GITHUB_ORGS wrangler secret put STRATEGY_SWITCH_ADMIN_LOGINS @@ -211,7 +218,7 @@ wrangler kv namespace create STRATEGY_SWITCH_CONFIG Add the returned namespace id to `wrangler.toml`. -For GitHub Actions auto-deploy, configure `STRATEGY_SWITCH_CONFIG_KV_NAMESPACE_ID`, `STRATEGY_SWITCH_CONSOLE_URL`, `STRATEGY_SWITCH_SYNC_TOKEN`, `M0_RESEARCH_SYNC_TOKEN`, and either `CLOUDFLARE_API_TOKEN` or `CLOUDFLARE_WRANGLER_CONFIG_TOML` in the `runtime-strategy-switch` environment (or reuse `RUNTIME_SETTINGS_GH_TOKEN` only if it matches the Worker sync secret). `CLOUDFLARE_ACCOUNT_ID` is optional when Wrangler can infer it from the token. `M0_RESEARCH_SYNC_TOKEN` must match the separately protected `m0-research-publisher` environment secret; it is only copied to the Worker binding. A missing M0 token fails the deployment before it can retain a stale Worker secret. The workflow deploys the Worker and then syncs the bundled strategy profile catalog into KV so the website is not left with stale profile/plugin metadata. +For GitHub Actions auto-deploy, configure `STRATEGY_SWITCH_CONFIG_KV_NAMESPACE_ID`, `STRATEGY_SWITCH_CONSOLE_URL`, `STRATEGY_SWITCH_SYNC_TOKEN`, `M0_RESEARCH_SYNC_TOKEN`, and either `CLOUDFLARE_API_TOKEN` or `CLOUDFLARE_WRANGLER_CONFIG_TOML` in the `runtime-strategy-switch` environment (or reuse `RUNTIME_SETTINGS_GH_TOKEN` only if it matches the Worker sync secret). Add a separate `RECONCILIATION_RECOVERY_SYNC_TOKEN` before enabling any legacy-recovery publisher; the deploy synchronizes it only when it is present. `CLOUDFLARE_ACCOUNT_ID` is optional when Wrangler can infer it from the token. `M0_RESEARCH_SYNC_TOKEN` must match the separately protected `m0-research-publisher` environment secret; it is only copied to the Worker binding. A missing M0 token fails the deployment before it can retain a stale Worker secret. The workflow deploys the Worker and then syncs the bundled strategy profile catalog into KV so the website is not left with stale profile/plugin metadata. An authenticated retry carrying the exact same immutable M0 source-artifact SHA is acknowledged with `200` and `replayed: true`, without another KV write. A different source/run replay or a ledger-time rollback remains rejected with `409`. diff --git a/web/strategy-switch-console/README.zh-CN.md b/web/strategy-switch-console/README.zh-CN.md index 55b9745..79607ee 100644 --- a/web/strategy-switch-console/README.zh-CN.md +++ b/web/strategy-switch-console/README.zh-CN.md @@ -38,6 +38,7 @@ STRATEGY_HEALTH_SYNC_TOKEN CONTROL_PLANE_SYNC_TOKEN RESEARCH_TASK_SYNC_TOKEN M0_RESEARCH_SYNC_TOKEN +RECONCILIATION_RECOVERY_SYNC_TOKEN ``` 可选: @@ -254,6 +255,20 @@ GET /api/control-plane 这个接口不是订单、paper、shadow 或 live API。`CONTROL_PLANE_SYNC_TOKEN` 必须与 OAuth、workflow dispatch、策略 root 和任何券商凭证完全分离。部署 workflow 会在 `runtime-strategy-switch` environment 提供该 secret 时,把它同步为 Worker secret;每个来源仓库需要保存同一值到其专用 GitHub Environment,URL 使用只读控制台的 `/api/internal/sync-control-plane-source`。 +## 旧实盘恢复核验(仅意图) + +已经获授权、但因异常进入 `RECONCILE_ONLY` 的**既有**实盘目标,不走 P6 新策略上线队列。平台私有运行时用独立的 `RECONCILIATION_RECOVERY_SYNC_TOKEN` 发布最小快照: + +```text +POST /api/internal/sync-reconciliation-recovery-source +GET /api/reconciliation-recovery +POST /api/reconciliation-recovery-confirmations +``` + +来源契约是 `qsl_reconciliation_recovery_source_snapshot.v1`。每项只允许携带不含账户或 broker 状态的:不透明恢复 ID、平台/策略、`RECONCILE_ONLY`、QPK 候选 SHA-256、两次以上只读样本的时间窗与数量、双 AI 审计结果/绑定 SHA-256,以及稳定阻断码。`awaiting_human_confirmation` 只有在“两次样本、至少相隔 1 分钟且不超过 15 分钟、至少两位审计者、双审绑定同一候选、无阻断项”同时满足时才会被接受;来源和最后一次候选观测均默认 30 分钟后过期。任何已过期来源或候选都会使确认入口保持关闭。 + +控制台管理员确认后,Worker 只保存 `qsl_reconciliation_recovery_confirmation.v1` 的不可执行意图,固定 `no_order=true`、`execution_authority_granted=false`。它不会调用 workflow、读取券商凭证、改账户、下单或启用目标。未来的私有恢复控制器仍必须在同一目标上重新验证来源收据与双审绑定,原子写入五项预期状态摘要并切换到 `ACTIVE_LKG`;任一条件不成立就保持 `RECONCILE_ONLY`。旧 `manual-strategy-switch.yml` 明确拒绝任何 `live_continuity_state != NONE`,避免绕开这条链路。 + ## 平台运行状态只读接口 已接入的券商/平台仓在自身运行监测完成后,使用与执行证据同样受限的专用同步凭据提交**脱敏状态**: @@ -351,6 +366,7 @@ wrangler secret put STRATEGY_SWITCH_SYNC_TOKEN # 可选;默认复用 RUNTIME_S wrangler secret put STRATEGY_HEALTH_SYNC_TOKEN wrangler secret put CONTROL_PLANE_SYNC_TOKEN wrangler secret put M0_RESEARCH_SYNC_TOKEN +wrangler secret put RECONCILIATION_RECOVERY_SYNC_TOKEN wrangler secret put ALLOWED_GITHUB_LOGINS wrangler secret put ALLOWED_GITHUB_ORGS wrangler secret put STRATEGY_SWITCH_ADMIN_LOGINS diff --git a/web/strategy-switch-console/app.js b/web/strategy-switch-console/app.js index aad5183..6f2f6ac 100644 --- a/web/strategy-switch-console/app.js +++ b/web/strategy-switch-console/app.js @@ -850,6 +850,24 @@ ownerDecisionSubmitting: "正在记录决定…", ownerDecisionSuccess: "所有者决定已记录;它不会自动启用实盘。", ownerDecisionFailed: "无法记录所有者决定", + reconciliationRecoveryBoard: "恢复核验(仅已存在实盘)", + reconciliationRecoveryLoginNotice: "登录后读取私有恢复核验;缺失快照不会被推断为可以恢复。", + reconciliationRecoveryStaleNotice: "恢复核验已过期;请等待平台重新采样和双审,不能确认。", + reconciliationRecoveryUnavailableNotice: "暂无可用恢复核验;页面保持 fail-closed 空状态。", + reconciliationRecoveryUpstreamNotice: "恢复核验已加载,但有 {count} 个上游提示;请先处理阻断项。", + reconciliationRecoveryEmpty: "没有待恢复的既有实盘目标。", + reconciliationRecoveryMeta: "{platform} · {strategy} · 仅恢复现有执行", + reconciliationRecoveryDetail: "状态:{state} · 只读样本:{samples} · 双审:{review} · 窗口截止:{lastObserved}", + reconciliationRecoveryBlocked: "仍被阻断:{blockers}", + reconciliationRecoveryReady: "两次对账一致且双审绑定当前摘要;需要管理员确认。", + reconciliationRecoveryAdminOnly: "只有控制台管理员可以确认恢复意图。", + reconciliationRecoveryConfirmed: "已记录恢复确认(仍未恢复运行)。", + reconciliationRecoveryConfirm: "确认恢复既有执行意图", + reconciliationRecoveryConfirmPrompt: "这只会记录与当前对账/双审摘要绑定的恢复意图;不会下单、修改资金或直接启用运行。继续吗?", + reconciliationRecoverySubmitting: "正在记录确认…", + reconciliationRecoverySuccess: "恢复确认已记录;私有控制器仍须重新核验后才可恢复既有执行。", + reconciliationRecoveryFailed: "无法记录恢复确认", + reconciliationRecoveryNoOrder: "固定边界:控制台不持有券商凭证,也不直接恢复、下单或启用实盘。", executionEvidenceBoard: "按需查看策略 × 平台证据覆盖", executionEvidenceLoginNotice: "登录后读取私有执行证据快照;缺失来源不会被推断成 paper 或 live 状态。", executionEvidenceStaleNotice: "执行证据已超过允许的新鲜度窗口;它保留为历史记录,不能作为当前运行或 P6 结论。", @@ -1157,6 +1175,24 @@ ownerDecisionSubmitting: "Recording decision…", ownerDecisionSuccess: "Owner decision recorded; it does not enable Live automatically.", ownerDecisionFailed: "Could not record owner decision", + reconciliationRecoveryBoard: "Recovery verification (existing live only)", + reconciliationRecoveryLoginNotice: "Sign in to read private recovery verification. Missing snapshots never imply that recovery is allowed.", + reconciliationRecoveryStaleNotice: "Recovery verification is stale. Wait for fresh platform sampling and dual review before confirming.", + reconciliationRecoveryUnavailableNotice: "No usable recovery verification is available. This view remains fail-closed and empty.", + reconciliationRecoveryUpstreamNotice: "Recovery verification loaded with {count} upstream notice(s). Resolve blockers first.", + reconciliationRecoveryEmpty: "No existing live target is awaiting recovery.", + reconciliationRecoveryMeta: "{platform} · {strategy} · existing-execution recovery only", + reconciliationRecoveryDetail: "state: {state} · read-only samples: {samples} · dual review: {review} · window ends: {lastObserved}", + reconciliationRecoveryBlocked: "Still blocked: {blockers}", + reconciliationRecoveryReady: "Two reconciliations match and dual review is bound to the current digest; an admin confirmation is required.", + reconciliationRecoveryAdminOnly: "Only console administrators can record a recovery confirmation.", + reconciliationRecoveryConfirmed: "Recovery confirmation recorded (runtime is still not restored).", + reconciliationRecoveryConfirm: "Record existing-execution recovery intent", + reconciliationRecoveryConfirmPrompt: "This only records a recovery intent bound to the current reconciliation and dual-review digest. It will not place orders, change funds, or directly enable runtime. Continue?", + reconciliationRecoverySubmitting: "Recording confirmation…", + reconciliationRecoverySuccess: "Recovery confirmation recorded. A private controller must re-verify before it can restore existing execution.", + reconciliationRecoveryFailed: "Could not record recovery confirmation", + reconciliationRecoveryNoOrder: "Fixed boundary: the console holds no broker credentials and cannot directly recover, trade, or enable Live.", executionEvidenceBoard: "View strategy × platform evidence on demand", executionEvidenceLoginNotice: "Sign in to read private execution-evidence snapshots. Missing sources never imply a paper or live state.", executionEvidenceStaleNotice: "This execution evidence is beyond its freshness window. It remains historical context, not a current runtime or P6 conclusion.", @@ -1485,6 +1521,17 @@ candidates: [], errors: [], }, + reconciliationRecovery: { + payload: { + data_status: "unavailable", + computed_at: null, + summary: { recovery_count: 0, awaiting_human_confirmation: 0, blocked: 0, confirmed: 0 }, + recoveries: [], + policy: { human_confirmation_required: true, current_evidence_required: true, no_order: true, execution_authority_granted: false }, + errors: [], + }, + submittingRecoveryId: null, + }, m0Research: { payload: { data_status: "unavailable", @@ -3824,6 +3871,116 @@ } } + function normalizeReconciliationRecoveryPayload(payload) { + if (!payload || typeof payload !== "object" || Array.isArray(payload)) { + throw new Error("invalid reconciliation recovery payload"); + } + const recoveries = Array.isArray(payload.recoveries) ? payload.recoveries : []; + return { + data_status: ["ready", "stale", "unavailable"].includes(payload.data_status) ? payload.data_status : "unavailable", + computed_at: payload.computed_at || null, + summary: payload.summary && typeof payload.summary === "object" ? payload.summary : {}, + recoveries: recoveries.filter((entry) => entry && typeof entry === "object" + && entry.recovery && typeof entry.recovery === "object" + && entry.recovery.dual_review && typeof entry.recovery.dual_review === "object"), + policy: payload.policy && typeof payload.policy === "object" ? payload.policy : {}, + errors: Array.isArray(payload.errors) ? payload.errors : [], + }; + } + + function renderReconciliationRecovery() { + const payload = state.reconciliationRecovery.payload; + const notice = el("reconciliation-recovery-notice"); + if (!state.auth.allowed) { + notice.textContent = t("reconciliationRecoveryLoginNotice"); + } else if (payload.data_status === "stale") { + notice.textContent = t("reconciliationRecoveryStaleNotice"); + } else if (payload.data_status !== "ready") { + notice.textContent = t("reconciliationRecoveryUnavailableNotice"); + } else if (payload.errors?.length) { + notice.textContent = t("reconciliationRecoveryUpstreamNotice").replace("{count}", payload.errors.length); + } else { + notice.textContent = payload.policy?.notice || t("reconciliationRecoveryNoOrder"); + } + const list = el("reconciliation-recovery-list"); + list.replaceChildren(); + if (!payload.recoveries.length) { + const empty = document.createElement("div"); + empty.className = "health-card__empty"; + empty.textContent = t("reconciliationRecoveryEmpty"); + list.appendChild(empty); + return; + } + for (const entry of payload.recoveries) { + const recovery = entry.recovery || {}; + const card = document.createElement("article"); + card.className = "health-card"; + const main = document.createElement("div"); + main.className = "health-card__main"; + const meta = document.createElement("div"); + meta.className = "health-card__meta"; + meta.textContent = t("reconciliationRecoveryMeta") + .replace("{platform}", recovery.platform || "unknown") + .replace("{strategy}", recovery.strategy_profile || "unknown"); + const title = document.createElement("h4"); + title.className = "health-card__title"; + title.textContent = String(recovery.recovery_id || "unknown"); + const detail = document.createElement("p"); + detail.className = "health-card__reason"; + detail.textContent = t("reconciliationRecoveryDetail") + .replace("{state}", recovery.reconciliation_state || "unknown") + .replace("{samples}", String(recovery.evidence_sample_count || 0)) + .replace("{review}", recovery.dual_review?.outcome || "unknown") + .replace("{lastObserved}", recovery.last_observed_at || "—"); + const status = document.createElement("div"); + status.className = "health-card__meta"; + const ready = payload.data_status === "ready" + && entry.freshness?.data_status === "ready" + && recovery.readiness === "awaiting_human_confirmation" + && recovery.blocker_codes?.length === 0 + && recovery.dual_review?.outcome === "approved" + && recovery.dual_review?.evidence_binding_sha256 === recovery.candidate_sha256; + if (entry.confirmation) { + status.textContent = t("reconciliationRecoveryConfirmed"); + } else if (!ready) { + status.textContent = t("reconciliationRecoveryBlocked") + .replace("{blockers}", (recovery.blocker_codes || []).join(", ") || recovery.readiness || "unknown"); + } else if (!state.auth.admin) { + status.textContent = t("reconciliationRecoveryAdminOnly"); + } else { + status.textContent = t("reconciliationRecoveryReady"); + } + main.append(meta, title, detail, status); + if (ready && !entry.confirmation && state.auth.admin) { + const actions = document.createElement("div"); + actions.className = "owner-decision__actions"; + const button = document.createElement("button"); + button.type = "button"; + button.className = "owner-decision__button"; + button.dataset.reconciliationRecoveryConfirm = "true"; + button.dataset.recoveryId = recovery.recovery_id || ""; + button.dataset.candidateSha256 = recovery.candidate_sha256 || ""; + button.dataset.dualReviewBindingSha256 = recovery.dual_review?.evidence_binding_sha256 || ""; + const submitting = state.reconciliationRecovery.submittingRecoveryId === recovery.recovery_id; + button.disabled = submitting; + button.textContent = submitting ? t("reconciliationRecoverySubmitting") : t("reconciliationRecoveryConfirm"); + actions.appendChild(button); + main.appendChild(actions); + } + const stateBlock = document.createElement("div"); + stateBlock.className = "health-card__score"; + const label = document.createElement("small"); + label.textContent = t("controlNext"); + const stateText = document.createElement("strong"); + stateText.textContent = entry.confirmation ? "confirmed" : (ready ? "confirm" : "blocked"); + const freshness = document.createElement("small"); + freshness.textContent = entry.freshness?.data_status || "unknown"; + stateBlock.append(label, stateText, freshness); + card.append(main, stateBlock); + list.appendChild(card); + } + } + const M0_RESEARCH_DISPLAY_LIMIT = 100; const m0ResearchLabels = { @@ -4519,6 +4676,7 @@ if (state.auth.allowed) { await refreshControlPlane(); await refreshOwnerDecisions(); + await refreshReconciliationRecovery(); await refreshM0Research(); await refreshAdaptiveSelection(); await refreshExecutionEvidence(); @@ -4591,6 +4749,28 @@ renderControlPlane(); } + async function refreshReconciliationRecovery() { + if (!state.auth.allowed) { + renderReconciliationRecovery(); + return; + } + try { + state.reconciliationRecovery.payload = normalizeReconciliationRecoveryPayload( + await requestJson("/api/reconciliation-recovery"), + ); + } catch { + state.reconciliationRecovery.payload = { + data_status: "unavailable", + computed_at: null, + summary: { recovery_count: 0, awaiting_human_confirmation: 0, blocked: 0, confirmed: 0 }, + recoveries: [], + policy: { human_confirmation_required: true, current_evidence_required: true, no_order: true, execution_authority_granted: false }, + errors: ["reconciliation_recovery_request_failed"], + }; + } + renderReconciliationRecovery(); + } + async function refreshM0Research() { if (!state.auth.allowed) { renderM0Research(); @@ -4671,6 +4851,37 @@ } } + async function recordReconciliationRecoveryConfirmation(button) { + if (!state.auth.admin) return; + const recoveryId = String(button.dataset.recoveryId || ""); + const candidateSha256 = String(button.dataset.candidateSha256 || ""); + const dualReviewBindingSha256 = String(button.dataset.dualReviewBindingSha256 || ""); + if (!recoveryId || !candidateSha256 || !dualReviewBindingSha256) return; + if (!window.confirm(t("reconciliationRecoveryConfirmPrompt"))) return; + state.reconciliationRecovery.submittingRecoveryId = recoveryId; + renderReconciliationRecovery(); + try { + const response = await fetch("/api/reconciliation-recovery-confirmations", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + recovery_id: recoveryId, + candidate_sha256: candidateSha256, + dual_review_binding_sha256: dualReviewBindingSha256, + }), + }); + const payload = await response.json(); + if (!response.ok || !payload.ok) throw new Error(payload.error || t("reconciliationRecoveryFailed")); + showToast(t("reconciliationRecoverySuccess"), { duration: 9000 }); + await refreshReconciliationRecovery(); + } catch (error) { + showToast(`${t("reconciliationRecoveryFailed")}: ${error.message}`, { duration: 12000 }); + } finally { + state.reconciliationRecovery.submittingRecoveryId = null; + renderReconciliationRecovery(); + } + } + async function refreshExecutionEvidence() { if (!state.auth.allowed) { renderExecutionEvidence(); @@ -4925,6 +5136,7 @@ if (state.view === "control") { refreshControlPlane(); refreshOwnerDecisions(); + refreshReconciliationRecovery(); refreshM0Research(); refreshAdaptiveSelection(); refreshExecutionEvidence(); @@ -4947,6 +5159,12 @@ recordOwnerDecision(button); }); + el("reconciliation-recovery-list").addEventListener("click", (event) => { + const button = event.target.closest("[data-reconciliation-recovery-confirm]"); + if (!button || button.disabled) return; + recordReconciliationRecoveryConfirmation(button); + }); + el("platform-strip").addEventListener("click", (event) => { const button = event.target.closest("[data-platform]"); if (!button) return; diff --git a/web/strategy-switch-console/app_js.js b/web/strategy-switch-console/app_js.js index 2ab9a0e..2958b17 100644 --- a/web/strategy-switch-console/app_js.js +++ b/web/strategy-switch-console/app_js.js @@ -1,2 +1,2 @@ // Generated — JS asset -export const APP_JS = "\n\n let platformMeta = {\n binance: { label: \"Binance\", code: \"BN\", accent: \"var(--bn)\" },\n firstrade: { label: \"Firstrade\", code: \"FT\", accent: \"var(--ft)\" },\n ibkr: { label: \"IBKR\", code: \"IB\", accent: \"var(--ib)\" },\n longbridge: { label: \"LongBridge\", code: \"LB\", accent: \"var(--lb)\" },\n qmt: { label: \"QMT\", code: \"QM\", accent: \"var(--qmt)\" },\n schwab: { label: \"Schwab\", code: \"SW\", accent: \"var(--sw)\" },\n };\n\n const platformRepositories = {\n binance: \"QuantStrategyLab/BinancePlatform\",\n firstrade: \"QuantStrategyLab/FirstradePlatform\",\n ibkr: \"QuantStrategyLab/InteractiveBrokersPlatform\",\n longbridge: \"QuantStrategyLab/LongBridgePlatform\",\n qmt: \"QuantStrategyLab/QmtPlatform\",\n schwab: \"QuantStrategyLab/CharlesSchwabPlatform\",\n };\n // Alias for backward compatibility\n const defaultRepositories = platformRepositories;\n\n const defaultAccountOptions = window.__DEFAULT_ACCOUNT_OPTIONS__ || {\n binance: [{\"key\": \"default\", \"label\": \"Binance\", \"target_name\": \"default\", \"cash_currency\": \"USD\", \"supported_domains\": [\"crypto\"]}],\n firstrade: [{\"key\": \"preview\", \"label\": \"Firstrade\", \"target_name\": \"preview\", \"supported_domains\": [\"us_equity\"], \"cash_currency\": \"USD\", \"default_execution_mode\": \"live\", \"service_name\": \"firstrade-quant-service\"}],\n ibkr: [{\"key\": \"preview\", \"label\": \"IBKR\", \"target_name\": \"preview\", \"supported_domains\": [\"us_equity\", \"hk_equity\"], \"cash_currency\": \"USD\", \"default_execution_mode\": \"live\"}],\n longbridge: [{\"key\": \"preview\", \"label\": \"LongBridge\", \"target_name\": \"preview\", \"supported_domains\": [\"us_equity\", \"hk_equity\"], \"cash_currency\": \"USD\", \"default_execution_mode\": \"live\"}],\n qmt: [{\"key\": \"default\", \"label\": \"QMT\", \"target_name\": \"default\", \"cash_currency\": \"CNY\", \"supported_domains\": [\"cn_equity\"], \"service_name\": \"qmt-quant-service\"}],\n schwab: [{\"key\": \"preview\", \"label\": \"Schwab\", \"target_name\": \"preview\", \"supported_domains\": [\"us_equity\"], \"cash_currency\": \"USD\", \"default_execution_mode\": \"live\", \"service_name\": \"charles-schwab-quant-service\"}],\n };\n\n const domainLabels = window.__DOMAIN_LABELS__ || {\n cn_equity: { zh: \"A股\", en: \"CN A-share\" },\n crypto: { zh: \"加密\", en: \"Crypto\" },\n hk_equity: { zh: \"港股\", en: \"HK Equity\" },\n us_equity: { zh: \"美股\", en: \"US Equity\" },\n };\n\n const platformConfig = window.__PLATFORM_CONFIG__ || {\n binance: {\n dry_run_only: false,\n margin_policy: false,\n reserved_cash: false,\n income_layer: false,\n option_overlay: false,\n dca: false,\n execution_mode: \"live\",\n service_name: \"\",\n default_execution_mode: \"live\"\n },\n firstrade: {\n dry_run_only: false,\n margin_policy: true,\n reserved_cash: true,\n income_layer: true,\n option_overlay: true,\n dca: true,\n execution_mode: \"live\",\n service_name: \"firstrade-quant-service\",\n default_execution_mode: \"live\"\n },\n ibkr: {\n dry_run_only: false,\n margin_policy: true,\n reserved_cash: true,\n income_layer: true,\n option_overlay: true,\n dca: true,\n execution_mode: \"live\",\n service_name: \"\",\n default_execution_mode: \"live\"\n },\n longbridge: {\n dry_run_only: false,\n margin_policy: true,\n reserved_cash: true,\n income_layer: true,\n option_overlay: true,\n dca: true,\n execution_mode: \"live\",\n service_name: \"\",\n default_execution_mode: \"live\"\n },\n qmt: {\n dry_run_only: true,\n margin_policy: false,\n reserved_cash: false,\n income_layer: false,\n option_overlay: false,\n dca: false,\n execution_mode: \"paper\",\n service_name: \"qmt-quant-service\",\n default_execution_mode: \"paper\"\n },\n schwab: {\n dry_run_only: false,\n margin_policy: true,\n reserved_cash: true,\n income_layer: true,\n option_overlay: true,\n dca: true,\n execution_mode: \"live\",\n service_name: \"charles-schwab-quant-service\",\n default_execution_mode: \"live\"\n },\n };\n const runtimeAuthorityStatus = window.__QSL_RUNTIME_AUTHORITY_STATUS__ || {};\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n const reservePolicyModes = [\"none\", \"ratio\", \"floor\", \"max\"];\n const incomeLayerModes = [\"enabled\", \"disabled\"];\n const optionOverlayModes = [\"enabled\", \"disabled\"];\n const cashOnlyExecutionModes = [\"enabled\", \"disabled\"];\n const runtimeTargetModes = [\"enabled\", \"disabled\"];\n const pluginModes = [\"none\"];\n const dcaModes = [\"fixed\", \"smart\"];\n const runtimeTargetEnabledVariable = \"RUNTIME_TARGET_ENABLED\";\n const incomeLayerEnabledVariable = \"INCOME_LAYER_ENABLED\";\n const incomeLayerStartUsdVariable = \"INCOME_LAYER_START_USD\";\n const incomeLayerMaxRatioVariable = \"INCOME_LAYER_MAX_RATIO\";\n const dcaProfileDefaults = window.__DCA_PROFILE_DEFAULTS__ || {\n nasdaq_sp500_smart_dca: { defaultMode: \"fixed\", defaultBaseInvestmentUsd: \"1000\" },\n ibit_smart_dca: { defaultMode: \"fixed\", defaultBaseInvestmentUsd: \"1000\" },\n };\n const APP_BOOT_TIMEOUT_MS = 15000;\n const platformMinReservedCashVariables = {\n longbridge: \"LONGBRIDGE_MIN_RESERVED_CASH_USD\",\n ibkr: \"IBKR_MIN_RESERVED_CASH_USD\",\n schwab: \"SCHWAB_MIN_RESERVED_CASH_USD\",\n firstrade: \"FIRSTRADE_MIN_RESERVED_CASH_USD\",\n };\n const platformReservedCashRatioVariables = {\n longbridge: \"LONGBRIDGE_RESERVED_CASH_RATIO\",\n ibkr: \"IBKR_RESERVED_CASH_RATIO\",\n schwab: \"SCHWAB_RESERVED_CASH_RATIO\",\n firstrade: \"FIRSTRADE_RESERVED_CASH_RATIO\",\n };\n\n const defaultStrategyProfiles = window.__DEFAULT_STRATEGY_PROFILES__ || [\n {\n \"profile\": \"tqqq_growth_income\",\n \"label\": \"纳斯达克增长收益\",\n \"label_en\": \"NASDAQ Growth Income\",\n \"label_zh\": \"纳斯达克增长收益\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": true,\n \"option_overlay_enabled\": true,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\",\n \"income_layer_start_usd\": \"250000\",\n \"income_layer_max_ratio\": \"0.55\",\n \"income_layer_allocations\": {\n \"SCHD\": 0.3,\n \"DGRO\": 0.2,\n \"SGOV\": 0.4,\n \"SPYI\": 0.08,\n \"QQQI\": 0.02\n },\n \"option_overlay_live_gate\": \"promotion_required\",\n \"option_overlay_live_status\": \"research_only\",\n \"option_growth_overlay_enabled\": true,\n \"option_growth_overlay_recipe\": \"tqqq_leaps_growth_v1\",\n \"option_growth_overlay_start_usd\": \"250000\",\n \"option_growth_overlay_nav_budget_ratio\": \"0.03\"\n },\n {\n \"profile\": \"soxl_soxx_trend_income\",\n \"label\": \"半导体趋势收益\",\n \"label_en\": \"Semiconductor Trend Income\",\n \"label_zh\": \"半导体趋势收益\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": true,\n \"option_overlay_enabled\": true,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\",\n \"income_layer_start_usd\": \"150000\",\n \"income_layer_max_ratio\": \"0.95\",\n \"income_layer_allocations\": {\n \"SCHD\": 0.15,\n \"DGRO\": 0.1,\n \"SGOV\": 0.7,\n \"SPYI\": 0.04,\n \"QQQI\": 0.01\n },\n \"option_overlay_live_gate\": \"promotion_required\",\n \"option_overlay_live_status\": \"research_only\",\n \"option_income_overlay_enabled\": true,\n \"option_income_overlay_recipe\": \"soxx_put_credit_spread_income_v1\",\n \"option_income_overlay_start_usd\": \"150000\",\n \"option_income_overlay_nav_risk_ratio\": \"0.01\"\n },\n {\n \"profile\": \"nasdaq_sp500_smart_dca\",\n \"label\": \"纳指标普定投\",\n \"label_en\": \"NASDAQ/S&P 500 DCA\",\n \"label_zh\": \"纳指标普定投\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\",\n \"dca_enabled\": true,\n \"dca_default_mode\": \"fixed\",\n \"dca_default_base_investment_usd\": \"1000\"\n },\n {\n \"profile\": \"ibit_smart_dca\",\n \"label\": \"IBIT比特币定投\",\n \"label_en\": \"IBIT Bitcoin DCA\",\n \"label_zh\": \"IBIT比特币定投\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\",\n \"dca_enabled\": true,\n \"dca_default_mode\": \"fixed\",\n \"dca_default_base_investment_usd\": \"1000\"\n },\n {\n \"profile\": \"global_etf_rotation\",\n \"label\": \"全球ETF轮动\",\n \"label_en\": \"Global ETF Rotation\",\n \"label_zh\": \"全球ETF轮动\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": true,\n \"option_overlay_enabled\": true,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\",\n \"income_layer_start_usd\": \"500000\",\n \"income_layer_max_ratio\": \"0.15\",\n \"income_layer_allocations\": {\n \"SCHD\": 0.4,\n \"DGRO\": 0.25,\n \"SGOV\": 0.3,\n \"SPYI\": 0.05\n },\n \"option_overlay_live_gate\": \"promotion_required\",\n \"option_overlay_live_status\": \"research_only\",\n \"option_growth_overlay_enabled\": true,\n \"option_growth_overlay_recipe\": \"spy_leaps_growth_v1\",\n \"option_growth_overlay_start_usd\": \"500000\",\n \"option_growth_overlay_nav_budget_ratio\": \"0.015\"\n },\n {\n \"profile\": \"russell_top50_leader_rotation\",\n \"label\": \"罗素Top50领涨\",\n \"label_en\": \"Russell Top50 Leaders\",\n \"label_zh\": \"罗素Top50领涨\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": true,\n \"option_overlay_enabled\": true,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\",\n \"income_layer_start_usd\": \"300000\",\n \"income_layer_max_ratio\": \"0.25\",\n \"income_layer_allocations\": {\n \"SCHD\": 0.45,\n \"DGRO\": 0.3,\n \"SGOV\": 0.25\n },\n \"option_overlay_live_gate\": \"promotion_required\",\n \"option_overlay_live_status\": \"research_only\",\n \"option_growth_overlay_enabled\": true,\n \"option_growth_overlay_recipe\": \"spy_leaps_growth_v1\",\n \"option_growth_overlay_start_usd\": \"300000\",\n \"option_growth_overlay_nav_budget_ratio\": \"0.015\"\n },\n {\n \"profile\": \"tecl_xlk_trend_income\",\n \"label\": \"TECL/XLK趋势收益\",\n \"label_en\": \"TECL/XLK Trend Income\",\n \"label_zh\": \"TECL/XLK趋势收益\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"failed_promotion_vs_live_profiles\"\n },\n {\n \"profile\": \"us_equity_combo\",\n \"label\": \"美股核心组合\",\n \"label_en\": \"US Core Combo\",\n \"label_zh\": \"美股核心组合\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": true,\n \"option_overlay_enabled\": true,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"shadow_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"shadow_candidate_requires_evidence_package\",\n \"combo_mode\": \"dynamic\",\n \"income_layer_start_usd\": \"300000\",\n \"income_layer_max_ratio\": \"0.25\",\n \"income_layer_allocations\": {\n \"SCHD\": 0.25,\n \"DGRO\": 0.25,\n \"SGOV\": 0.2,\n \"SPYI\": 0.15,\n \"QQQI\": 0.15\n },\n \"option_overlay_live_gate\": \"promotion_required\",\n \"option_overlay_live_status\": \"research_only\",\n \"option_growth_overlay_enabled\": true,\n \"option_growth_overlay_recipe\": \"spy_leaps_growth_v1\",\n \"option_growth_overlay_start_usd\": \"300000\",\n \"option_growth_overlay_nav_budget_ratio\": \"0.015\"\n },\n {\n \"profile\": \"us_equity_combo_core\",\n \"label\": \"美股核心组合影子\",\n \"label_en\": \"US Core Combo Shadow\",\n \"label_zh\": \"美股核心组合影子\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"shadow_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"shadow_candidate_requires_evidence_package\",\n \"combo_mode\": \"dynamic\"\n },\n {\n \"profile\": \"us_equity_combo_leveraged\",\n \"label\": \"美股加速组合\",\n \"label_en\": \"US Alpha Combo\",\n \"label_zh\": \"美股加速组合\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"shadow_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"shadow_candidate_requires_evidence_package\",\n \"combo_mode\": \"dynamic\"\n },\n {\n \"profile\": \"hk_global_etf_tactical_rotation\",\n \"label\": \"港股ETF战术轮动\",\n \"label_en\": \"HK ETF Tactical Rotation\",\n \"label_zh\": \"港股ETF战术轮动\",\n \"domain\": \"hk_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"hk_low_vol_dividend_quality_snapshot\",\n \"label\": \"港股红利质量\",\n \"label_en\": \"HK Dividend Quality\",\n \"label_zh\": \"港股红利质量\",\n \"domain\": \"hk_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\"\n },\n {\n \"profile\": \"hk_equity_combo\",\n \"label\": \"港股恒生组合\",\n \"label_en\": \"HK Core Combo\",\n \"label_zh\": \"港股恒生组合\",\n \"domain\": \"hk_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\",\n \"combo_mode\": \"dynamic\"\n },\n {\n \"profile\": \"cn_industry_etf_rotation\",\n \"label\": \"A股行业ETF轮动\",\n \"label_en\": \"CN Industry ETF Rotation\",\n \"label_zh\": \"A股行业ETF轮动\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\"\n },\n {\n \"profile\": \"cn_industry_etf_rotation_aggressive\",\n \"label\": \"A股ETF轮动\",\n \"label_en\": \"CN ETF Rotation\",\n \"label_zh\": \"A股ETF轮动\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"live_candidate\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"live_candidate_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_index_etf_tactical_rotation\",\n \"label\": \"A股宽基ETF战术轮动\",\n \"label_en\": \"CN Index ETF Tactical Rotation\",\n \"label_zh\": \"A股宽基ETF战术轮动\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_chinext_tactical_rotation\",\n \"label\": \"创业板战术轮动\",\n \"label_en\": \"CN ChiNext Tactical Rotation\",\n \"label_zh\": \"创业板战术轮动\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_chinext_growth_momentum_quality\",\n \"label\": \"创业板成长动量质量\",\n \"label_en\": \"CN ChiNext Growth Momentum Quality\",\n \"label_zh\": \"创业板成长动量质量\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_dividend_quality_snapshot\",\n \"label\": \"A股红利质量\",\n \"label_en\": \"CN Dividend Quality\",\n \"label_zh\": \"A股红利质量\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_chinext_growth_momentum_quality_snapshot\",\n \"label\": \"创业板成长质量快照\",\n \"label_en\": \"CN ChiNext Growth Quality Snapshot\",\n \"label_zh\": \"创业板成长质量快照\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_star_growth_momentum_quality\",\n \"label\": \"科创板成长动量质量\",\n \"label_en\": \"CN STAR Growth Momentum Quality\",\n \"label_zh\": \"科创板成长动量质量\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_equity_combo\",\n \"label\": \"A股进取组合\",\n \"label_en\": \"CN Alpha Combo\",\n \"label_zh\": \"A股进取组合\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\",\n \"combo_mode\": \"dynamic\"\n },\n {\n \"profile\": \"crypto_live_pool_rotation\",\n \"label\": \"加密实时池轮动\",\n \"label_en\": \"Crypto Live Pool Rotation\",\n \"label_zh\": \"加密实时池轮动\",\n \"domain\": \"crypto\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\"\n },\n {\n \"profile\": \"crypto_btc_dca\",\n \"label\": \"BTC定投\",\n \"label_en\": \"BTC DCA\",\n \"label_zh\": \"BTC定投\",\n \"domain\": \"crypto\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"shadow_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"shadow_candidate_requires_evidence_package\"\n },\n {\n \"profile\": \"crypto_trend_rotation\",\n \"label\": \"山寨趋势轮动\",\n \"label_en\": \"Altcoin Trend\",\n \"label_zh\": \"山寨趋势轮动\",\n \"domain\": \"crypto\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"crypto_equity_combo\",\n \"label\": \"加密动量组合\",\n \"label_en\": \"Crypto Core Combo\",\n \"label_zh\": \"加密动量组合\",\n \"domain\": \"crypto\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\",\n \"combo_mode\": \"dynamic\"\n }\n ];\n\n const localStrategyLabels = {\n tqqq_growth_income: { zh: \"纳斯达克增长收益\", en: \"NASDAQ Growth Income\" },\n soxl_soxx_trend_income: { zh: \"半导体趋势收益\", en: \"Semiconductor Trend Income\" },\n nasdaq_sp500_smart_dca: { zh: \"纳指标普定投\", en: \"NASDAQ/S&P 500 DCA\" },\n ibit_smart_dca: { zh: \"IBIT比特币定投\", en: \"IBIT Bitcoin DCA\" },\n global_etf_rotation: { zh: \"全球ETF轮动\", en: \"Global ETF Rotation\" },\n russell_top50_leader_rotation: { zh: \"罗素Top50领涨\", en: \"Russell Top50 Leaders\" },\n hk_global_etf_tactical_rotation: { zh: \"港股ETF战术轮动\", en: \"HK ETF Tactical Rotation\" },\n hk_low_vol_dividend_quality_snapshot: { zh: \"港股红利质量\", en: \"HK Dividend Quality\" },\n cn_industry_etf_rotation: { zh: \"A股行业ETF轮动\", en: \"CN Industry ETF Rotation\" },\n cn_dividend_quality_snapshot: { zh: \"A股红利质量\", en: \"CN Dividend Quality\" },\n us_equity_combo: { zh: \"美股核心组合\", en: \"US Core Combo\" },\n us_equity_combo_leveraged: { zh: \"美股加速组合\", en: \"US Alpha Combo\" },\n hk_equity_combo: { zh: \"港股恒生组合\", en: \"HK Core Combo\" },\n cn_industry_etf_rotation_aggressive: { zh: \"A股ETF轮动\", en: \"CN ETF Rotation\" },\n cn_stock_momentum_rotation: { zh: \"A股个股动量\", en: \"CN Stock Momentum\" },\n cn_equity_combo: { zh: \"A股进取组合\", en: \"CN Alpha Combo\" },\n crypto_btc_dca: { zh: \"BTC定投\", en: \"BTC DCA\" },\n crypto_trend_rotation: { zh: \"山寨趋势轮动\", en: \"Altcoin Trend\" },\n crypto_equity_combo: { zh: \"加密动量组合\", en: \"Crypto Core Combo\" },\n };\n\n const fallbackIncomeLayerDefaults = window.__INCOME_LAYER_DEFAULTS__ || {\n tqqq_growth_income: {\n startUsd: 250000,\n maxRatio: \"0.55\",\n allocations: { SCHD: 0.30, DGRO: 0.20, SGOV: 0.40, SPYI: 0.08, QQQI: 0.02 },\n },\n soxl_soxx_trend_income: {\n startUsd: 150000,\n maxRatio: \"0.95\",\n allocations: { SCHD: 0.15, DGRO: 0.10, SGOV: 0.70, SPYI: 0.04, QQQI: 0.01 },\n },\n global_etf_rotation: {\n startUsd: 500000,\n maxRatio: \"0.15\",\n allocations: { SCHD: 0.40, DGRO: 0.25, SGOV: 0.30, SPYI: 0.05 },\n },\n russell_top50_leader_rotation: {\n startUsd: 300000,\n maxRatio: \"0.25\",\n allocations: { SCHD: 0.45, DGRO: 0.30, SGOV: 0.25 },\n },\n us_equity_combo: {\n startUsd: 300000,\n maxRatio: \"0.25\",\n allocations: { SCHD: 0.25, DGRO: 0.25, SGOV: 0.20, SPYI: 0.15, QQQI: 0.15 },\n }};\n let incomeLayerDefaults = {};\n const fallbackOptionOverlayDefaults = window.__OPTION_OVERLAY_DEFAULTS__ || {\n tqqq_growth_income: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [\n { family: \"growth\", recipe: \"tqqq_leaps_growth_v1\", startUsd: \"250000\", ratio: \"0.03\", ratioKind: \"budget\" },\n ],\n },\n soxl_soxx_trend_income: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [\n { family: \"income\", recipe: \"soxx_put_credit_spread_income_v1\", startUsd: \"150000\", ratio: \"0.01\", ratioKind: \"risk\" },\n ],\n },\n global_etf_rotation: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [\n { family: \"growth\", recipe: \"spy_leaps_growth_v1\", startUsd: \"500000\", ratio: \"0.015\", ratioKind: \"budget\" },\n ],\n },\n russell_top50_leader_rotation: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [\n { family: \"growth\", recipe: \"spy_leaps_growth_v1\", startUsd: \"300000\", ratio: \"0.015\", ratioKind: \"budget\" },\n ],\n },\n us_equity_combo: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [\n { family: \"growth\", recipe: \"spy_leaps_growth_v1\", startUsd: \"300000\", ratio: \"0.015\", ratioKind: \"budget\" },\n ],\n },\n us_equity_combo_leveraged: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [],\n }};\n let optionOverlayDefaults = {};\n\n const strategyDomains = [\"us_equity\", \"hk_equity\", \"cn_equity\", \"crypto\"];\n let strategyOptions = [];\n let strategyLabels = {};\n let strategyCatalog = {};\n\n\n const copy = {\n zh: {\n appTitle: \"QuantStrategyLab\",\n appSubtitle: \"已验证证据、运行许可和所有者决策。\",\n controlPlaneView: \"待处理\",\n healthView: \"运行保障\",\n switchView: \"运行计划\",\n controlPlaneEyebrow: \"今日待处理 / 只读\",\n controlPlaneTitle: \"只处理需要你决定的事。\",\n controlPlaneSubtitle: \"这里不是交易大屏:只显示经验证的待办、证据是否足够,以及需要人工做出的边界决定。\",\n controlCandidateTotal: \"候选总数\",\n controlDeferred: \"本次延期\",\n controlParked: \"已停车\",\n controlOwnerDecision: \"等待我决定\",\n controlCandidateBoard: \"需要跟进\",\n controlDataReady: \"快照已同步(不代表策略可运行)\",\n controlDataStale: \"快照已过期\",\n controlDataUnavailable: \"等待可用快照\",\n controlAttentionResearchOnly: \"仅研究,未授予执行权限\",\n controlAttentionRequired: \"研究状态需要关注\",\n controlAttentionUnavailable: \"尚无已验证研究状态\",\n controlComputedAt: \"最近计算:{time}\",\n controlLoginNotice: \"登录后读取私有全局快照;没有快照时不会展示虚构信息。\",\n controlStaleNotice: \"全局快照已超过允许的新鲜度窗口;页面保留原始记录,但不把它作为当前结论。\",\n controlUnavailableNotice: \"还没有可用的全局快照;当前页面保持 fail-closed 空状态。\",\n controlUpstreamNotice: \"快照已加载,但有 {count} 个上游提示;缺失信息不会被补成推断结论。\",\n controlAttentionNotice: \"研究状态需要关注(延期 {deferred}、停车 {parked}、信号 {signals})。快照送达不等于策略验证或执行授权;系统保持不交易。\",\n controlEmptyCandidates: \"暂无可展示的候选快照。\",\n controlNoRecommendation: \"没有可用的机器建议。\",\n controlStageMeta: \"阶段:{stage} · 状态:{status} · 证据:{freshness}\",\n controlNext: \"下一步\",\n ownerDecisionTitle: \"所有者决定\",\n ownerDecisionAdminOnly: \"只有控制台管理员可以记录决定。\",\n ownerDecisionReady: \"当前证据已绑定;请人工选择后记录不可执行意图。\",\n ownerDecisionRecorded: \"已记录:{decision}(仅意图,不会启用实盘)\",\n ownerDecisionApprove: \"批准受限试运行意图\",\n ownerDecisionPark: \"保持暂停\",\n ownerDecisionRetire: \"退役候选\",\n ownerDecisionConfirm: \"这只会记录不可执行的所有者决定意图,不会下单、改资金或启用实盘。继续吗?\",\n ownerDecisionSubmitting: \"正在记录决定…\",\n ownerDecisionSuccess: \"所有者决定已记录;它不会自动启用实盘。\",\n ownerDecisionFailed: \"无法记录所有者决定\",\n executionEvidenceBoard: \"按需查看策略 × 平台证据覆盖\",\n executionEvidenceLoginNotice: \"登录后读取私有执行证据快照;缺失来源不会被推断成 paper 或 live 状态。\",\n executionEvidenceStaleNotice: \"执行证据已超过允许的新鲜度窗口;它保留为历史记录,不能作为当前运行或 P6 结论。\",\n executionEvidenceUnavailableNotice: \"还没有可用的执行证据来源;当前列表保持 fail-closed 空状态。\",\n executionEvidenceUpstreamNotice: \"执行证据已加载,但有 {count} 个上游提示;页面不会补造平台能力或运行资格。\",\n executionEvidenceEmpty: \"暂无可展示的策略 × 平台执行证据。\",\n executionEvidenceMeta: \"{platform} · 当前通道:{environment} · 来源:{source}\",\n executionEvidenceDetail: \"策略:{strategy} · 数据:{data} · 执行:{execution} · Shadow:{shadow} · Paper:{paper}\",\n executionEvidenceNoOrder: \"固定边界:只读证据;不包含账户、订单、资金或 P6 实盘授权。\",\n executionEvidenceNext: \"下一步\",\n runtimeTargetLifecycleBoard: \"按需查看平台运行状态\",\n runtimeTargetLifecycleLoginNotice: \"登录后读取私有平台运行状态;页面不会改变启用/停用或提交订单。\",\n runtimeTargetLifecycleStaleNotice: \"平台运行状态已超过允许的新鲜度窗口;请先检查监测链路。\",\n runtimeTargetLifecycleUnavailableNotice: \"还没有可用的平台运行状态;缺失数据不会被推断成已停用或正常。\",\n runtimeTargetLifecycleUpstreamNotice: \"平台运行状态已加载,但有 {count} 个上游提示;请先核对。\",\n runtimeTargetLifecycleEmpty: \"暂无可展示的平台运行状态。\",\n runtimeTargetLifecycleMeta: \"{platform} · {state} · 通道:{mode}\",\n runtimeTargetLifecycleDetail: \"运行监测:{guard} · 执行心跳:{heartbeat}\",\n runtimeTargetLifecycleNoOrder: \"固定边界:只读状态;不会启用目标、变更策略或提交订单。\",\n runtimeTargetLifecycleNext: \"当前处理\",\n runtimeTargetLifecycleStateEnabled: \"已启用\",\n runtimeTargetLifecycleStateDisabled: \"已停用\",\n runtimeTargetLifecycleCheckPass: \"通过\",\n runtimeTargetLifecycleCheckAttention: \"需处理\",\n runtimeTargetLifecycleCheckNotDue: \"未到检查时间\",\n runtimeTargetLifecycleCheckNotApplicable: \"不适用\",\n runtimeTargetLifecycleCheckUnavailable: \"不可用\",\n runtimeTargetLifecycleDispositionEnabled: \"持续监控\",\n runtimeTargetLifecycleDispositionDisabled: \"无执行验证\",\n runtimeTargetLifecycleDispositionParked: \"已暂停\",\n runtimeTargetLifecycleReasonNone: \"监测正常\",\n runtimeTargetLifecycleReasonDisabled: \"按配置停用,仍持续验证\",\n runtimeTargetLifecycleReasonRuntimeGuard: \"运行监测需要复核\",\n runtimeTargetLifecycleReasonHeartbeat: \"执行心跳需要复核\",\n runtimeTargetLifecycleReasonUnavailable: \"监测数据不可用\",\n m0ResearchBoard: \"按需查看 M0 研究观察\",\n m0ResearchLoginNotice: \"登录后读取私有 M0 研究台账;缺失台账不会被补成研究结论。\",\n m0ResearchStaleNotice: \"M0 研究观察已超过有效期,仅保留为历史上下文。\",\n m0ResearchUnavailableNotice: \"还没有可用的 M0 研究台账;页面保持 fail-closed 空状态。\",\n m0ResearchUpstreamNotice: \"M0 研究台账已加载,但有 {count} 个上游提示;页面不会补造研究结论。\",\n m0ResearchEmpty: \"暂无可展示的已验证 M0 研究观察。\",\n m0ResearchMore: \"仅显示前 {count} 条研究观察;其余仍保留在只读台账中。\",\n m0ResearchMeta: \"{kind} · 查看于:{viewed}\",\n m0ResearchStateFreshness: \"状态:{state} · 新鲜度:{freshness}\",\n m0ResearchHorizons: \"主要观察期:{primary} · 适用观察期:{suitable}\",\n m0ResearchEvidence: \"置信:{confidence} · 风格:{style} · 资料摘要:{digest}\",\n m0ResearchConsistency: \"当前周期分歧:{conflict} · 历史失效漂移:{drift}\",\n m0ResearchNoOrder: \"固定边界:仅研究、无订单;不产生任何运行动作。\",\n adaptiveSelectionBoard: \"按需查看 M1 Shadow 建议\",\n adaptiveSelectionLoginNotice: \"登录后读取私有 Shadow 建议;缺失来源不会被补成策略或订单。\",\n adaptiveSelectionStaleNotice: \"此 Shadow 建议已超出新鲜度窗口,仅保留为历史上下文。\",\n adaptiveSelectionUnavailableNotice: \"还没有可用的 M1 Shadow 建议;页面保持 fail-closed 空状态。\",\n adaptiveSelectionUpstreamNotice: \"Shadow 建议带有 {count} 个上游提示;页面不会推断策略、平台或订单。\",\n adaptiveSelectionEmpty: \"暂无可展示的 Shadow 建议。\",\n adaptiveSelectionMeta: \"{source} · {domain} · 市场截至 {asOf}\",\n adaptiveSelectionRecommended: \"建议观察\",\n adaptiveSelectionNoCandidate: \"无合格 Shadow 候选\",\n adaptiveSelectionReason: \"原因:{reasons}\",\n adaptiveSelectionNoOrder: \"固定边界:仅供人工查看,零建议权重,不改策略、平台、资金、运行状态或订单。\",\n researchTaskBoard: \"按需查看研究任务队列\",\n researchTaskLoginNotice: \"登录后读取私有研究任务索引;没有来源快照时不会展示虚构任务。\",\n researchTaskStaleNotice: \"研究任务来源已超过允许的新鲜度窗口;任务保留为历史记录,但不作为当前工作指令。\",\n researchTaskUnavailableNotice: \"还没有可用的研究任务来源;当前队列保持 fail-closed 空状态。\",\n researchTaskUpstreamNotice: \"研究任务索引已加载,但有 {count} 个上游提示;页面不会补造任务。\",\n researchTaskEmpty: \"暂无可展示的已验证研究任务。\",\n researchTaskMeta: \"{type} · {domain} · 创建于:{created}\",\n researchTaskLimits: \"离线研究:最多 {runs} 次 / {seconds} 秒 · P1 {p1} · P2 {p2} · P3 {p3}\",\n researchTaskNoOrder: \"固定边界:仅研究、零仓位、无订单;不进入 P4/P5/P6。\",\n healthEyebrow: \"运行保障 / 只读\",\n healthTitle: \"先看机器结论,再决定是否介入。\",\n healthSubtitle: \"健康不是实盘许可;它只告诉你是否要复核、降级或保持观察。\",\n runtimeAuthorityTitle: \"P0–P6 控制面授权状态\",\n runtimeAuthorityNotice: \"这里的 live/default_execution_mode/live_configured 是历史配置元数据,不是当前运行、订单、资金或阶段升级授权。\",\n runtimeAuthorityAsOf: \"截至:{asOf} · P1–P3 non-live 数据获取仍需独立、精确的契约;P4–P6 尚未定义。\",\n healthTotal: \"策略总数\",\n healthHealthy: \"健康\",\n healthWatch: \"观察\",\n healthReview: \"需要复核\",\n healthCritical: \"严重\",\n healthBoard: \"策略状态\",\n bootKicker: \"初始化控制台\",\n bootTitle: \"读取策略配置\",\n bootMessage: \"正在读取登录状态、账号配置和当前状态。\",\n bootStrategy: \"正在读取策略目录。\",\n bootSession: \"正在验证登录状态。\",\n bootConfig: \"正在读取账号配置和当前状态。\",\n bootTimeout: \"加载超时,已切换到公开预览(登录后可重试)。\",\n bootPublic: \"公开预览已就绪。\",\n login: \"登录\",\n logout: \"退出\",\n signedInAs: \"已登录 {login}\",\n planEyebrow: \"运行计划 / 受限变更\",\n planTitle: \"先定义边界,再提交配置。\",\n planSubtitle: \"一次只为一个平台和账号准备一个可审计的运行计划。页面只提交配置意图;运行、订单和实盘授权由独立的证据与阶段门槛决定。\",\n planScopeTitle: \"选择范围\",\n planScopeSubtitle: \"平台、账号、策略与目标环境\",\n planRuntimeTitle: \"运行保护\",\n planRuntimeSubtitle: \"停用、插件和附加层都受同一计划约束\",\n planOverlayTitle: \"策略附加层\",\n planOverlaySubtitle: \"仅使用策略已定义的默认边界\",\n planCashSubtitle: \"现金预留优先于融资;两者不能同时覆盖\",\n activePlatform: \"目标平台\",\n account: \"目标账号\",\n strategy: \"策略\",\n mode: \"运行环境\",\n live: \"实盘(需独立授权)\",\n paper: \"旧版非实盘\",\n dryRun: \"演练(不下单)\",\n liveModeUnavailable: \"当前没有同时满足策略发布、运行许可与证据门槛的实盘候选;请保持非实盘。\",\n runtimeTargetMode: \"账号运行状态\",\n runtimeSectionTitle: \"运行与插件\",\n runtimeTargetCurrent: \"沿用当前状态\",\n runtimeTargetEnabled: \"启用\",\n runtimeTargetDisabled: \"禁用\",\n runtimeTargetModeMeta: \"停用后正式运行会跳过,模拟运行和健康检查仍可用。\",\n pluginMode: \"插件状态\",\n pluginModeNone: \"不挂载旧插件\",\n pluginModeMeta: \"当前候选未绑定插件;旧插件不会自动挂载。\",\n incomeLayerMode: \"收入层状态\",\n incomeLayerSectionTitle: \"收入层\",\n incomeLayerCurrent: \"沿用当前配置\",\n incomeLayerEnabled: \"开启收入层\",\n incomeLayerDisabled: \"关闭收入层\",\n incomeLayerNotSupported: \"该策略未定义收入层\",\n incomeLayerStartUsd: \"收入层起始金额\",\n incomeLayerMaxRatio: \"收入层最高比例\",\n incomeLayerModeMeta: \"仅对已定义收入层的美股策略生效。\",\n incomeLayerDefaultMeta: \"策略默认:起始 {start},最高 {ratio}。\",\n incomeLayerAllocationMeta: \"默认分配:{allocations}。\",\n incomeLayerStartMeta: \"总资产达到该金额后启用收入层。\",\n incomeLayerRatioMeta: \"例如 0.55 表示最高 55%。\",\n optionOverlayMode: \"期权层状态\",\n optionOverlaySectionTitle: \"期权层\",\n optionOverlayCurrent: \"沿用当前配置\",\n optionOverlayEnabled: \"启用期权层\",\n optionOverlayDisabled: \"关闭期权层\",\n optionOverlayNotSupported: \"该策略未定义期权层\",\n optionOverlayModeMeta: \"启用时使用策略默认的最佳 recipe 和预算,不在这里手动调比例。\",\n optionOverlayDefaultMeta: \"{defaults}\",\n optionOverlayFamilyGrowth: \"增长\",\n optionOverlayFamilyIncome: \"收入\",\n optionOverlayBudgetRatio: \"预算 {ratio}\",\n optionOverlayRiskRatio: \"风险 {ratio}\",\n cashOnlyExecutionMode: \"允许融资\",\n cashOnlyExecutionCurrent: \"沿用当前配置\",\n cashOnlyExecutionYes: \"是\",\n cashOnlyExecutionNo: \"否\",\n cashOnlyExecutionModeMeta: \"选「否」时只按真实现金下单,不会动用 margin 购买力。\",\n cashOnlyExecutionValueYes: \"是\",\n cashOnlyExecutionValueNo: \"否\",\n currentCashOnlyExecution: \"当前允许融资\",\n pendingCashOnlyExecution: \"待提交允许融资\",\n executionCashPolicyTitle: \"资本边界\",\n executionCashPolicyNote: \"允许融资与预留现金覆盖不能同时生效;选「是」会清空预留覆盖,设预留覆盖会强制「否」。\",\n executionCashMarginBlocksReserve: \"已选允许融资;提交时会清空预留现金覆盖。\",\n executionCashReserveBlocksMargin: \"已设预留现金覆盖;提交时会强制不允许融资。\",\n qmtPlatformCashNote: \"A 股 QMT 不使用 margin / 平台预留现金;现金约束在策略参数 execution_cash_reserve_ratio 内配置。\",\n qmtDryRunOnlyNote: \"当前平台仅支持不下单演练,尚无可用的实盘执行通道。\",\n binancePlatformNote: \"Binance 平台不使用券商级收入层与期权层;相关功能由策略内部实现。\",\n invalidExecutionCashPolicyNote: \"允许融资与预留现金覆盖冲突,请只保留一种约束。\",\n dcaMode: \"定投模式\",\n dcaSectionTitle: \"定投\",\n dcaModeFixed: \"定额定投\",\n dcaModeSmart: \"智能定投\",\n dcaBaseInvestmentUsd: \"定投基准金额\",\n dcaModeMeta: \"仅定投策略可配置。\",\n dcaDefaultMeta: \"默认:{mode},基准金额 {amount}。\",\n dcaNotSupported: \"该策略不是定投策略\",\n dcaPlatformNotSupported: \"当前平台不支持定投策略\",\n currentDca: \"当前定投设置\",\n pendingDca: \"待提交定投设置\",\n dcaText: \"{mode},基准金额 {amount}\",\n minReservedCash: \"最小预留现金 ({currency})\",\n reservedCashRatio: \"预留现金比例\",\n reservedCashMode: \"预留现金策略\",\n reservePolicyCurrent: \"沿用当前配置\",\n reservePolicyNone: \"不设置平台预留现金\",\n reservePolicyRatio: \"仅按比例\",\n reservePolicyFloor: \"仅按固定金额\",\n reservePolicyMax: \"固定金额和比例取较大值\",\n reservedCashModeMeta: \"选择是否沿用、清空或覆盖平台预留现金。\",\n reservedCashNone: \"不设置\",\n reservedCashDefault: \"未配置(平台默认:0 {currency} / 0%)\",\n reservedCashMeta: \"固定金额下限,可单独设置或与比例取较大值。\",\n reservedCashRatioMeta: \"例如 0.03 表示 3%。\",\n summary: \"风险与变更摘要\",\n summaryCurrent: \"当前边界\",\n summaryPending: \"待提交变更\",\n planReadinessTitle: \"提交前核对\",\n planCheckAccount: \"账号与作用范围\",\n planCheckStrategy: \"策略与目标环境\",\n planCheckRisk: \"现金与风险边界\",\n planCheckAuthority: \"实盘执行授权\",\n planCheckWaiting: \"等待配置\",\n planCheckSelected: \"已读取\",\n planCheckValid: \"已校验\",\n planCheckFix: \"需修正\",\n planCheckNonLive: \"非实盘\",\n planCheckNoAuthority: \"未授予\",\n planAuditNote: \"提交记录会写入审计链;它不等同于订单、资金或实盘许可。\",\n copySummary: \"复制状态\",\n loginToRun: \"登录后提交计划\",\n loadingConfig: \"读取配置中\",\n configureAccounts: \"配置账号后切换\",\n runSwitch: \"提交受限变更\",\n noChanges: \"无变更\",\n readonlyNote: \"登录后才可提交受限配置。\",\n publicReadonly: \"登录后查看账号配置。\",\n loadingConfigNote: \"正在读取账号配置和当前状态。\",\n missingConfigNote: \"账号配置未加载,暂时不能执行。\",\n readyNote: \"提交的是受限配置变更;它不授予订单、资金或实盘权限。\",\n invalidStrategyNote: \"当前账号没有可执行策略,暂时不能切换。\",\n invalidReservePolicyNote: \"请为当前预留现金策略填写有效金额或比例。\",\n invalidIncomeLayerNote: \"请填写有效的收入层起始金额和最高比例。\",\n invalidOptionOverlayNote: \"当前策略未定义可启用的期权层。\",\n invalidDcaNote: \"请填写有效的定投模式和基准金额。\",\n publicOauthTitle: \"GitHub OAuth 保护\",\n publicOauthText: \"只允许白名单账号进入私有配置。\",\n publicWorkerTitle: \"Worker 端触发\",\n publicWorkerText: \"令牌保留在服务端,浏览器只提交切换意图。\",\n publicAuditTitle: \"变更可回溯\",\n publicAuditText: \"受限配置变更由 GitHub Actions 执行,便于审计和回滚。\",\n noAccount: \"没有账号选项\",\n noStrategy: \"没有支持的策略\",\n repository: \"平台仓库\",\n selectedAccount: \"账号\",\n selectedMarket: \"市场\",\n currentRuntimeTarget: \"当前账号状态\",\n pendingRuntimeTarget: \"待提交账号状态\",\n reservedCashPolicy: \"当前预留现金\",\n currentIncomeLayer: \"当前收入层\",\n pendingIncomeLayer: \"待提交收入层\",\n currentOptionOverlay: \"当前期权层\",\n pendingOptionOverlay: \"待提交期权层\",\n pendingReservedCashPolicy: \"待提交预留现金\",\n pendingMode: \"待提交模式\",\n currentPluginMode: \"当前插件范围\",\n pendingPluginMode: \"待提交插件范围\",\n unchanged: \"不变\",\n copied: \"已复制状态\",\n dispatching: \"正在触发工作流...\",\n dispatched: \"已触发工作流\",\n dispatchFailed: \"触发失败\",\n targetMeta: \"目标 {target} · 服务 {service} · 市场 {domains}\",\n strategyMeta: \"支持市场:{domains}\",\n strategyLifecycleMeta: \"当前门槛 {stage}\",\n strategyBlockedCountMeta: \"{count} 个策略未达 live 门槛\",\n strategyDefaultBlockedMeta: \"默认策略 {profile} 已阻断:{reason}\",\n usEquity: \"美股\",\n hkEquity: \"港股\",\n cnEquity: \"A股\",\n cryptoEquity: \"加密\",\n currentStrategy: \"当前策略\",\n nextStrategy: \"切换策略\",\n notRead: \"读取失败\",\n runtimeTargetOn: \"启用\",\n runtimeTargetOff: \"禁用\",\n incomeLayerDefault: \"开启,{start}起 {ratio}\",\n incomeLayerOff: \"关闭\",\n incomeLayerOn: \"开启,起始 {start},最高 {ratio}\",\n optionOverlayOff: \"关闭\",\n optionOverlayOn: \"开启\",\n optionOverlayDefaultSimple: \"开启\",\n optionOverlayDefault: \"开启,{detail}\",\n cashOnlyExecutionDefault: \"仅用现金\",\n },\n en: {\n appTitle: \"QuantStrategyLab\",\n appSubtitle: \"Verified evidence, runtime eligibility, and owner decisions.\",\n controlPlaneView: \"Attention\",\n healthView: \"Runtime guard\",\n switchView: \"Run plan\",\n controlPlaneEyebrow: \"Today / read only\",\n controlPlaneTitle: \"Only decide what needs you.\",\n controlPlaneSubtitle: \"This is not a trading screen: it shows verified follow-ups, evidence coverage, and the boundaries that need an owner decision.\",\n controlCandidateTotal: \"Candidates\",\n controlDeferred: \"Deferred now\",\n controlParked: \"Parked\",\n controlOwnerDecision: \"Needs my decision\",\n controlCandidateBoard: \"Follow-ups\",\n controlDataReady: \"Snapshot synced (not strategy readiness)\",\n controlDataStale: \"Snapshot is stale\",\n controlDataUnavailable: \"Waiting for a usable snapshot\",\n controlAttentionResearchOnly: \"Research only; no execution authority\",\n controlAttentionRequired: \"Research state needs attention\",\n controlAttentionUnavailable: \"No verified research state\",\n controlComputedAt: \"Last computed: {time}\",\n controlLoginNotice: \"Sign in to read the private global snapshot. Missing data is never invented.\",\n controlStaleNotice: \"This snapshot is outside its freshness window. The record remains visible, but is not a current conclusion.\",\n controlUnavailableNotice: \"No usable global snapshot is available yet. This page remains fail-closed and empty.\",\n controlUpstreamNotice: \"The snapshot loaded with {count} upstream notice(s); missing information is never inferred.\",\n controlAttentionNotice: \"Research needs attention ({deferred} deferred, {parked} parked, {signals} signal(s)). Snapshot delivery is not strategy validation or execution authority; the system remains no-order.\",\n controlEmptyCandidates: \"No candidate snapshot is available to display.\",\n controlNoRecommendation: \"No machine recommendation is available.\",\n controlStageMeta: \"Stage: {stage} · status: {status} · evidence: {freshness}\",\n controlNext: \"NEXT\",\n ownerDecisionTitle: \"Owner decision\",\n ownerDecisionAdminOnly: \"Only console administrators can record a decision.\",\n ownerDecisionReady: \"Current evidence is bound; choose an action to record a non-executable intent.\",\n ownerDecisionRecorded: \"Recorded: {decision} (intent only; Live remains disabled)\",\n ownerDecisionApprove: \"Approve limited-canary intent\",\n ownerDecisionPark: \"Keep parked\",\n ownerDecisionRetire: \"Retire candidate\",\n ownerDecisionConfirm: \"This records a non-executable owner intent only. It will not place orders, change funds, or enable Live. Continue?\",\n ownerDecisionSubmitting: \"Recording decision…\",\n ownerDecisionSuccess: \"Owner decision recorded; it does not enable Live automatically.\",\n ownerDecisionFailed: \"Could not record owner decision\",\n executionEvidenceBoard: \"View strategy × platform evidence on demand\",\n executionEvidenceLoginNotice: \"Sign in to read private execution-evidence snapshots. Missing sources never imply a paper or live state.\",\n executionEvidenceStaleNotice: \"This execution evidence is beyond its freshness window. It remains historical context, not a current runtime or P6 conclusion.\",\n executionEvidenceUnavailableNotice: \"No usable execution-evidence source is available yet. This list remains fail-closed and empty.\",\n executionEvidenceUpstreamNotice: \"Execution evidence loaded with {count} upstream notice(s); no platform capability or runtime authority is inferred.\",\n executionEvidenceEmpty: \"No strategy × platform execution evidence is available to display.\",\n executionEvidenceMeta: \"{platform} · current lane: {environment} · source: {source}\",\n executionEvidenceDetail: \"strategy: {strategy} · data: {data} · execution: {execution} · shadow: {shadow} · paper: {paper}\",\n executionEvidenceNoOrder: \"Fixed boundary: read-only evidence; no account, order, funds, or P6 live authority.\",\n executionEvidenceNext: \"NEXT\",\n runtimeTargetLifecycleBoard: \"View platform runtime status on demand\",\n runtimeTargetLifecycleLoginNotice: \"Sign in to read private platform runtime status. This page cannot change target state or place orders.\",\n runtimeTargetLifecycleStaleNotice: \"Platform runtime status is outside its freshness window. Check the monitoring path first.\",\n runtimeTargetLifecycleUnavailableNotice: \"No usable platform runtime status is available. Missing data never implies a disabled or healthy target.\",\n runtimeTargetLifecycleUpstreamNotice: \"Platform runtime status loaded with {count} upstream notice(s); review them first.\",\n runtimeTargetLifecycleEmpty: \"No platform runtime status is available to display.\",\n runtimeTargetLifecycleMeta: \"{platform} · {state} · lane: {mode}\",\n runtimeTargetLifecycleDetail: \"runtime guard: {guard} · execution heartbeat: {heartbeat}\",\n runtimeTargetLifecycleNoOrder: \"Fixed boundary: read-only status; it cannot enable a target, change a strategy, or place an order.\",\n runtimeTargetLifecycleNext: \"CURRENT DISPOSITION\",\n runtimeTargetLifecycleStateEnabled: \"enabled\",\n runtimeTargetLifecycleStateDisabled: \"disabled\",\n runtimeTargetLifecycleCheckPass: \"pass\",\n runtimeTargetLifecycleCheckAttention: \"attention\",\n runtimeTargetLifecycleCheckNotDue: \"not due\",\n runtimeTargetLifecycleCheckNotApplicable: \"not applicable\",\n runtimeTargetLifecycleCheckUnavailable: \"unavailable\",\n runtimeTargetLifecycleDispositionEnabled: \"continue monitoring\",\n runtimeTargetLifecycleDispositionDisabled: \"disabled validation\",\n runtimeTargetLifecycleDispositionParked: \"parked\",\n runtimeTargetLifecycleReasonNone: \"monitoring normal\",\n runtimeTargetLifecycleReasonDisabled: \"intentionally disabled; validation continues\",\n runtimeTargetLifecycleReasonRuntimeGuard: \"runtime guard needs review\",\n runtimeTargetLifecycleReasonHeartbeat: \"execution heartbeat needs review\",\n runtimeTargetLifecycleReasonUnavailable: \"monitoring data unavailable\",\n m0ResearchBoard: \"View M0 research observations on demand\",\n m0ResearchLoginNotice: \"Sign in to read the private M0 research ledger. A missing ledger never implies a research conclusion.\",\n m0ResearchStaleNotice: \"These M0 research observations are beyond their validity window and remain historical context only.\",\n m0ResearchUnavailableNotice: \"No usable M0 research ledger is available yet. This view remains fail-closed and empty.\",\n m0ResearchUpstreamNotice: \"The M0 research ledger loaded with {count} upstream notice(s); no research conclusion is invented.\",\n m0ResearchEmpty: \"No verified M0 research observation is available to display.\",\n m0ResearchMore: \"Showing the first {count} research observations; the remainder stays in the read-only ledger.\",\n m0ResearchMeta: \"{kind} · viewed {viewed}\",\n m0ResearchStateFreshness: \"State: {state} · freshness: {freshness}\",\n m0ResearchHorizons: \"Primary horizon: {primary} · suitable horizons: {suitable}\",\n m0ResearchEvidence: \"Confidence: {confidence} · style: {style} · source digest: {digest}\",\n m0ResearchConsistency: \"Current horizon conflict: {conflict} · historical stale drift: {drift}\",\n m0ResearchNoOrder: \"Fixed boundary: research only, no order, and no runtime action.\",\n adaptiveSelectionBoard: \"View M1 Shadow suggestions on demand\",\n adaptiveSelectionLoginNotice: \"Sign in to read private Shadow suggestions. Missing sources never imply a strategy or order.\",\n adaptiveSelectionStaleNotice: \"This Shadow suggestion is beyond its freshness window and remains historical context only.\",\n adaptiveSelectionUnavailableNotice: \"No usable M1 Shadow suggestion is available yet. This view remains fail-closed and empty.\",\n adaptiveSelectionUpstreamNotice: \"Shadow suggestions include {count} upstream notice(s); no strategy, platform, or order is inferred.\",\n adaptiveSelectionEmpty: \"No Shadow suggestion is available to display.\",\n adaptiveSelectionMeta: \"{source} · {domain} · market as of {asOf}\",\n adaptiveSelectionRecommended: \"Observe suggestion\",\n adaptiveSelectionNoCandidate: \"No eligible Shadow candidate\",\n adaptiveSelectionReason: \"Reasons: {reasons}\",\n adaptiveSelectionNoOrder: \"Fixed boundary: human-readable only, zero proposed weight, no strategy, platform, funds, runtime, or order change.\",\n researchTaskBoard: \"View research queue on demand\",\n researchTaskLoginNotice: \"Sign in to read the private research task index. Missing source snapshots never imply a task.\",\n researchTaskStaleNotice: \"The research task source is beyond its freshness window. It remains historical context, not a current instruction.\",\n researchTaskUnavailableNotice: \"No research task source is available yet. The queue remains fail-closed and empty.\",\n researchTaskUpstreamNotice: \"The research task index loaded with {count} upstream notices; no task is inferred from missing data.\",\n researchTaskEmpty: \"No verified research task is available to display.\",\n researchTaskMeta: \"{type} · {domain} · created {created}\",\n researchTaskLimits: \"Offline research: up to {runs} run(s) / {seconds}s · P1 {p1} · P2 {p2} · P3 {p3}\",\n researchTaskNoOrder: \"Fixed boundary: research only, zero size, no order; never P4/P5/P6.\",\n healthEyebrow: \"Runtime guard / read only\",\n healthTitle: \"Read the machine conclusion before deciding to intervene.\",\n healthSubtitle: \"Health is not live approval. It only tells you whether to review, reduce, or keep watching.\",\n runtimeAuthorityTitle: \"P0–P6 control-plane authority\",\n runtimeAuthorityNotice: \"live/default_execution_mode/live_configured are legacy configuration metadata, not authorization for current runtime, orders, funds, or stage changes.\",\n runtimeAuthorityAsOf: \"As of {asOf} · P1–P3 non-live data acquisition still needs its own precise contract; P4–P6 are undefined.\",\n healthTotal: \"Strategies\",\n healthHealthy: \"Healthy\",\n healthWatch: \"Watch\",\n healthReview: \"Review\",\n healthCritical: \"Critical\",\n healthBoard: \"Strategy status\",\n bootKicker: \"Starting console\",\n bootTitle: \"Loading strategy config\",\n bootMessage: \"Reading session, account config, and current state.\",\n bootStrategy: \"Reading strategy catalog.\",\n bootSession: \"Checking sign-in status.\",\n bootConfig: \"Reading account config and current state.\",\n bootTimeout: \"Loading timed out; switched to public preview. Retry after signing in.\",\n bootPublic: \"Public preview is ready.\",\n login: \"Sign in\",\n logout: \"Sign out\",\n signedInAs: \"Signed in as {login}\",\n planEyebrow: \"Run plan / bounded change\",\n planTitle: \"Set the boundary before submitting configuration.\",\n planSubtitle: \"Prepare one auditable plan for one platform and account. This page submits configuration intent only; runtime, order, and live authority remain governed by independent evidence and lifecycle gates.\",\n planScopeTitle: \"Choose scope\",\n planScopeSubtitle: \"Platform, account, strategy, and target environment\",\n planRuntimeTitle: \"Runtime protection\",\n planRuntimeSubtitle: \"Disable state, plugins, and overlays share one plan boundary\",\n planOverlayTitle: \"Strategy overlays\",\n planOverlaySubtitle: \"Only strategy-defined default guardrails are available\",\n planCashSubtitle: \"Cash reserve takes precedence over margin; they cannot override together\",\n activePlatform: \"Target platform\",\n account: \"Target account\",\n strategy: \"Strategy\",\n mode: \"Target environment\",\n live: \"Live (independent approval required)\",\n paper: \"Legacy non-live\",\n dryRun: \"Dry run (no orders)\",\n liveModeUnavailable: \"No candidate currently meets the strategy-release, runtime-eligibility, and evidence gates for Live. Keep this plan non-live.\",\n runtimeTargetMode: \"Account status\",\n runtimeSectionTitle: \"Runtime and plugins\",\n runtimeTargetCurrent: \"Keep current status\",\n runtimeTargetEnabled: \"Enabled\",\n runtimeTargetDisabled: \"Disabled\",\n runtimeTargetModeMeta: \"Disabled accounts skip live runs; dry runs and health checks still work.\",\n pluginMode: \"Plugin status\",\n pluginModeNone: \"Do not mount legacy plugins\",\n pluginModeMeta: \"The current candidate has no bound plugin; legacy plugins are not auto-mounted.\",\n incomeLayerMode: \"Income layer\",\n incomeLayerSectionTitle: \"Income layer\",\n incomeLayerCurrent: \"Keep current config\",\n incomeLayerEnabled: \"Enable income layer\",\n incomeLayerDisabled: \"Disable income layer\",\n incomeLayerNotSupported: \"No income layer for this strategy\",\n incomeLayerStartUsd: \"Income layer start amount\",\n incomeLayerMaxRatio: \"Income layer max ratio\",\n incomeLayerModeMeta: \"Only applies to US equity strategies with an income layer.\",\n incomeLayerDefaultMeta: \"Strategy default: starts at {start}, max {ratio}.\",\n incomeLayerAllocationMeta: \"Default allocation: {allocations}.\",\n incomeLayerStartMeta: \"Income layer activates after total assets reach this amount.\",\n incomeLayerRatioMeta: \"Use 0.55 for a 55% cap.\",\n optionOverlayMode: \"Option layer\",\n optionOverlaySectionTitle: \"Option layer\",\n optionOverlayCurrent: \"Keep current config\",\n optionOverlayEnabled: \"Enable option layer\",\n optionOverlayDisabled: \"Disable option layer\",\n optionOverlayNotSupported: \"No option layer for this strategy\",\n optionOverlayModeMeta: \"Enabled mode uses the strategy's default recipe and budget; ratios are not edited here.\",\n optionOverlayDefaultMeta: \"{defaults}\",\n optionOverlayFamilyGrowth: \"Growth\",\n optionOverlayFamilyIncome: \"Income\",\n optionOverlayBudgetRatio: \"budget {ratio}\",\n optionOverlayRiskRatio: \"risk {ratio}\",\n cashOnlyExecutionMode: \"Allow margin\",\n cashOnlyExecutionCurrent: \"Keep current config\",\n cashOnlyExecutionYes: \"Yes\",\n cashOnlyExecutionNo: \"No\",\n cashOnlyExecutionModeMeta: \"Choose No to use available cash only and avoid margin buying power.\",\n cashOnlyExecutionValueYes: \"Yes\",\n cashOnlyExecutionValueNo: \"No\",\n currentCashOnlyExecution: \"Current allow margin\",\n pendingCashOnlyExecution: \"Pending allow margin\",\n executionCashPolicyTitle: \"Capital boundary\",\n executionCashPolicyNote: \"Allow margin and reserve-cash overrides cannot both apply. Yes clears reserve overrides; reserve overrides force No.\",\n executionCashMarginBlocksReserve: \"Allow margin is selected; submitting will clear reserve-cash overrides.\",\n executionCashReserveBlocksMargin: \"Reserve-cash override is active; submitting will force allow margin to No.\",\n qmtPlatformCashNote: \"QMT A-share does not use margin or platform reserve cash; cash constraints live in strategy execution_cash_reserve_ratio.\",\n qmtDryRunOnlyNote: \"This platform supports no-order dry runs only; no live execution route is configured.\",\n binancePlatformNote: \"Binance does not use broker-level income/option layers; features are implemented inside strategies.\",\n invalidExecutionCashPolicyNote: \"Allow margin and reserve-cash overrides conflict. Keep only one constraint.\",\n dcaMode: \"DCA mode\",\n dcaSectionTitle: \"DCA\",\n dcaModeFixed: \"Fixed DCA\",\n dcaModeSmart: \"Smart DCA\",\n dcaBaseInvestmentUsd: \"Base DCA amount\",\n dcaModeMeta: \"Only DCA strategies can use this.\",\n dcaDefaultMeta: \"Default: {mode}, base amount {amount}.\",\n dcaNotSupported: \"This is not a DCA strategy\",\n dcaPlatformNotSupported: \"DCA not supported on this platform\",\n currentDca: \"Current DCA settings\",\n pendingDca: \"Pending DCA settings\",\n dcaText: \"{mode}, base amount {amount}\",\n minReservedCash: \"Minimum reserved cash ({currency})\",\n reservedCashRatio: \"Reserved cash ratio\",\n reservedCashMode: \"Reserved cash policy\",\n reservePolicyCurrent: \"Keep current config\",\n reservePolicyNone: \"No platform reserve\",\n reservePolicyRatio: \"Ratio only\",\n reservePolicyFloor: \"Fixed amount only\",\n reservePolicyMax: \"Max of amount and ratio\",\n reservedCashModeMeta: \"Choose whether to keep, clear, or override platform reserved cash.\",\n reservedCashNone: \"None\",\n reservedCashDefault: \"Not configured (platform default: 0 {currency} / 0%)\",\n reservedCashMeta: \"Fixed cash floor. Use alone or with a ratio.\",\n reservedCashRatioMeta: \"Use 0.03 for 3%.\",\n summary: \"Risk and change summary\",\n summaryCurrent: \"Current boundary\",\n summaryPending: \"Pending change\",\n planReadinessTitle: \"Before submission\",\n planCheckAccount: \"Account and scope\",\n planCheckStrategy: \"Strategy and environment\",\n planCheckRisk: \"Cash and risk boundary\",\n planCheckAuthority: \"Live execution authority\",\n planCheckWaiting: \"Waiting\",\n planCheckSelected: \"Read\",\n planCheckValid: \"Checked\",\n planCheckFix: \"Needs correction\",\n planCheckNonLive: \"Non-live\",\n planCheckNoAuthority: \"Not granted\",\n planAuditNote: \"A submission creates an audit record; it is not an order, funding, or live permission.\",\n copySummary: \"Copy state\",\n loginToRun: \"Sign in to submit a plan\",\n loadingConfig: \"Loading config\",\n configureAccounts: \"Configure accounts\",\n runSwitch: \"Submit bounded change\",\n noChanges: \"No changes\",\n readonlyNote: \"Sign in to submit a bounded configuration.\",\n publicReadonly: \"Sign in to view account config.\",\n loadingConfigNote: \"Reading account config and current state.\",\n missingConfigNote: \"Account config is not loaded, so switching is disabled.\",\n readyNote: \"This submits a bounded configuration change; it grants no order, funding, or live authority.\",\n invalidStrategyNote: \"This account has no runnable strategy, so switching is disabled.\",\n invalidReservePolicyNote: \"Enter a valid amount or ratio for the selected reserved-cash policy.\",\n invalidIncomeLayerNote: \"Enter a valid income layer start amount and max ratio.\",\n invalidOptionOverlayNote: \"This strategy does not define an option layer to enable.\",\n invalidDcaNote: \"Enter a valid DCA mode and base amount.\",\n publicOauthTitle: \"Protected by GitHub OAuth\",\n publicOauthText: \"Only allowlisted accounts can open private config.\",\n publicWorkerTitle: \"Worker-side dispatch\",\n publicWorkerText: \"Tokens stay server-side; the browser submits intent only.\",\n publicAuditTitle: \"Traceable changes\",\n publicAuditText: \"Bounded configuration changes run through GitHub Actions for audit and rollback.\",\n noAccount: \"No accounts\",\n noStrategy: \"No supported strategies\",\n repository: \"Repository\",\n selectedAccount: \"Account\",\n selectedMarket: \"Market\",\n currentRuntimeTarget: \"Current account status\",\n pendingRuntimeTarget: \"Pending account status\",\n reservedCashPolicy: \"Current reserved cash\",\n currentIncomeLayer: \"Current income layer\",\n pendingIncomeLayer: \"Pending income layer\",\n currentOptionOverlay: \"Current option layer\",\n pendingOptionOverlay: \"Pending option layer\",\n pendingReservedCashPolicy: \"Pending reserved cash\",\n pendingMode: \"Pending mode\",\n currentPluginMode: \"Current plugin scope\",\n pendingPluginMode: \"Pending plugin scope\",\n unchanged: \"Unchanged\",\n copied: \"State copied\",\n dispatching: \"Dispatching workflow...\",\n dispatched: \"Workflow dispatched\",\n dispatchFailed: \"Dispatch failed\",\n targetMeta: \"target {target} · service {service} · market {domains}\",\n strategyMeta: \"Markets: {domains}\",\n strategyLifecycleMeta: \"current gate {stage}\",\n strategyBlockedCountMeta: \"{count} strategies are blocked from live\",\n strategyDefaultBlockedMeta: \"Default strategy {profile} is blocked: {reason}\",\n usEquity: \"US equity\",\n hkEquity: \"HK equity\",\n cnEquity: \"CN A-share\",\n cryptoEquity: \"Crypto\",\n currentStrategy: \"Current strategy\",\n nextStrategy: \"Switch strategy\",\n notRead: \"Not read\",\n runtimeTargetOn: \"Enabled\",\n runtimeTargetOff: \"Disabled\",\n incomeLayerDefault: \"Enabled, {start} start, {ratio} max\",\n incomeLayerOff: \"Disabled\",\n incomeLayerOn: \"Enabled, starts at {start}, max {ratio}\",\n optionOverlayOff: \"Disabled\",\n optionOverlayOn: \"Enabled\",\n optionOverlayDefaultSimple: \"Strategy default: enabled\",\n optionOverlayDefault: \"Enabled, {detail}\",\n cashOnlyExecutionDefault: \"Cash only\",\n },\n };\n\n const storedLang = localStorage.getItem(\"qsl-switch-lang\");\n const initialLang = storedLang === \"zh\" || storedLang === \"en\"\n ? storedLang\n : ((navigator.language || \"\").toLowerCase().startsWith(\"zh\") ? \"zh\" : \"en\");\n const clone = (value) => JSON.parse(JSON.stringify(value));\n const defaultReserveForm = () => ({\n reservePolicyMode: \"current\",\n minReservedCashUsd: \"\",\n reservedCashRatio: \"\",\n reservedCashTouched: false,\n incomeLayerMode: \"current\",\n incomeLayerStartUsd: \"\",\n incomeLayerMaxRatio: \"\",\n incomeLayerTouched: false,\n optionOverlayMode: \"current\",\n optionOverlayTouched: false,\n cashOnlyExecutionMode: \"current\",\n cashOnlyExecutionTouched: false,\n runtimeTargetMode: \"current\",\n runtimeTargetTouched: false,\n dcaMode: \"fixed\",\n dcaBaseInvestmentUsd: \"\",\n dcaTouched: false,\n strategyTouched: false,\n });\n\n const state = {\n selected: \"longbridge\",\n lang: initialLang,\n view: \"control\",\n appReady: false,\n bootMessageKey: \"bootMessage\",\n auth: { available: false, allowed: false, admin: false, login: null },\n accountOptions: clone(defaultAccountOptions),\n currentStrategies: {},\n health: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { strategy_count: 0, healthy: 0, watch: 0, review: 0, critical: 0 },\n strategies: [],\n },\n filter: \"all\",\n },\n controlPlane: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { candidate_count: 0, deferred: 0, parked: 0, owner_decision_required: 0 },\n attention: { status: \"unavailable\", reason_codes: [\"control_plane_source_unavailable\"] },\n candidates: [],\n policy: { p4_p5_automation: \"not_configured\", p6_owner_decision_required: true },\n errors: [],\n },\n },\n ownerDecisions: {\n data_status: \"unavailable\",\n candidates: [],\n errors: [],\n },\n m0Research: {\n payload: {\n data_status: \"unavailable\",\n viewed_at: null,\n summary: {\n subject_count: 0,\n observation_count: 0,\n fresh_observation_count: 0,\n stale_observation_count: 0,\n unknown_observation_count: 0,\n horizon_conflict_count: 0,\n historical_stale_horizon_drift_count: 0,\n },\n subjects: [],\n policy: { authority: \"research_only\", no_order: true, permitted_next_step: \"research_validation_only\" },\n errors: [],\n },\n },\n adaptiveSelection: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { source_count: 0, decision_count: 0, candidate_count: 0, recommended_count: 0, rejected_candidate_count: 0 },\n selections: [],\n policy: { authority: \"shadow_only\", no_order: true, execution_authority_granted: false },\n errors: [],\n },\n },\n executionEvidence: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { deployment_count: 0, autonomous_shadow: 0, autonomous_paper: 0, owner_canary_decision: 0, parked: 0 },\n deployments: [],\n policy: { execution_evidence_read_only: true, p6_owner_decision_required: true, limited_live_canary_active: false },\n errors: [],\n },\n },\n runtimeTargetLifecycle: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { target_count: 0, enabled: 0, disabled: 0, attention: 0 },\n targets: [],\n policy: { lifecycle_status_read_only: true, no_order: true },\n errors: [],\n },\n },\n researchTasks: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { task_count: 0 },\n tasks: [],\n policy: { research_only: true, no_order: true, size_zero_required: true, p4_p5_p6_authorized: false },\n errors: [],\n },\n },\n configSource: \"default\",\n repositories: clone(defaultRepositories),\n forms: {\n longbridge: { accountKey: \"preview\", strategy: \"\", executionMode: \"live\", pluginMode: \"none\", ...defaultReserveForm() },\n ibkr: { accountKey: \"preview\", strategy: \"\", executionMode: \"live\", pluginMode: \"none\", ...defaultReserveForm() },\n schwab: { accountKey: \"preview\", strategy: \"\", executionMode: \"live\", pluginMode: \"none\", ...defaultReserveForm() },\n firstrade: { accountKey: \"preview\", strategy: \"\", executionMode: \"live\", pluginMode: \"none\", ...defaultReserveForm() },\n qmt: { accountKey: \"preview\", strategy: \"\", executionMode: \"paper\", pluginMode: \"none\", ...defaultReserveForm() },\n binance: { accountKey: \"preview\", strategy: \"\", executionMode: \"live\", pluginMode: \"none\" },\n },\n };\n\n const el = (id) => document.getElementById(id);\n const t = (key) => copy[state.lang][key] || copy.en[key] || key;\n let toastTimer = null;\n\n function showToast(message, { duration = 4000 } = {}) {\n const node = el(\"toast\");\n if (toastTimer) {\n window.clearTimeout(toastTimer);\n toastTimer = null;\n }\n node.textContent = message || \"\";\n if (message && duration > 0) {\n toastTimer = window.setTimeout(() => {\n node.textContent = \"\";\n toastTimer = null;\n }, duration);\n }\n }\n\n async function fetchWithTimeout(url, init = {}, timeoutMs = APP_BOOT_TIMEOUT_MS) {\n const controller = new AbortController();\n const timeoutId = window.setTimeout(() => controller.abort(), timeoutMs);\n try {\n return await fetch(url, { ...init, signal: controller.signal });\n } catch (error) {\n if (error?.name === \"AbortError\") {\n throw new Error(\"request timeout\");\n }\n throw error;\n } finally {\n window.clearTimeout(timeoutId);\n }\n }\n\n async function requestJson(url, init = {}, timeoutMs = APP_BOOT_TIMEOUT_MS) {\n const response = await fetchWithTimeout(url, { ...init, cache: \"no-store\" }, timeoutMs);\n if (!response.ok) throw new Error(\"request failed\");\n return response.json();\n }\n\n function isRequestTimeoutError(error) {\n return String(error?.message || \"\").toLowerCase() === \"request timeout\";\n }\n\n function optionsFor(platform) {\n return state.accountOptions[platform] && state.accountOptions[platform].length\n ? state.accountOptions[platform]\n : defaultAccountOptions[platform];\n }\n\n function selectedAccount(platform = state.selected) {\n const options = optionsFor(platform);\n const form = state.forms[platform];\n return options.find((option) => option.key === form.accountKey) || options[0];\n }\n\n function hasPrivateConfig() {\n return Boolean(state.auth.allowed && state.configSource === \"private\");\n }\n\n function cleanStrategyProfile(value) {\n const profile = String(value || \"\").trim();\n return /^[a-z0-9._=-]{1,120}$/.test(profile) ? profile : \"\";\n }\n\n function cleanStrategyDomain(value) {\n const domain = String(value || \"\").trim();\n return strategyDomains.includes(domain) ? domain : \"\";\n }\n\n function domainLabel(domain) {\n const entry = domainLabels[domain];\n if (entry) return state.lang === \"zh\" ? entry.zh : entry.en;\n return domain;\n }\n\n function platformSupportsMarginPolicy(platform = state.selected) {\n return platformConfig[platform]?.margin_policy ?? true;\n }\n\n function platformSupportsReservedCashPolicy(platform = state.selected) {\n return platformConfig[platform]?.reserved_cash ?? true;\n }\n\n function platformDryRunOnly(platform = state.selected) {\n return platformConfig[platform]?.dry_run_only ?? false;\n }\n\n function supportedExecutionModesForPlatform(platform = state.selected) {\n const modes = platformConfig[platform]?.supported_execution_modes;\n return Array.isArray(modes) ? modes.filter((mode) => mode === \"live\" || mode === \"dry_run\") : [];\n }\n\n function allowMarginExplicitlySelected(form) {\n return normalizeCashOnlyExecutionMode(form?.cashOnlyExecutionMode) === \"disabled\";\n }\n\n function reserveCashOverrideActive(form) {\n const mode = normalizeReservePolicyMode(form?.reservePolicyMode);\n return mode === \"ratio\" || mode === \"floor\" || mode === \"max\";\n }\n\n function executionCashPolicyConflict(form) {\n return allowMarginExplicitlySelected(form) && reserveCashOverrideActive(form);\n }\n\n function reconcileExecutionCashPolicy(form, changed) {\n if (!form) return;\n if (changed === \"margin\" && allowMarginExplicitlySelected(form)) {\n if (form.reservePolicyMode !== \"none\" && (form.minReservedCashUsd || form.reservedCashRatio)) {\n form._prevReserve = {\n mode: form.reservePolicyMode,\n floor: form.minReservedCashUsd,\n ratio: form.reservedCashRatio,\n };\n }\n form.reservePolicyMode = \"none\";\n form.reservedCashTouched = true;\n } else if (changed === \"reserve\" && reserveCashOverrideActive(form)) {\n form.cashOnlyExecutionMode = \"enabled\";\n form.cashOnlyExecutionTouched = true;\n }\n }\n\n function restoreReserveAfterMarginDisabled(form) {\n if (!form || allowMarginExplicitlySelected(form) || !form._prevReserve) return;\n form.reservePolicyMode = form._prevReserve.mode;\n form.minReservedCashUsd = form._prevReserve.floor;\n form.reservedCashRatio = form._prevReserve.ratio;\n form.reservedCashTouched = true;\n delete form._prevReserve;\n }\n\n function strategyDomain(profile) {\n return strategyCatalog[profile]?.domain || \"\";\n }\n\n function selectedCashCurrency(platform = state.selected, account = selectedAccount(platform)) {\n const configured = String(account?.cash_currency || \"\").trim().toUpperCase();\n if (configured === \"USD\" || configured === \"HKD\" || configured === \"CNY\") return configured;\n const domain = strategyDomain(state.forms[platform]?.strategy);\n if (domain === \"hk_equity\") return \"HKD\";\n if (domain === \"cn_equity\") return \"CNY\";\n return \"USD\";\n }\n\n function applyStrategyProfiles(rawProfiles) {\n const profiles = Array.isArray(rawProfiles) && rawProfiles.length\n ? rawProfiles\n : defaultStrategyProfiles;\n const nextOptions = [];\n const nextLabels = {};\n const nextCatalog = {};\n const nextIncomeLayerDefaults = {};\n const nextOptionOverlayDefaults = {};\n for (const item of profiles) {\n const profile = cleanStrategyProfile(item?.profile || item?.strategy_profile);\n if (!profile || nextOptions.includes(profile)) continue;\n const domain = cleanStrategyDomain(item?.domain || \"us_equity\");\n if (!domain) continue;\n nextOptions.push(profile);\n nextLabels[profile] = strategyLabelSet(profile, item);\n nextCatalog[profile] = {\n profile,\n label: nextLabels[profile].en || nextLabels[profile].zh || profile,\n label_en: nextLabels[profile].en || \"\",\n label_zh: nextLabels[profile].zh || \"\",\n domain,\n runtime_enabled: cleanOptionalBoolean(item?.runtime_enabled ?? false) === true,\n };\n const lifecycleStage = normalizeLifecycleStage(item?.lifecycle_stage ?? item?.lifecycleStage);\n if (lifecycleStage) nextCatalog[profile].lifecycle_stage = lifecycleStage;\n const allowedExecutionModes = normalizeAllowedExecutionModes(item?.allowed_execution_modes);\n if (allowedExecutionModes.length) nextCatalog[profile].allowed_execution_modes = allowedExecutionModes;\n const canSwitchLive = cleanOptionalBoolean(item?.can_switch_live);\n if (canSwitchLive !== null) nextCatalog[profile].can_switch_live = canSwitchLive;\n const blockedLiveReason = cleanDisplayText(item?.blocked_live_reason);\n if (blockedLiveReason) nextCatalog[profile].blocked_live_reason = blockedLiveReason;\n const latestEvidenceStatus = cleanDisplayText(item?.latest_evidence_status);\n if (latestEvidenceStatus) nextCatalog[profile].latest_evidence_status = latestEvidenceStatus;\n const pluginGateStatus = cleanDisplayText(item?.plugin_gate_status);\n if (pluginGateStatus) nextCatalog[profile].plugin_gate_status = pluginGateStatus;\n const dcaDefaults = dcaProfileDefaults[profile] || null;\n if (dcaDefaults || item?.dca_enabled === true) {\n nextCatalog[profile].dca_enabled = true;\n nextCatalog[profile].dca_default_mode = normalizeDcaMode(\n item?.dca_default_mode || item?.default_dca_mode || dcaDefaults?.defaultMode || \"fixed\",\n );\n nextCatalog[profile].dca_default_base_investment_usd = cleanDisplayPositiveNumber(\n item?.dca_default_base_investment_usd ||\n item?.default_dca_base_investment_usd ||\n dcaDefaults?.defaultBaseInvestmentUsd ||\n \"1000\",\n ) || \"1000\";\n }\n const profileIncomeDefaults = incomeLayerDefaultsFromProfileItem(item);\n const incomeDefaults = profileIncomeDefaults === false\n ? null\n : (profileIncomeDefaults || fallbackIncomeLayerDefaults[profile] || null);\n if (incomeDefaults) {\n nextIncomeLayerDefaults[profile] = incomeDefaults;\n nextCatalog[profile].income_layer_enabled = true;\n nextCatalog[profile].income_layer_start_usd = String(incomeDefaults.startUsd);\n nextCatalog[profile].income_layer_max_ratio = incomeDefaults.maxRatio;\n nextCatalog[profile].income_layer_allocations = incomeDefaults.allocations;\n }\n const profileOptionDefaults = optionOverlayDefaultsFromProfileItem(item);\n const optionDefaults = profileOptionDefaults === false\n ? null\n : (profileOptionDefaults || fallbackOptionOverlayDefaults[profile] || null);\n if (optionDefaults) {\n nextOptionOverlayDefaults[profile] = optionDefaults;\n nextCatalog[profile].option_overlay_enabled = true;\n nextCatalog[profile].option_overlay_live_gate = optionDefaults.liveGate || \"\";\n nextCatalog[profile].option_overlay_live_status = optionDefaults.liveStatus || \"\";\n }\n }\n if (!nextOptions.length && profiles !== defaultStrategyProfiles) return applyStrategyProfiles(defaultStrategyProfiles);\n strategyOptions = nextOptions;\n strategyLabels = nextLabels;\n strategyCatalog = nextCatalog;\n incomeLayerDefaults = nextIncomeLayerDefaults;\n optionOverlayDefaults = nextOptionOverlayDefaults;\n }\n\n function incomeLayerDefaultsFromProfileItem(item) {\n const enabled = cleanOptionalBoolean(item?.income_layer_enabled);\n const hasConfig = enabled !== null ||\n item?.income_layer_start_usd !== undefined ||\n item?.income_layer_max_ratio !== undefined ||\n item?.income_layer_allocations !== undefined;\n if (!hasConfig) return null;\n if (enabled === false) return false;\n const startUsd = cleanDisplayNumber(item?.income_layer_start_usd);\n const maxRatio = cleanDisplayRatio(item?.income_layer_max_ratio);\n const allocations = cleanIncomeLayerAllocations(item?.income_layer_allocations);\n if (!startUsd || !maxRatio || !allocations) return null;\n return { startUsd, maxRatio, allocations };\n }\n\n function cleanIncomeLayerAllocations(value) {\n if (!value || Array.isArray(value) || typeof value !== \"object\") return null;\n const allocations = {};\n let total = 0;\n for (const [rawSymbol, rawRatio] of Object.entries(value)) {\n const symbol = String(rawSymbol || \"\").trim().toUpperCase();\n const ratio = cleanDisplayPositiveNumber(rawRatio);\n if (!/^[A-Z0-9.-]{1,12}$/.test(symbol) || !ratio) continue;\n allocations[symbol] = Number(ratio);\n total += Number(ratio);\n }\n return total > 0 && Object.keys(allocations).length ? allocations : null;\n }\n\n function optionOverlayDefaultsFromProfileItem(item) {\n const enabled = cleanOptionalBoolean(item?.option_overlay_enabled);\n const hasConfig = enabled !== null ||\n item?.option_growth_overlay_enabled !== undefined ||\n item?.option_income_overlay_enabled !== undefined ||\n item?.option_overlay_live_gate !== undefined ||\n item?.option_overlay_live_status !== undefined;\n if (!hasConfig) return null;\n if (enabled === false) return false;\n const families = [\n optionOverlayFamilyDefaultsFromProfileItem(item, \"growth\"),\n optionOverlayFamilyDefaultsFromProfileItem(item, \"income\"),\n ].filter(Boolean);\n if (!families.length) return null;\n return {\n liveGate: String(item?.option_overlay_live_gate || \"promotion_required\"),\n liveStatus: String(item?.option_overlay_live_status || \"research_only\"),\n families,\n };\n }\n\n function optionOverlayFamilyDefaultsFromProfileItem(item, family) {\n const prefix = `option_${family}_overlay`;\n const enabled = cleanOptionalBoolean(item?.[`${prefix}_enabled`]);\n if (enabled !== true) return null;\n const recipe = cleanStrategyProfile(item?.[`${prefix}_recipe`]);\n const startUsd = cleanDisplayNumber(item?.[`${prefix}_start_usd`]);\n const ratioField = family === \"growth\"\n ? \"option_growth_overlay_nav_budget_ratio\"\n : \"option_income_overlay_nav_risk_ratio\";\n const ratio = cleanDisplayRatio(item?.[ratioField]);\n if (!recipe || !startUsd || !ratio) return null;\n return {\n family,\n recipe,\n startUsd,\n ratio,\n ratioKind: family === \"growth\" ? \"budget\" : \"risk\",\n };\n }\n\n function supportedDomainsForAccount(platform, account) {\n if (Array.isArray(account?.supported_domains) && account.supported_domains.length) {\n const cleaned = account.supported_domains.map(cleanStrategyDomain).filter(Boolean);\n if (cleaned.length) return [...new Set(cleaned)];\n }\n return inferSupportedDomains(platform, account);\n }\n\n function inferSupportedDomains(platform, account) {\n void account;\n if (platform === \"qmt\") return [\"cn_equity\"];\n if (platform === \"longbridge\" || platform === \"ibkr\") return [\"us_equity\", \"hk_equity\"];\n return [\"us_equity\"];\n }\n\n function supportedDomainLabel(platform, account) {\n return supportedDomainsForAccount(platform, account).map(domainLabel).join(\" / \");\n }\n\n function platformSupportsDca(platform = state.selected) {\n return platformConfig[platform]?.dca ?? false;\n }\n\n function strategyCatalogEntry(profile) {\n return strategyCatalog[cleanStrategyProfile(profile)] || {};\n }\n\n function normalizeLifecycleStage(value) {\n const text = String(value || \"\").trim().toLowerCase();\n if (!text || text.length > 40 || !/^[a-z0-9._-]+$/.test(text)) return \"\";\n if ([\"research_backtest_only\", \"ai_monitored_candidate\"].includes(text)) return \"research_active\";\n if (text === \"shadow_candidate\") return \"shadow_active\";\n if (text === \"runtime_enabled\") return \"live_candidate\";\n return text;\n }\n\n function normalizeAllowedExecutionModes(value) {\n const items = Array.isArray(value)\n ? value\n : String(value || \"\").split(/[,\\s/|]+/);\n const modes = [];\n for (const item of items) {\n const mode = String(item || \"\").trim().toLowerCase();\n if (!mode || mode.length > 32 || !/^[a-z0-9._-]+$/.test(mode)) continue;\n if (!modes.includes(mode)) modes.push(mode);\n }\n return modes;\n }\n\n function cleanDisplayText(value) {\n const text = String(value ?? \"\").trim();\n if (!text || text.length > 120 || /[<>]/.test(text)) return \"\";\n return text;\n }\n\n function strategyCanSwitchLive(entry) {\n if (!entry || typeof entry !== \"object\") return false;\n if (entry.runtime_enabled !== true) return false;\n const allowedModes = normalizeAllowedExecutionModes(entry.allowed_execution_modes);\n if (!allowedModes.includes(\"live\")) return false;\n if (cleanOptionalBoolean(entry.can_switch_live) !== true) return false;\n const lifecycleStage = normalizeLifecycleStage(entry.lifecycle_stage);\n if (![\"live_enabled\", \"runtime_enabled\"].includes(lifecycleStage)) return false;\n const blockedReason = cleanDisplayText(entry.blocked_live_reason);\n if (blockedReason) return false;\n const evidenceStatus = cleanDisplayText(entry.latest_evidence_status);\n if (evidenceStatus && [\"research_only\", \"blocked\", \"pending\"].includes(evidenceStatus.toLowerCase())) return false;\n const pluginGateStatus = cleanDisplayText(entry.plugin_gate_status);\n if (pluginGateStatus && [\"blocked\", \"locked\", \"disabled\"].includes(pluginGateStatus.toLowerCase())) return false;\n return true;\n }\n\n function strategyDisplayMetaText(platform, account, profile) {\n void profile;\n return t(\"strategyMeta\").replace(\"{domains}\", supportedDomainLabel(platform, account));\n }\n\n function strategyActionNoteText(platform = state.selected, account = selectedAccount(platform)) {\n const profile = state.forms[platform]?.strategy || \"\";\n const meta = strategyDisplayMetaText(platform, account, profile);\n return meta ? `${t(\"invalidStrategyNote\")}\\n${meta}` : t(\"invalidStrategyNote\");\n }\n\n function strategyChoiceLabel(profile, platform = state.selected, account = selectedAccount(platform), executionMode = state.forms[platform]?.executionMode) {\n const label = strategyLabel(profile);\n const entry = strategyCatalogEntry(profile);\n const domain = entry.domain ? domainLabel(entry.domain) : \"\";\n if (!entry.profile) return label;\n if (normalizeExecutionMode(executionMode, false) === \"live\" && !strategyCanSwitchLive(entry)) {\n return domain ? `${label}(${domain})` : label;\n }\n if (entry.runtime_enabled === false) {\n return domain ? `${label}(${domain})` : label;\n }\n return label;\n }\n\n function strategyAllowedForAccount(platform, account, profile, executionMode = state.forms[platform]?.executionMode) {\n const cleanProfile = cleanStrategyProfile(profile);\n const catalogEntry = strategyCatalogEntry(cleanProfile);\n if (!catalogEntry.profile) return false;\n if (dcaConfigForStrategy(cleanProfile) && !platformSupportsDca(platform)) return false;\n if (!supportedDomainsForAccount(platform, account).includes(catalogEntry.domain)) return false;\n const mode = normalizeExecutionMode(executionMode, false);\n if (!supportedExecutionModesForPlatform(platform).includes(mode)) return false;\n if (mode === \"live\") return strategyCanSwitchLive(catalogEntry);\n const allowedModes = normalizeAllowedExecutionModes(catalogEntry.allowed_execution_modes);\n if (allowedModes.length && !allowedModes.includes(mode)) return false;\n return true;\n }\n\n function strategyChoicesForAccount(platform = state.selected, account = selectedAccount(platform), executionMode = state.forms[platform]?.executionMode) {\n const choices = strategyOptions.filter((profile) => strategyAllowedForAccount(platform, account, profile, executionMode));\n const addChoice = (value) => {\n const profile = cleanStrategyProfile(value);\n if (profile && !choices.includes(profile) && strategyAllowedForAccount(platform, account, profile, executionMode)) {\n choices.push(profile);\n }\n };\n return choices;\n }\n\n function hasLiveStrategyOption(platform = state.selected, account = selectedAccount(platform)) {\n return strategyOptions.some((profile) => strategyAllowedForAccount(platform, account, profile, \"live\"));\n }\n\n function strategyLabel(profile) {\n const labels = strategyLabels[profile] || localStrategyLabels[profile];\n if (!labels) return profile;\n return state.lang === \"zh\"\n ? (labels.zh || labels.en || profile)\n : (labels.en || labels.zh || profile);\n }\n\n function strategyLabelSet(profile, item) {\n const local = localStrategyLabels[profile] || {};\n const label = String(item?.label || item?.display_name || \"\").trim();\n const labelEn = String(item?.label_en || item?.display_name_en || \"\").trim();\n const labelZh = String(item?.label_zh || item?.display_name_zh || \"\").trim();\n return {\n zh: labelZh || local.zh || label || local.en || profile,\n en: labelEn || label || local.en || labelZh || local.zh || profile,\n };\n }\n\n function modeLabel(mode) {\n return mode === \"dry_run\" ? t(\"dryRun\") : mode === \"paper\" ? t(\"paper\") : t(\"live\");\n }\n\n function normalizePluginMode(value) {\n return pluginModes.includes(value) ? value : \"none\";\n }\n\n function pluginModeLabel(mode) {\n void mode;\n return t(\"pluginModeNone\");\n }\n\n function dcaConfigForStrategy(profile) {\n const cleanProfile = cleanStrategyProfile(profile);\n const catalog = strategyCatalog[cleanProfile] || {};\n if (catalog.dca_enabled === true) {\n return {\n defaultMode: normalizeDcaMode(catalog.dca_default_mode || \"fixed\"),\n defaultBaseInvestmentUsd: cleanDisplayPositiveNumber(catalog.dca_default_base_investment_usd) || \"1000\",\n };\n }\n return dcaProfileDefaults[cleanProfile] || null;\n }\n\n function dcaSupported(profile) {\n return Boolean(dcaConfigForStrategy(profile));\n }\n\n function normalizeDcaMode(value) {\n const mode = String(value || \"\").trim().toLowerCase();\n if (mode === \"smart_dca\") return \"smart\";\n if (mode === \"fixed_dca\" || mode === \"ordinary\" || mode === \"ordinary_dca\") return \"fixed\";\n return dcaModes.includes(mode) ? mode : \"fixed\";\n }\n\n function dcaModeLabel(mode) {\n return normalizeDcaMode(mode) === \"smart\" ? t(\"dcaModeSmart\") : t(\"dcaModeFixed\");\n }\n\n function normalizeRuntimeTargetMode(value) {\n return runtimeTargetModes.includes(value) ? value : \"current\";\n }\n\n function runtimeTargetModeLabel(mode) {\n if (mode === \"enabled\") return t(\"runtimeTargetEnabled\");\n if (mode === \"disabled\") return t(\"runtimeTargetDisabled\");\n return t(\"runtimeTargetCurrent\");\n }\n\n function runtimeTargetEnabledForAccount(platform, account) {\n return cleanOptionalBoolean(currentEntryForAccount(platform, account)?.runtime_target_enabled);\n }\n\n function runtimeTargetStateForAccount(platform = state.selected, account = selectedAccount(platform)) {\n const entry = currentEntryForAccount(platform, account);\n if (!entry) return { known: false, enabled: null };\n const configured = cleanOptionalBoolean(entry.runtime_target_enabled);\n return { known: true, enabled: configured ?? true };\n }\n\n function runtimeTargetText(enabled) {\n return enabled ? t(\"runtimeTargetOn\") : t(\"runtimeTargetOff\");\n }\n\n function runtimeTargetTone(enabled) {\n return enabled ? \"enabled\" : \"disabled\";\n }\n\n function currentRuntimeTargetText(platform = state.selected, account = selectedAccount(platform)) {\n const target = runtimeTargetStateForAccount(platform, account);\n if (!target.known) return t(\"notRead\");\n return runtimeTargetText(target.enabled);\n }\n\n function currentRuntimeTargetTone(platform = state.selected, account = selectedAccount(platform)) {\n const target = runtimeTargetStateForAccount(platform, account);\n if (!target.known) return \"neutral\";\n return runtimeTargetTone(target.enabled);\n }\n\n function incomeLayerDefaultForStrategy(profile) {\n return incomeLayerDefaults[cleanStrategyProfile(profile)] || null;\n }\n\n function incomeLayerSupported(profile) {\n return Boolean(incomeLayerDefaultForStrategy(profile));\n }\n\n function normalizeIncomeLayerMode(value) {\n return incomeLayerModes.includes(value) ? value : \"current\";\n }\n\n function incomeLayerModeLabel(mode) {\n if (mode === \"enabled\") return t(\"incomeLayerEnabled\");\n if (mode === \"disabled\") return t(\"incomeLayerDisabled\");\n return t(\"incomeLayerCurrent\");\n }\n\n function optionOverlayDefaultForStrategy(profile) {\n return optionOverlayDefaults[cleanStrategyProfile(profile)] || null;\n }\n\n function optionOverlaySupported(profile) {\n return Boolean(optionOverlayDefaultForStrategy(profile));\n }\n\n function normalizeOptionOverlayMode(value) {\n return optionOverlayModes.includes(value) ? value : \"current\";\n }\n\n function optionOverlayModeLabel(mode) {\n if (mode === \"enabled\") return t(\"optionOverlayEnabled\");\n if (mode === \"disabled\") return t(\"optionOverlayDisabled\");\n return t(\"optionOverlayCurrent\");\n }\n\n function optionOverlayText(enabled) {\n return enabled ? t(\"optionOverlayOn\") : t(\"optionOverlayOff\");\n }\n\n function normalizeCashOnlyExecutionMode(value) {\n return cashOnlyExecutionModes.includes(value) ? value : \"current\";\n }\n\n function cashOnlyExecutionModeLabel(mode) {\n if (mode === \"enabled\") return t(\"cashOnlyExecutionNo\");\n if (mode === \"disabled\") return t(\"cashOnlyExecutionYes\");\n return t(\"cashOnlyExecutionCurrent\");\n }\n\n function cashOnlyExecutionText(enabled) {\n if (enabled === null) return t(\"notRead\");\n return enabled ? t(\"cashOnlyExecutionValueNo\") : t(\"cashOnlyExecutionValueYes\");\n }\n\n function platformCashOnlyExecutionDefault() {\n return true;\n }\n\n function effectiveCashOnlyExecutionForAccount(platform, account) {\n const configured = currentCashOnlyExecutionForAccount(platform, account);\n if (configured !== null) return configured;\n if (!platformSupportsMarginPolicy(platform)) return null;\n return platformCashOnlyExecutionDefault();\n }\n\n function currentCashOnlyExecutionForAccount(platform, account) {\n const entry = currentEntryForAccount(platform, account);\n if (entry) {\n const val = cleanOptionalBoolean(entry.cash_only_execution);\n if (val !== null) return val;\n }\n return platformCashOnlyExecutionDefault();\n }\n\n function currentCashOnlyExecutionText(platform = state.selected, account = selectedAccount(platform)) {\n if (!platformSupportsMarginPolicy(platform)) return t(\"notRead\");\n const entry = currentEntryForAccount(platform, account);\n if (!entry) return t(\"notRead\");\n const configured = cleanOptionalBoolean(entry.cash_only_execution);\n if (configured === null) return t(\"cashOnlyExecutionDefault\");\n return cashOnlyExecutionText(configured);\n }\n\n function currentOptionOverlayForAccount(platform, account) {\n return cleanOptionalBoolean(currentEntryForAccount(platform, account)?.option_overlay_enabled);\n }\n\n function effectiveOptionOverlayForAccount(platform, account, profile = state.forms[platform]?.strategy) {\n const configured = currentOptionOverlayForAccount(platform, account);\n if (configured !== null) return configured;\n if (!optionOverlaySupported(profile)) return null;\n return true;\n }\n\n function optionOverlayDefaultSummaryDetail(defaults) {\n if (!defaults?.families?.length) return \"\";\n return defaults.families.map((item) => {\n const family = item.family === \"income\" ? t(\"optionOverlayFamilyIncome\") : t(\"optionOverlayFamilyGrowth\");\n const ratioText = item.ratioKind === \"risk\"\n ? t(\"optionOverlayRiskRatio\").replace(\"{ratio}\", formatRatioPercent(item.ratio))\n : t(\"optionOverlayBudgetRatio\").replace(\"{ratio}\", formatRatioPercent(item.ratio));\n return `${family} ${ratioText}`;\n }).join(\" / \");\n }\n\n function optionOverlayDefaultText(profile) {\n const defaults = optionOverlayDefaultForStrategy(profile);\n if (!defaults) return t(\"optionOverlayNotSupported\");\n const detail = optionOverlayDefaultSummaryDetail(defaults);\n return detail ? t(\"optionOverlayDefault\").replace(\"{detail}\", detail) : t(\"optionOverlayDefaultSimple\");\n }\n\n function currentOptionOverlayText(platform = state.selected, account = selectedAccount(platform), profile = state.forms[platform]?.strategy) {\n const entry = currentEntryForAccount(platform, account);\n if (!entry) return t(\"notRead\");\n const configured = cleanOptionalBoolean(entry.option_overlay_enabled);\n if (!optionOverlaySupported(profile)) {\n return configured === null ? t(\"optionOverlayNotSupported\") : optionOverlayText(configured);\n }\n if (configured === null) return optionOverlayDefaultText(profile);\n return optionOverlayText(configured);\n }\n\n function currentIncomeLayerForAccount(platform, account) {\n return incomeLayerFromEntry(currentEntryForAccount(platform, account));\n }\n\n function incomeLayerFromEntry(entry) {\n return {\n enabled: cleanOptionalBoolean(entry?.income_layer_enabled),\n startUsd: cleanDisplayNumber(entry?.income_layer_start_usd),\n maxRatio: cleanDisplayRatio(entry?.income_layer_max_ratio),\n };\n }\n\n function incomeLayerFieldsConfigured(entry) {\n const current = incomeLayerFromEntry(entry);\n return current.enabled !== null || Boolean(current.startUsd || current.maxRatio);\n }\n\n function effectiveIncomeLayerForAccount(platform, account, profile = state.forms[platform]?.strategy) {\n const defaults = incomeLayerDefaultForStrategy(profile);\n if (!defaults) return null;\n const entry = currentEntryForAccount(platform, account);\n if (!entry) return null;\n const current = incomeLayerFromEntry(entry);\n if (!incomeLayerFieldsConfigured(entry)) {\n return {\n enabled: true,\n startUsd: String(defaults.startUsd),\n maxRatio: defaults.maxRatio,\n };\n }\n return {\n enabled: current.enabled ?? true,\n startUsd: current.startUsd || String(defaults.startUsd),\n maxRatio: current.maxRatio || defaults.maxRatio,\n };\n }\n\n function currentDcaForAccount(platform, account, profile = state.forms[platform]?.strategy) {\n const defaults = dcaConfigForStrategy(profile);\n if (!defaults) return { supported: false, mode: \"\", baseInvestmentUsd: \"\" };\n const entry = currentEntryForAccount(platform, account);\n return {\n supported: true,\n mode: normalizeDcaMode(entry?.dca_mode || account?.dca_mode || defaults.defaultMode),\n baseInvestmentUsd: cleanDisplayPositiveNumber(entry?.dca_base_investment_usd) ||\n cleanDisplayPositiveNumber(account?.dca_base_investment_usd) ||\n defaults.defaultBaseInvestmentUsd,\n };\n }\n\n function normalizeAccountLookupKey(value) {\n return String(value || \"\").trim().toLowerCase();\n }\n\n function collectAccountLookupCandidates(keys) {\n const candidates = new Set();\n for (const rawKey of keys) {\n const key = normalizeAccountLookupKey(rawKey);\n if (!key) continue;\n\n candidates.add(key);\n\n const compact = key.replace(/[^a-z0-9]+/g, \"\");\n if (compact) candidates.add(compact);\n\n const parts = key.split(/[^a-z0-9]+/).filter(Boolean);\n if (parts.length > 1) candidates.add(parts[parts.length - 1]);\n }\n return [...candidates];\n }\n\n function resolveCurrentEntryByKey(byPlatform, keys) {\n const candidates = new Set(collectAccountLookupCandidates(keys));\n if (!candidates.size) return null;\n\n for (const key of keys) {\n const entry = byPlatform[key];\n if (currentEntryHasState(entry)) return entry;\n }\n\n for (const [rawKey, entry] of Object.entries(byPlatform)) {\n if (!currentEntryHasState(entry)) continue;\n const rawCandidates = collectAccountLookupCandidates([rawKey]);\n const hasMatch = rawCandidates.some((candidate) => candidates.has(candidate));\n if (hasMatch) return entry;\n }\n\n return null;\n }\n\n function currentEntryForAccount(platform, account) {\n const byPlatform = state.currentStrategies[platform] || {};\n const keys = [account?.key, account?.target_name, account?.label]\n .filter(Boolean)\n .map((value) => String(value));\n const entry = resolveCurrentEntryByKey(byPlatform, keys);\n if (entry) return entry;\n const globalDefaults = window.__DEFAULT_ACCOUNT_OPTIONS__?.[platform]?.[0] || {};\n const merged = { ...globalDefaults, ...(account || {}) };\n const synth = {\n strategy_profile: \"\",\n source: \"account_defaults\",\n };\n const cashMode = merged.cash_only_execution_mode;\n if (cashMode === \"enabled\") synth.cash_only_execution = true;\n else if (cashMode === \"disabled\") synth.cash_only_execution = false;\n else if (platformSupportsMarginPolicy(platform)) synth.cash_only_execution = true;\n if (merged.min_reserved_cash_usd) synth.min_reserved_cash_usd = merged.min_reserved_cash_usd;\n if (merged.reserved_cash_ratio) synth.reserved_cash_ratio = merged.reserved_cash_ratio;\n synth.runtime_target_enabled = merged.runtime_target_enabled !== false;\n const execMode = merged.default_execution_mode || platformConfig[platform]?.default_execution_mode || \"live\";\n synth.execution_mode = execMode;\n synth.dry_run_only = execMode === \"paper\";\n return synth;\n }\n\n function currentEntryHasState(entry) {\n if (!entry || typeof entry !== \"object\" || Array.isArray(entry)) return false;\n return Boolean(\n cleanStrategyProfile(entry.strategy_profile) ||\n cleanDisplayNumber(entry.min_reserved_cash_usd ?? entry.reserved_cash_floor_usd) ||\n cleanDisplayRatio(entry.reserved_cash_ratio) ||\n cleanOptionalBoolean(entry.income_layer_enabled) !== null ||\n cleanDisplayNumber(entry.income_layer_start_usd) ||\n cleanDisplayRatio(entry.income_layer_max_ratio) ||\n cleanOptionalBoolean(entry.option_overlay_enabled) !== null ||\n cleanOptionalBoolean(entry.cash_only_execution) !== null ||\n cleanOptionalBoolean(entry.runtime_target_enabled) !== null ||\n normalizeDcaMode(entry.dca_mode || \"\") !== \"fixed\" ||\n cleanDisplayPositiveNumber(entry.dca_base_investment_usd) ||\n normalizeExecutionMode(entry.execution_mode, entry.dry_run_only),\n );\n }\n\n function currentStrategyForAccount(platform, account) {\n const entry = currentEntryForAccount(platform, account);\n return cleanStrategyProfile(entry?.strategy_profile) || \"\";\n }\n\n function currentReservePolicyForAccount(platform, account) {\n const entry = currentEntryForAccount(platform, account);\n return reservePolicyFromEntry(entry);\n }\n\n function currentPluginModeForAccount(platform, account) {\n void platform;\n return normalizePluginMode(account?.plugin_mode);\n }\n\n function reservePolicyFromEntry(entry) {\n return {\n minReservedCashUsd: cleanDisplayNumber(entry?.min_reserved_cash_usd ?? entry?.reserved_cash_floor_usd),\n reservedCashRatio: cleanDisplayRatio(entry?.reserved_cash_ratio),\n };\n }\n\n function cleanDisplayNumber(value) {\n const text = String(value ?? \"\").trim();\n if (!text || text.length > 32 || !/^(?:\\d+|\\d*\\.\\d+)$/.test(text)) return \"\";\n const numeric = Number(text);\n if (!Number.isFinite(numeric) || numeric < 0) return \"\";\n return text;\n }\n\n function cleanDisplayRatio(value) {\n const text = cleanDisplayNumber(value);\n if (!text) return \"\";\n const numeric = Number(text);\n return numeric >= 0 && numeric <= 1 ? text : \"\";\n }\n\n function cleanDisplayPositiveNumber(value) {\n const text = cleanDisplayNumber(value);\n return text && Number(text) > 0 ? text : \"\";\n }\n\n function normalizeExecutionMode(value, dryRunOnly) {\n const mode = String(value || \"\").trim().toLowerCase();\n if (mode === \"live\") return \"live\";\n if (mode === \"paper\" || mode === \"dry_run\" || mode === \"dry-run\") return \"dry_run\";\n if (dryRunOnly === true || dryRunOnly === \"true\" || dryRunOnly === \"1\" || dryRunOnly === 1) return \"dry_run\";\n if (dryRunOnly === false || dryRunOnly === \"false\" || dryRunOnly === \"0\" || dryRunOnly === 0) return \"live\";\n return \"\";\n }\n\n function cleanOptionalBoolean(value) {\n if (value === true || value === 1) return true;\n if (value === false || value === 0) return false;\n if (typeof value === \"string\") {\n const text = value.trim().toLowerCase();\n if (text === \"true\" || text === \"1\") return true;\n if (text === \"false\" || text === \"0\") return false;\n }\n return null;\n }\n\n function defaultExecutionModeForAccount(platform, account, fallback = \"live\") {\n if (platformDryRunOnly(platform)) return \"dry_run\";\n const currentMode = normalizeExecutionMode(\n currentEntryForAccount(platform, account)?.execution_mode,\n currentEntryForAccount(platform, account)?.dry_run_only,\n );\n if (currentMode) return currentMode;\n const hint = [\n account?.key,\n account?.label,\n account?.target_name,\n account?.deployment_selector,\n account?.account_scope,\n account?.service_name,\n ].join(\" \").toLowerCase();\n if (hint.split(/\\s+/).includes(\"paper\") || hint.includes(\"-paper\") || hint.includes(\"_paper\") || hint.includes(\"dry_run\") || hint.includes(\"dry-run\")) {\n return \"dry_run\";\n }\n return fallback;\n }\n\n function defaultStrategyForAccount(platform, account) {\n const currentProfile = currentStrategyForAccount(platform, account);\n if (currentProfile) return currentProfile;\n return \"\";\n }\n\n function syncStrategyForAccount(platform) {\n const account = selectedAccount(platform);\n if (!account) return;\n state.forms[platform].strategy = defaultStrategyForAccount(platform, account);\n state.forms[platform].executionMode = defaultExecutionModeForAccount(\n platform,\n account,\n );\n state.forms[platform].pluginMode = currentPluginModeForAccount(platform, account);\n syncRuntimeTargetForAccount(platform);\n syncReservePolicyForAccount(platform);\n syncIncomeLayerForAccount(platform);\n syncOptionOverlayForAccount(platform);\n syncCashOnlyExecutionForAccount(platform);\n reconcileExecutionCashPolicy(state.forms[platform], \"margin\");\n syncDcaForAccount(platform);\n }\n\n function syncRuntimeTargetForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.runtimeTargetTouched) return;\n const current = runtimeTargetEnabledForAccount(platform, selectedAccount(platform));\n form.runtimeTargetMode = current === false ? \"disabled\" : \"enabled\";\n }\n\n function syncReservePolicyForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.reservedCashTouched) return;\n const policy = currentReservePolicyForAccount(platform, selectedAccount(platform));\n const hasFloor = Boolean(policy.minReservedCashUsd);\n const hasRatio = Boolean(policy.reservedCashRatio);\n if (hasFloor && hasRatio) form.reservePolicyMode = \"max\";\n else if (hasFloor) form.reservePolicyMode = \"floor\";\n else if (hasRatio) form.reservePolicyMode = \"ratio\";\n else form.reservePolicyMode = \"none\";\n form.minReservedCashUsd = policy.minReservedCashUsd;\n form.reservedCashRatio = policy.reservedCashRatio;\n }\n\n function syncIncomeLayerForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.incomeLayerTouched) return;\n const defaults = incomeLayerDefaultForStrategy(form.strategy);\n const current = currentIncomeLayerForAccount(platform, selectedAccount(platform));\n const entry = currentEntryForAccount(platform, selectedAccount(platform));\n if (entry && incomeLayerFieldsConfigured(entry)) {\n form.incomeLayerMode = current.enabled === false ? \"disabled\" : \"enabled\";\n } else {\n form.incomeLayerMode = incomeLayerSupported(form.strategy) ? \"enabled\" : \"disabled\";\n }\n form.incomeLayerStartUsd = current.startUsd || String(defaults?.startUsd || \"\");\n form.incomeLayerMaxRatio = current.maxRatio || defaults?.maxRatio || \"\";\n }\n\n function syncOptionOverlayForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.optionOverlayTouched) return;\n const configured = normalizeOptionOverlayMode(selectedAccount(platform)?.option_overlay_mode);\n if (configured !== \"current\") {\n form.optionOverlayMode = configured;\n return;\n }\n const entry = currentEntryForAccount(platform, selectedAccount(platform));\n const rawValue = cleanOptionalBoolean(entry?.option_overlay_enabled);\n if (rawValue !== null) {\n form.optionOverlayMode = rawValue ? \"enabled\" : \"disabled\";\n return;\n }\n form.optionOverlayMode = optionOverlaySupported(form.strategy) ? \"enabled\" : \"disabled\";\n }\n\n function syncCashOnlyExecutionForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.cashOnlyExecutionTouched) return;\n const configured = normalizeCashOnlyExecutionMode(selectedAccount(platform)?.cash_only_execution_mode);\n if (configured !== \"current\") {\n form.cashOnlyExecutionMode = configured;\n return;\n }\n const entry = currentEntryForAccount(platform, selectedAccount(platform));\n const rawValue = cleanOptionalBoolean(entry?.cash_only_execution);\n if (rawValue !== null) {\n form.cashOnlyExecutionMode = rawValue ? \"enabled\" : \"disabled\";\n return;\n }\n // No explicit config — use platform default (cash-only for margin-capable platforms)\n form.cashOnlyExecutionMode = platformSupportsMarginPolicy(platform) ? \"enabled\" : \"disabled\";\n }\n\n function syncDcaForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.dcaTouched) return;\n const current = currentDcaForAccount(platform, selectedAccount(platform), form.strategy);\n form.dcaMode = current.supported ? current.mode : \"fixed\";\n form.dcaBaseInvestmentUsd = current.supported ? current.baseInvestmentUsd : \"\";\n }\n\n function ensureAccountSelection(platform) {\n const options = optionsFor(platform);\n if (!options.length) return;\n if (!options.some((option) => option.key === state.forms[platform].accountKey)) {\n state.forms[platform].accountKey = options[0].key;\n state.forms[platform].runtimeTargetTouched = false;\n state.forms[platform].reservedCashTouched = false;\n state.forms[platform].incomeLayerTouched = false;\n state.forms[platform].optionOverlayTouched = false;\n state.forms[platform].cashOnlyExecutionTouched = false;\n state.forms[platform].dcaTouched = false;\n state.forms[platform].strategy = defaultStrategyForAccount(platform, options[0]);\n state.forms[platform].pluginMode = currentPluginModeForAccount(platform, options[0]);\n syncRuntimeTargetForAccount(platform);\n syncReservePolicyForAccount(platform);\n syncIncomeLayerForAccount(platform);\n syncOptionOverlayForAccount(platform);\n syncCashOnlyExecutionForAccount(platform);\n syncDcaForAccount(platform);\n }\n }\n\n function derivedService(platform, targetName) {\n if (platform === \"longbridge\") return `longbridge-quant-${targetName.toLowerCase()}-service`;\n if (platform === \"ibkr\") return `interactive-brokers-${targetName.toLowerCase()}-service`;\n if (platform === \"schwab\") return \"charles-schwab-quant-service\";\n if (platform === \"firstrade\") return \"firstrade-quant-service\";\n if (platform === \"qmt\") return \"qmt-quant-service\";\n return \"\";\n }\n\n function accountMetaText(platform = state.selected) {\n const account = selectedAccount(platform);\n const targetName = account.target_name || account.key;\n const raw = account.service_name || derivedService(platform, targetName);\n const service = raw || (state.lang === \"zh\" ? \"无\" : \"-\");\n return t(\"targetMeta\")\n .replace(\"{target}\", targetName)\n .replace(\"{service}\", service)\n .replace(\"{domains}\", supportedDomainLabel(platform, account));\n }\n\n function hasRunnableStrategySelection(platform = state.selected) {\n const form = state.forms[platform];\n const account = selectedAccount(platform);\n return Boolean(form?.strategy && account && strategyAllowedForAccount(platform, account, form.strategy, form.executionMode));\n }\n\n function hasValidReservePolicy(platform = state.selected) {\n if (!platformSupportsReservedCashPolicy(platform)) return true;\n const form = state.forms[platform];\n const mode = normalizeReservePolicyMode(form?.reservePolicyMode);\n if (mode === \"current\" || mode === \"none\") return true;\n return Boolean(reservePolicyOverrideForForm(form, platform));\n }\n\n function hasValidExecutionCashPolicy(platform = state.selected) {\n if (!platformSupportsMarginPolicy(platform) && !platformSupportsReservedCashPolicy(platform)) return true;\n const form = state.forms[platform];\n return !executionCashPolicyConflict(form) && hasValidReservePolicy(platform);\n }\n\n function hasValidIncomeLayerPolicy(platform = state.selected) {\n const form = state.forms[platform];\n if (!incomeLayerSupported(form?.strategy)) return true;\n const mode = normalizeIncomeLayerMode(form?.incomeLayerMode);\n if (mode === \"current\" || mode === \"disabled\") return true;\n const defaults = incomeLayerDefaultForStrategy(form?.strategy);\n const startUsd = cleanDisplayNumber(form?.incomeLayerStartUsd || defaults?.startUsd);\n const maxRatio = cleanDisplayRatio(form?.incomeLayerMaxRatio || defaults?.maxRatio);\n return Boolean(startUsd && maxRatio);\n }\n\n function hasValidOptionOverlayPolicy(platform = state.selected) {\n const form = state.forms[platform];\n const mode = normalizeOptionOverlayMode(form?.optionOverlayMode);\n return mode !== \"enabled\" || optionOverlaySupported(form?.strategy);\n }\n\n function hasValidDcaPolicy(platform = state.selected) {\n const form = state.forms[platform];\n if (!dcaSupported(form?.strategy) || !platformSupportsDca(platform)) return true;\n return Boolean(dcaModes.includes(normalizeDcaMode(form?.dcaMode)) && cleanDisplayPositiveNumber(form?.dcaBaseInvestmentUsd));\n }\n\n function hasValidStrategySelection(platform = state.selected) {\n return hasRunnableStrategySelection(platform) &&\n hasValidExecutionCashPolicy(platform) &&\n hasValidIncomeLayerPolicy(platform) &&\n hasValidOptionOverlayPolicy(platform) &&\n hasValidDcaPolicy(platform);\n }\n\n function normalizeReservePolicyMode(value) {\n return reservePolicyModes.includes(value) ? value : \"current\";\n }\n\n function reservePolicyOverrideForForm(form, platform = state.selected) {\n if (!platformSupportsReservedCashPolicy(platform)) return null;\n const mode = normalizeReservePolicyMode(form?.reservePolicyMode);\n const floor = cleanDisplayNumber(form?.minReservedCashUsd);\n const ratio = cleanDisplayRatio(form?.reservedCashRatio);\n const extraVariables = {};\n if (mode === \"current\") return null;\n if (mode === \"none\") {\n extraVariables[platformMinReservedCashVariables[platform]] = \"\";\n extraVariables[platformReservedCashRatioVariables[platform]] = \"\";\n return { inputs: {}, extraVariables };\n }\n if (mode === \"ratio\") {\n if (!ratio) return null;\n extraVariables[platformMinReservedCashVariables[platform]] = \"\";\n return { inputs: { reserved_cash_ratio: ratio }, extraVariables };\n }\n if (mode === \"floor\") {\n if (!floor) return null;\n extraVariables[platformReservedCashRatioVariables[platform]] = \"\";\n return { inputs: { min_reserved_cash_usd: floor }, extraVariables };\n }\n if (mode === \"max\") {\n if (!floor || !ratio) return null;\n return { inputs: { min_reserved_cash_usd: floor, reserved_cash_ratio: ratio }, extraVariables };\n }\n return null;\n }\n\n function runtimeTargetOverrideForForm(form) {\n const mode = normalizeRuntimeTargetMode(form?.runtimeTargetMode);\n if (mode === \"current\") return null;\n return {\n inputs: { runtime_target_enabled_mode: mode },\n extraVariables: { [runtimeTargetEnabledVariable]: mode === \"enabled\" ? \"true\" : \"false\" },\n };\n }\n\n function incomeLayerOverrideForForm(form) {\n const defaults = incomeLayerDefaultForStrategy(form?.strategy);\n if (!defaults) return null;\n const mode = normalizeIncomeLayerMode(form?.incomeLayerMode);\n if (mode === \"current\") return null;\n const extraVariables = {};\n if (mode === \"disabled\") {\n extraVariables[incomeLayerEnabledVariable] = \"false\";\n extraVariables[incomeLayerStartUsdVariable] = \"\";\n extraVariables[incomeLayerMaxRatioVariable] = \"\";\n return { inputs: { income_layer_mode: mode }, extraVariables };\n }\n const startUsd = cleanDisplayNumber(form?.incomeLayerStartUsd || defaults.startUsd);\n const maxRatio = cleanDisplayRatio(form?.incomeLayerMaxRatio || defaults.maxRatio);\n if (!startUsd || !maxRatio) return null;\n extraVariables[incomeLayerEnabledVariable] = \"true\";\n extraVariables[incomeLayerStartUsdVariable] = startUsd;\n extraVariables[incomeLayerMaxRatioVariable] = maxRatio;\n return { inputs: { income_layer_mode: mode, income_layer_start_usd: startUsd, income_layer_max_ratio: maxRatio }, extraVariables };\n }\n\n function optionOverlayOverrideForForm(form) {\n const mode = normalizeOptionOverlayMode(form?.optionOverlayMode);\n if (mode === \"current\") return null;\n if (mode === \"enabled\" && !optionOverlaySupported(form?.strategy)) return null;\n return { inputs: { option_overlay_mode: mode } };\n }\n\n function cashOnlyExecutionOverrideForForm(form, platform = state.selected) {\n if (!platformSupportsMarginPolicy(platform)) return null;\n const mode = normalizeCashOnlyExecutionMode(form?.cashOnlyExecutionMode);\n if (mode === \"current\") return null;\n return { inputs: { cash_only_execution_mode: mode } };\n }\n\n function dcaOverrideForForm(form) {\n if (!dcaSupported(form?.strategy) || !platformSupportsDca(state.selected)) return null;\n const mode = normalizeDcaMode(form?.dcaMode);\n const baseInvestmentUsd = cleanDisplayPositiveNumber(form?.dcaBaseInvestmentUsd);\n if (!baseInvestmentUsd) return null;\n return { inputs: { dca_mode: mode, dca_base_investment_usd: baseInvestmentUsd } };\n }\n\n function mergeExtraVariables(inputs, extraVariables) {\n if (!extraVariables || !Object.keys(extraVariables).length) return;\n const merged = inputs.extra_variables_json ? JSON.parse(inputs.extra_variables_json) : {};\n Object.assign(merged, extraVariables);\n inputs.extra_variables_json = JSON.stringify(merged);\n }\n\n function buildInputs(platform = state.selected) {\n const form = state.forms[platform];\n const account = selectedAccount(platform);\n const targetName = account.target_name || account.key;\n const inputs = {\n platform,\n target_name: targetName,\n strategy_profile: form.strategy,\n execution_mode: form.executionMode,\n variable_scope: account.variable_scope || \"default\",\n plugin_mode: normalizePluginMode(form.pluginMode),\n service_targets_mode: \"auto\",\n apply: \"true\",\n trigger_platform_sync: \"true\",\n confirm_apply: \"APPLY_AND_SYNC\",\n platform_sync_workflow: \"sync-cloud-run-env.yml\",\n };\n for (const field of [\n \"github_environment\",\n \"deployment_selector\",\n \"account_selector\",\n \"account_scope\",\n \"service_name\",\n ]) {\n if (account[field]) inputs[field] = account[field];\n }\n const reserveOverride = platformSupportsReservedCashPolicy(platform)\n ? reservePolicyOverrideForForm(form, platform)\n : null;\n if (platformSupportsReservedCashPolicy(platform)) {\n inputs.reserved_cash_policy_mode = normalizeReservePolicyMode(form.reservePolicyMode);\n if (reserveOverride) {\n Object.assign(inputs, reserveOverride.inputs);\n mergeExtraVariables(inputs, reserveOverride.extraVariables);\n }\n }\n const runtimeTargetOverride = runtimeTargetOverrideForForm(form);\n inputs.runtime_target_enabled_mode = normalizeRuntimeTargetMode(form.runtimeTargetMode);\n if (runtimeTargetOverride) {\n Object.assign(inputs, runtimeTargetOverride.inputs);\n mergeExtraVariables(inputs, runtimeTargetOverride.extraVariables);\n }\n const incomeOverride = incomeLayerOverrideForForm(form);\n inputs.income_layer_mode = normalizeIncomeLayerMode(form.incomeLayerMode);\n if (incomeOverride) {\n Object.assign(inputs, incomeOverride.inputs);\n mergeExtraVariables(inputs, incomeOverride.extraVariables);\n }\n const optionOverlayOverride = optionOverlayOverrideForForm(form);\n inputs.option_overlay_mode = normalizeOptionOverlayMode(form.optionOverlayMode);\n if (optionOverlayOverride) {\n Object.assign(inputs, optionOverlayOverride.inputs);\n }\n const cashOnlyOverride = cashOnlyExecutionOverrideForForm(form, platform);\n if (platformSupportsMarginPolicy(platform)) {\n inputs.cash_only_execution_mode = normalizeCashOnlyExecutionMode(form.cashOnlyExecutionMode);\n if (cashOnlyOverride) {\n Object.assign(inputs, cashOnlyOverride.inputs);\n }\n }\n const dcaOverride = dcaOverrideForForm(form);\n if (dcaOverride) {\n Object.assign(inputs, dcaOverride.inputs);\n }\n return inputs;\n }\n\n function reservedCashPolicyText(inputs, platform = state.selected, account = selectedAccount(platform), fallback = t(\"unchanged\")) {\n if (inputs?.reserved_cash_policy_mode === \"none\") return t(\"reservedCashNone\");\n const floor = cleanDisplayNumber(inputs?.min_reserved_cash_usd);\n const ratio = cleanDisplayRatio(inputs?.reserved_cash_ratio);\n const currency = selectedCashCurrency(platform, account);\n const hasEffectiveFloor = Boolean(floor && !(ratio && Number(floor) === 0));\n if (!hasEffectiveFloor && !ratio) return fallback;\n if (hasEffectiveFloor && ratio) return `max(${floor} ${currency}, ${formatRatioPercent(ratio)})`;\n if (hasEffectiveFloor) return `${floor} ${currency}`;\n return formatRatioPercent(ratio);\n }\n\n function platformReservedCashDefaultText(platform = state.selected, account = selectedAccount(platform)) {\n return t(\"reservedCashDefault\").replace(\"{currency}\", selectedCashCurrency(platform, account));\n }\n\n function currentReservedCashPolicyText(platform = state.selected, account = selectedAccount(platform)) {\n const entry = currentEntryForAccount(platform, account);\n const policy = currentReservePolicyForAccount(platform, account);\n return reservedCashPolicyText(\n {\n min_reserved_cash_usd: policy.minReservedCashUsd,\n reserved_cash_ratio: policy.reservedCashRatio,\n },\n platform,\n account,\n entry ? platformReservedCashDefaultText(platform, account) : t(\"notRead\"),\n );\n }\n\n function pendingReservedCashPolicyText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n return reservedCashPolicyText(pendingReservePolicy(inputs, platform, account).inputs, platform, account, t(\"unchanged\"));\n }\n\n function pendingReservePolicy(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const current = currentReservePolicyForAccount(platform, account);\n const currentFloor = cleanDisplayNumber(current.minReservedCashUsd);\n const currentRatio = cleanDisplayRatio(current.reservedCashRatio);\n const mode = normalizeReservePolicyMode(inputs.reserved_cash_policy_mode);\n const next = {\n min_reserved_cash_usd: cleanDisplayNumber(inputs.min_reserved_cash_usd),\n reserved_cash_ratio: cleanDisplayRatio(inputs.reserved_cash_ratio),\n };\n if (mode === \"none\") {\n next.reserved_cash_policy_mode = \"none\";\n }\n const entry = currentEntryForAccount(platform, account);\n const changed = Boolean(entry && (\n next.min_reserved_cash_usd !== currentFloor ||\n next.reserved_cash_ratio !== currentRatio ||\n (mode === \"none\" && (currentFloor || currentRatio))\n ));\n return { changed, inputs: next };\n }\n\n function currentIncomeLayerText(platform = state.selected, account = selectedAccount(platform), profile = state.forms[platform]?.strategy) {\n const defaults = incomeLayerDefaultForStrategy(profile);\n if (!defaults) return t(\"incomeLayerNotSupported\");\n const entry = currentEntryForAccount(platform, account);\n if (!entry) return t(\"notRead\");\n const current = incomeLayerFromEntry(entry);\n if (!incomeLayerFieldsConfigured(entry)) {\n return t(\"incomeLayerDefault\")\n .replace(\"{start}\", formatUsd(defaults.startUsd))\n .replace(\"{ratio}\", formatRatioPercent(defaults.maxRatio));\n }\n const enabled = current.enabled ?? true;\n const startUsd = current.startUsd || String(defaults.startUsd);\n const ratio = current.maxRatio || defaults.maxRatio;\n return enabled\n ? t(\"incomeLayerOn\")\n .replace(\"{start}\", formatUsd(startUsd))\n .replace(\"{ratio}\", formatRatioPercent(ratio))\n : t(\"incomeLayerOff\");\n }\n\n function pendingIncomeLayerText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingIncomeLayer(inputs, platform, account);\n if (!pending.supported) return t(\"incomeLayerNotSupported\");\n if (!pending.changed) return t(\"unchanged\");\n if (pending.inputs.income_layer_enabled === false) return t(\"incomeLayerOff\");\n return t(\"incomeLayerOn\")\n .replace(\"{start}\", formatUsd(pending.inputs.income_layer_start_usd))\n .replace(\"{ratio}\", formatRatioPercent(pending.inputs.income_layer_max_ratio));\n }\n\n function pendingOptionOverlayText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingOptionOverlay(inputs, platform, account);\n if (!pending.supported && pending.inputs.option_overlay_enabled !== false) return t(\"optionOverlayNotSupported\");\n if (!pending.changed) return t(\"unchanged\");\n return optionOverlayText(pending.inputs.option_overlay_enabled);\n }\n\n function pendingCashOnlyExecutionText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingCashOnlyExecution(inputs, platform, account);\n if (!pending.changed) return t(\"unchanged\");\n return cashOnlyExecutionText(pending.inputs.cash_only_execution);\n }\n\n function pendingRuntimeTargetText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingRuntimeTarget(inputs, platform, account);\n if (!pending.changed) return t(\"unchanged\");\n return runtimeTargetText(pending.inputs.runtime_target_enabled);\n }\n\n function pendingRuntimeTargetTone(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingRuntimeTarget(inputs, platform, account);\n if (!pending.changed) return \"neutral\";\n return runtimeTargetTone(pending.inputs.runtime_target_enabled);\n }\n\n function currentDcaText(platform = state.selected, account = selectedAccount(platform), profile = state.forms[platform]?.strategy) {\n const current = currentDcaForAccount(platform, account, profile);\n if (!current.supported) return t(\"dcaNotSupported\");\n return t(\"dcaText\")\n .replace(\"{mode}\", dcaModeLabel(current.mode))\n .replace(\"{amount}\", formatUsd(current.baseInvestmentUsd));\n }\n\n function pendingDcaText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingDca(inputs, platform, account);\n if (!pending.supported) return t(\"dcaNotSupported\");\n if (!pending.changed) return t(\"unchanged\");\n return t(\"dcaText\")\n .replace(\"{mode}\", dcaModeLabel(pending.inputs.dca_mode))\n .replace(\"{amount}\", formatUsd(pending.inputs.dca_base_investment_usd));\n }\n\n function pendingRuntimeTarget(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const mode = normalizeRuntimeTargetMode(inputs.runtime_target_enabled_mode);\n if (mode === \"current\") {\n return {\n changed: false,\n inputs: { runtime_target_enabled: runtimeTargetEnabledForAccount(platform, account) ?? true },\n };\n }\n const current = runtimeTargetEnabledForAccount(platform, account);\n const currentEnabled = current ?? true;\n const nextEnabled = mode === \"enabled\";\n const entry = currentEntryForAccount(platform, account);\n return {\n changed: Boolean(entry && current !== null && currentEnabled !== nextEnabled),\n inputs: { runtime_target_enabled: nextEnabled },\n };\n }\n\n function pendingIncomeLayer(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const profile = cleanStrategyProfile(inputs.strategy_profile || state.forms[platform]?.strategy);\n const defaults = incomeLayerDefaultForStrategy(profile);\n if (!defaults) return { supported: false, changed: false, inputs: {} };\n const mode = normalizeIncomeLayerMode(inputs.income_layer_mode);\n const entry = currentEntryForAccount(platform, account);\n const rawCurrent = currentIncomeLayerForAccount(platform, account);\n const effective = effectiveIncomeLayerForAccount(platform, account, profile);\n const currentEnabled = effective?.enabled ?? true;\n const currentStartUsd = effective?.startUsd ?? String(defaults.startUsd);\n const currentRatio = effective?.maxRatio ?? defaults.maxRatio;\n if (mode === \"current\") {\n return {\n supported: true,\n changed: false,\n inputs: {\n income_layer_enabled: rawCurrent.enabled,\n income_layer_start_usd: rawCurrent.startUsd,\n income_layer_max_ratio: rawCurrent.maxRatio,\n },\n };\n }\n if (mode === \"disabled\") {\n if (!entry) {\n return {\n supported: true,\n changed: false,\n inputs: {\n income_layer_enabled: false,\n income_layer_start_usd: \"\",\n income_layer_max_ratio: \"\",\n },\n };\n }\n return {\n supported: true,\n changed: currentEnabled !== false || Boolean(rawCurrent.startUsd || rawCurrent.maxRatio),\n inputs: {\n income_layer_enabled: false,\n income_layer_start_usd: \"\",\n income_layer_max_ratio: \"\",\n },\n };\n }\n const nextStartUsd = cleanDisplayNumber(inputs.income_layer_start_usd || defaults.startUsd);\n const nextRatio = cleanDisplayRatio(inputs.income_layer_max_ratio || defaults.maxRatio);\n return {\n supported: true,\n changed: Boolean(entry && (currentEnabled !== true || nextStartUsd !== currentStartUsd || nextRatio !== currentRatio)),\n inputs: {\n income_layer_enabled: true,\n income_layer_start_usd: nextStartUsd,\n income_layer_max_ratio: nextRatio,\n },\n };\n }\n\n function pendingOptionOverlay(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const profile = cleanStrategyProfile(inputs.strategy_profile || state.forms[platform]?.strategy);\n const supported = optionOverlaySupported(profile);\n const mode = normalizeOptionOverlayMode(inputs.option_overlay_mode);\n const current = effectiveOptionOverlayForAccount(platform, account, profile);\n if (mode === \"current\") {\n return {\n supported,\n changed: false,\n inputs: { option_overlay_enabled: currentOptionOverlayForAccount(platform, account) },\n };\n }\n if (mode === \"enabled\") {\n return {\n supported,\n changed: supported && current !== null && current !== true,\n inputs: { option_overlay_enabled: true },\n };\n }\n return {\n supported,\n changed: current === true,\n inputs: { option_overlay_enabled: false },\n };\n }\n\n function pendingCashOnlyExecution(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const mode = normalizeCashOnlyExecutionMode(inputs.cash_only_execution_mode);\n if (!platformSupportsMarginPolicy(platform) || mode === \"current\") {\n return { changed: false, inputs: {} };\n }\n const current = effectiveCashOnlyExecutionForAccount(platform, account);\n const nextEnabled = mode === \"enabled\";\n const entry = currentEntryForAccount(platform, account);\n return {\n changed: Boolean(entry && current !== null && current !== nextEnabled),\n inputs: { cash_only_execution: nextEnabled },\n };\n }\n\n function pendingDca(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const profile = cleanStrategyProfile(inputs.strategy_profile || state.forms[platform]?.strategy);\n const defaults = dcaConfigForStrategy(profile);\n if (!defaults) return { supported: false, changed: false, inputs: {} };\n const current = currentDcaForAccount(platform, account, profile);\n const nextMode = normalizeDcaMode(inputs.dca_mode || defaults.defaultMode);\n const nextBase = cleanDisplayPositiveNumber(inputs.dca_base_investment_usd || defaults.defaultBaseInvestmentUsd);\n return {\n supported: true,\n changed: Boolean(current.mode !== nextMode || current.baseInvestmentUsd !== nextBase),\n inputs: {\n dca_mode: nextMode,\n dca_base_investment_usd: nextBase,\n },\n };\n }\n\n function pendingChangeState(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const currentProfile = currentStrategyForAccount(platform, account);\n const nextProfile = cleanStrategyProfile(inputs.strategy_profile);\n const currentEntry = currentEntryForAccount(platform, account);\n const currentMode = normalizeExecutionMode(currentEntry?.execution_mode, currentEntry?.dry_run_only);\n const currentPluginMode = currentPluginModeForAccount(platform, account);\n const nextPluginMode = normalizePluginMode(inputs.plugin_mode);\n const runtimeTarget = pendingRuntimeTarget(inputs, platform, account);\n const reserve = pendingReservePolicy(inputs, platform, account);\n const income = pendingIncomeLayer(inputs, platform, account);\n const optionOverlay = pendingOptionOverlay(inputs, platform, account);\n const cashOnly = pendingCashOnlyExecution(inputs, platform, account);\n const dca = pendingDca(inputs, platform, account);\n return {\n currentProfile,\n nextProfile,\n currentMode,\n currentPluginMode,\n nextPluginMode,\n strategyChanged: Boolean(nextProfile && ((state.forms[platform]?.strategyTouched) || (currentProfile && currentProfile !== nextProfile))),\n modeChanged: Boolean(inputs.execution_mode && currentMode && currentMode !== inputs.execution_mode),\n pluginModeChanged: Boolean(nextPluginMode && currentPluginMode && currentPluginMode !== nextPluginMode),\n runtimeTargetChanged: runtimeTarget.changed,\n reserveCashChanged: reserve.changed,\n incomeLayerChanged: income.changed,\n optionOverlayChanged: optionOverlay.changed,\n cashOnlyChanged: cashOnly.changed,\n dcaChanged: dca.changed,\n runtimeTarget,\n reserve,\n income,\n optionOverlay,\n cashOnly,\n dca,\n };\n }\n\n function hasPendingChanges(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const changes = pendingChangeState(inputs, platform, account);\n return Boolean(\n changes.strategyChanged ||\n changes.modeChanged ||\n changes.pluginModeChanged ||\n changes.runtimeTargetChanged ||\n changes.reserveCashChanged ||\n changes.incomeLayerChanged ||\n changes.optionOverlayChanged ||\n changes.cashOnlyChanged ||\n changes.dcaChanged\n );\n }\n\n function formatRatioPercent(value) {\n const numeric = Number(value);\n if (!Number.isFinite(numeric)) return String(value);\n return `${(numeric * 100).toFixed(2).replace(/\\.?0+$/, \"\")}%`;\n }\n\n function formatUsd(value) {\n const numeric = Number(value);\n if (!Number.isFinite(numeric)) return String(value);\n return `$${numeric.toLocaleString(\"en-US\", { maximumFractionDigits: 0 })}`;\n }\n\n function incomeLayerAllocationText(defaults) {\n if (!defaults?.allocations) return \"\";\n return Object.entries(defaults.allocations)\n .map(([symbol, ratio]) => `${symbol} ${formatRatioPercent(ratio)}`)\n .join(\" / \");\n }\n\n function incomeLayerDefaultMetaText(defaults) {\n if (!defaults) return t(\"incomeLayerModeMeta\");\n return t(\"incomeLayerDefaultMeta\")\n .replace(\"{start}\", formatUsd(defaults.startUsd))\n .replace(\"{ratio}\", formatRatioPercent(defaults.maxRatio));\n }\n\n function optionOverlayDefaultMetaText(defaults) {\n if (!defaults?.families?.length) return t(\"optionOverlayModeMeta\");\n const familyText = defaults.families.map((item) => {\n const family = item.family === \"income\" ? t(\"optionOverlayFamilyIncome\") : t(\"optionOverlayFamilyGrowth\");\n const ratioText = item.ratioKind === \"risk\"\n ? t(\"optionOverlayRiskRatio\").replace(\"{ratio}\", formatRatioPercent(item.ratio))\n : t(\"optionOverlayBudgetRatio\").replace(\"{ratio}\", formatRatioPercent(item.ratio));\n return `${family}: ${item.recipe}, ${formatUsd(item.startUsd)}, ${ratioText}`;\n }).join(\" / \");\n return t(\"optionOverlayDefaultMeta\").replace(\"{defaults}\", familyText);\n }\n\n function summaryRows(inputs) {\n const account = selectedAccount();\n const changes = pendingChangeState(inputs, state.selected, account);\n const currentStrategyText = changes.currentProfile ? strategyLabel(changes.currentProfile) : t(\"notRead\");\n const rows = [\n [t(\"repository\"), state.repositories[state.selected] || defaultRepositories[state.selected]],\n [t(\"selectedAccount\"), account.label],\n [t(\"currentStrategy\"), currentStrategyText],\n [t(\"selectedMarket\"), supportedDomainLabel(state.selected, account)],\n [\n t(\"currentRuntimeTarget\"),\n currentRuntimeTargetText(state.selected, account),\n \"\",\n currentRuntimeTargetTone(state.selected, account),\n ],\n [t(\"currentPluginMode\"), pluginModeLabel(changes.currentPluginMode)],\n [t(\"reservedCashPolicy\"), currentReservedCashPolicyText(state.selected, account)],\n ];\n if (platformSupportsMarginPolicy(state.selected)) {\n rows.push([t(\"currentCashOnlyExecution\"), currentCashOnlyExecutionText(state.selected, account)]);\n }\n if (incomeLayerSupported(inputs.strategy_profile)) {\n rows.push([t(\"currentIncomeLayer\"), currentIncomeLayerText(state.selected, account, inputs.strategy_profile)]);\n }\n if (optionOverlaySupported(inputs.strategy_profile) || changes.optionOverlayChanged) {\n rows.push([t(\"currentOptionOverlay\"), currentOptionOverlayText(state.selected, account, inputs.strategy_profile)]);\n }\n if (dcaSupported(inputs.strategy_profile)) {\n rows.push([t(\"currentDca\"), currentDcaText(state.selected, account, inputs.strategy_profile)]);\n }\n if (changes.reserveCashChanged) {\n rows.push([t(\"pendingReservedCashPolicy\"), pendingReservedCashPolicyText(inputs, state.selected, account), \"pending\"]);\n }\n if (changes.incomeLayerChanged) {\n rows.push([t(\"pendingIncomeLayer\"), pendingIncomeLayerText(inputs, state.selected, account), \"pending\"]);\n }\n if (changes.optionOverlayChanged) {\n rows.push([t(\"pendingOptionOverlay\"), pendingOptionOverlayText(inputs, state.selected, account), \"pending\"]);\n }\n if (changes.cashOnlyChanged) {\n rows.push([t(\"pendingCashOnlyExecution\"), pendingCashOnlyExecutionText(inputs, state.selected, account), \"pending\"]);\n }\n if (changes.dcaChanged) {\n rows.push([t(\"pendingDca\"), pendingDcaText(inputs, state.selected, account), \"pending\"]);\n }\n if (changes.modeChanged) {\n rows.push([t(\"pendingMode\"), modeLabel(inputs.execution_mode), \"pending\"]);\n }\n if (changes.pluginModeChanged) {\n rows.push([t(\"pendingPluginMode\"), pluginModeLabel(changes.nextPluginMode), \"pending\"]);\n }\n if (changes.runtimeTargetChanged) {\n rows.push([\n t(\"pendingRuntimeTarget\"),\n pendingRuntimeTargetText(inputs, state.selected, account),\n \"pending\",\n pendingRuntimeTargetTone(inputs, state.selected, account),\n ]);\n }\n if (changes.strategyChanged && changes.nextProfile) {\n rows.push([t(\"nextStrategy\"), strategyLabel(changes.nextProfile), \"pending\"]);\n }\n return rows;\n }\n\n function applyLanguage() {\n document.documentElement.lang = state.lang === \"zh\" ? \"zh-CN\" : \"en\";\n document.querySelectorAll(\"[data-i18n]\").forEach((node) => {\n node.textContent = t(node.dataset.i18n);\n });\n el(\"lang-button\").textContent = state.lang === \"zh\" ? \"EN\" : \"中\";\n }\n\n function renderRuntimeAuthorityStatus() {\n const status = String(runtimeAuthorityStatus.status || \"UNKNOWN_RUNTIME_AUTHORITY_STATUS\");\n const asOf = String(runtimeAuthorityStatus.status_as_of || \"—\");\n el(\"runtime-authority-status\").textContent = status;\n el(\"runtime-authority-as-of\").textContent = t(\"runtimeAuthorityAsOf\").replace(\"{asOf}\", asOf);\n }\n\n function renderPlatforms() {\n const strip = el(\"platform-strip\");\n strip.replaceChildren();\n const showPrivateConfig = hasPrivateConfig();\n for (const platform of Object.keys(platformMeta)) {\n ensureAccountSelection(platform);\n const meta = platformMeta[platform];\n const form = state.forms[platform];\n const account = selectedAccount(platform);\n const button = document.createElement(\"button\");\n button.className = \"platform-button\";\n button.type = \"button\";\n button.dataset.platform = platform;\n button.classList.toggle(\"active\", platform === state.selected);\n const mark = document.createElement(\"span\");\n mark.className = \"mark\";\n mark.textContent = meta.code;\n const copyNode = document.createElement(\"span\");\n copyNode.className = \"platform-copy\";\n const labelNode = document.createElement(\"strong\");\n labelNode.textContent = meta.label;\n copyNode.append(labelNode);\n if (showPrivateConfig) {\n const accountNode = document.createElement(\"span\");\n accountNode.textContent = account.label;\n const strategyNode = document.createElement(\"small\");\n strategyNode.textContent = strategyLabel(form.strategy);\n copyNode.append(accountNode, strategyNode);\n }\n button.append(mark, copyNode);\n strip.appendChild(button);\n }\n }\n\n function renderControls() {\n const platform = state.selected;\n const meta = platformMeta[platform];\n const form = state.forms[platform];\n const accounts = optionsFor(platform);\n const account = selectedAccount(platform);\n const choices = strategyChoicesForAccount(platform, account, form.executionMode);\n const currentStrategy = currentStrategyForAccount(platform, account);\n const currentStrategyBlocked = Boolean(\n currentStrategy &&\n !strategyAllowedForAccount(platform, account, currentStrategy, form.executionMode),\n );\n const accountSelect = el(\"account-select\");\n const strategySelect = el(\"strategy-select\");\n const runtimeTargetEnabledSelect = el(\"runtime-target-enabled-select\");\n const pluginModeSelect = el(\"plugin-mode-select\");\n const incomeLayerModeSelect = el(\"income-layer-mode-select\");\n const incomeLayerStartUsdInput = el(\"income-layer-start-usd-input\");\n const incomeLayerMaxRatioInput = el(\"income-layer-max-ratio-input\");\n const optionOverlayModeSelect = el(\"option-overlay-mode-select\");\n const cashOnlyExecutionModeSelect = el(\"cash-only-execution-mode-select\");\n const dcaModeSelect = el(\"dca-mode-select\");\n const dcaBaseInvestmentUsdInput = el(\"dca-base-investment-usd-input\");\n const reservePolicyModeSelect = el(\"reserve-policy-mode-select\");\n const minReservedCashInput = el(\"min-reserved-cash-input\");\n const reservedCashRatioInput = el(\"reserved-cash-ratio-input\");\n const showPrivateControls = hasPrivateConfig();\n\n el(\"switch-panel\").style.setProperty(\"--platform-color\", meta.accent);\n el(\"platform-title\").textContent = meta.label;\n el(\"quick-form\").hidden = !showPrivateControls;\n el(\"run-area\").hidden = !showPrivateControls;\n el(\"public-note\").hidden = showPrivateControls;\n el(\"public-preview\").hidden = showPrivateControls;\n el(\"public-note\").textContent = state.auth.allowed ? t(\"missingConfigNote\") : t(\"publicReadonly\");\n\n if (!showPrivateControls) {\n accountSelect.replaceChildren();\n strategySelect.replaceChildren();\n runtimeTargetEnabledSelect.replaceChildren();\n pluginModeSelect.replaceChildren();\n incomeLayerModeSelect.replaceChildren();\n optionOverlayModeSelect.replaceChildren();\n cashOnlyExecutionModeSelect.replaceChildren();\n dcaModeSelect.replaceChildren();\n reservePolicyModeSelect.replaceChildren();\n incomeLayerStartUsdInput.value = \"\";\n incomeLayerMaxRatioInput.value = \"\";\n dcaBaseInvestmentUsdInput.value = \"\";\n minReservedCashInput.value = \"\";\n reservedCashRatioInput.value = \"\";\n el(\"account-meta\").textContent = \"\";\n el(\"strategy-meta\").textContent = \"\";\n el(\"income-layer-mode-meta\").textContent = \"\";\n el(\"income-layer-start-meta\").textContent = \"\";\n el(\"income-layer-ratio-meta\").textContent = \"\";\n el(\"option-overlay-mode-meta\").textContent = \"\";\n el(\"cash-only-execution-mode-meta\").textContent = \"\";\n el(\"dca-mode-meta\").textContent = \"\";\n el(\"dca-base-meta\").textContent = \"\";\n return;\n }\n\n accountSelect.replaceChildren();\n if (accounts.length) {\n for (const account of accounts) {\n accountSelect.append(new Option(account.label, account.key, false, account.key === form.accountKey));\n }\n } else {\n accountSelect.append(new Option(t(\"noAccount\"), \"\"));\n }\n el(\"account-meta\").textContent = accounts.length ? accountMetaText(platform) : \"\";\n\n if (choices.length && !choices.includes(form.strategy) && !currentStrategyBlocked) {\n form.strategy = choices[0];\n }\n strategySelect.disabled = !choices.length;\n strategySelect.replaceChildren();\n if (currentStrategyBlocked) {\n const blockedOption = new Option(\n strategyChoiceLabel(currentStrategy, platform, account, form.executionMode),\n currentStrategy,\n true,\n currentStrategy === form.strategy,\n );\n blockedOption.disabled = true;\n strategySelect.append(blockedOption);\n }\n if (choices.length) {\n for (const strategy of choices) {\n strategySelect.append(\n new Option(strategyChoiceLabel(strategy, platform, account, form.executionMode), strategy, false, strategy === form.strategy),\n );\n }\n } else {\n strategySelect.append(new Option(t(\"noStrategy\"), \"\"));\n }\n el(\"strategy-meta\").textContent = account\n ? strategyDisplayMetaText(platform, account, form.strategy)\n : \"\";\n runtimeTargetEnabledSelect.replaceChildren();\n for (const mode of runtimeTargetModes) {\n runtimeTargetEnabledSelect.append(\n new Option(runtimeTargetModeLabel(mode), mode, false, mode === normalizeRuntimeTargetMode(form.runtimeTargetMode)),\n );\n }\n pluginModeSelect.replaceChildren();\n for (const mode of pluginModes) {\n pluginModeSelect.append(new Option(pluginModeLabel(mode), mode, false, mode === normalizePluginMode(form.pluginMode)));\n }\n const incomeDefaults = incomeLayerDefaultForStrategy(form.strategy);\n el(\"income-layer-section\").hidden = false;\n el(\"option-overlay-section\").hidden = false;\n incomeLayerModeSelect.replaceChildren();\n if (incomeDefaults) {\n incomeLayerModeSelect.disabled = false;\n for (const mode of incomeLayerModes) {\n incomeLayerModeSelect.append(new Option(incomeLayerModeLabel(mode), mode, false, mode === normalizeIncomeLayerMode(form.incomeLayerMode)));\n }\n el(\"income-layer-mode-meta\").textContent = incomeLayerDefaultMetaText(incomeDefaults);\n el(\"income-layer-start-meta\").textContent = t(\"incomeLayerStartMeta\");\n el(\"income-layer-ratio-meta\").textContent = t(\"incomeLayerAllocationMeta\").replace(\n \"{allocations}\",\n incomeLayerAllocationText(incomeDefaults),\n );\n } else {\n incomeLayerModeSelect.disabled = true;\n incomeLayerModeSelect.append(new Option(t(\"incomeLayerNotSupported\"), \"current\"));\n el(\"income-layer-mode-meta\").textContent = t(\"incomeLayerModeMeta\");\n el(\"income-layer-start-meta\").textContent = t(\"incomeLayerStartMeta\");\n el(\"income-layer-ratio-meta\").textContent = t(\"incomeLayerRatioMeta\");\n }\n const supportsMargin = platformSupportsMarginPolicy(platform);\n const supportsReserve = platformSupportsReservedCashPolicy(platform);\n if (supportsMargin) syncCashOnlyExecutionForAccount(platform);\n reconcileExecutionCashPolicy(form, \"margin\");\n const executionCashPolicyGrid = el(\"execution-cash-policy-grid\");\n const qmtPlatformCashNote = el(\"qmt-platform-cash-note\");\n const executionCashPolicyNote = el(\"execution-cash-policy-note\");\n executionCashPolicyGrid.hidden = !supportsMargin && !supportsReserve;\n qmtPlatformCashNote.hidden = supportsMargin || supportsReserve || platform !== \"qmt\";\n executionCashPolicyNote.hidden = !supportsMargin || !supportsReserve;\n\n const marginBlocksReserve = supportsMargin && supportsReserve && allowMarginExplicitlySelected(form);\n const reserveBlocksMargin = supportsMargin && supportsReserve && reserveCashOverrideActive(form);\n\n if (supportsReserve) {\n reservePolicyModeSelect.replaceChildren();\n for (const mode of reservePolicyModes) {\n reservePolicyModeSelect.append(new Option(t(`reservePolicy${mode[0].toUpperCase()}${mode.slice(1)}`), mode, false, mode === normalizeReservePolicyMode(form.reservePolicyMode)));\n }\n const reserveMode = normalizeReservePolicyMode(form.reservePolicyMode);\n el(\"min-reserved-cash-label\").textContent = t(\"minReservedCash\").replace(\n \"{currency}\",\n selectedCashCurrency(platform, account),\n );\n reservePolicyModeSelect.disabled = false;\n minReservedCashInput.disabled = reserveMode === \"current\" || reserveMode === \"none\" || reserveMode === \"ratio\";\n reservedCashRatioInput.disabled = reserveMode === \"current\" || reserveMode === \"none\" || reserveMode === \"floor\";\n minReservedCashInput.value = reserveMode === \"ratio\" || reserveMode === \"none\" ? \"\" : form.minReservedCashUsd;\n reservedCashRatioInput.value = reserveMode === \"floor\" || reserveMode === \"none\" ? \"\" : form.reservedCashRatio;\n el(\"reserve-policy-block\").classList.toggle(\"policy-block-muted\", marginBlocksReserve);\n el(\"min-reserve-block\").classList.toggle(\"policy-block-muted\", marginBlocksReserve);\n el(\"reserve-ratio-block\").classList.toggle(\"policy-block-muted\", marginBlocksReserve);\n el(\"reserve-policy-mode-meta\").textContent = marginBlocksReserve\n ? t(\"executionCashMarginBlocksReserve\")\n : t(\"reservedCashModeMeta\");\n } else {\n reservePolicyModeSelect.replaceChildren();\n minReservedCashInput.value = \"\";\n reservedCashRatioInput.value = \"\";\n }\n const incomeMode = normalizeIncomeLayerMode(form.incomeLayerMode);\n const incomeLayerInputsDisabled = !incomeDefaults || incomeMode === \"disabled\";\n incomeLayerStartUsdInput.disabled = incomeLayerInputsDisabled;\n incomeLayerMaxRatioInput.disabled = incomeLayerInputsDisabled;\n if (incomeDefaults && incomeMode !== \"disabled\" && !cleanDisplayNumber(form.incomeLayerStartUsd)) {\n form.incomeLayerStartUsd = String(incomeDefaults.startUsd);\n }\n if (incomeDefaults && incomeMode !== \"disabled\" && !cleanDisplayRatio(form.incomeLayerMaxRatio)) {\n form.incomeLayerMaxRatio = incomeDefaults.maxRatio;\n }\n incomeLayerStartUsdInput.value = incomeDefaults && incomeMode !== \"disabled\" ? form.incomeLayerStartUsd : \"\";\n incomeLayerMaxRatioInput.value = incomeDefaults && incomeMode !== \"disabled\" ? form.incomeLayerMaxRatio : \"\";\n\n const optionDefaults = optionOverlayDefaultForStrategy(form.strategy);\n optionOverlayModeSelect.replaceChildren();\n if (optionDefaults) {\n optionOverlayModeSelect.disabled = false;\n for (const mode of optionOverlayModes) {\n optionOverlayModeSelect.append(\n new Option(optionOverlayModeLabel(mode), mode, false, mode === normalizeOptionOverlayMode(form.optionOverlayMode)),\n );\n }\n el(\"option-overlay-mode-meta\").textContent = optionOverlayDefaultMetaText(optionDefaults);\n } else {\n optionOverlayModeSelect.disabled = true;\n optionOverlayModeSelect.append(new Option(t(\"optionOverlayNotSupported\"), \"current\"));\n el(\"option-overlay-mode-meta\").textContent = t(\"optionOverlayModeMeta\");\n }\n\n if (supportsMargin) {\n cashOnlyExecutionModeSelect.replaceChildren();\n for (const mode of cashOnlyExecutionModes) {\n const option = new Option(\n mode === \"enabled\" ? t(\"cashOnlyExecutionNo\") : t(\"cashOnlyExecutionYes\"),\n mode,\n false,\n mode === normalizeCashOnlyExecutionMode(form.cashOnlyExecutionMode),\n );\n cashOnlyExecutionModeSelect.append(option);\n }\n el(\"cash-only-policy-block\").classList.toggle(\"policy-block-muted\", reserveBlocksMargin);\n el(\"cash-only-execution-mode-meta\").textContent = reserveBlocksMargin\n ? t(\"executionCashReserveBlocksMargin\")\n : t(\"cashOnlyExecutionModeMeta\");\n } else {\n cashOnlyExecutionModeSelect.replaceChildren();\n el(\"cash-only-execution-mode-meta\").textContent = \"\";\n }\n\n const dcaDefaults = dcaConfigForStrategy(form.strategy);\n dcaModeSelect.replaceChildren();\n const dcaAllowed = Boolean(dcaDefaults) && platformSupportsDca(platform);\n if (dcaAllowed) {\n dcaModeSelect.disabled = false;\n for (const mode of dcaModes) {\n dcaModeSelect.append(new Option(dcaModeLabel(mode), mode, false, mode === normalizeDcaMode(form.dcaMode)));\n }\n if (!cleanDisplayPositiveNumber(form.dcaBaseInvestmentUsd)) {\n form.dcaBaseInvestmentUsd = dcaDefaults.defaultBaseInvestmentUsd;\n }\n dcaBaseInvestmentUsdInput.disabled = false;\n dcaBaseInvestmentUsdInput.value = form.dcaBaseInvestmentUsd;\n el(\"dca-mode-meta\").textContent = t(\"dcaDefaultMeta\")\n .replace(\"{mode}\", dcaModeLabel(dcaDefaults.defaultMode))\n .replace(\"{amount}\", formatUsd(dcaDefaults.defaultBaseInvestmentUsd));\n el(\"dca-base-meta\").textContent = t(\"dcaModeMeta\");\n } else {\n dcaModeSelect.disabled = true;\n dcaModeSelect.append(new Option(\n dcaDefaults && !platformSupportsDca(platform) ? t(\"dcaPlatformNotSupported\") : t(\"dcaNotSupported\"),\n \"fixed\",\n ));\n dcaBaseInvestmentUsdInput.disabled = true;\n dcaBaseInvestmentUsdInput.value = \"\";\n el(\"dca-mode-meta\").textContent = t(\"dcaModeMeta\");\n el(\"dca-base-meta\").textContent = t(\"dcaModeMeta\");\n }\n\n const supportedModes = supportedExecutionModesForPlatform(platform);\n const liveModeAvailable = supportedModes.includes(\"live\") && hasLiveStrategyOption(platform, account);\n if (!supportedModes.includes(form.executionMode)) form.executionMode = \"dry_run\";\n document.querySelectorAll(\"#mode-control [data-mode]\").forEach((button) => {\n button.disabled = !supportedModes.includes(button.dataset.mode) || (\n button.dataset.mode === \"live\" && !liveModeAvailable\n );\n button.classList.toggle(\"active\", button.dataset.mode === form.executionMode);\n });\n el(\"mode-meta\").textContent = !supportedModes.includes(\"live\")\n ? t(\"qmtDryRunOnlyNote\")\n : (!liveModeAvailable ? t(\"liveModeUnavailable\") : \"\");\n }\n\n function renderSummary() {\n const showSummary = hasPrivateConfig();\n const summaryPanel = document.querySelector(\".summary-panel\");\n const switchSurface = document.querySelector(\".switch-surface\");\n summaryPanel.hidden = !showSummary;\n switchSurface.classList.toggle(\"summary-hidden\", !showSummary);\n if (!showSummary) return;\n\n const inputs = buildInputs();\n const list = el(\"summary-list\");\n list.replaceChildren();\n document.querySelector(\".summary-head h2\").textContent = t(\"summary\");\n const currentHeading = document.createElement(\"p\");\n currentHeading.className = \"summary-section-title\";\n currentHeading.textContent = t(\"summaryCurrent\");\n list.appendChild(currentHeading);\n let pendingSectionInserted = false;\n for (const [label, value, rowClass, valueTone] of summaryRows(inputs)) {\n if (!pendingSectionInserted && rowClass === \"pending\") {\n const pendingHeading = document.createElement(\"p\");\n pendingHeading.className = \"summary-section-title\";\n pendingHeading.textContent = t(\"summaryPending\");\n list.appendChild(pendingHeading);\n pendingSectionInserted = true;\n }\n const row = document.createElement(\"div\");\n row.className = \"summary-row\";\n row.setAttribute(\"role\", \"listitem\");\n if (rowClass) row.classList.add(rowClass);\n const labelNode = document.createElement(\"div\");\n labelNode.className = \"summary-label\";\n labelNode.textContent = label;\n const valueNode = document.createElement(\"div\");\n valueNode.className = \"summary-value\";\n if (valueTone) {\n const badge = document.createElement(\"span\");\n badge.className = `summary-status ${valueTone}`;\n badge.textContent = value;\n valueNode.appendChild(badge);\n } else {\n valueNode.textContent = value;\n }\n row.append(labelNode, valueNode);\n list.appendChild(row);\n }\n\n const account = selectedAccount();\n const currentEntry = currentEntryForAccount(state.selected, account);\n const currentMode = normalizeExecutionMode(currentEntry?.execution_mode, currentEntry?.dry_run_only);\n el(\"mode-pill\").textContent = currentMode ? modeLabel(currentMode) : t(\"notRead\");\n }\n\n function setPlanCheck(id, value, tone) {\n const node = el(id);\n if (!node) return;\n node.textContent = value;\n node.dataset.tone = tone || \"neutral\";\n }\n\n function renderPlanReadiness() {\n const platform = state.selected;\n const account = selectedAccount(platform);\n const configured = hasPrivateConfig() && Boolean(account?.key);\n const runnable = configured && hasRunnableStrategySelection(platform);\n const riskValid = configured && hasValidExecutionCashPolicy(platform) &&\n hasValidIncomeLayerPolicy(platform) && hasValidOptionOverlayPolicy(platform) && hasValidDcaPolicy(platform);\n const form = state.forms[platform];\n const isNonLive = normalizeExecutionMode(form?.executionMode, false) !== \"live\";\n setPlanCheck(\n \"plan-check-account\",\n configured ? t(\"planCheckSelected\") : t(\"planCheckWaiting\"),\n configured ? \"ready\" : \"neutral\",\n );\n setPlanCheck(\n \"plan-check-strategy\",\n runnable ? t(\"planCheckValid\") : (configured ? t(\"planCheckFix\") : t(\"planCheckWaiting\")),\n runnable ? \"ready\" : (configured ? \"warning\" : \"neutral\"),\n );\n setPlanCheck(\n \"plan-check-risk\",\n riskValid ? t(\"planCheckValid\") : (configured ? t(\"planCheckFix\") : t(\"planCheckWaiting\")),\n riskValid ? \"ready\" : (configured ? \"warning\" : \"neutral\"),\n );\n setPlanCheck(\n \"plan-check-authority\",\n isNonLive ? t(\"planCheckNonLive\") : t(\"planCheckNoAuthority\"),\n isNonLive ? \"ready\" : \"warning\",\n );\n }\n\n function renderAuth() {\n const status = el(\"auth-status\");\n const loginLink = el(\"login-link\");\n const logoutButton = el(\"logout-button\");\n const signedIn = Boolean(state.auth.allowed && state.auth.login);\n\n status.hidden = !signedIn;\n status.textContent = signedIn ? t(\"signedInAs\").replace(\"{login}\", state.auth.login) : \"\";\n loginLink.hidden = signedIn;\n loginLink.href = \"/login\";\n loginLink.textContent = t(\"login\");\n logoutButton.hidden = !signedIn;\n logoutButton.textContent = t(\"logout\");\n\n const dispatch = el(\"dispatch-button\");\n const hasPrivateAccounts = state.configSource === \"private\";\n const loadingConfig = state.configSource === \"loading\";\n const hasRunnableStrategy = hasRunnableStrategySelection();\n const hasValidReserve = hasValidExecutionCashPolicy();\n const hasValidIncomeLayer = hasValidIncomeLayerPolicy();\n const hasValidOptionOverlay = hasValidOptionOverlayPolicy();\n const hasValidDca = hasValidDcaPolicy();\n const hasValidStrategy = hasRunnableStrategy &&\n hasValidReserve &&\n hasValidIncomeLayer &&\n hasValidOptionOverlay &&\n hasValidDca;\n const hasPendingChange = hasPrivateAccounts && hasValidStrategy && hasPendingChanges(buildInputs());\n dispatch.disabled = !state.auth.allowed || loadingConfig || !hasPrivateAccounts || !hasValidStrategy || !hasPendingChange;\n dispatch.textContent = state.auth.allowed\n ? (loadingConfig\n ? t(\"loadingConfig\")\n : (hasPrivateAccounts ? (hasValidStrategy ? (hasPendingChange ? t(\"runSwitch\") : t(\"noChanges\")) : t(\"configureAccounts\")) : t(\"configureAccounts\")))\n : t(\"loginToRun\");\n const note = el(\"action-note\");\n note.textContent = state.auth.allowed\n ? (loadingConfig\n ? t(\"loadingConfigNote\")\n : (hasPrivateAccounts\n ? (hasRunnableStrategy\n ? (hasValidReserve\n ? (hasValidIncomeLayer\n ? (hasValidOptionOverlay\n ? (hasValidDca ? (hasPendingChange ? t(\"readyNote\") : \"\") : t(\"invalidDcaNote\"))\n : t(\"invalidOptionOverlayNote\"))\n : t(\"invalidIncomeLayerNote\"))\n : (executionCashPolicyConflict(state.forms[state.selected])\n ? t(\"invalidExecutionCashPolicyNote\")\n : t(\"invalidReservePolicyNote\")))\n : strategyActionNoteText())\n : t(\"missingConfigNote\")))\n : t(\"readonlyNote\");\n note.classList.toggle(\n \"warning\",\n state.auth.allowed && !loadingConfig && (!hasPrivateAccounts || !hasValidStrategy),\n );\n }\n\n function renderAppVisibility() {\n document.body.classList.toggle(\"app-loading\", !state.appReady);\n el(\"boot-message\").textContent = t(state.bootMessageKey);\n }\n\n function normalizeControlPlanePayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid control plane payload\");\n const candidates = Array.isArray(payload.candidates) ? payload.candidates : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n attention: payload.attention && typeof payload.attention === \"object\" && !Array.isArray(payload.attention)\n ? {\n status: [\"research_only\", \"attention_required\", \"unavailable\"].includes(payload.attention.status)\n ? payload.attention.status\n : \"unavailable\",\n reason_codes: Array.isArray(payload.attention.reason_codes) ? payload.attention.reason_codes : [],\n }\n : { status: \"unavailable\", reason_codes: [\"control_plane_attention_missing\"] },\n candidates: candidates.filter((item) => item && typeof item === \"object\" && item.lifecycle && typeof item.lifecycle === \"object\"),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function normalizeOwnerDecisionQueue(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid owner decision queue\");\n const candidates = Array.isArray(payload.candidates) ? payload.candidates : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n candidates: candidates.filter((entry) => entry && typeof entry === \"object\"\n && entry.candidate && typeof entry.candidate === \"object\"\n && typeof entry.candidate_evidence_sha256 === \"string\"),\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function ownerDecisionEntry(candidateId) {\n return state.ownerDecisions.candidates.find((entry) => entry.candidate?.candidate_id === candidateId) || null;\n }\n\n function ownerDecisionLabel(decision) {\n return {\n approve_limited_live_canary: t(\"ownerDecisionApprove\"),\n keep_parked: t(\"ownerDecisionPark\"),\n retire_candidate: t(\"ownerDecisionRetire\"),\n }[decision] || String(decision || \"—\");\n }\n\n function controlPlaneDataStatusText(status) {\n return status === \"ready\"\n ? t(\"controlDataReady\")\n : (status === \"stale\" ? t(\"controlDataStale\") : t(\"controlDataUnavailable\"));\n }\n\n function controlPlaneAttentionText(attention) {\n const status = attention?.status || \"unavailable\";\n if (status === \"research_only\") return t(\"controlAttentionResearchOnly\");\n if (status === \"attention_required\") return t(\"controlAttentionRequired\");\n return t(\"controlAttentionUnavailable\");\n }\n\n function renderControlPlane() {\n const payload = state.controlPlane.payload;\n const summary = payload.summary || {};\n el(\"control-plane-status\").textContent = `${controlPlaneDataStatusText(payload.data_status)} · ${controlPlaneAttentionText(payload.attention)}`;\n el(\"control-plane-computed-at\").textContent = payload.computed_at\n ? t(\"controlComputedAt\").replace(\"{time}\", new Date(payload.computed_at).toLocaleString())\n : t(\"controlComputedAt\").replace(\"{time}\", \"—\");\n el(\"control-count-candidates\").textContent = String(Number(summary.candidate_count) || 0);\n el(\"control-count-deferred\").textContent = String(Number(summary.deferred) || 0);\n el(\"control-count-parked\").textContent = String(Number(summary.parked) || 0);\n el(\"control-count-owner-decision\").textContent = String(Number(summary.owner_decision_required) || 0);\n\n const notice = el(\"control-plane-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"controlLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"controlStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"controlUnavailableNotice\");\n } else if (payload.attention?.status === \"attention_required\") {\n notice.textContent = t(\"controlAttentionNotice\")\n .replace(\"{deferred}\", String(Number(summary.deferred) || 0))\n .replace(\"{parked}\", String(Number(summary.parked) || 0))\n .replace(\"{signals}\", String(payload.attention.reason_codes?.length || 0));\n } else if (payload.errors?.length) {\n notice.textContent = t(\"controlUpstreamNotice\").replace(\"{count}\", payload.errors.length);\n } else {\n notice.textContent = payload.policy?.notice || \"live 仍需所有者明确决定。\";\n }\n\n const list = el(\"control-plane-list\");\n list.replaceChildren();\n if (!payload.candidates.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"controlEmptyCandidates\");\n list.appendChild(empty);\n return;\n }\n for (const item of payload.candidates) {\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = `${item.candidate_kind || \"candidate\"} · ${domainLabel(item.domain || \"\")}`;\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = String(item.candidate_id || \"unknown\");\n const reason = document.createElement(\"p\");\n reason.className = \"health-card__reason\";\n reason.textContent = item.recommendation?.reason || t(\"controlNoRecommendation\");\n const detail = document.createElement(\"div\");\n detail.className = \"health-card__meta\";\n detail.textContent = t(\"controlStageMeta\")\n .replace(\"{stage}\", item.lifecycle?.stage || \"—\")\n .replace(\"{status}\", item.lifecycle?.status || \"unknown\")\n .replace(\"{freshness}\", item.freshness?.status || \"unknown\");\n main.append(meta, title, reason, detail);\n const stateBlock = document.createElement(\"div\");\n stateBlock.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = t(\"controlNext\");\n const stage = document.createElement(\"strong\");\n stage.textContent = item.lifecycle?.stage || \"—\";\n const recommendation = document.createElement(\"small\");\n recommendation.textContent = item.recommendation?.code || \"none\";\n stateBlock.append(label, stage, recommendation);\n const ownerEntry = ownerDecisionEntry(item.candidate_id);\n if (ownerEntry) {\n const ownerDecision = document.createElement(\"div\");\n ownerDecision.className = \"owner-decision\";\n const ownerTitle = document.createElement(\"strong\");\n ownerTitle.textContent = t(\"ownerDecisionTitle\");\n const ownerDetail = document.createElement(\"small\");\n if (ownerEntry.intent) {\n ownerDetail.textContent = t(\"ownerDecisionRecorded\")\n .replace(\"{decision}\", ownerDecisionLabel(ownerEntry.intent.decision));\n } else if (!state.auth.admin) {\n ownerDetail.textContent = t(\"ownerDecisionAdminOnly\");\n } else {\n ownerDetail.textContent = t(\"ownerDecisionReady\");\n }\n ownerDecision.append(ownerTitle, ownerDetail);\n if (!ownerEntry.intent && state.auth.admin) {\n const actions = document.createElement(\"div\");\n actions.className = \"owner-decision__actions\";\n const submitting = state.ownerDecisions.submittingCandidateId === item.candidate_id;\n for (const [decision, labelKey] of [\n [\"approve_limited_live_canary\", \"ownerDecisionApprove\"],\n [\"keep_parked\", \"ownerDecisionPark\"],\n [\"retire_candidate\", \"ownerDecisionRetire\"],\n ]) {\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.className = \"owner-decision__button\";\n button.dataset.ownerDecision = decision;\n button.dataset.candidateId = item.candidate_id;\n button.dataset.candidateEvidenceSha256 = ownerEntry.candidate_evidence_sha256;\n button.disabled = submitting;\n button.textContent = submitting ? t(\"ownerDecisionSubmitting\") : t(labelKey);\n actions.appendChild(button);\n }\n ownerDecision.appendChild(actions);\n }\n main.appendChild(ownerDecision);\n }\n card.append(main, stateBlock);\n list.appendChild(card);\n }\n }\n\n const M0_RESEARCH_DISPLAY_LIMIT = 100;\n\n const m0ResearchLabels = {\n subject: {\n asset_idea: { zh: \"资产观察\", en: \"Asset idea\" },\n theme_context: { zh: \"主题背景\", en: \"Theme context\" },\n strategy_hypothesis: { zh: \"研究假设\", en: \"Research hypothesis\" },\n risk_context: { zh: \"风险背景\", en: \"Risk context\" },\n },\n state: {\n candidate: { zh: \"待验证\", en: \"Candidate\" },\n source_verification_required: { zh: \"等待来源核验\", en: \"Source verification required\" },\n deferred: { zh: \"暂缓\", en: \"Deferred\" },\n context_only: { zh: \"仅作背景\", en: \"Context only\" },\n },\n freshness: {\n fresh: { zh: \"有效\", en: \"Fresh\" },\n stale: { zh: \"已过期\", en: \"Stale\" },\n unknown: { zh: \"未知\", en: \"Unknown\" },\n },\n horizon: {\n short: { zh: \"短期\", en: \"Short\" },\n medium: { zh: \"中期\", en: \"Medium\" },\n long: { zh: \"长期\", en: \"Long\" },\n not_applicable: { zh: \"不适用\", en: \"Not applicable\" },\n },\n confidence: {\n high: { zh: \"高\", en: \"High\" },\n medium: { zh: \"中\", en: \"Medium\" },\n low: { zh: \"低\", en: \"Low\" },\n mixed: { zh: \"混合\", en: \"Mixed\" },\n no_event: { zh: \"无事件\", en: \"No event\" },\n unknown: { zh: \"未知\", en: \"Unknown\" },\n },\n style: {\n event_driven: { zh: \"事件驱动\", en: \"Event driven\" },\n long_horizon_growth: { zh: \"长期成长\", en: \"Long-horizon growth\" },\n value_quality: { zh: \"价值质量\", en: \"Value quality\" },\n macro_context: { zh: \"宏观背景\", en: \"Macro context\" },\n mixed_research: { zh: \"综合研究\", en: \"Mixed research\" },\n },\n conflict: {\n none: { zh: \"无\", en: \"None\" },\n conflict: { zh: \"存在\", en: \"Present\" },\n unavailable: { zh: \"不可用\", en: \"Unavailable\" },\n },\n drift: {\n none: { zh: \"无\", en: \"None\" },\n drift: { zh: \"存在\", en: \"Present\" },\n unavailable: { zh: \"不可用\", en: \"Unavailable\" },\n },\n };\n\n function m0ResearchLabel(group, value) {\n const entry = m0ResearchLabels[group]?.[value];\n return entry ? (state.lang === \"zh\" ? entry.zh : entry.en) : \"—\";\n }\n\n function m0ResearchTimestamp(value) {\n if (typeof value !== \"string\" || !value) return \"—\";\n const parsed = new Date(value);\n return Number.isNaN(parsed.getTime()) ? \"—\" : parsed.toLocaleString();\n }\n\n function m0ResearchHorizons(values) {\n if (!Array.isArray(values) || !values.length) return \"—\";\n const labels = values.map((value) => m0ResearchLabel(\"horizon\", value)).filter((value) => value !== \"—\");\n return labels.length ? labels.join(\" / \") : \"—\";\n }\n\n function m0ResearchEntries(subjects) {\n if (!Array.isArray(subjects)) return [];\n const entries = [];\n for (const item of subjects) {\n if (!item || typeof item !== \"object\" || !item.subject || typeof item.subject !== \"object\") continue;\n if (!Array.isArray(item.observations)) continue;\n for (const observation of item.observations) {\n if (!observation || typeof observation !== \"object\" || !observation.research_context) continue;\n entries.push({\n subject: item.subject,\n observation,\n horizonConflict: item.horizon_conflict,\n historicalStaleHorizonDrift: item.historical_stale_horizon_drift,\n });\n }\n }\n return entries;\n }\n\n function normalizeM0ResearchPayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid M0 research payload\");\n const subjects = Array.isArray(payload.subjects) ? payload.subjects : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n viewed_at: typeof payload.viewed_at === \"string\" ? payload.viewed_at : null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n subjects: subjects.filter((item) => item && typeof item === \"object\"\n && item.subject && typeof item.subject === \"object\" && Array.isArray(item.observations)),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function renderM0Research() {\n const payload = state.m0Research.payload;\n const entries = m0ResearchEntries(payload.subjects);\n const shownEntries = entries.slice(0, M0_RESEARCH_DISPLAY_LIMIT);\n const notice = el(\"m0-research-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"m0ResearchLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"m0ResearchStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"m0ResearchUnavailableNotice\");\n } else if (payload.errors?.length) {\n notice.textContent = t(\"m0ResearchUpstreamNotice\").replace(\"{count}\", String(payload.errors.length));\n } else {\n const more = entries.length > M0_RESEARCH_DISPLAY_LIMIT\n ? ` ${t(\"m0ResearchMore\").replace(\"{count}\", String(M0_RESEARCH_DISPLAY_LIMIT))}`\n : \"\";\n notice.textContent = `${t(\"m0ResearchNoOrder\")}${more}`;\n }\n\n const list = el(\"m0-research-list\");\n list.replaceChildren();\n if (!shownEntries.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"m0ResearchEmpty\");\n list.appendChild(empty);\n return;\n }\n for (const entry of shownEntries) {\n const subject = entry.subject || {};\n const observation = entry.observation || {};\n const context = observation.research_context || {};\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = t(\"m0ResearchMeta\")\n .replace(\"{kind}\", m0ResearchLabel(\"subject\", subject.kind))\n .replace(\"{viewed}\", m0ResearchTimestamp(payload.viewed_at));\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = typeof subject.identifier === \"string\" ? subject.identifier : \"—\";\n const stateFreshness = document.createElement(\"p\");\n stateFreshness.className = \"health-card__reason\";\n stateFreshness.textContent = t(\"m0ResearchStateFreshness\")\n .replace(\"{state}\", m0ResearchLabel(\"state\", context.state))\n .replace(\"{freshness}\", m0ResearchLabel(\"freshness\", observation.freshness?.status));\n const horizons = document.createElement(\"div\");\n horizons.className = \"health-card__meta\";\n horizons.textContent = t(\"m0ResearchHorizons\")\n .replace(\"{primary}\", m0ResearchLabel(\"horizon\", context.primary_horizon))\n .replace(\"{suitable}\", m0ResearchHorizons(context.suitable_horizons));\n const evidence = document.createElement(\"div\");\n evidence.className = \"health-card__meta\";\n evidence.textContent = t(\"m0ResearchEvidence\")\n .replace(\"{confidence}\", m0ResearchLabel(\"confidence\", context.source_confidence))\n .replace(\"{style}\", m0ResearchLabel(\"style\", context.source_style))\n .replace(\"{digest}\", `${shortResearchDigest(observation.source_report_digest)} / ${shortResearchDigest(observation.source_entry_digest)}`);\n const consistency = document.createElement(\"div\");\n consistency.className = \"health-card__meta\";\n consistency.textContent = t(\"m0ResearchConsistency\")\n .replace(\"{conflict}\", m0ResearchLabel(\"conflict\", entry.horizonConflict?.status))\n .replace(\"{drift}\", m0ResearchLabel(\"drift\", entry.historicalStaleHorizonDrift?.status));\n main.append(meta, title, stateFreshness, horizons, evidence, consistency);\n const status = document.createElement(\"div\");\n status.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = \"M0\";\n const freshness = document.createElement(\"strong\");\n freshness.textContent = m0ResearchLabel(\"freshness\", observation.freshness?.status);\n const stateLabel = document.createElement(\"small\");\n stateLabel.textContent = m0ResearchLabel(\"state\", context.state);\n status.append(label, freshness, stateLabel);\n card.append(main, status);\n list.appendChild(card);\n }\n }\n\n function normalizeAdaptiveSelectionPayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid adaptive selection payload\");\n const selections = Array.isArray(payload.selections) ? payload.selections : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n selections: selections.filter((item) => item && typeof item === \"object\"\n && typeof item.source_id === \"string\" && item.decision && typeof item.decision === \"object\"),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function renderAdaptiveSelection() {\n const payload = state.adaptiveSelection.payload;\n const notice = el(\"adaptive-selection-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"adaptiveSelectionLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"adaptiveSelectionStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"adaptiveSelectionUnavailableNotice\");\n } else if (payload.errors?.length) {\n notice.textContent = t(\"adaptiveSelectionUpstreamNotice\").replace(\"{count}\", String(payload.errors.length));\n } else {\n notice.textContent = payload.policy?.notice || t(\"adaptiveSelectionNoOrder\");\n }\n\n const list = el(\"adaptive-selection-list\");\n list.replaceChildren();\n if (!payload.selections.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"adaptiveSelectionEmpty\");\n list.appendChild(empty);\n return;\n }\n for (const entry of payload.selections) {\n const decision = entry.decision || {};\n const context = decision.market_context || {};\n const recommended = decision.candidates?.find((item) => item.accepted\n && item.strategy_profile === decision.recommended_strategy_profile) || null;\n const rejected = (decision.candidates || []).filter((item) => !item.accepted);\n const reasons = (recommended?.reasons?.length ? recommended.reasons : rejected.flatMap((item) => item.reasons || []))\n .slice(0, 3)\n .join(\", \");\n\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = t(\"adaptiveSelectionMeta\")\n .replace(\"{source}\", entry.source_id || \"unknown\")\n .replace(\"{domain}\", domainLabel(context.domain || \"\"))\n .replace(\"{asOf}\", context.as_of || \"—\");\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = recommended\n ? `${t(\"adaptiveSelectionRecommended\")} · ${recommended.strategy_profile}`\n : t(\"adaptiveSelectionNoCandidate\");\n const reason = document.createElement(\"p\");\n reason.className = \"health-card__reason\";\n reason.textContent = reasons\n ? t(\"adaptiveSelectionReason\").replace(\"{reasons}\", reasons)\n : t(\"adaptiveSelectionNoOrder\");\n main.append(meta, title, reason);\n const status = document.createElement(\"div\");\n status.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = \"NO ORDER\";\n const score = document.createElement(\"strong\");\n score.textContent = typeof recommended?.score === \"number\" ? recommended.score.toFixed(3) : \"—\";\n const freshness = document.createElement(\"small\");\n freshness.textContent = entry.freshness?.data_status || \"unknown\";\n status.append(label, score, freshness);\n card.append(main, status);\n list.appendChild(card);\n }\n }\n\n function normalizeExecutionEvidencePayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid execution evidence payload\");\n const deployments = Array.isArray(payload.deployments) ? payload.deployments : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n deployments: deployments.filter((item) => item && typeof item === \"object\" && item.deployment && typeof item.deployment === \"object\"),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function renderExecutionEvidence() {\n const payload = state.executionEvidence.payload;\n const notice = el(\"execution-evidence-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"executionEvidenceLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"executionEvidenceStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"executionEvidenceUnavailableNotice\");\n } else if (payload.errors?.length) {\n notice.textContent = t(\"executionEvidenceUpstreamNotice\").replace(\"{count}\", payload.errors.length);\n } else {\n notice.textContent = payload.policy?.notice || t(\"executionEvidenceNoOrder\");\n }\n\n const list = el(\"execution-evidence-list\");\n list.replaceChildren();\n if (!payload.deployments.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"executionEvidenceEmpty\");\n list.appendChild(empty);\n return;\n }\n for (const entry of payload.deployments) {\n const deployment = entry.deployment || {};\n const strategy = deployment.strategy || {};\n const target = deployment.target || {};\n const capabilities = deployment.capabilities || {};\n const evidence = deployment.evidence || {};\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = t(\"executionEvidenceMeta\")\n .replace(\"{platform}\", target.platform || \"unknown\")\n .replace(\"{environment}\", target.environment || \"unknown\")\n .replace(\"{source}\", entry.source_id || \"unknown\");\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = String(strategy.candidate_id || deployment.deployment_id || \"unknown\");\n const reason = document.createElement(\"p\");\n reason.className = \"health-card__reason\";\n reason.textContent = deployment.recommendation?.reason_code || t(\"executionEvidenceNoOrder\");\n const detail = document.createElement(\"div\");\n detail.className = \"health-card__meta\";\n detail.textContent = t(\"executionEvidenceDetail\")\n .replace(\"{strategy}\", evidence.strategy || \"unknown\")\n .replace(\"{data}\", evidence.target_data || \"unknown\")\n .replace(\"{execution}\", evidence.target_execution || \"unknown\")\n .replace(\"{shadow}\", capabilities.shadow || \"unknown\")\n .replace(\"{paper}\", capabilities.paper || \"unknown\");\n main.append(meta, title, reason, detail);\n const stateBlock = document.createElement(\"div\");\n stateBlock.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = t(\"executionEvidenceNext\");\n const recommendation = document.createElement(\"strong\");\n recommendation.textContent = deployment.recommendation?.code || \"parked\";\n const freshness = document.createElement(\"small\");\n freshness.textContent = entry.freshness?.data_status || \"unknown\";\n stateBlock.append(label, recommendation, freshness);\n card.append(main, stateBlock);\n list.appendChild(card);\n }\n }\n\n function normalizeRuntimeTargetLifecyclePayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) {\n throw new Error(\"invalid runtime target lifecycle payload\");\n }\n const targets = Array.isArray(payload.targets) ? payload.targets : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n targets: targets.filter((entry) => entry && typeof entry === \"object\"\n && entry.target && typeof entry.target === \"object\"\n && entry.target.target && typeof entry.target.target === \"object\"\n && entry.target.monitoring && typeof entry.target.monitoring === \"object\"\n && entry.target.disposition && typeof entry.target.disposition === \"object\"),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function runtimeTargetLifecycleCheckLabel(value) {\n return {\n pass: t(\"runtimeTargetLifecycleCheckPass\"),\n attention: t(\"runtimeTargetLifecycleCheckAttention\"),\n not_due: t(\"runtimeTargetLifecycleCheckNotDue\"),\n not_applicable: t(\"runtimeTargetLifecycleCheckNotApplicable\"),\n unavailable: t(\"runtimeTargetLifecycleCheckUnavailable\"),\n }[value] || t(\"runtimeTargetLifecycleCheckUnavailable\");\n }\n\n function runtimeTargetLifecycleStateLabel(value) {\n return value === \"enabled\"\n ? t(\"runtimeTargetLifecycleStateEnabled\")\n : t(\"runtimeTargetLifecycleStateDisabled\");\n }\n\n function runtimeTargetLifecycleDispositionLabel(value) {\n return {\n continue_enabled_monitoring: t(\"runtimeTargetLifecycleDispositionEnabled\"),\n continue_disabled_validation: t(\"runtimeTargetLifecycleDispositionDisabled\"),\n parked: t(\"runtimeTargetLifecycleDispositionParked\"),\n }[value] || t(\"runtimeTargetLifecycleDispositionParked\");\n }\n\n function runtimeTargetLifecycleReasonLabel(value) {\n return {\n none: t(\"runtimeTargetLifecycleReasonNone\"),\n target_intentionally_disabled: t(\"runtimeTargetLifecycleReasonDisabled\"),\n runtime_guard_attention: t(\"runtimeTargetLifecycleReasonRuntimeGuard\"),\n execution_heartbeat_attention: t(\"runtimeTargetLifecycleReasonHeartbeat\"),\n monitoring_unavailable: t(\"runtimeTargetLifecycleReasonUnavailable\"),\n }[value] || t(\"runtimeTargetLifecycleReasonUnavailable\");\n }\n\n function renderRuntimeTargetLifecycle() {\n const payload = state.runtimeTargetLifecycle.payload;\n const notice = el(\"runtime-target-lifecycle-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"runtimeTargetLifecycleLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"runtimeTargetLifecycleStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"runtimeTargetLifecycleUnavailableNotice\");\n } else if (payload.errors?.length) {\n notice.textContent = t(\"runtimeTargetLifecycleUpstreamNotice\").replace(\"{count}\", String(payload.errors.length));\n } else {\n notice.textContent = payload.policy?.notice || t(\"runtimeTargetLifecycleNoOrder\");\n }\n\n const list = el(\"runtime-target-lifecycle-list\");\n list.replaceChildren();\n if (!payload.targets.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"runtimeTargetLifecycleEmpty\");\n list.appendChild(empty);\n return;\n }\n\n const targets = [...payload.targets].sort((left, right) => {\n const leftParked = left.target?.disposition?.code === \"parked\" ? 0 : 1;\n const rightParked = right.target?.disposition?.code === \"parked\" ? 0 : 1;\n if (leftParked !== rightParked) return leftParked - rightParked;\n return String(left.target?.target_id || \"\").localeCompare(String(right.target?.target_id || \"\"));\n });\n for (const entry of targets) {\n const target = entry.target || {};\n const configuration = target.target || {};\n const monitoring = target.monitoring || {};\n const disposition = target.disposition || {};\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = t(\"runtimeTargetLifecycleMeta\")\n .replace(\"{platform}\", configuration.platform || \"unknown\")\n .replace(\"{state}\", runtimeTargetLifecycleStateLabel(configuration.configured_state))\n .replace(\"{mode}\", configuration.execution_mode || \"unknown\");\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = String(target.target_id || \"unknown\");\n const reason = document.createElement(\"p\");\n reason.className = \"health-card__reason\";\n reason.textContent = runtimeTargetLifecycleReasonLabel(disposition.reason_code);\n const detail = document.createElement(\"div\");\n detail.className = \"health-card__meta\";\n detail.textContent = t(\"runtimeTargetLifecycleDetail\")\n .replace(\"{guard}\", runtimeTargetLifecycleCheckLabel(monitoring.runtime_guard))\n .replace(\"{heartbeat}\", runtimeTargetLifecycleCheckLabel(monitoring.execution_heartbeat));\n main.append(meta, title, reason, detail);\n\n const stateBlock = document.createElement(\"div\");\n stateBlock.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = t(\"runtimeTargetLifecycleNext\");\n const current = document.createElement(\"strong\");\n current.textContent = runtimeTargetLifecycleDispositionLabel(disposition.code);\n const freshness = document.createElement(\"small\");\n freshness.textContent = entry.freshness?.data_status || \"unknown\";\n stateBlock.append(label, current, freshness);\n card.append(main, stateBlock);\n list.appendChild(card);\n }\n }\n\n function normalizeResearchTaskPayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid research task payload\");\n const tasks = Array.isArray(payload.tasks) ? payload.tasks : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n tasks: tasks.filter((item) => item && typeof item === \"object\" && item.task && typeof item.task === \"object\"),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function shortResearchDigest(value) {\n const text = String(value || \"\");\n return /^[0-9a-f]{64}$/.test(text) ? `${text.slice(0, 10)}…` : \"—\";\n }\n\n function renderResearchTasks() {\n const payload = state.researchTasks.payload;\n const notice = el(\"research-task-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"researchTaskLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"researchTaskStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"researchTaskUnavailableNotice\");\n } else if (payload.errors?.length) {\n notice.textContent = t(\"researchTaskUpstreamNotice\").replace(\"{count}\", payload.errors.length);\n } else {\n notice.textContent = payload.policy?.notice || t(\"researchTaskNoOrder\");\n }\n\n const list = el(\"research-task-list\");\n list.replaceChildren();\n if (!payload.tasks.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"researchTaskEmpty\");\n list.appendChild(empty);\n return;\n }\n for (const entry of payload.tasks) {\n const task = entry.task || {};\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = t(\"researchTaskMeta\")\n .replace(\"{type}\", task.task_type || \"research\")\n .replace(\"{domain}\", domainLabel(task.target?.domain || \"\"))\n .replace(\"{created}\", task.created_at ? new Date(task.created_at).toLocaleString() : \"—\");\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = `${task.target?.candidate_id || \"unknown\"} · ${task.task_id || \"unknown\"}`;\n const hypothesis = document.createElement(\"p\");\n hypothesis.className = \"health-card__reason\";\n hypothesis.textContent = task.experiment?.hypothesis || t(\"researchTaskNoOrder\");\n const detail = document.createElement(\"div\");\n detail.className = \"health-card__meta\";\n detail.textContent = t(\"researchTaskLimits\")\n .replace(\"{runs}\", String(task.experiment?.max_runs || \"—\"))\n .replace(\"{seconds}\", String(task.experiment?.max_wall_seconds || \"—\"))\n .replace(\"{p1}\", shortResearchDigest(task.evidence?.p1_input_digest))\n .replace(\"{p2}\", shortResearchDigest(task.evidence?.p2_config_digest))\n .replace(\"{p3}\", shortResearchDigest(task.evidence?.p3_evidence_id));\n main.append(meta, title, hypothesis, detail);\n const status = document.createElement(\"div\");\n status.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = \"NO ORDER\";\n const count = document.createElement(\"strong\");\n count.textContent = String(task.experiment?.max_runs || \"—\");\n const source = document.createElement(\"small\");\n source.textContent = entry.freshness?.data_status || \"unknown\";\n status.append(label, count, source);\n card.append(main, status);\n list.appendChild(card);\n }\n }\n\n function healthStatusLabel(status) {\n return { healthy: \"健康\", watch: \"观察\", review: \"复核\", critical: \"严重\" }[status] || \"未知\";\n }\n\n function normalizeHealthPayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid health payload\");\n const strategies = Array.isArray(payload.strategies) ? payload.strategies : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n strategies: strategies.filter((item) => item && typeof item === \"object\" && [\"healthy\", \"watch\", \"review\", \"critical\"].includes(item.status)),\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function renderHealth() {\n const payload = state.health.payload;\n const summary = payload.summary || {};\n const statusText = payload.data_status === \"ready\"\n ? \"快照已加载\"\n : (payload.data_status === \"stale\" ? \"快照已过期\" : \"等待可用快照\");\n el(\"health-status\").textContent = statusText;\n el(\"health-computed-at\").textContent = payload.computed_at\n ? `最近计算:${new Date(payload.computed_at).toLocaleString()}`\n : \"最近计算:—\";\n el(\"health-count-total\").textContent = String(Number(summary.strategy_count) || 0);\n el(\"health-count-healthy\").textContent = String(Number(summary.healthy) || 0);\n el(\"health-count-watch\").textContent = String(Number(summary.watch) || 0);\n el(\"health-count-review\").textContent = String(Number(summary.review) || 0);\n el(\"health-count-critical\").textContent = String(Number(summary.critical) || 0);\n\n const notice = el(\"health-notice\");\n if (!state.auth.allowed) {\n notice.textContent = \"登录后读取私有策略健康快照;没有快照时不会展示虚构指标。\";\n } else if (payload.data_status === \"stale\") {\n notice.textContent = \"健康快照已超过允许的新鲜度窗口;页面保留原始状态,但不会把它当作当前健康结论。\";\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = \"还没有可用的策略健康快照;当前页面保持 fail-closed 空状态。\";\n } else if (payload.errors?.length) {\n notice.textContent = `快照已加载,但有 ${payload.errors.length} 个上游提示;缺失数据不会被替换成虚构指标。`;\n } else {\n notice.textContent = \"健康不等于已批准 live;正常实盘、资金和杠杆变更仍需人工确认。\";\n }\n\n const list = el(\"health-list\");\n list.replaceChildren();\n const strategies = payload.strategies.filter((item) => state.health.filter === \"all\" || item.status === state.health.filter);\n if (!strategies.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = \"暂无可展示的策略健康快照。\";\n list.appendChild(empty);\n return;\n }\n for (const item of strategies) {\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = `${healthStatusLabel(item.status)} · ${item.domain || \"unknown\"}`;\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = String(item.profile || \"unknown\");\n const reason = document.createElement(\"p\");\n reason.className = \"health-card__reason\";\n reason.textContent = `${item.decision?.label || \"证据不足,保持研究态\"}。${item.decision?.reason || \"没有可用的机器检查结果。\"}`;\n const detail = document.createElement(\"div\");\n detail.className = \"health-card__meta\";\n detail.textContent = `阶段:${item.review?.requested_stage || \"未标记\"} · 截至:${item.as_of || \"—\"}`;\n main.append(meta, title, reason, detail);\n const scoreBlock = document.createElement(\"div\");\n scoreBlock.className = \"health-card__score\";\n const scoreLabel = document.createElement(\"small\");\n scoreLabel.textContent = \"HEALTH\";\n const score = document.createElement(\"strong\");\n score.textContent = typeof item.score === \"number\" ? item.score.toFixed(1) : \"—\";\n const decision = document.createElement(\"small\");\n decision.textContent = item.decision?.code || \"evidence_missing\";\n scoreBlock.append(scoreLabel, score, decision);\n card.append(main, scoreBlock);\n list.appendChild(card);\n }\n }\n\n function renderConsoleView() {\n const controlButton = el(\"control-plane-view-button\");\n const healthButton = el(\"health-view-button\");\n const switchButton = el(\"switch-view-button\");\n const controlVisible = state.view === \"control\";\n const healthVisible = state.view === \"health\";\n el(\"control-plane-view\").hidden = !controlVisible;\n el(\"health-view\").hidden = !healthVisible;\n el(\"switch-view\").hidden = controlVisible || healthVisible;\n controlButton.classList.toggle(\"active\", controlVisible);\n healthButton.classList.toggle(\"active\", healthVisible);\n switchButton.classList.toggle(\"active\", !controlVisible && !healthVisible);\n }\n\n function render() {\n applyLanguage();\n renderRuntimeAuthorityStatus();\n renderConsoleView();\n renderControlPlane();\n renderM0Research();\n renderAdaptiveSelection();\n renderExecutionEvidence();\n renderRuntimeTargetLifecycle();\n renderResearchTasks();\n renderHealth();\n renderPlatforms();\n renderControls();\n renderSummary();\n renderPlanReadiness();\n renderAuth();\n renderAppVisibility();\n }\n\n async function refreshSession() {\n state.bootMessageKey = \"bootSession\";\n render();\n try {\n const session = await requestJson(\"/api/session\");\n state.auth = {\n available: true,\n allowed: Boolean(session.allowed),\n admin: Boolean(session.admin),\n login: session.login || null,\n };\n } catch {\n state.auth = { available: false, allowed: false, admin: false, login: null };\n }\n if (state.auth.allowed) {\n await refreshControlPlane();\n await refreshOwnerDecisions();\n await refreshM0Research();\n await refreshAdaptiveSelection();\n await refreshExecutionEvidence();\n await refreshRuntimeTargetLifecycle();\n await refreshResearchTasks();\n await refreshHealth();\n await refreshConfig();\n } else {\n state.bootMessageKey = \"bootPublic\";\n state.appReady = true;\n render();\n }\n }\n\n async function refreshHealth() {\n if (!state.auth.allowed) {\n renderHealth();\n return;\n }\n try {\n state.health.payload = normalizeHealthPayload(await requestJson(\"/api/strategy-health\"));\n } catch {\n state.health.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { strategy_count: 0, healthy: 0, watch: 0, review: 0, critical: 0 },\n strategies: [],\n errors: [\"health_request_failed\"],\n };\n }\n renderHealth();\n }\n\n async function refreshControlPlane() {\n if (!state.auth.allowed) {\n renderControlPlane();\n return;\n }\n try {\n state.controlPlane.payload = normalizeControlPlanePayload(await requestJson(\"/api/control-plane\"));\n } catch {\n state.controlPlane.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { candidate_count: 0, deferred: 0, parked: 0, owner_decision_required: 0 },\n attention: { status: \"unavailable\", reason_codes: [\"control_plane_request_failed\"] },\n candidates: [],\n policy: { p4_p5_automation: \"not_configured\", p6_owner_decision_required: true },\n errors: [\"control_plane_request_failed\"],\n };\n }\n renderControlPlane();\n }\n\n async function refreshOwnerDecisions() {\n if (!state.auth.allowed) {\n state.ownerDecisions = { data_status: \"unavailable\", candidates: [], errors: [] };\n renderControlPlane();\n return;\n }\n try {\n state.ownerDecisions = normalizeOwnerDecisionQueue(await requestJson(\"/api/owner-decisions\"));\n } catch {\n state.ownerDecisions = {\n data_status: \"unavailable\",\n candidates: [],\n errors: [\"owner_decision_queue_request_failed\"],\n };\n }\n renderControlPlane();\n }\n\n async function refreshM0Research() {\n if (!state.auth.allowed) {\n renderM0Research();\n return;\n }\n try {\n state.m0Research.payload = normalizeM0ResearchPayload(await requestJson(\"/api/m0-research\"));\n } catch {\n state.m0Research.payload = {\n data_status: \"unavailable\",\n viewed_at: null,\n summary: {\n subject_count: 0,\n observation_count: 0,\n fresh_observation_count: 0,\n stale_observation_count: 0,\n unknown_observation_count: 0,\n horizon_conflict_count: 0,\n historical_stale_horizon_drift_count: 0,\n },\n subjects: [],\n policy: { authority: \"research_only\", no_order: true, permitted_next_step: \"research_validation_only\" },\n errors: [\"m0_research_request_failed\"],\n };\n }\n renderM0Research();\n }\n\n async function refreshAdaptiveSelection() {\n if (!state.auth.allowed) {\n renderAdaptiveSelection();\n return;\n }\n try {\n state.adaptiveSelection.payload = normalizeAdaptiveSelectionPayload(await requestJson(\"/api/adaptive-selection\"));\n } catch {\n state.adaptiveSelection.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { source_count: 0, decision_count: 0, candidate_count: 0, recommended_count: 0, rejected_candidate_count: 0 },\n selections: [],\n policy: { authority: \"shadow_only\", no_order: true, execution_authority_granted: false },\n errors: [\"adaptive_selection_request_failed\"],\n };\n }\n renderAdaptiveSelection();\n }\n\n async function recordOwnerDecision(button) {\n if (!state.auth.admin) return;\n const candidateId = String(button.dataset.candidateId || \"\");\n const candidateEvidenceSha256 = String(button.dataset.candidateEvidenceSha256 || \"\");\n const decision = String(button.dataset.ownerDecision || \"\");\n if (!candidateId || !candidateEvidenceSha256 || !decision) return;\n if (!window.confirm(t(\"ownerDecisionConfirm\"))) return;\n state.ownerDecisions.submittingCandidateId = candidateId;\n renderControlPlane();\n try {\n const response = await fetch(\"/api/owner-decisions\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n candidate_id: candidateId,\n decision,\n candidate_evidence_sha256: candidateEvidenceSha256,\n }),\n });\n const payload = await response.json();\n if (!response.ok || !payload.ok) throw new Error(payload.error || t(\"ownerDecisionFailed\"));\n showToast(t(\"ownerDecisionSuccess\"), { duration: 7000 });\n await refreshControlPlane();\n await refreshOwnerDecisions();\n } catch (error) {\n showToast(`${t(\"ownerDecisionFailed\")}: ${error.message}`, { duration: 12000 });\n } finally {\n delete state.ownerDecisions.submittingCandidateId;\n renderControlPlane();\n }\n }\n\n async function refreshExecutionEvidence() {\n if (!state.auth.allowed) {\n renderExecutionEvidence();\n return;\n }\n try {\n state.executionEvidence.payload = normalizeExecutionEvidencePayload(await requestJson(\"/api/execution-evidence\"));\n } catch {\n state.executionEvidence.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { deployment_count: 0, autonomous_shadow: 0, autonomous_paper: 0, owner_canary_decision: 0, parked: 0 },\n deployments: [],\n policy: { execution_evidence_read_only: true, p6_owner_decision_required: true, limited_live_canary_active: false },\n errors: [\"execution_evidence_request_failed\"],\n };\n }\n renderExecutionEvidence();\n }\n\n async function refreshRuntimeTargetLifecycle() {\n if (!state.auth.allowed) {\n renderRuntimeTargetLifecycle();\n return;\n }\n try {\n state.runtimeTargetLifecycle.payload = normalizeRuntimeTargetLifecyclePayload(\n await requestJson(\"/api/runtime-target-lifecycle\"),\n );\n } catch {\n state.runtimeTargetLifecycle.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { target_count: 0, enabled: 0, disabled: 0, attention: 0 },\n targets: [],\n policy: { lifecycle_status_read_only: true, no_order: true },\n errors: [\"runtime_target_lifecycle_request_failed\"],\n };\n }\n renderRuntimeTargetLifecycle();\n }\n\n async function refreshResearchTasks() {\n if (!state.auth.allowed) {\n renderResearchTasks();\n return;\n }\n try {\n state.researchTasks.payload = normalizeResearchTaskPayload(await requestJson(\"/api/research-tasks\"));\n } catch {\n state.researchTasks.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { task_count: 0 },\n tasks: [],\n policy: { research_only: true, no_order: true, size_zero_required: true, p4_p5_p6_authorized: false },\n errors: [\"research_task_request_failed\"],\n };\n }\n renderResearchTasks();\n }\n\n async function refreshStrategyProfiles() {\n state.bootMessageKey = \"bootStrategy\";\n render();\n try {\n const payload = await requestJson(\"/api/strategy-profiles\");\n applyStrategyProfiles(payload.strategyProfiles || []);\n if (payload.platformMeta) platformMeta = payload.platformMeta;\n for (const platform of Object.keys(platformMeta)) syncStrategyForAccount(platform);\n render();\n } catch {\n applyStrategyProfiles(defaultStrategyProfiles);\n for (const platform of Object.keys(platformMeta)) syncStrategyForAccount(platform);\n }\n }\n\n async function refreshConfig() {\n if (!state.auth.available || !state.auth.allowed) return;\n state.configSource = \"loading\";\n state.bootMessageKey = \"bootConfig\";\n render();\n try {\n const payload = await requestJson(\"/api/config\");\n if (payload.accountOptions) {\n applyStrategyProfiles(payload.strategyProfiles || defaultStrategyProfiles);\n state.accountOptions = normalizeAccountOptions(payload.accountOptions);\n if (payload.platformMeta) platformMeta = payload.platformMeta;\n state.repositories = normalizePlatformRepositories(payload.platformRepositories || {});\n state.currentStrategies = normalizeCurrentStrategies(payload.currentStrategies || {});\n state.configSource = \"private\";\n for (const platform of Object.keys(platformMeta)) {\n ensureAccountSelection(platform);\n syncStrategyForAccount(platform);\n }\n } else {\n state.configSource = \"default\";\n state.currentStrategies = {};\n }\n } catch (error) {\n state.configSource = \"default\";\n state.currentStrategies = {};\n if (isRequestTimeoutError(error)) {\n state.bootMessageKey = \"bootTimeout\";\n } else {\n state.bootMessageKey = \"bootPublic\";\n }\n } finally {\n state.appReady = true;\n render();\n }\n }\n\n function normalizeAccountOptions(raw) {\n const normalized = clone(defaultAccountOptions);\n for (const platform of Object.keys(platformMeta)) {\n if (!Array.isArray(raw[platform]) || !raw[platform].length) continue;\n normalized[platform] = raw[platform].map((item, index) => ({\n key: String(item.key || item.target_name || index),\n label: String(item.label || item.target_name || item.key || platform),\n target_name: String(item.target_name || item.key || \"\"),\n account_selector: item.account_selector ? String(item.account_selector) : \"\",\n deployment_selector: item.deployment_selector ? String(item.deployment_selector) : \"\",\n account_scope: item.account_scope ? String(item.account_scope) : \"\",\n service_name: item.service_name ? String(item.service_name) : \"\",\n cash_currency: item.cash_currency || item.market_currency || item.trading_currency\n ? String(item.cash_currency || item.market_currency || item.trading_currency).trim().toUpperCase()\n : \"\",\n supported_domains: normalizeSupportedDomains(platform, item),\n github_environment: item.github_environment ? String(item.github_environment) : \"\",\n variable_scope: item.variable_scope ? String(item.variable_scope) : \"\",\n plugin_mode: item.plugin_mode ? String(item.plugin_mode) : \"\",\n option_overlay_mode: item.option_overlay_mode ? normalizeOptionOverlayMode(item.option_overlay_mode) : \"\",\n cash_only_execution_mode: item.cash_only_execution_mode\n ? normalizeCashOnlyExecutionMode(item.cash_only_execution_mode)\n : \"\",\n dca_mode: item.dca_mode ? normalizeDcaMode(item.dca_mode) : \"\",\n dca_base_investment_usd: cleanDisplayPositiveNumber(item.dca_base_investment_usd),\n }));\n }\n return normalized;\n }\n\n function normalizeSupportedDomains(platform, item) {\n const raw = Array.isArray(item?.supported_domains)\n ? item.supported_domains\n : String(item?.supported_domains || \"\").split(/[\\s,;]+/);\n const cleaned = raw.map(cleanStrategyDomain).filter(Boolean);\n if (cleaned.length) return [...new Set(cleaned)];\n return inferSupportedDomains(platform, item || {});\n }\n\n function normalizeCurrentStrategies(raw) {\n const normalized = {};\n for (const platform of Object.keys(platformMeta)) {\n if (!raw[platform] || typeof raw[platform] !== \"object\" || Array.isArray(raw[platform])) continue;\n normalized[platform] = {};\n for (const [key, entry] of Object.entries(raw[platform])) {\n const profile = cleanStrategyProfile(entry?.strategy_profile);\n const minReservedCashUsd = cleanDisplayNumber(entry?.min_reserved_cash_usd ?? entry?.reserved_cash_floor_usd);\n const reservedCashRatio = cleanDisplayRatio(entry?.reserved_cash_ratio);\n const incomeLayerEnabled = cleanOptionalBoolean(entry?.income_layer_enabled);\n const incomeLayerStartUsd = cleanDisplayNumber(entry?.income_layer_start_usd);\n const incomeLayerMaxRatio = cleanDisplayRatio(entry?.income_layer_max_ratio);\n const optionOverlayEnabled = cleanOptionalBoolean(entry?.option_overlay_enabled);\n const cashOnlyExecution = cleanOptionalBoolean(entry?.cash_only_execution);\n const runtimeTargetEnabled = cleanOptionalBoolean(entry?.runtime_target_enabled);\n const dcaMode = entry?.dca_mode ? normalizeDcaMode(entry.dca_mode) : \"\";\n const dcaBaseInvestmentUsd = cleanDisplayPositiveNumber(entry?.dca_base_investment_usd);\n const executionMode = normalizeExecutionMode(entry?.execution_mode, entry?.dry_run_only);\n if (\n !profile &&\n !minReservedCashUsd &&\n !reservedCashRatio &&\n incomeLayerEnabled === null &&\n !incomeLayerStartUsd &&\n !incomeLayerMaxRatio &&\n optionOverlayEnabled === null &&\n cashOnlyExecution === null &&\n runtimeTargetEnabled === null &&\n !dcaMode &&\n !dcaBaseInvestmentUsd &&\n !executionMode\n ) continue;\n normalized[platform][String(key)] = {\n strategy_profile: profile,\n execution_mode: executionMode,\n dry_run_only: entry?.dry_run_only === true || entry?.dry_run_only === \"true\" || entry?.dry_run_only === \"1\",\n min_reserved_cash_usd: minReservedCashUsd,\n reserved_cash_ratio: reservedCashRatio,\n income_layer_enabled: incomeLayerEnabled,\n income_layer_start_usd: incomeLayerStartUsd,\n income_layer_max_ratio: incomeLayerMaxRatio,\n option_overlay_enabled: optionOverlayEnabled,\n cash_only_execution: cashOnlyExecution,\n runtime_target_enabled: runtimeTargetEnabled,\n dca_mode: dcaMode,\n dca_base_investment_usd: dcaBaseInvestmentUsd,\n source: entry?.source ? String(entry.source) : \"\",\n };\n }\n if (!Object.keys(normalized[platform]).length) delete normalized[platform];\n }\n return normalized;\n }\n\n function normalizePlatformRepositories(raw) {\n const normalized = clone(defaultRepositories);\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return normalized;\n for (const platform of Object.keys(platformMeta)) {\n const repository = String(raw[platform] || \"\").trim();\n if (/^[A-Za-z0-9_.-]+\\/[A-Za-z0-9_.-]+$/.test(repository)) {\n normalized[platform] = repository;\n }\n }\n return normalized;\n }\n\n async function dispatchSwitch() {\n if (!state.auth.allowed) return;\n showToast(t(\"dispatching\"), { duration: 0 });\n try {\n const response = await fetch(\"/api/switch\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(buildInputs()),\n });\n const payload = await response.json();\n if (!response.ok || !payload.ok) throw new Error(payload.error || t(\"dispatchFailed\"));\n showToast(t(\"dispatched\"), { duration: 4000 });\n if (payload.actions_url) window.open(payload.actions_url, \"_blank\", \"noopener,noreferrer\");\n await refreshConfig();\n } catch (error) {\n showToast(`${t(\"dispatchFailed\")}: ${error.message}`, { duration: 12000 });\n }\n }\n\n async function handleLogout() {\n await fetch(\"/api/logout\", { method: \"POST\" });\n window.location.reload();\n }\n\n function summaryText() {\n const inputs = buildInputs();\n return summaryRows(inputs).map(([label, value]) => `${label}: ${value}`).join(\"\\\n\");\n }\n\n document.querySelectorAll(\"[data-view]\").forEach((button) => button.addEventListener(\"click\", () => {\n state.view = [\"control\", \"health\", \"switch\"].includes(button.dataset.view) ? button.dataset.view : \"control\";\n renderConsoleView();\n if (state.view === \"control\") {\n refreshControlPlane();\n refreshOwnerDecisions();\n refreshM0Research();\n refreshAdaptiveSelection();\n refreshExecutionEvidence();\n refreshRuntimeTargetLifecycle();\n refreshResearchTasks();\n }\n if (state.view === \"health\") refreshHealth();\n }));\n\n document.querySelectorAll(\"[data-health-filter]\").forEach((button) => button.addEventListener(\"click\", () => {\n document.querySelectorAll(\"[data-health-filter]\").forEach((node) => node.classList.remove(\"active\"));\n button.classList.add(\"active\");\n state.health.filter = button.dataset.healthFilter;\n renderHealth();\n }));\n\n el(\"control-plane-list\").addEventListener(\"click\", (event) => {\n const button = event.target.closest(\"[data-owner-decision]\");\n if (!button || button.disabled) return;\n recordOwnerDecision(button);\n });\n\n el(\"platform-strip\").addEventListener(\"click\", (event) => {\n const button = event.target.closest(\"[data-platform]\");\n if (!button) return;\n state.selected = button.dataset.platform;\n state.forms[state.selected].strategyTouched = false;\n render();\n });\n\n el(\"account-select\").addEventListener(\"change\", () => {\n state.forms[state.selected].accountKey = el(\"account-select\").value;\n state.forms[state.selected].runtimeTargetTouched = false;\n state.forms[state.selected].reservedCashTouched = false;\n state.forms[state.selected].incomeLayerTouched = false;\n state.forms[state.selected].optionOverlayTouched = false;\n state.forms[state.selected].cashOnlyExecutionTouched = false;\n state.forms[state.selected].dcaTouched = false;\n state.forms[state.selected].strategyTouched = false;\n syncStrategyForAccount(state.selected);\n render();\n });\n\n el(\"strategy-select\").addEventListener(\"change\", () => {\n state.forms[state.selected].strategy = el(\"strategy-select\").value;\n state.forms[state.selected].strategyTouched = true;\n state.forms[state.selected].incomeLayerTouched = false;\n state.forms[state.selected].optionOverlayTouched = false;\n state.forms[state.selected].dcaTouched = false;\n syncIncomeLayerForAccount(state.selected);\n syncOptionOverlayForAccount(state.selected);\n syncDcaForAccount(state.selected);\n render();\n });\n\n el(\"mode-control\").addEventListener(\"click\", (event) => {\n const button = event.target.closest(\"[data-mode]\");\n if (!button || button.disabled) return;\n if (!supportedExecutionModesForPlatform(state.selected).includes(button.dataset.mode)) return;\n state.forms[state.selected].executionMode = button.dataset.mode;\n render();\n });\n\n el(\"plugin-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.pluginMode = normalizePluginMode(el(\"plugin-mode-select\").value);\n render();\n });\n\n el(\"runtime-target-enabled-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.runtimeTargetMode = normalizeRuntimeTargetMode(el(\"runtime-target-enabled-select\").value);\n form.runtimeTargetTouched = form.runtimeTargetMode !== \"current\";\n render();\n });\n\n el(\"income-layer-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.incomeLayerMode = normalizeIncomeLayerMode(el(\"income-layer-mode-select\").value);\n form.incomeLayerTouched = form.incomeLayerMode !== \"current\";\n if (form.incomeLayerMode === \"current\") {\n form.incomeLayerTouched = false;\n syncIncomeLayerForAccount(state.selected);\n }\n render();\n });\n\n el(\"income-layer-start-usd-input\").addEventListener(\"input\", () => {\n const form = state.forms[state.selected];\n form.incomeLayerTouched = true;\n form.incomeLayerMode = \"enabled\";\n form.incomeLayerStartUsd = el(\"income-layer-start-usd-input\").value.trim();\n render();\n });\n\n el(\"income-layer-max-ratio-input\").addEventListener(\"input\", () => {\n const form = state.forms[state.selected];\n form.incomeLayerTouched = true;\n form.incomeLayerMode = \"enabled\";\n form.incomeLayerMaxRatio = el(\"income-layer-max-ratio-input\").value.trim();\n render();\n });\n\n el(\"option-overlay-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.optionOverlayMode = normalizeOptionOverlayMode(el(\"option-overlay-mode-select\").value);\n form.optionOverlayTouched = form.optionOverlayMode !== \"current\";\n render();\n });\n\n el(\"cash-only-execution-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.cashOnlyExecutionMode = normalizeCashOnlyExecutionMode(el(\"cash-only-execution-mode-select\").value);\n form.cashOnlyExecutionTouched = form.cashOnlyExecutionMode !== \"current\";\n if (allowMarginExplicitlySelected(form)) reconcileExecutionCashPolicy(form, \"margin\");\n else restoreReserveAfterMarginDisabled(form);\n render();\n });\n\n el(\"dca-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.dcaTouched = true;\n form.dcaMode = normalizeDcaMode(el(\"dca-mode-select\").value);\n render();\n });\n\n el(\"dca-base-investment-usd-input\").addEventListener(\"input\", () => {\n const form = state.forms[state.selected];\n form.dcaTouched = true;\n form.dcaBaseInvestmentUsd = el(\"dca-base-investment-usd-input\").value.trim();\n render();\n });\n\n el(\"reserve-policy-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.reservePolicyMode = normalizeReservePolicyMode(el(\"reserve-policy-mode-select\").value);\n form.reservedCashTouched = form.reservePolicyMode !== \"current\";\n if (form.reservePolicyMode === \"current\") syncReservePolicyForAccount(state.selected);\n reconcileExecutionCashPolicy(form, \"reserve\");\n render();\n });\n\n el(\"min-reserved-cash-input\").addEventListener(\"input\", () => {\n state.forms[state.selected].reservedCashTouched = true;\n state.forms[state.selected].minReservedCashUsd = el(\"min-reserved-cash-input\").value.trim();\n render();\n });\n\n el(\"reserved-cash-ratio-input\").addEventListener(\"input\", () => {\n state.forms[state.selected].reservedCashTouched = true;\n state.forms[state.selected].reservedCashRatio = el(\"reserved-cash-ratio-input\").value.trim();\n render();\n });\n\n el(\"copy-button\").addEventListener(\"click\", async () => {\n try {\n await navigator.clipboard.writeText(summaryText());\n showToast(t(\"copied\"), { duration: 3000 });\n } catch {\n showToast(summaryText(), { duration: 0 });\n }\n });\n\n el(\"dispatch-button\").addEventListener(\"click\", dispatchSwitch);\n el(\"logout-button\").addEventListener(\"click\", handleLogout);\n el(\"lang-button\").addEventListener(\"click\", () => {\n state.lang = state.lang === \"zh\" ? \"en\" : \"zh\";\n localStorage.setItem(\"qsl-switch-lang\", state.lang);\n render();\n });\n\n applyStrategyProfiles(defaultStrategyProfiles);\n for (const platform of Object.keys(platformMeta)) syncStrategyForAccount(platform);\n render();\n boot();\n\n async function boot() {\n try {\n await refreshStrategyProfiles();\n await refreshSession();\n } catch {\n state.auth = { available: false, allowed: false, admin: false, login: null };\n state.configSource = \"default\";\n state.currentStrategies = {};\n state.bootMessageKey = \"bootTimeout\";\n state.appReady = true;\n render();\n }\n }\n"; +export const APP_JS = "\n\n let platformMeta = {\n binance: { label: \"Binance\", code: \"BN\", accent: \"var(--bn)\" },\n firstrade: { label: \"Firstrade\", code: \"FT\", accent: \"var(--ft)\" },\n ibkr: { label: \"IBKR\", code: \"IB\", accent: \"var(--ib)\" },\n longbridge: { label: \"LongBridge\", code: \"LB\", accent: \"var(--lb)\" },\n qmt: { label: \"QMT\", code: \"QM\", accent: \"var(--qmt)\" },\n schwab: { label: \"Schwab\", code: \"SW\", accent: \"var(--sw)\" },\n };\n\n const platformRepositories = {\n binance: \"QuantStrategyLab/BinancePlatform\",\n firstrade: \"QuantStrategyLab/FirstradePlatform\",\n ibkr: \"QuantStrategyLab/InteractiveBrokersPlatform\",\n longbridge: \"QuantStrategyLab/LongBridgePlatform\",\n qmt: \"QuantStrategyLab/QmtPlatform\",\n schwab: \"QuantStrategyLab/CharlesSchwabPlatform\",\n };\n // Alias for backward compatibility\n const defaultRepositories = platformRepositories;\n\n const defaultAccountOptions = window.__DEFAULT_ACCOUNT_OPTIONS__ || {\n binance: [{\"key\": \"default\", \"label\": \"Binance\", \"target_name\": \"default\", \"cash_currency\": \"USD\", \"supported_domains\": [\"crypto\"]}],\n firstrade: [{\"key\": \"preview\", \"label\": \"Firstrade\", \"target_name\": \"preview\", \"supported_domains\": [\"us_equity\"], \"cash_currency\": \"USD\", \"default_execution_mode\": \"live\", \"service_name\": \"firstrade-quant-service\"}],\n ibkr: [{\"key\": \"preview\", \"label\": \"IBKR\", \"target_name\": \"preview\", \"supported_domains\": [\"us_equity\", \"hk_equity\"], \"cash_currency\": \"USD\", \"default_execution_mode\": \"live\"}],\n longbridge: [{\"key\": \"preview\", \"label\": \"LongBridge\", \"target_name\": \"preview\", \"supported_domains\": [\"us_equity\", \"hk_equity\"], \"cash_currency\": \"USD\", \"default_execution_mode\": \"live\"}],\n qmt: [{\"key\": \"default\", \"label\": \"QMT\", \"target_name\": \"default\", \"cash_currency\": \"CNY\", \"supported_domains\": [\"cn_equity\"], \"service_name\": \"qmt-quant-service\"}],\n schwab: [{\"key\": \"preview\", \"label\": \"Schwab\", \"target_name\": \"preview\", \"supported_domains\": [\"us_equity\"], \"cash_currency\": \"USD\", \"default_execution_mode\": \"live\", \"service_name\": \"charles-schwab-quant-service\"}],\n };\n\n const domainLabels = window.__DOMAIN_LABELS__ || {\n cn_equity: { zh: \"A股\", en: \"CN A-share\" },\n crypto: { zh: \"加密\", en: \"Crypto\" },\n hk_equity: { zh: \"港股\", en: \"HK Equity\" },\n us_equity: { zh: \"美股\", en: \"US Equity\" },\n };\n\n const platformConfig = window.__PLATFORM_CONFIG__ || {\n binance: {\n dry_run_only: false,\n margin_policy: false,\n reserved_cash: false,\n income_layer: false,\n option_overlay: false,\n dca: false,\n execution_mode: \"live\",\n service_name: \"\",\n default_execution_mode: \"live\"\n },\n firstrade: {\n dry_run_only: false,\n margin_policy: true,\n reserved_cash: true,\n income_layer: true,\n option_overlay: true,\n dca: true,\n execution_mode: \"live\",\n service_name: \"firstrade-quant-service\",\n default_execution_mode: \"live\"\n },\n ibkr: {\n dry_run_only: false,\n margin_policy: true,\n reserved_cash: true,\n income_layer: true,\n option_overlay: true,\n dca: true,\n execution_mode: \"live\",\n service_name: \"\",\n default_execution_mode: \"live\"\n },\n longbridge: {\n dry_run_only: false,\n margin_policy: true,\n reserved_cash: true,\n income_layer: true,\n option_overlay: true,\n dca: true,\n execution_mode: \"live\",\n service_name: \"\",\n default_execution_mode: \"live\"\n },\n qmt: {\n dry_run_only: true,\n margin_policy: false,\n reserved_cash: false,\n income_layer: false,\n option_overlay: false,\n dca: false,\n execution_mode: \"paper\",\n service_name: \"qmt-quant-service\",\n default_execution_mode: \"paper\"\n },\n schwab: {\n dry_run_only: false,\n margin_policy: true,\n reserved_cash: true,\n income_layer: true,\n option_overlay: true,\n dca: true,\n execution_mode: \"live\",\n service_name: \"charles-schwab-quant-service\",\n default_execution_mode: \"live\"\n },\n };\n const runtimeAuthorityStatus = window.__QSL_RUNTIME_AUTHORITY_STATUS__ || {};\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n const reservePolicyModes = [\"none\", \"ratio\", \"floor\", \"max\"];\n const incomeLayerModes = [\"enabled\", \"disabled\"];\n const optionOverlayModes = [\"enabled\", \"disabled\"];\n const cashOnlyExecutionModes = [\"enabled\", \"disabled\"];\n const runtimeTargetModes = [\"enabled\", \"disabled\"];\n const pluginModes = [\"none\"];\n const dcaModes = [\"fixed\", \"smart\"];\n const runtimeTargetEnabledVariable = \"RUNTIME_TARGET_ENABLED\";\n const incomeLayerEnabledVariable = \"INCOME_LAYER_ENABLED\";\n const incomeLayerStartUsdVariable = \"INCOME_LAYER_START_USD\";\n const incomeLayerMaxRatioVariable = \"INCOME_LAYER_MAX_RATIO\";\n const dcaProfileDefaults = window.__DCA_PROFILE_DEFAULTS__ || {\n nasdaq_sp500_smart_dca: { defaultMode: \"fixed\", defaultBaseInvestmentUsd: \"1000\" },\n ibit_smart_dca: { defaultMode: \"fixed\", defaultBaseInvestmentUsd: \"1000\" },\n };\n const APP_BOOT_TIMEOUT_MS = 15000;\n const platformMinReservedCashVariables = {\n longbridge: \"LONGBRIDGE_MIN_RESERVED_CASH_USD\",\n ibkr: \"IBKR_MIN_RESERVED_CASH_USD\",\n schwab: \"SCHWAB_MIN_RESERVED_CASH_USD\",\n firstrade: \"FIRSTRADE_MIN_RESERVED_CASH_USD\",\n };\n const platformReservedCashRatioVariables = {\n longbridge: \"LONGBRIDGE_RESERVED_CASH_RATIO\",\n ibkr: \"IBKR_RESERVED_CASH_RATIO\",\n schwab: \"SCHWAB_RESERVED_CASH_RATIO\",\n firstrade: \"FIRSTRADE_RESERVED_CASH_RATIO\",\n };\n\n const defaultStrategyProfiles = window.__DEFAULT_STRATEGY_PROFILES__ || [\n {\n \"profile\": \"tqqq_growth_income\",\n \"label\": \"纳斯达克增长收益\",\n \"label_en\": \"NASDAQ Growth Income\",\n \"label_zh\": \"纳斯达克增长收益\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": true,\n \"option_overlay_enabled\": true,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\",\n \"income_layer_start_usd\": \"250000\",\n \"income_layer_max_ratio\": \"0.55\",\n \"income_layer_allocations\": {\n \"SCHD\": 0.3,\n \"DGRO\": 0.2,\n \"SGOV\": 0.4,\n \"SPYI\": 0.08,\n \"QQQI\": 0.02\n },\n \"option_overlay_live_gate\": \"promotion_required\",\n \"option_overlay_live_status\": \"research_only\",\n \"option_growth_overlay_enabled\": true,\n \"option_growth_overlay_recipe\": \"tqqq_leaps_growth_v1\",\n \"option_growth_overlay_start_usd\": \"250000\",\n \"option_growth_overlay_nav_budget_ratio\": \"0.03\"\n },\n {\n \"profile\": \"soxl_soxx_trend_income\",\n \"label\": \"半导体趋势收益\",\n \"label_en\": \"Semiconductor Trend Income\",\n \"label_zh\": \"半导体趋势收益\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": true,\n \"option_overlay_enabled\": true,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\",\n \"income_layer_start_usd\": \"150000\",\n \"income_layer_max_ratio\": \"0.95\",\n \"income_layer_allocations\": {\n \"SCHD\": 0.15,\n \"DGRO\": 0.1,\n \"SGOV\": 0.7,\n \"SPYI\": 0.04,\n \"QQQI\": 0.01\n },\n \"option_overlay_live_gate\": \"promotion_required\",\n \"option_overlay_live_status\": \"research_only\",\n \"option_income_overlay_enabled\": true,\n \"option_income_overlay_recipe\": \"soxx_put_credit_spread_income_v1\",\n \"option_income_overlay_start_usd\": \"150000\",\n \"option_income_overlay_nav_risk_ratio\": \"0.01\"\n },\n {\n \"profile\": \"nasdaq_sp500_smart_dca\",\n \"label\": \"纳指标普定投\",\n \"label_en\": \"NASDAQ/S&P 500 DCA\",\n \"label_zh\": \"纳指标普定投\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\",\n \"dca_enabled\": true,\n \"dca_default_mode\": \"fixed\",\n \"dca_default_base_investment_usd\": \"1000\"\n },\n {\n \"profile\": \"ibit_smart_dca\",\n \"label\": \"IBIT比特币定投\",\n \"label_en\": \"IBIT Bitcoin DCA\",\n \"label_zh\": \"IBIT比特币定投\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\",\n \"dca_enabled\": true,\n \"dca_default_mode\": \"fixed\",\n \"dca_default_base_investment_usd\": \"1000\"\n },\n {\n \"profile\": \"global_etf_rotation\",\n \"label\": \"全球ETF轮动\",\n \"label_en\": \"Global ETF Rotation\",\n \"label_zh\": \"全球ETF轮动\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": true,\n \"option_overlay_enabled\": true,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\",\n \"income_layer_start_usd\": \"500000\",\n \"income_layer_max_ratio\": \"0.15\",\n \"income_layer_allocations\": {\n \"SCHD\": 0.4,\n \"DGRO\": 0.25,\n \"SGOV\": 0.3,\n \"SPYI\": 0.05\n },\n \"option_overlay_live_gate\": \"promotion_required\",\n \"option_overlay_live_status\": \"research_only\",\n \"option_growth_overlay_enabled\": true,\n \"option_growth_overlay_recipe\": \"spy_leaps_growth_v1\",\n \"option_growth_overlay_start_usd\": \"500000\",\n \"option_growth_overlay_nav_budget_ratio\": \"0.015\"\n },\n {\n \"profile\": \"russell_top50_leader_rotation\",\n \"label\": \"罗素Top50领涨\",\n \"label_en\": \"Russell Top50 Leaders\",\n \"label_zh\": \"罗素Top50领涨\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": true,\n \"option_overlay_enabled\": true,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\",\n \"income_layer_start_usd\": \"300000\",\n \"income_layer_max_ratio\": \"0.25\",\n \"income_layer_allocations\": {\n \"SCHD\": 0.45,\n \"DGRO\": 0.3,\n \"SGOV\": 0.25\n },\n \"option_overlay_live_gate\": \"promotion_required\",\n \"option_overlay_live_status\": \"research_only\",\n \"option_growth_overlay_enabled\": true,\n \"option_growth_overlay_recipe\": \"spy_leaps_growth_v1\",\n \"option_growth_overlay_start_usd\": \"300000\",\n \"option_growth_overlay_nav_budget_ratio\": \"0.015\"\n },\n {\n \"profile\": \"tecl_xlk_trend_income\",\n \"label\": \"TECL/XLK趋势收益\",\n \"label_en\": \"TECL/XLK Trend Income\",\n \"label_zh\": \"TECL/XLK趋势收益\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"failed_promotion_vs_live_profiles\"\n },\n {\n \"profile\": \"us_equity_combo\",\n \"label\": \"美股核心组合\",\n \"label_en\": \"US Core Combo\",\n \"label_zh\": \"美股核心组合\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": true,\n \"option_overlay_enabled\": true,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"shadow_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"shadow_candidate_requires_evidence_package\",\n \"combo_mode\": \"dynamic\",\n \"income_layer_start_usd\": \"300000\",\n \"income_layer_max_ratio\": \"0.25\",\n \"income_layer_allocations\": {\n \"SCHD\": 0.25,\n \"DGRO\": 0.25,\n \"SGOV\": 0.2,\n \"SPYI\": 0.15,\n \"QQQI\": 0.15\n },\n \"option_overlay_live_gate\": \"promotion_required\",\n \"option_overlay_live_status\": \"research_only\",\n \"option_growth_overlay_enabled\": true,\n \"option_growth_overlay_recipe\": \"spy_leaps_growth_v1\",\n \"option_growth_overlay_start_usd\": \"300000\",\n \"option_growth_overlay_nav_budget_ratio\": \"0.015\"\n },\n {\n \"profile\": \"us_equity_combo_core\",\n \"label\": \"美股核心组合影子\",\n \"label_en\": \"US Core Combo Shadow\",\n \"label_zh\": \"美股核心组合影子\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"shadow_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"shadow_candidate_requires_evidence_package\",\n \"combo_mode\": \"dynamic\"\n },\n {\n \"profile\": \"us_equity_combo_leveraged\",\n \"label\": \"美股加速组合\",\n \"label_en\": \"US Alpha Combo\",\n \"label_zh\": \"美股加速组合\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"shadow_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"shadow_candidate_requires_evidence_package\",\n \"combo_mode\": \"dynamic\"\n },\n {\n \"profile\": \"hk_global_etf_tactical_rotation\",\n \"label\": \"港股ETF战术轮动\",\n \"label_en\": \"HK ETF Tactical Rotation\",\n \"label_zh\": \"港股ETF战术轮动\",\n \"domain\": \"hk_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"hk_low_vol_dividend_quality_snapshot\",\n \"label\": \"港股红利质量\",\n \"label_en\": \"HK Dividend Quality\",\n \"label_zh\": \"港股红利质量\",\n \"domain\": \"hk_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\"\n },\n {\n \"profile\": \"hk_equity_combo\",\n \"label\": \"港股恒生组合\",\n \"label_en\": \"HK Core Combo\",\n \"label_zh\": \"港股恒生组合\",\n \"domain\": \"hk_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\",\n \"combo_mode\": \"dynamic\"\n },\n {\n \"profile\": \"cn_industry_etf_rotation\",\n \"label\": \"A股行业ETF轮动\",\n \"label_en\": \"CN Industry ETF Rotation\",\n \"label_zh\": \"A股行业ETF轮动\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\"\n },\n {\n \"profile\": \"cn_industry_etf_rotation_aggressive\",\n \"label\": \"A股ETF轮动\",\n \"label_en\": \"CN ETF Rotation\",\n \"label_zh\": \"A股ETF轮动\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"live_candidate\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"live_candidate_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_index_etf_tactical_rotation\",\n \"label\": \"A股宽基ETF战术轮动\",\n \"label_en\": \"CN Index ETF Tactical Rotation\",\n \"label_zh\": \"A股宽基ETF战术轮动\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_chinext_tactical_rotation\",\n \"label\": \"创业板战术轮动\",\n \"label_en\": \"CN ChiNext Tactical Rotation\",\n \"label_zh\": \"创业板战术轮动\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_chinext_growth_momentum_quality\",\n \"label\": \"创业板成长动量质量\",\n \"label_en\": \"CN ChiNext Growth Momentum Quality\",\n \"label_zh\": \"创业板成长动量质量\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_dividend_quality_snapshot\",\n \"label\": \"A股红利质量\",\n \"label_en\": \"CN Dividend Quality\",\n \"label_zh\": \"A股红利质量\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_chinext_growth_momentum_quality_snapshot\",\n \"label\": \"创业板成长质量快照\",\n \"label_en\": \"CN ChiNext Growth Quality Snapshot\",\n \"label_zh\": \"创业板成长质量快照\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_star_growth_momentum_quality\",\n \"label\": \"科创板成长动量质量\",\n \"label_en\": \"CN STAR Growth Momentum Quality\",\n \"label_zh\": \"科创板成长动量质量\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_equity_combo\",\n \"label\": \"A股进取组合\",\n \"label_en\": \"CN Alpha Combo\",\n \"label_zh\": \"A股进取组合\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\",\n \"combo_mode\": \"dynamic\"\n },\n {\n \"profile\": \"crypto_live_pool_rotation\",\n \"label\": \"加密实时池轮动\",\n \"label_en\": \"Crypto Live Pool Rotation\",\n \"label_zh\": \"加密实时池轮动\",\n \"domain\": \"crypto\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\"\n },\n {\n \"profile\": \"crypto_btc_dca\",\n \"label\": \"BTC定投\",\n \"label_en\": \"BTC DCA\",\n \"label_zh\": \"BTC定投\",\n \"domain\": \"crypto\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"shadow_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"shadow_candidate_requires_evidence_package\"\n },\n {\n \"profile\": \"crypto_trend_rotation\",\n \"label\": \"山寨趋势轮动\",\n \"label_en\": \"Altcoin Trend\",\n \"label_zh\": \"山寨趋势轮动\",\n \"domain\": \"crypto\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"crypto_equity_combo\",\n \"label\": \"加密动量组合\",\n \"label_en\": \"Crypto Core Combo\",\n \"label_zh\": \"加密动量组合\",\n \"domain\": \"crypto\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\",\n \"combo_mode\": \"dynamic\"\n }\n ];\n\n const localStrategyLabels = {\n tqqq_growth_income: { zh: \"纳斯达克增长收益\", en: \"NASDAQ Growth Income\" },\n soxl_soxx_trend_income: { zh: \"半导体趋势收益\", en: \"Semiconductor Trend Income\" },\n nasdaq_sp500_smart_dca: { zh: \"纳指标普定投\", en: \"NASDAQ/S&P 500 DCA\" },\n ibit_smart_dca: { zh: \"IBIT比特币定投\", en: \"IBIT Bitcoin DCA\" },\n global_etf_rotation: { zh: \"全球ETF轮动\", en: \"Global ETF Rotation\" },\n russell_top50_leader_rotation: { zh: \"罗素Top50领涨\", en: \"Russell Top50 Leaders\" },\n hk_global_etf_tactical_rotation: { zh: \"港股ETF战术轮动\", en: \"HK ETF Tactical Rotation\" },\n hk_low_vol_dividend_quality_snapshot: { zh: \"港股红利质量\", en: \"HK Dividend Quality\" },\n cn_industry_etf_rotation: { zh: \"A股行业ETF轮动\", en: \"CN Industry ETF Rotation\" },\n cn_dividend_quality_snapshot: { zh: \"A股红利质量\", en: \"CN Dividend Quality\" },\n us_equity_combo: { zh: \"美股核心组合\", en: \"US Core Combo\" },\n us_equity_combo_leveraged: { zh: \"美股加速组合\", en: \"US Alpha Combo\" },\n hk_equity_combo: { zh: \"港股恒生组合\", en: \"HK Core Combo\" },\n cn_industry_etf_rotation_aggressive: { zh: \"A股ETF轮动\", en: \"CN ETF Rotation\" },\n cn_stock_momentum_rotation: { zh: \"A股个股动量\", en: \"CN Stock Momentum\" },\n cn_equity_combo: { zh: \"A股进取组合\", en: \"CN Alpha Combo\" },\n crypto_btc_dca: { zh: \"BTC定投\", en: \"BTC DCA\" },\n crypto_trend_rotation: { zh: \"山寨趋势轮动\", en: \"Altcoin Trend\" },\n crypto_equity_combo: { zh: \"加密动量组合\", en: \"Crypto Core Combo\" },\n };\n\n const fallbackIncomeLayerDefaults = window.__INCOME_LAYER_DEFAULTS__ || {\n tqqq_growth_income: {\n startUsd: 250000,\n maxRatio: \"0.55\",\n allocations: { SCHD: 0.30, DGRO: 0.20, SGOV: 0.40, SPYI: 0.08, QQQI: 0.02 },\n },\n soxl_soxx_trend_income: {\n startUsd: 150000,\n maxRatio: \"0.95\",\n allocations: { SCHD: 0.15, DGRO: 0.10, SGOV: 0.70, SPYI: 0.04, QQQI: 0.01 },\n },\n global_etf_rotation: {\n startUsd: 500000,\n maxRatio: \"0.15\",\n allocations: { SCHD: 0.40, DGRO: 0.25, SGOV: 0.30, SPYI: 0.05 },\n },\n russell_top50_leader_rotation: {\n startUsd: 300000,\n maxRatio: \"0.25\",\n allocations: { SCHD: 0.45, DGRO: 0.30, SGOV: 0.25 },\n },\n us_equity_combo: {\n startUsd: 300000,\n maxRatio: \"0.25\",\n allocations: { SCHD: 0.25, DGRO: 0.25, SGOV: 0.20, SPYI: 0.15, QQQI: 0.15 },\n }};\n let incomeLayerDefaults = {};\n const fallbackOptionOverlayDefaults = window.__OPTION_OVERLAY_DEFAULTS__ || {\n tqqq_growth_income: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [\n { family: \"growth\", recipe: \"tqqq_leaps_growth_v1\", startUsd: \"250000\", ratio: \"0.03\", ratioKind: \"budget\" },\n ],\n },\n soxl_soxx_trend_income: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [\n { family: \"income\", recipe: \"soxx_put_credit_spread_income_v1\", startUsd: \"150000\", ratio: \"0.01\", ratioKind: \"risk\" },\n ],\n },\n global_etf_rotation: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [\n { family: \"growth\", recipe: \"spy_leaps_growth_v1\", startUsd: \"500000\", ratio: \"0.015\", ratioKind: \"budget\" },\n ],\n },\n russell_top50_leader_rotation: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [\n { family: \"growth\", recipe: \"spy_leaps_growth_v1\", startUsd: \"300000\", ratio: \"0.015\", ratioKind: \"budget\" },\n ],\n },\n us_equity_combo: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [\n { family: \"growth\", recipe: \"spy_leaps_growth_v1\", startUsd: \"300000\", ratio: \"0.015\", ratioKind: \"budget\" },\n ],\n },\n us_equity_combo_leveraged: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [],\n }};\n let optionOverlayDefaults = {};\n\n const strategyDomains = [\"us_equity\", \"hk_equity\", \"cn_equity\", \"crypto\"];\n let strategyOptions = [];\n let strategyLabels = {};\n let strategyCatalog = {};\n\n\n const copy = {\n zh: {\n appTitle: \"QuantStrategyLab\",\n appSubtitle: \"已验证证据、运行许可和所有者决策。\",\n controlPlaneView: \"待处理\",\n healthView: \"运行保障\",\n switchView: \"运行计划\",\n controlPlaneEyebrow: \"今日待处理 / 只读\",\n controlPlaneTitle: \"只处理需要你决定的事。\",\n controlPlaneSubtitle: \"这里不是交易大屏:只显示经验证的待办、证据是否足够,以及需要人工做出的边界决定。\",\n controlCandidateTotal: \"候选总数\",\n controlDeferred: \"本次延期\",\n controlParked: \"已停车\",\n controlOwnerDecision: \"等待我决定\",\n controlCandidateBoard: \"需要跟进\",\n controlDataReady: \"快照已同步(不代表策略可运行)\",\n controlDataStale: \"快照已过期\",\n controlDataUnavailable: \"等待可用快照\",\n controlAttentionResearchOnly: \"仅研究,未授予执行权限\",\n controlAttentionRequired: \"研究状态需要关注\",\n controlAttentionUnavailable: \"尚无已验证研究状态\",\n controlComputedAt: \"最近计算:{time}\",\n controlLoginNotice: \"登录后读取私有全局快照;没有快照时不会展示虚构信息。\",\n controlStaleNotice: \"全局快照已超过允许的新鲜度窗口;页面保留原始记录,但不把它作为当前结论。\",\n controlUnavailableNotice: \"还没有可用的全局快照;当前页面保持 fail-closed 空状态。\",\n controlUpstreamNotice: \"快照已加载,但有 {count} 个上游提示;缺失信息不会被补成推断结论。\",\n controlAttentionNotice: \"研究状态需要关注(延期 {deferred}、停车 {parked}、信号 {signals})。快照送达不等于策略验证或执行授权;系统保持不交易。\",\n controlEmptyCandidates: \"暂无可展示的候选快照。\",\n controlNoRecommendation: \"没有可用的机器建议。\",\n controlStageMeta: \"阶段:{stage} · 状态:{status} · 证据:{freshness}\",\n controlNext: \"下一步\",\n ownerDecisionTitle: \"所有者决定\",\n ownerDecisionAdminOnly: \"只有控制台管理员可以记录决定。\",\n ownerDecisionReady: \"当前证据已绑定;请人工选择后记录不可执行意图。\",\n ownerDecisionRecorded: \"已记录:{decision}(仅意图,不会启用实盘)\",\n ownerDecisionApprove: \"批准受限试运行意图\",\n ownerDecisionPark: \"保持暂停\",\n ownerDecisionRetire: \"退役候选\",\n ownerDecisionConfirm: \"这只会记录不可执行的所有者决定意图,不会下单、改资金或启用实盘。继续吗?\",\n ownerDecisionSubmitting: \"正在记录决定…\",\n ownerDecisionSuccess: \"所有者决定已记录;它不会自动启用实盘。\",\n ownerDecisionFailed: \"无法记录所有者决定\",\n reconciliationRecoveryBoard: \"恢复核验(仅已存在实盘)\",\n reconciliationRecoveryLoginNotice: \"登录后读取私有恢复核验;缺失快照不会被推断为可以恢复。\",\n reconciliationRecoveryStaleNotice: \"恢复核验已过期;请等待平台重新采样和双审,不能确认。\",\n reconciliationRecoveryUnavailableNotice: \"暂无可用恢复核验;页面保持 fail-closed 空状态。\",\n reconciliationRecoveryUpstreamNotice: \"恢复核验已加载,但有 {count} 个上游提示;请先处理阻断项。\",\n reconciliationRecoveryEmpty: \"没有待恢复的既有实盘目标。\",\n reconciliationRecoveryMeta: \"{platform} · {strategy} · 仅恢复现有执行\",\n reconciliationRecoveryDetail: \"状态:{state} · 只读样本:{samples} · 双审:{review} · 窗口截止:{lastObserved}\",\n reconciliationRecoveryBlocked: \"仍被阻断:{blockers}\",\n reconciliationRecoveryReady: \"两次对账一致且双审绑定当前摘要;需要管理员确认。\",\n reconciliationRecoveryAdminOnly: \"只有控制台管理员可以确认恢复意图。\",\n reconciliationRecoveryConfirmed: \"已记录恢复确认(仍未恢复运行)。\",\n reconciliationRecoveryConfirm: \"确认恢复既有执行意图\",\n reconciliationRecoveryConfirmPrompt: \"这只会记录与当前对账/双审摘要绑定的恢复意图;不会下单、修改资金或直接启用运行。继续吗?\",\n reconciliationRecoverySubmitting: \"正在记录确认…\",\n reconciliationRecoverySuccess: \"恢复确认已记录;私有控制器仍须重新核验后才可恢复既有执行。\",\n reconciliationRecoveryFailed: \"无法记录恢复确认\",\n reconciliationRecoveryNoOrder: \"固定边界:控制台不持有券商凭证,也不直接恢复、下单或启用实盘。\",\n executionEvidenceBoard: \"按需查看策略 × 平台证据覆盖\",\n executionEvidenceLoginNotice: \"登录后读取私有执行证据快照;缺失来源不会被推断成 paper 或 live 状态。\",\n executionEvidenceStaleNotice: \"执行证据已超过允许的新鲜度窗口;它保留为历史记录,不能作为当前运行或 P6 结论。\",\n executionEvidenceUnavailableNotice: \"还没有可用的执行证据来源;当前列表保持 fail-closed 空状态。\",\n executionEvidenceUpstreamNotice: \"执行证据已加载,但有 {count} 个上游提示;页面不会补造平台能力或运行资格。\",\n executionEvidenceEmpty: \"暂无可展示的策略 × 平台执行证据。\",\n executionEvidenceMeta: \"{platform} · 当前通道:{environment} · 来源:{source}\",\n executionEvidenceDetail: \"策略:{strategy} · 数据:{data} · 执行:{execution} · Shadow:{shadow} · Paper:{paper}\",\n executionEvidenceNoOrder: \"固定边界:只读证据;不包含账户、订单、资金或 P6 实盘授权。\",\n executionEvidenceNext: \"下一步\",\n runtimeTargetLifecycleBoard: \"按需查看平台运行状态\",\n runtimeTargetLifecycleLoginNotice: \"登录后读取私有平台运行状态;页面不会改变启用/停用或提交订单。\",\n runtimeTargetLifecycleStaleNotice: \"平台运行状态已超过允许的新鲜度窗口;请先检查监测链路。\",\n runtimeTargetLifecycleUnavailableNotice: \"还没有可用的平台运行状态;缺失数据不会被推断成已停用或正常。\",\n runtimeTargetLifecycleUpstreamNotice: \"平台运行状态已加载,但有 {count} 个上游提示;请先核对。\",\n runtimeTargetLifecycleEmpty: \"暂无可展示的平台运行状态。\",\n runtimeTargetLifecycleMeta: \"{platform} · {state} · 通道:{mode}\",\n runtimeTargetLifecycleDetail: \"运行监测:{guard} · 执行心跳:{heartbeat}\",\n runtimeTargetLifecycleNoOrder: \"固定边界:只读状态;不会启用目标、变更策略或提交订单。\",\n runtimeTargetLifecycleNext: \"当前处理\",\n runtimeTargetLifecycleStateEnabled: \"已启用\",\n runtimeTargetLifecycleStateDisabled: \"已停用\",\n runtimeTargetLifecycleCheckPass: \"通过\",\n runtimeTargetLifecycleCheckAttention: \"需处理\",\n runtimeTargetLifecycleCheckNotDue: \"未到检查时间\",\n runtimeTargetLifecycleCheckNotApplicable: \"不适用\",\n runtimeTargetLifecycleCheckUnavailable: \"不可用\",\n runtimeTargetLifecycleDispositionEnabled: \"持续监控\",\n runtimeTargetLifecycleDispositionDisabled: \"无执行验证\",\n runtimeTargetLifecycleDispositionParked: \"已暂停\",\n runtimeTargetLifecycleReasonNone: \"监测正常\",\n runtimeTargetLifecycleReasonDisabled: \"按配置停用,仍持续验证\",\n runtimeTargetLifecycleReasonRuntimeGuard: \"运行监测需要复核\",\n runtimeTargetLifecycleReasonHeartbeat: \"执行心跳需要复核\",\n runtimeTargetLifecycleReasonUnavailable: \"监测数据不可用\",\n m0ResearchBoard: \"按需查看 M0 研究观察\",\n m0ResearchLoginNotice: \"登录后读取私有 M0 研究台账;缺失台账不会被补成研究结论。\",\n m0ResearchStaleNotice: \"M0 研究观察已超过有效期,仅保留为历史上下文。\",\n m0ResearchUnavailableNotice: \"还没有可用的 M0 研究台账;页面保持 fail-closed 空状态。\",\n m0ResearchUpstreamNotice: \"M0 研究台账已加载,但有 {count} 个上游提示;页面不会补造研究结论。\",\n m0ResearchEmpty: \"暂无可展示的已验证 M0 研究观察。\",\n m0ResearchMore: \"仅显示前 {count} 条研究观察;其余仍保留在只读台账中。\",\n m0ResearchMeta: \"{kind} · 查看于:{viewed}\",\n m0ResearchStateFreshness: \"状态:{state} · 新鲜度:{freshness}\",\n m0ResearchHorizons: \"主要观察期:{primary} · 适用观察期:{suitable}\",\n m0ResearchEvidence: \"置信:{confidence} · 风格:{style} · 资料摘要:{digest}\",\n m0ResearchConsistency: \"当前周期分歧:{conflict} · 历史失效漂移:{drift}\",\n m0ResearchNoOrder: \"固定边界:仅研究、无订单;不产生任何运行动作。\",\n adaptiveSelectionBoard: \"按需查看 M1 Shadow 建议\",\n adaptiveSelectionLoginNotice: \"登录后读取私有 Shadow 建议;缺失来源不会被补成策略或订单。\",\n adaptiveSelectionStaleNotice: \"此 Shadow 建议已超出新鲜度窗口,仅保留为历史上下文。\",\n adaptiveSelectionUnavailableNotice: \"还没有可用的 M1 Shadow 建议;页面保持 fail-closed 空状态。\",\n adaptiveSelectionUpstreamNotice: \"Shadow 建议带有 {count} 个上游提示;页面不会推断策略、平台或订单。\",\n adaptiveSelectionEmpty: \"暂无可展示的 Shadow 建议。\",\n adaptiveSelectionMeta: \"{source} · {domain} · 市场截至 {asOf}\",\n adaptiveSelectionRecommended: \"建议观察\",\n adaptiveSelectionNoCandidate: \"无合格 Shadow 候选\",\n adaptiveSelectionReason: \"原因:{reasons}\",\n adaptiveSelectionNoOrder: \"固定边界:仅供人工查看,零建议权重,不改策略、平台、资金、运行状态或订单。\",\n researchTaskBoard: \"按需查看研究任务队列\",\n researchTaskLoginNotice: \"登录后读取私有研究任务索引;没有来源快照时不会展示虚构任务。\",\n researchTaskStaleNotice: \"研究任务来源已超过允许的新鲜度窗口;任务保留为历史记录,但不作为当前工作指令。\",\n researchTaskUnavailableNotice: \"还没有可用的研究任务来源;当前队列保持 fail-closed 空状态。\",\n researchTaskUpstreamNotice: \"研究任务索引已加载,但有 {count} 个上游提示;页面不会补造任务。\",\n researchTaskEmpty: \"暂无可展示的已验证研究任务。\",\n researchTaskMeta: \"{type} · {domain} · 创建于:{created}\",\n researchTaskLimits: \"离线研究:最多 {runs} 次 / {seconds} 秒 · P1 {p1} · P2 {p2} · P3 {p3}\",\n researchTaskNoOrder: \"固定边界:仅研究、零仓位、无订单;不进入 P4/P5/P6。\",\n healthEyebrow: \"运行保障 / 只读\",\n healthTitle: \"先看机器结论,再决定是否介入。\",\n healthSubtitle: \"健康不是实盘许可;它只告诉你是否要复核、降级或保持观察。\",\n runtimeAuthorityTitle: \"P0–P6 控制面授权状态\",\n runtimeAuthorityNotice: \"这里的 live/default_execution_mode/live_configured 是历史配置元数据,不是当前运行、订单、资金或阶段升级授权。\",\n runtimeAuthorityAsOf: \"截至:{asOf} · P1–P3 non-live 数据获取仍需独立、精确的契约;P4–P6 尚未定义。\",\n healthTotal: \"策略总数\",\n healthHealthy: \"健康\",\n healthWatch: \"观察\",\n healthReview: \"需要复核\",\n healthCritical: \"严重\",\n healthBoard: \"策略状态\",\n bootKicker: \"初始化控制台\",\n bootTitle: \"读取策略配置\",\n bootMessage: \"正在读取登录状态、账号配置和当前状态。\",\n bootStrategy: \"正在读取策略目录。\",\n bootSession: \"正在验证登录状态。\",\n bootConfig: \"正在读取账号配置和当前状态。\",\n bootTimeout: \"加载超时,已切换到公开预览(登录后可重试)。\",\n bootPublic: \"公开预览已就绪。\",\n login: \"登录\",\n logout: \"退出\",\n signedInAs: \"已登录 {login}\",\n planEyebrow: \"运行计划 / 受限变更\",\n planTitle: \"先定义边界,再提交配置。\",\n planSubtitle: \"一次只为一个平台和账号准备一个可审计的运行计划。页面只提交配置意图;运行、订单和实盘授权由独立的证据与阶段门槛决定。\",\n planScopeTitle: \"选择范围\",\n planScopeSubtitle: \"平台、账号、策略与目标环境\",\n planRuntimeTitle: \"运行保护\",\n planRuntimeSubtitle: \"停用、插件和附加层都受同一计划约束\",\n planOverlayTitle: \"策略附加层\",\n planOverlaySubtitle: \"仅使用策略已定义的默认边界\",\n planCashSubtitle: \"现金预留优先于融资;两者不能同时覆盖\",\n activePlatform: \"目标平台\",\n account: \"目标账号\",\n strategy: \"策略\",\n mode: \"运行环境\",\n live: \"实盘(需独立授权)\",\n paper: \"旧版非实盘\",\n dryRun: \"演练(不下单)\",\n liveModeUnavailable: \"当前没有同时满足策略发布、运行许可与证据门槛的实盘候选;请保持非实盘。\",\n runtimeTargetMode: \"账号运行状态\",\n runtimeSectionTitle: \"运行与插件\",\n runtimeTargetCurrent: \"沿用当前状态\",\n runtimeTargetEnabled: \"启用\",\n runtimeTargetDisabled: \"禁用\",\n runtimeTargetModeMeta: \"停用后正式运行会跳过,模拟运行和健康检查仍可用。\",\n pluginMode: \"插件状态\",\n pluginModeNone: \"不挂载旧插件\",\n pluginModeMeta: \"当前候选未绑定插件;旧插件不会自动挂载。\",\n incomeLayerMode: \"收入层状态\",\n incomeLayerSectionTitle: \"收入层\",\n incomeLayerCurrent: \"沿用当前配置\",\n incomeLayerEnabled: \"开启收入层\",\n incomeLayerDisabled: \"关闭收入层\",\n incomeLayerNotSupported: \"该策略未定义收入层\",\n incomeLayerStartUsd: \"收入层起始金额\",\n incomeLayerMaxRatio: \"收入层最高比例\",\n incomeLayerModeMeta: \"仅对已定义收入层的美股策略生效。\",\n incomeLayerDefaultMeta: \"策略默认:起始 {start},最高 {ratio}。\",\n incomeLayerAllocationMeta: \"默认分配:{allocations}。\",\n incomeLayerStartMeta: \"总资产达到该金额后启用收入层。\",\n incomeLayerRatioMeta: \"例如 0.55 表示最高 55%。\",\n optionOverlayMode: \"期权层状态\",\n optionOverlaySectionTitle: \"期权层\",\n optionOverlayCurrent: \"沿用当前配置\",\n optionOverlayEnabled: \"启用期权层\",\n optionOverlayDisabled: \"关闭期权层\",\n optionOverlayNotSupported: \"该策略未定义期权层\",\n optionOverlayModeMeta: \"启用时使用策略默认的最佳 recipe 和预算,不在这里手动调比例。\",\n optionOverlayDefaultMeta: \"{defaults}\",\n optionOverlayFamilyGrowth: \"增长\",\n optionOverlayFamilyIncome: \"收入\",\n optionOverlayBudgetRatio: \"预算 {ratio}\",\n optionOverlayRiskRatio: \"风险 {ratio}\",\n cashOnlyExecutionMode: \"允许融资\",\n cashOnlyExecutionCurrent: \"沿用当前配置\",\n cashOnlyExecutionYes: \"是\",\n cashOnlyExecutionNo: \"否\",\n cashOnlyExecutionModeMeta: \"选「否」时只按真实现金下单,不会动用 margin 购买力。\",\n cashOnlyExecutionValueYes: \"是\",\n cashOnlyExecutionValueNo: \"否\",\n currentCashOnlyExecution: \"当前允许融资\",\n pendingCashOnlyExecution: \"待提交允许融资\",\n executionCashPolicyTitle: \"资本边界\",\n executionCashPolicyNote: \"允许融资与预留现金覆盖不能同时生效;选「是」会清空预留覆盖,设预留覆盖会强制「否」。\",\n executionCashMarginBlocksReserve: \"已选允许融资;提交时会清空预留现金覆盖。\",\n executionCashReserveBlocksMargin: \"已设预留现金覆盖;提交时会强制不允许融资。\",\n qmtPlatformCashNote: \"A 股 QMT 不使用 margin / 平台预留现金;现金约束在策略参数 execution_cash_reserve_ratio 内配置。\",\n qmtDryRunOnlyNote: \"当前平台仅支持不下单演练,尚无可用的实盘执行通道。\",\n binancePlatformNote: \"Binance 平台不使用券商级收入层与期权层;相关功能由策略内部实现。\",\n invalidExecutionCashPolicyNote: \"允许融资与预留现金覆盖冲突,请只保留一种约束。\",\n dcaMode: \"定投模式\",\n dcaSectionTitle: \"定投\",\n dcaModeFixed: \"定额定投\",\n dcaModeSmart: \"智能定投\",\n dcaBaseInvestmentUsd: \"定投基准金额\",\n dcaModeMeta: \"仅定投策略可配置。\",\n dcaDefaultMeta: \"默认:{mode},基准金额 {amount}。\",\n dcaNotSupported: \"该策略不是定投策略\",\n dcaPlatformNotSupported: \"当前平台不支持定投策略\",\n currentDca: \"当前定投设置\",\n pendingDca: \"待提交定投设置\",\n dcaText: \"{mode},基准金额 {amount}\",\n minReservedCash: \"最小预留现金 ({currency})\",\n reservedCashRatio: \"预留现金比例\",\n reservedCashMode: \"预留现金策略\",\n reservePolicyCurrent: \"沿用当前配置\",\n reservePolicyNone: \"不设置平台预留现金\",\n reservePolicyRatio: \"仅按比例\",\n reservePolicyFloor: \"仅按固定金额\",\n reservePolicyMax: \"固定金额和比例取较大值\",\n reservedCashModeMeta: \"选择是否沿用、清空或覆盖平台预留现金。\",\n reservedCashNone: \"不设置\",\n reservedCashDefault: \"未配置(平台默认:0 {currency} / 0%)\",\n reservedCashMeta: \"固定金额下限,可单独设置或与比例取较大值。\",\n reservedCashRatioMeta: \"例如 0.03 表示 3%。\",\n summary: \"风险与变更摘要\",\n summaryCurrent: \"当前边界\",\n summaryPending: \"待提交变更\",\n planReadinessTitle: \"提交前核对\",\n planCheckAccount: \"账号与作用范围\",\n planCheckStrategy: \"策略与目标环境\",\n planCheckRisk: \"现金与风险边界\",\n planCheckAuthority: \"实盘执行授权\",\n planCheckWaiting: \"等待配置\",\n planCheckSelected: \"已读取\",\n planCheckValid: \"已校验\",\n planCheckFix: \"需修正\",\n planCheckNonLive: \"非实盘\",\n planCheckNoAuthority: \"未授予\",\n planAuditNote: \"提交记录会写入审计链;它不等同于订单、资金或实盘许可。\",\n copySummary: \"复制状态\",\n loginToRun: \"登录后提交计划\",\n loadingConfig: \"读取配置中\",\n configureAccounts: \"配置账号后切换\",\n runSwitch: \"提交受限变更\",\n noChanges: \"无变更\",\n readonlyNote: \"登录后才可提交受限配置。\",\n publicReadonly: \"登录后查看账号配置。\",\n loadingConfigNote: \"正在读取账号配置和当前状态。\",\n missingConfigNote: \"账号配置未加载,暂时不能执行。\",\n readyNote: \"提交的是受限配置变更;它不授予订单、资金或实盘权限。\",\n invalidStrategyNote: \"当前账号没有可执行策略,暂时不能切换。\",\n invalidReservePolicyNote: \"请为当前预留现金策略填写有效金额或比例。\",\n invalidIncomeLayerNote: \"请填写有效的收入层起始金额和最高比例。\",\n invalidOptionOverlayNote: \"当前策略未定义可启用的期权层。\",\n invalidDcaNote: \"请填写有效的定投模式和基准金额。\",\n publicOauthTitle: \"GitHub OAuth 保护\",\n publicOauthText: \"只允许白名单账号进入私有配置。\",\n publicWorkerTitle: \"Worker 端触发\",\n publicWorkerText: \"令牌保留在服务端,浏览器只提交切换意图。\",\n publicAuditTitle: \"变更可回溯\",\n publicAuditText: \"受限配置变更由 GitHub Actions 执行,便于审计和回滚。\",\n noAccount: \"没有账号选项\",\n noStrategy: \"没有支持的策略\",\n repository: \"平台仓库\",\n selectedAccount: \"账号\",\n selectedMarket: \"市场\",\n currentRuntimeTarget: \"当前账号状态\",\n pendingRuntimeTarget: \"待提交账号状态\",\n reservedCashPolicy: \"当前预留现金\",\n currentIncomeLayer: \"当前收入层\",\n pendingIncomeLayer: \"待提交收入层\",\n currentOptionOverlay: \"当前期权层\",\n pendingOptionOverlay: \"待提交期权层\",\n pendingReservedCashPolicy: \"待提交预留现金\",\n pendingMode: \"待提交模式\",\n currentPluginMode: \"当前插件范围\",\n pendingPluginMode: \"待提交插件范围\",\n unchanged: \"不变\",\n copied: \"已复制状态\",\n dispatching: \"正在触发工作流...\",\n dispatched: \"已触发工作流\",\n dispatchFailed: \"触发失败\",\n targetMeta: \"目标 {target} · 服务 {service} · 市场 {domains}\",\n strategyMeta: \"支持市场:{domains}\",\n strategyLifecycleMeta: \"当前门槛 {stage}\",\n strategyBlockedCountMeta: \"{count} 个策略未达 live 门槛\",\n strategyDefaultBlockedMeta: \"默认策略 {profile} 已阻断:{reason}\",\n usEquity: \"美股\",\n hkEquity: \"港股\",\n cnEquity: \"A股\",\n cryptoEquity: \"加密\",\n currentStrategy: \"当前策略\",\n nextStrategy: \"切换策略\",\n notRead: \"读取失败\",\n runtimeTargetOn: \"启用\",\n runtimeTargetOff: \"禁用\",\n incomeLayerDefault: \"开启,{start}起 {ratio}\",\n incomeLayerOff: \"关闭\",\n incomeLayerOn: \"开启,起始 {start},最高 {ratio}\",\n optionOverlayOff: \"关闭\",\n optionOverlayOn: \"开启\",\n optionOverlayDefaultSimple: \"开启\",\n optionOverlayDefault: \"开启,{detail}\",\n cashOnlyExecutionDefault: \"仅用现金\",\n },\n en: {\n appTitle: \"QuantStrategyLab\",\n appSubtitle: \"Verified evidence, runtime eligibility, and owner decisions.\",\n controlPlaneView: \"Attention\",\n healthView: \"Runtime guard\",\n switchView: \"Run plan\",\n controlPlaneEyebrow: \"Today / read only\",\n controlPlaneTitle: \"Only decide what needs you.\",\n controlPlaneSubtitle: \"This is not a trading screen: it shows verified follow-ups, evidence coverage, and the boundaries that need an owner decision.\",\n controlCandidateTotal: \"Candidates\",\n controlDeferred: \"Deferred now\",\n controlParked: \"Parked\",\n controlOwnerDecision: \"Needs my decision\",\n controlCandidateBoard: \"Follow-ups\",\n controlDataReady: \"Snapshot synced (not strategy readiness)\",\n controlDataStale: \"Snapshot is stale\",\n controlDataUnavailable: \"Waiting for a usable snapshot\",\n controlAttentionResearchOnly: \"Research only; no execution authority\",\n controlAttentionRequired: \"Research state needs attention\",\n controlAttentionUnavailable: \"No verified research state\",\n controlComputedAt: \"Last computed: {time}\",\n controlLoginNotice: \"Sign in to read the private global snapshot. Missing data is never invented.\",\n controlStaleNotice: \"This snapshot is outside its freshness window. The record remains visible, but is not a current conclusion.\",\n controlUnavailableNotice: \"No usable global snapshot is available yet. This page remains fail-closed and empty.\",\n controlUpstreamNotice: \"The snapshot loaded with {count} upstream notice(s); missing information is never inferred.\",\n controlAttentionNotice: \"Research needs attention ({deferred} deferred, {parked} parked, {signals} signal(s)). Snapshot delivery is not strategy validation or execution authority; the system remains no-order.\",\n controlEmptyCandidates: \"No candidate snapshot is available to display.\",\n controlNoRecommendation: \"No machine recommendation is available.\",\n controlStageMeta: \"Stage: {stage} · status: {status} · evidence: {freshness}\",\n controlNext: \"NEXT\",\n ownerDecisionTitle: \"Owner decision\",\n ownerDecisionAdminOnly: \"Only console administrators can record a decision.\",\n ownerDecisionReady: \"Current evidence is bound; choose an action to record a non-executable intent.\",\n ownerDecisionRecorded: \"Recorded: {decision} (intent only; Live remains disabled)\",\n ownerDecisionApprove: \"Approve limited-canary intent\",\n ownerDecisionPark: \"Keep parked\",\n ownerDecisionRetire: \"Retire candidate\",\n ownerDecisionConfirm: \"This records a non-executable owner intent only. It will not place orders, change funds, or enable Live. Continue?\",\n ownerDecisionSubmitting: \"Recording decision…\",\n ownerDecisionSuccess: \"Owner decision recorded; it does not enable Live automatically.\",\n ownerDecisionFailed: \"Could not record owner decision\",\n reconciliationRecoveryBoard: \"Recovery verification (existing live only)\",\n reconciliationRecoveryLoginNotice: \"Sign in to read private recovery verification. Missing snapshots never imply that recovery is allowed.\",\n reconciliationRecoveryStaleNotice: \"Recovery verification is stale. Wait for fresh platform sampling and dual review before confirming.\",\n reconciliationRecoveryUnavailableNotice: \"No usable recovery verification is available. This view remains fail-closed and empty.\",\n reconciliationRecoveryUpstreamNotice: \"Recovery verification loaded with {count} upstream notice(s). Resolve blockers first.\",\n reconciliationRecoveryEmpty: \"No existing live target is awaiting recovery.\",\n reconciliationRecoveryMeta: \"{platform} · {strategy} · existing-execution recovery only\",\n reconciliationRecoveryDetail: \"state: {state} · read-only samples: {samples} · dual review: {review} · window ends: {lastObserved}\",\n reconciliationRecoveryBlocked: \"Still blocked: {blockers}\",\n reconciliationRecoveryReady: \"Two reconciliations match and dual review is bound to the current digest; an admin confirmation is required.\",\n reconciliationRecoveryAdminOnly: \"Only console administrators can record a recovery confirmation.\",\n reconciliationRecoveryConfirmed: \"Recovery confirmation recorded (runtime is still not restored).\",\n reconciliationRecoveryConfirm: \"Record existing-execution recovery intent\",\n reconciliationRecoveryConfirmPrompt: \"This only records a recovery intent bound to the current reconciliation and dual-review digest. It will not place orders, change funds, or directly enable runtime. Continue?\",\n reconciliationRecoverySubmitting: \"Recording confirmation…\",\n reconciliationRecoverySuccess: \"Recovery confirmation recorded. A private controller must re-verify before it can restore existing execution.\",\n reconciliationRecoveryFailed: \"Could not record recovery confirmation\",\n reconciliationRecoveryNoOrder: \"Fixed boundary: the console holds no broker credentials and cannot directly recover, trade, or enable Live.\",\n executionEvidenceBoard: \"View strategy × platform evidence on demand\",\n executionEvidenceLoginNotice: \"Sign in to read private execution-evidence snapshots. Missing sources never imply a paper or live state.\",\n executionEvidenceStaleNotice: \"This execution evidence is beyond its freshness window. It remains historical context, not a current runtime or P6 conclusion.\",\n executionEvidenceUnavailableNotice: \"No usable execution-evidence source is available yet. This list remains fail-closed and empty.\",\n executionEvidenceUpstreamNotice: \"Execution evidence loaded with {count} upstream notice(s); no platform capability or runtime authority is inferred.\",\n executionEvidenceEmpty: \"No strategy × platform execution evidence is available to display.\",\n executionEvidenceMeta: \"{platform} · current lane: {environment} · source: {source}\",\n executionEvidenceDetail: \"strategy: {strategy} · data: {data} · execution: {execution} · shadow: {shadow} · paper: {paper}\",\n executionEvidenceNoOrder: \"Fixed boundary: read-only evidence; no account, order, funds, or P6 live authority.\",\n executionEvidenceNext: \"NEXT\",\n runtimeTargetLifecycleBoard: \"View platform runtime status on demand\",\n runtimeTargetLifecycleLoginNotice: \"Sign in to read private platform runtime status. This page cannot change target state or place orders.\",\n runtimeTargetLifecycleStaleNotice: \"Platform runtime status is outside its freshness window. Check the monitoring path first.\",\n runtimeTargetLifecycleUnavailableNotice: \"No usable platform runtime status is available. Missing data never implies a disabled or healthy target.\",\n runtimeTargetLifecycleUpstreamNotice: \"Platform runtime status loaded with {count} upstream notice(s); review them first.\",\n runtimeTargetLifecycleEmpty: \"No platform runtime status is available to display.\",\n runtimeTargetLifecycleMeta: \"{platform} · {state} · lane: {mode}\",\n runtimeTargetLifecycleDetail: \"runtime guard: {guard} · execution heartbeat: {heartbeat}\",\n runtimeTargetLifecycleNoOrder: \"Fixed boundary: read-only status; it cannot enable a target, change a strategy, or place an order.\",\n runtimeTargetLifecycleNext: \"CURRENT DISPOSITION\",\n runtimeTargetLifecycleStateEnabled: \"enabled\",\n runtimeTargetLifecycleStateDisabled: \"disabled\",\n runtimeTargetLifecycleCheckPass: \"pass\",\n runtimeTargetLifecycleCheckAttention: \"attention\",\n runtimeTargetLifecycleCheckNotDue: \"not due\",\n runtimeTargetLifecycleCheckNotApplicable: \"not applicable\",\n runtimeTargetLifecycleCheckUnavailable: \"unavailable\",\n runtimeTargetLifecycleDispositionEnabled: \"continue monitoring\",\n runtimeTargetLifecycleDispositionDisabled: \"disabled validation\",\n runtimeTargetLifecycleDispositionParked: \"parked\",\n runtimeTargetLifecycleReasonNone: \"monitoring normal\",\n runtimeTargetLifecycleReasonDisabled: \"intentionally disabled; validation continues\",\n runtimeTargetLifecycleReasonRuntimeGuard: \"runtime guard needs review\",\n runtimeTargetLifecycleReasonHeartbeat: \"execution heartbeat needs review\",\n runtimeTargetLifecycleReasonUnavailable: \"monitoring data unavailable\",\n m0ResearchBoard: \"View M0 research observations on demand\",\n m0ResearchLoginNotice: \"Sign in to read the private M0 research ledger. A missing ledger never implies a research conclusion.\",\n m0ResearchStaleNotice: \"These M0 research observations are beyond their validity window and remain historical context only.\",\n m0ResearchUnavailableNotice: \"No usable M0 research ledger is available yet. This view remains fail-closed and empty.\",\n m0ResearchUpstreamNotice: \"The M0 research ledger loaded with {count} upstream notice(s); no research conclusion is invented.\",\n m0ResearchEmpty: \"No verified M0 research observation is available to display.\",\n m0ResearchMore: \"Showing the first {count} research observations; the remainder stays in the read-only ledger.\",\n m0ResearchMeta: \"{kind} · viewed {viewed}\",\n m0ResearchStateFreshness: \"State: {state} · freshness: {freshness}\",\n m0ResearchHorizons: \"Primary horizon: {primary} · suitable horizons: {suitable}\",\n m0ResearchEvidence: \"Confidence: {confidence} · style: {style} · source digest: {digest}\",\n m0ResearchConsistency: \"Current horizon conflict: {conflict} · historical stale drift: {drift}\",\n m0ResearchNoOrder: \"Fixed boundary: research only, no order, and no runtime action.\",\n adaptiveSelectionBoard: \"View M1 Shadow suggestions on demand\",\n adaptiveSelectionLoginNotice: \"Sign in to read private Shadow suggestions. Missing sources never imply a strategy or order.\",\n adaptiveSelectionStaleNotice: \"This Shadow suggestion is beyond its freshness window and remains historical context only.\",\n adaptiveSelectionUnavailableNotice: \"No usable M1 Shadow suggestion is available yet. This view remains fail-closed and empty.\",\n adaptiveSelectionUpstreamNotice: \"Shadow suggestions include {count} upstream notice(s); no strategy, platform, or order is inferred.\",\n adaptiveSelectionEmpty: \"No Shadow suggestion is available to display.\",\n adaptiveSelectionMeta: \"{source} · {domain} · market as of {asOf}\",\n adaptiveSelectionRecommended: \"Observe suggestion\",\n adaptiveSelectionNoCandidate: \"No eligible Shadow candidate\",\n adaptiveSelectionReason: \"Reasons: {reasons}\",\n adaptiveSelectionNoOrder: \"Fixed boundary: human-readable only, zero proposed weight, no strategy, platform, funds, runtime, or order change.\",\n researchTaskBoard: \"View research queue on demand\",\n researchTaskLoginNotice: \"Sign in to read the private research task index. Missing source snapshots never imply a task.\",\n researchTaskStaleNotice: \"The research task source is beyond its freshness window. It remains historical context, not a current instruction.\",\n researchTaskUnavailableNotice: \"No research task source is available yet. The queue remains fail-closed and empty.\",\n researchTaskUpstreamNotice: \"The research task index loaded with {count} upstream notices; no task is inferred from missing data.\",\n researchTaskEmpty: \"No verified research task is available to display.\",\n researchTaskMeta: \"{type} · {domain} · created {created}\",\n researchTaskLimits: \"Offline research: up to {runs} run(s) / {seconds}s · P1 {p1} · P2 {p2} · P3 {p3}\",\n researchTaskNoOrder: \"Fixed boundary: research only, zero size, no order; never P4/P5/P6.\",\n healthEyebrow: \"Runtime guard / read only\",\n healthTitle: \"Read the machine conclusion before deciding to intervene.\",\n healthSubtitle: \"Health is not live approval. It only tells you whether to review, reduce, or keep watching.\",\n runtimeAuthorityTitle: \"P0–P6 control-plane authority\",\n runtimeAuthorityNotice: \"live/default_execution_mode/live_configured are legacy configuration metadata, not authorization for current runtime, orders, funds, or stage changes.\",\n runtimeAuthorityAsOf: \"As of {asOf} · P1–P3 non-live data acquisition still needs its own precise contract; P4–P6 are undefined.\",\n healthTotal: \"Strategies\",\n healthHealthy: \"Healthy\",\n healthWatch: \"Watch\",\n healthReview: \"Review\",\n healthCritical: \"Critical\",\n healthBoard: \"Strategy status\",\n bootKicker: \"Starting console\",\n bootTitle: \"Loading strategy config\",\n bootMessage: \"Reading session, account config, and current state.\",\n bootStrategy: \"Reading strategy catalog.\",\n bootSession: \"Checking sign-in status.\",\n bootConfig: \"Reading account config and current state.\",\n bootTimeout: \"Loading timed out; switched to public preview. Retry after signing in.\",\n bootPublic: \"Public preview is ready.\",\n login: \"Sign in\",\n logout: \"Sign out\",\n signedInAs: \"Signed in as {login}\",\n planEyebrow: \"Run plan / bounded change\",\n planTitle: \"Set the boundary before submitting configuration.\",\n planSubtitle: \"Prepare one auditable plan for one platform and account. This page submits configuration intent only; runtime, order, and live authority remain governed by independent evidence and lifecycle gates.\",\n planScopeTitle: \"Choose scope\",\n planScopeSubtitle: \"Platform, account, strategy, and target environment\",\n planRuntimeTitle: \"Runtime protection\",\n planRuntimeSubtitle: \"Disable state, plugins, and overlays share one plan boundary\",\n planOverlayTitle: \"Strategy overlays\",\n planOverlaySubtitle: \"Only strategy-defined default guardrails are available\",\n planCashSubtitle: \"Cash reserve takes precedence over margin; they cannot override together\",\n activePlatform: \"Target platform\",\n account: \"Target account\",\n strategy: \"Strategy\",\n mode: \"Target environment\",\n live: \"Live (independent approval required)\",\n paper: \"Legacy non-live\",\n dryRun: \"Dry run (no orders)\",\n liveModeUnavailable: \"No candidate currently meets the strategy-release, runtime-eligibility, and evidence gates for Live. Keep this plan non-live.\",\n runtimeTargetMode: \"Account status\",\n runtimeSectionTitle: \"Runtime and plugins\",\n runtimeTargetCurrent: \"Keep current status\",\n runtimeTargetEnabled: \"Enabled\",\n runtimeTargetDisabled: \"Disabled\",\n runtimeTargetModeMeta: \"Disabled accounts skip live runs; dry runs and health checks still work.\",\n pluginMode: \"Plugin status\",\n pluginModeNone: \"Do not mount legacy plugins\",\n pluginModeMeta: \"The current candidate has no bound plugin; legacy plugins are not auto-mounted.\",\n incomeLayerMode: \"Income layer\",\n incomeLayerSectionTitle: \"Income layer\",\n incomeLayerCurrent: \"Keep current config\",\n incomeLayerEnabled: \"Enable income layer\",\n incomeLayerDisabled: \"Disable income layer\",\n incomeLayerNotSupported: \"No income layer for this strategy\",\n incomeLayerStartUsd: \"Income layer start amount\",\n incomeLayerMaxRatio: \"Income layer max ratio\",\n incomeLayerModeMeta: \"Only applies to US equity strategies with an income layer.\",\n incomeLayerDefaultMeta: \"Strategy default: starts at {start}, max {ratio}.\",\n incomeLayerAllocationMeta: \"Default allocation: {allocations}.\",\n incomeLayerStartMeta: \"Income layer activates after total assets reach this amount.\",\n incomeLayerRatioMeta: \"Use 0.55 for a 55% cap.\",\n optionOverlayMode: \"Option layer\",\n optionOverlaySectionTitle: \"Option layer\",\n optionOverlayCurrent: \"Keep current config\",\n optionOverlayEnabled: \"Enable option layer\",\n optionOverlayDisabled: \"Disable option layer\",\n optionOverlayNotSupported: \"No option layer for this strategy\",\n optionOverlayModeMeta: \"Enabled mode uses the strategy's default recipe and budget; ratios are not edited here.\",\n optionOverlayDefaultMeta: \"{defaults}\",\n optionOverlayFamilyGrowth: \"Growth\",\n optionOverlayFamilyIncome: \"Income\",\n optionOverlayBudgetRatio: \"budget {ratio}\",\n optionOverlayRiskRatio: \"risk {ratio}\",\n cashOnlyExecutionMode: \"Allow margin\",\n cashOnlyExecutionCurrent: \"Keep current config\",\n cashOnlyExecutionYes: \"Yes\",\n cashOnlyExecutionNo: \"No\",\n cashOnlyExecutionModeMeta: \"Choose No to use available cash only and avoid margin buying power.\",\n cashOnlyExecutionValueYes: \"Yes\",\n cashOnlyExecutionValueNo: \"No\",\n currentCashOnlyExecution: \"Current allow margin\",\n pendingCashOnlyExecution: \"Pending allow margin\",\n executionCashPolicyTitle: \"Capital boundary\",\n executionCashPolicyNote: \"Allow margin and reserve-cash overrides cannot both apply. Yes clears reserve overrides; reserve overrides force No.\",\n executionCashMarginBlocksReserve: \"Allow margin is selected; submitting will clear reserve-cash overrides.\",\n executionCashReserveBlocksMargin: \"Reserve-cash override is active; submitting will force allow margin to No.\",\n qmtPlatformCashNote: \"QMT A-share does not use margin or platform reserve cash; cash constraints live in strategy execution_cash_reserve_ratio.\",\n qmtDryRunOnlyNote: \"This platform supports no-order dry runs only; no live execution route is configured.\",\n binancePlatformNote: \"Binance does not use broker-level income/option layers; features are implemented inside strategies.\",\n invalidExecutionCashPolicyNote: \"Allow margin and reserve-cash overrides conflict. Keep only one constraint.\",\n dcaMode: \"DCA mode\",\n dcaSectionTitle: \"DCA\",\n dcaModeFixed: \"Fixed DCA\",\n dcaModeSmart: \"Smart DCA\",\n dcaBaseInvestmentUsd: \"Base DCA amount\",\n dcaModeMeta: \"Only DCA strategies can use this.\",\n dcaDefaultMeta: \"Default: {mode}, base amount {amount}.\",\n dcaNotSupported: \"This is not a DCA strategy\",\n dcaPlatformNotSupported: \"DCA not supported on this platform\",\n currentDca: \"Current DCA settings\",\n pendingDca: \"Pending DCA settings\",\n dcaText: \"{mode}, base amount {amount}\",\n minReservedCash: \"Minimum reserved cash ({currency})\",\n reservedCashRatio: \"Reserved cash ratio\",\n reservedCashMode: \"Reserved cash policy\",\n reservePolicyCurrent: \"Keep current config\",\n reservePolicyNone: \"No platform reserve\",\n reservePolicyRatio: \"Ratio only\",\n reservePolicyFloor: \"Fixed amount only\",\n reservePolicyMax: \"Max of amount and ratio\",\n reservedCashModeMeta: \"Choose whether to keep, clear, or override platform reserved cash.\",\n reservedCashNone: \"None\",\n reservedCashDefault: \"Not configured (platform default: 0 {currency} / 0%)\",\n reservedCashMeta: \"Fixed cash floor. Use alone or with a ratio.\",\n reservedCashRatioMeta: \"Use 0.03 for 3%.\",\n summary: \"Risk and change summary\",\n summaryCurrent: \"Current boundary\",\n summaryPending: \"Pending change\",\n planReadinessTitle: \"Before submission\",\n planCheckAccount: \"Account and scope\",\n planCheckStrategy: \"Strategy and environment\",\n planCheckRisk: \"Cash and risk boundary\",\n planCheckAuthority: \"Live execution authority\",\n planCheckWaiting: \"Waiting\",\n planCheckSelected: \"Read\",\n planCheckValid: \"Checked\",\n planCheckFix: \"Needs correction\",\n planCheckNonLive: \"Non-live\",\n planCheckNoAuthority: \"Not granted\",\n planAuditNote: \"A submission creates an audit record; it is not an order, funding, or live permission.\",\n copySummary: \"Copy state\",\n loginToRun: \"Sign in to submit a plan\",\n loadingConfig: \"Loading config\",\n configureAccounts: \"Configure accounts\",\n runSwitch: \"Submit bounded change\",\n noChanges: \"No changes\",\n readonlyNote: \"Sign in to submit a bounded configuration.\",\n publicReadonly: \"Sign in to view account config.\",\n loadingConfigNote: \"Reading account config and current state.\",\n missingConfigNote: \"Account config is not loaded, so switching is disabled.\",\n readyNote: \"This submits a bounded configuration change; it grants no order, funding, or live authority.\",\n invalidStrategyNote: \"This account has no runnable strategy, so switching is disabled.\",\n invalidReservePolicyNote: \"Enter a valid amount or ratio for the selected reserved-cash policy.\",\n invalidIncomeLayerNote: \"Enter a valid income layer start amount and max ratio.\",\n invalidOptionOverlayNote: \"This strategy does not define an option layer to enable.\",\n invalidDcaNote: \"Enter a valid DCA mode and base amount.\",\n publicOauthTitle: \"Protected by GitHub OAuth\",\n publicOauthText: \"Only allowlisted accounts can open private config.\",\n publicWorkerTitle: \"Worker-side dispatch\",\n publicWorkerText: \"Tokens stay server-side; the browser submits intent only.\",\n publicAuditTitle: \"Traceable changes\",\n publicAuditText: \"Bounded configuration changes run through GitHub Actions for audit and rollback.\",\n noAccount: \"No accounts\",\n noStrategy: \"No supported strategies\",\n repository: \"Repository\",\n selectedAccount: \"Account\",\n selectedMarket: \"Market\",\n currentRuntimeTarget: \"Current account status\",\n pendingRuntimeTarget: \"Pending account status\",\n reservedCashPolicy: \"Current reserved cash\",\n currentIncomeLayer: \"Current income layer\",\n pendingIncomeLayer: \"Pending income layer\",\n currentOptionOverlay: \"Current option layer\",\n pendingOptionOverlay: \"Pending option layer\",\n pendingReservedCashPolicy: \"Pending reserved cash\",\n pendingMode: \"Pending mode\",\n currentPluginMode: \"Current plugin scope\",\n pendingPluginMode: \"Pending plugin scope\",\n unchanged: \"Unchanged\",\n copied: \"State copied\",\n dispatching: \"Dispatching workflow...\",\n dispatched: \"Workflow dispatched\",\n dispatchFailed: \"Dispatch failed\",\n targetMeta: \"target {target} · service {service} · market {domains}\",\n strategyMeta: \"Markets: {domains}\",\n strategyLifecycleMeta: \"current gate {stage}\",\n strategyBlockedCountMeta: \"{count} strategies are blocked from live\",\n strategyDefaultBlockedMeta: \"Default strategy {profile} is blocked: {reason}\",\n usEquity: \"US equity\",\n hkEquity: \"HK equity\",\n cnEquity: \"CN A-share\",\n cryptoEquity: \"Crypto\",\n currentStrategy: \"Current strategy\",\n nextStrategy: \"Switch strategy\",\n notRead: \"Not read\",\n runtimeTargetOn: \"Enabled\",\n runtimeTargetOff: \"Disabled\",\n incomeLayerDefault: \"Enabled, {start} start, {ratio} max\",\n incomeLayerOff: \"Disabled\",\n incomeLayerOn: \"Enabled, starts at {start}, max {ratio}\",\n optionOverlayOff: \"Disabled\",\n optionOverlayOn: \"Enabled\",\n optionOverlayDefaultSimple: \"Strategy default: enabled\",\n optionOverlayDefault: \"Enabled, {detail}\",\n cashOnlyExecutionDefault: \"Cash only\",\n },\n };\n\n const storedLang = localStorage.getItem(\"qsl-switch-lang\");\n const initialLang = storedLang === \"zh\" || storedLang === \"en\"\n ? storedLang\n : ((navigator.language || \"\").toLowerCase().startsWith(\"zh\") ? \"zh\" : \"en\");\n const clone = (value) => JSON.parse(JSON.stringify(value));\n const defaultReserveForm = () => ({\n reservePolicyMode: \"current\",\n minReservedCashUsd: \"\",\n reservedCashRatio: \"\",\n reservedCashTouched: false,\n incomeLayerMode: \"current\",\n incomeLayerStartUsd: \"\",\n incomeLayerMaxRatio: \"\",\n incomeLayerTouched: false,\n optionOverlayMode: \"current\",\n optionOverlayTouched: false,\n cashOnlyExecutionMode: \"current\",\n cashOnlyExecutionTouched: false,\n runtimeTargetMode: \"current\",\n runtimeTargetTouched: false,\n dcaMode: \"fixed\",\n dcaBaseInvestmentUsd: \"\",\n dcaTouched: false,\n strategyTouched: false,\n });\n\n const state = {\n selected: \"longbridge\",\n lang: initialLang,\n view: \"control\",\n appReady: false,\n bootMessageKey: \"bootMessage\",\n auth: { available: false, allowed: false, admin: false, login: null },\n accountOptions: clone(defaultAccountOptions),\n currentStrategies: {},\n health: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { strategy_count: 0, healthy: 0, watch: 0, review: 0, critical: 0 },\n strategies: [],\n },\n filter: \"all\",\n },\n controlPlane: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { candidate_count: 0, deferred: 0, parked: 0, owner_decision_required: 0 },\n attention: { status: \"unavailable\", reason_codes: [\"control_plane_source_unavailable\"] },\n candidates: [],\n policy: { p4_p5_automation: \"not_configured\", p6_owner_decision_required: true },\n errors: [],\n },\n },\n ownerDecisions: {\n data_status: \"unavailable\",\n candidates: [],\n errors: [],\n },\n reconciliationRecovery: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { recovery_count: 0, awaiting_human_confirmation: 0, blocked: 0, confirmed: 0 },\n recoveries: [],\n policy: { human_confirmation_required: true, current_evidence_required: true, no_order: true, execution_authority_granted: false },\n errors: [],\n },\n submittingRecoveryId: null,\n },\n m0Research: {\n payload: {\n data_status: \"unavailable\",\n viewed_at: null,\n summary: {\n subject_count: 0,\n observation_count: 0,\n fresh_observation_count: 0,\n stale_observation_count: 0,\n unknown_observation_count: 0,\n horizon_conflict_count: 0,\n historical_stale_horizon_drift_count: 0,\n },\n subjects: [],\n policy: { authority: \"research_only\", no_order: true, permitted_next_step: \"research_validation_only\" },\n errors: [],\n },\n },\n adaptiveSelection: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { source_count: 0, decision_count: 0, candidate_count: 0, recommended_count: 0, rejected_candidate_count: 0 },\n selections: [],\n policy: { authority: \"shadow_only\", no_order: true, execution_authority_granted: false },\n errors: [],\n },\n },\n executionEvidence: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { deployment_count: 0, autonomous_shadow: 0, autonomous_paper: 0, owner_canary_decision: 0, parked: 0 },\n deployments: [],\n policy: { execution_evidence_read_only: true, p6_owner_decision_required: true, limited_live_canary_active: false },\n errors: [],\n },\n },\n runtimeTargetLifecycle: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { target_count: 0, enabled: 0, disabled: 0, attention: 0 },\n targets: [],\n policy: { lifecycle_status_read_only: true, no_order: true },\n errors: [],\n },\n },\n researchTasks: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { task_count: 0 },\n tasks: [],\n policy: { research_only: true, no_order: true, size_zero_required: true, p4_p5_p6_authorized: false },\n errors: [],\n },\n },\n configSource: \"default\",\n repositories: clone(defaultRepositories),\n forms: {\n longbridge: { accountKey: \"preview\", strategy: \"\", executionMode: \"live\", pluginMode: \"none\", ...defaultReserveForm() },\n ibkr: { accountKey: \"preview\", strategy: \"\", executionMode: \"live\", pluginMode: \"none\", ...defaultReserveForm() },\n schwab: { accountKey: \"preview\", strategy: \"\", executionMode: \"live\", pluginMode: \"none\", ...defaultReserveForm() },\n firstrade: { accountKey: \"preview\", strategy: \"\", executionMode: \"live\", pluginMode: \"none\", ...defaultReserveForm() },\n qmt: { accountKey: \"preview\", strategy: \"\", executionMode: \"paper\", pluginMode: \"none\", ...defaultReserveForm() },\n binance: { accountKey: \"preview\", strategy: \"\", executionMode: \"live\", pluginMode: \"none\" },\n },\n };\n\n const el = (id) => document.getElementById(id);\n const t = (key) => copy[state.lang][key] || copy.en[key] || key;\n let toastTimer = null;\n\n function showToast(message, { duration = 4000 } = {}) {\n const node = el(\"toast\");\n if (toastTimer) {\n window.clearTimeout(toastTimer);\n toastTimer = null;\n }\n node.textContent = message || \"\";\n if (message && duration > 0) {\n toastTimer = window.setTimeout(() => {\n node.textContent = \"\";\n toastTimer = null;\n }, duration);\n }\n }\n\n async function fetchWithTimeout(url, init = {}, timeoutMs = APP_BOOT_TIMEOUT_MS) {\n const controller = new AbortController();\n const timeoutId = window.setTimeout(() => controller.abort(), timeoutMs);\n try {\n return await fetch(url, { ...init, signal: controller.signal });\n } catch (error) {\n if (error?.name === \"AbortError\") {\n throw new Error(\"request timeout\");\n }\n throw error;\n } finally {\n window.clearTimeout(timeoutId);\n }\n }\n\n async function requestJson(url, init = {}, timeoutMs = APP_BOOT_TIMEOUT_MS) {\n const response = await fetchWithTimeout(url, { ...init, cache: \"no-store\" }, timeoutMs);\n if (!response.ok) throw new Error(\"request failed\");\n return response.json();\n }\n\n function isRequestTimeoutError(error) {\n return String(error?.message || \"\").toLowerCase() === \"request timeout\";\n }\n\n function optionsFor(platform) {\n return state.accountOptions[platform] && state.accountOptions[platform].length\n ? state.accountOptions[platform]\n : defaultAccountOptions[platform];\n }\n\n function selectedAccount(platform = state.selected) {\n const options = optionsFor(platform);\n const form = state.forms[platform];\n return options.find((option) => option.key === form.accountKey) || options[0];\n }\n\n function hasPrivateConfig() {\n return Boolean(state.auth.allowed && state.configSource === \"private\");\n }\n\n function cleanStrategyProfile(value) {\n const profile = String(value || \"\").trim();\n return /^[a-z0-9._=-]{1,120}$/.test(profile) ? profile : \"\";\n }\n\n function cleanStrategyDomain(value) {\n const domain = String(value || \"\").trim();\n return strategyDomains.includes(domain) ? domain : \"\";\n }\n\n function domainLabel(domain) {\n const entry = domainLabels[domain];\n if (entry) return state.lang === \"zh\" ? entry.zh : entry.en;\n return domain;\n }\n\n function platformSupportsMarginPolicy(platform = state.selected) {\n return platformConfig[platform]?.margin_policy ?? true;\n }\n\n function platformSupportsReservedCashPolicy(platform = state.selected) {\n return platformConfig[platform]?.reserved_cash ?? true;\n }\n\n function platformDryRunOnly(platform = state.selected) {\n return platformConfig[platform]?.dry_run_only ?? false;\n }\n\n function supportedExecutionModesForPlatform(platform = state.selected) {\n const modes = platformConfig[platform]?.supported_execution_modes;\n return Array.isArray(modes) ? modes.filter((mode) => mode === \"live\" || mode === \"dry_run\") : [];\n }\n\n function allowMarginExplicitlySelected(form) {\n return normalizeCashOnlyExecutionMode(form?.cashOnlyExecutionMode) === \"disabled\";\n }\n\n function reserveCashOverrideActive(form) {\n const mode = normalizeReservePolicyMode(form?.reservePolicyMode);\n return mode === \"ratio\" || mode === \"floor\" || mode === \"max\";\n }\n\n function executionCashPolicyConflict(form) {\n return allowMarginExplicitlySelected(form) && reserveCashOverrideActive(form);\n }\n\n function reconcileExecutionCashPolicy(form, changed) {\n if (!form) return;\n if (changed === \"margin\" && allowMarginExplicitlySelected(form)) {\n if (form.reservePolicyMode !== \"none\" && (form.minReservedCashUsd || form.reservedCashRatio)) {\n form._prevReserve = {\n mode: form.reservePolicyMode,\n floor: form.minReservedCashUsd,\n ratio: form.reservedCashRatio,\n };\n }\n form.reservePolicyMode = \"none\";\n form.reservedCashTouched = true;\n } else if (changed === \"reserve\" && reserveCashOverrideActive(form)) {\n form.cashOnlyExecutionMode = \"enabled\";\n form.cashOnlyExecutionTouched = true;\n }\n }\n\n function restoreReserveAfterMarginDisabled(form) {\n if (!form || allowMarginExplicitlySelected(form) || !form._prevReserve) return;\n form.reservePolicyMode = form._prevReserve.mode;\n form.minReservedCashUsd = form._prevReserve.floor;\n form.reservedCashRatio = form._prevReserve.ratio;\n form.reservedCashTouched = true;\n delete form._prevReserve;\n }\n\n function strategyDomain(profile) {\n return strategyCatalog[profile]?.domain || \"\";\n }\n\n function selectedCashCurrency(platform = state.selected, account = selectedAccount(platform)) {\n const configured = String(account?.cash_currency || \"\").trim().toUpperCase();\n if (configured === \"USD\" || configured === \"HKD\" || configured === \"CNY\") return configured;\n const domain = strategyDomain(state.forms[platform]?.strategy);\n if (domain === \"hk_equity\") return \"HKD\";\n if (domain === \"cn_equity\") return \"CNY\";\n return \"USD\";\n }\n\n function applyStrategyProfiles(rawProfiles) {\n const profiles = Array.isArray(rawProfiles) && rawProfiles.length\n ? rawProfiles\n : defaultStrategyProfiles;\n const nextOptions = [];\n const nextLabels = {};\n const nextCatalog = {};\n const nextIncomeLayerDefaults = {};\n const nextOptionOverlayDefaults = {};\n for (const item of profiles) {\n const profile = cleanStrategyProfile(item?.profile || item?.strategy_profile);\n if (!profile || nextOptions.includes(profile)) continue;\n const domain = cleanStrategyDomain(item?.domain || \"us_equity\");\n if (!domain) continue;\n nextOptions.push(profile);\n nextLabels[profile] = strategyLabelSet(profile, item);\n nextCatalog[profile] = {\n profile,\n label: nextLabels[profile].en || nextLabels[profile].zh || profile,\n label_en: nextLabels[profile].en || \"\",\n label_zh: nextLabels[profile].zh || \"\",\n domain,\n runtime_enabled: cleanOptionalBoolean(item?.runtime_enabled ?? false) === true,\n };\n const lifecycleStage = normalizeLifecycleStage(item?.lifecycle_stage ?? item?.lifecycleStage);\n if (lifecycleStage) nextCatalog[profile].lifecycle_stage = lifecycleStage;\n const allowedExecutionModes = normalizeAllowedExecutionModes(item?.allowed_execution_modes);\n if (allowedExecutionModes.length) nextCatalog[profile].allowed_execution_modes = allowedExecutionModes;\n const canSwitchLive = cleanOptionalBoolean(item?.can_switch_live);\n if (canSwitchLive !== null) nextCatalog[profile].can_switch_live = canSwitchLive;\n const blockedLiveReason = cleanDisplayText(item?.blocked_live_reason);\n if (blockedLiveReason) nextCatalog[profile].blocked_live_reason = blockedLiveReason;\n const latestEvidenceStatus = cleanDisplayText(item?.latest_evidence_status);\n if (latestEvidenceStatus) nextCatalog[profile].latest_evidence_status = latestEvidenceStatus;\n const pluginGateStatus = cleanDisplayText(item?.plugin_gate_status);\n if (pluginGateStatus) nextCatalog[profile].plugin_gate_status = pluginGateStatus;\n const dcaDefaults = dcaProfileDefaults[profile] || null;\n if (dcaDefaults || item?.dca_enabled === true) {\n nextCatalog[profile].dca_enabled = true;\n nextCatalog[profile].dca_default_mode = normalizeDcaMode(\n item?.dca_default_mode || item?.default_dca_mode || dcaDefaults?.defaultMode || \"fixed\",\n );\n nextCatalog[profile].dca_default_base_investment_usd = cleanDisplayPositiveNumber(\n item?.dca_default_base_investment_usd ||\n item?.default_dca_base_investment_usd ||\n dcaDefaults?.defaultBaseInvestmentUsd ||\n \"1000\",\n ) || \"1000\";\n }\n const profileIncomeDefaults = incomeLayerDefaultsFromProfileItem(item);\n const incomeDefaults = profileIncomeDefaults === false\n ? null\n : (profileIncomeDefaults || fallbackIncomeLayerDefaults[profile] || null);\n if (incomeDefaults) {\n nextIncomeLayerDefaults[profile] = incomeDefaults;\n nextCatalog[profile].income_layer_enabled = true;\n nextCatalog[profile].income_layer_start_usd = String(incomeDefaults.startUsd);\n nextCatalog[profile].income_layer_max_ratio = incomeDefaults.maxRatio;\n nextCatalog[profile].income_layer_allocations = incomeDefaults.allocations;\n }\n const profileOptionDefaults = optionOverlayDefaultsFromProfileItem(item);\n const optionDefaults = profileOptionDefaults === false\n ? null\n : (profileOptionDefaults || fallbackOptionOverlayDefaults[profile] || null);\n if (optionDefaults) {\n nextOptionOverlayDefaults[profile] = optionDefaults;\n nextCatalog[profile].option_overlay_enabled = true;\n nextCatalog[profile].option_overlay_live_gate = optionDefaults.liveGate || \"\";\n nextCatalog[profile].option_overlay_live_status = optionDefaults.liveStatus || \"\";\n }\n }\n if (!nextOptions.length && profiles !== defaultStrategyProfiles) return applyStrategyProfiles(defaultStrategyProfiles);\n strategyOptions = nextOptions;\n strategyLabels = nextLabels;\n strategyCatalog = nextCatalog;\n incomeLayerDefaults = nextIncomeLayerDefaults;\n optionOverlayDefaults = nextOptionOverlayDefaults;\n }\n\n function incomeLayerDefaultsFromProfileItem(item) {\n const enabled = cleanOptionalBoolean(item?.income_layer_enabled);\n const hasConfig = enabled !== null ||\n item?.income_layer_start_usd !== undefined ||\n item?.income_layer_max_ratio !== undefined ||\n item?.income_layer_allocations !== undefined;\n if (!hasConfig) return null;\n if (enabled === false) return false;\n const startUsd = cleanDisplayNumber(item?.income_layer_start_usd);\n const maxRatio = cleanDisplayRatio(item?.income_layer_max_ratio);\n const allocations = cleanIncomeLayerAllocations(item?.income_layer_allocations);\n if (!startUsd || !maxRatio || !allocations) return null;\n return { startUsd, maxRatio, allocations };\n }\n\n function cleanIncomeLayerAllocations(value) {\n if (!value || Array.isArray(value) || typeof value !== \"object\") return null;\n const allocations = {};\n let total = 0;\n for (const [rawSymbol, rawRatio] of Object.entries(value)) {\n const symbol = String(rawSymbol || \"\").trim().toUpperCase();\n const ratio = cleanDisplayPositiveNumber(rawRatio);\n if (!/^[A-Z0-9.-]{1,12}$/.test(symbol) || !ratio) continue;\n allocations[symbol] = Number(ratio);\n total += Number(ratio);\n }\n return total > 0 && Object.keys(allocations).length ? allocations : null;\n }\n\n function optionOverlayDefaultsFromProfileItem(item) {\n const enabled = cleanOptionalBoolean(item?.option_overlay_enabled);\n const hasConfig = enabled !== null ||\n item?.option_growth_overlay_enabled !== undefined ||\n item?.option_income_overlay_enabled !== undefined ||\n item?.option_overlay_live_gate !== undefined ||\n item?.option_overlay_live_status !== undefined;\n if (!hasConfig) return null;\n if (enabled === false) return false;\n const families = [\n optionOverlayFamilyDefaultsFromProfileItem(item, \"growth\"),\n optionOverlayFamilyDefaultsFromProfileItem(item, \"income\"),\n ].filter(Boolean);\n if (!families.length) return null;\n return {\n liveGate: String(item?.option_overlay_live_gate || \"promotion_required\"),\n liveStatus: String(item?.option_overlay_live_status || \"research_only\"),\n families,\n };\n }\n\n function optionOverlayFamilyDefaultsFromProfileItem(item, family) {\n const prefix = `option_${family}_overlay`;\n const enabled = cleanOptionalBoolean(item?.[`${prefix}_enabled`]);\n if (enabled !== true) return null;\n const recipe = cleanStrategyProfile(item?.[`${prefix}_recipe`]);\n const startUsd = cleanDisplayNumber(item?.[`${prefix}_start_usd`]);\n const ratioField = family === \"growth\"\n ? \"option_growth_overlay_nav_budget_ratio\"\n : \"option_income_overlay_nav_risk_ratio\";\n const ratio = cleanDisplayRatio(item?.[ratioField]);\n if (!recipe || !startUsd || !ratio) return null;\n return {\n family,\n recipe,\n startUsd,\n ratio,\n ratioKind: family === \"growth\" ? \"budget\" : \"risk\",\n };\n }\n\n function supportedDomainsForAccount(platform, account) {\n if (Array.isArray(account?.supported_domains) && account.supported_domains.length) {\n const cleaned = account.supported_domains.map(cleanStrategyDomain).filter(Boolean);\n if (cleaned.length) return [...new Set(cleaned)];\n }\n return inferSupportedDomains(platform, account);\n }\n\n function inferSupportedDomains(platform, account) {\n void account;\n if (platform === \"qmt\") return [\"cn_equity\"];\n if (platform === \"longbridge\" || platform === \"ibkr\") return [\"us_equity\", \"hk_equity\"];\n return [\"us_equity\"];\n }\n\n function supportedDomainLabel(platform, account) {\n return supportedDomainsForAccount(platform, account).map(domainLabel).join(\" / \");\n }\n\n function platformSupportsDca(platform = state.selected) {\n return platformConfig[platform]?.dca ?? false;\n }\n\n function strategyCatalogEntry(profile) {\n return strategyCatalog[cleanStrategyProfile(profile)] || {};\n }\n\n function normalizeLifecycleStage(value) {\n const text = String(value || \"\").trim().toLowerCase();\n if (!text || text.length > 40 || !/^[a-z0-9._-]+$/.test(text)) return \"\";\n if ([\"research_backtest_only\", \"ai_monitored_candidate\"].includes(text)) return \"research_active\";\n if (text === \"shadow_candidate\") return \"shadow_active\";\n if (text === \"runtime_enabled\") return \"live_candidate\";\n return text;\n }\n\n function normalizeAllowedExecutionModes(value) {\n const items = Array.isArray(value)\n ? value\n : String(value || \"\").split(/[,\\s/|]+/);\n const modes = [];\n for (const item of items) {\n const mode = String(item || \"\").trim().toLowerCase();\n if (!mode || mode.length > 32 || !/^[a-z0-9._-]+$/.test(mode)) continue;\n if (!modes.includes(mode)) modes.push(mode);\n }\n return modes;\n }\n\n function cleanDisplayText(value) {\n const text = String(value ?? \"\").trim();\n if (!text || text.length > 120 || /[<>]/.test(text)) return \"\";\n return text;\n }\n\n function strategyCanSwitchLive(entry) {\n if (!entry || typeof entry !== \"object\") return false;\n if (entry.runtime_enabled !== true) return false;\n const allowedModes = normalizeAllowedExecutionModes(entry.allowed_execution_modes);\n if (!allowedModes.includes(\"live\")) return false;\n if (cleanOptionalBoolean(entry.can_switch_live) !== true) return false;\n const lifecycleStage = normalizeLifecycleStage(entry.lifecycle_stage);\n if (![\"live_enabled\", \"runtime_enabled\"].includes(lifecycleStage)) return false;\n const blockedReason = cleanDisplayText(entry.blocked_live_reason);\n if (blockedReason) return false;\n const evidenceStatus = cleanDisplayText(entry.latest_evidence_status);\n if (evidenceStatus && [\"research_only\", \"blocked\", \"pending\"].includes(evidenceStatus.toLowerCase())) return false;\n const pluginGateStatus = cleanDisplayText(entry.plugin_gate_status);\n if (pluginGateStatus && [\"blocked\", \"locked\", \"disabled\"].includes(pluginGateStatus.toLowerCase())) return false;\n return true;\n }\n\n function strategyDisplayMetaText(platform, account, profile) {\n void profile;\n return t(\"strategyMeta\").replace(\"{domains}\", supportedDomainLabel(platform, account));\n }\n\n function strategyActionNoteText(platform = state.selected, account = selectedAccount(platform)) {\n const profile = state.forms[platform]?.strategy || \"\";\n const meta = strategyDisplayMetaText(platform, account, profile);\n return meta ? `${t(\"invalidStrategyNote\")}\\n${meta}` : t(\"invalidStrategyNote\");\n }\n\n function strategyChoiceLabel(profile, platform = state.selected, account = selectedAccount(platform), executionMode = state.forms[platform]?.executionMode) {\n const label = strategyLabel(profile);\n const entry = strategyCatalogEntry(profile);\n const domain = entry.domain ? domainLabel(entry.domain) : \"\";\n if (!entry.profile) return label;\n if (normalizeExecutionMode(executionMode, false) === \"live\" && !strategyCanSwitchLive(entry)) {\n return domain ? `${label}(${domain})` : label;\n }\n if (entry.runtime_enabled === false) {\n return domain ? `${label}(${domain})` : label;\n }\n return label;\n }\n\n function strategyAllowedForAccount(platform, account, profile, executionMode = state.forms[platform]?.executionMode) {\n const cleanProfile = cleanStrategyProfile(profile);\n const catalogEntry = strategyCatalogEntry(cleanProfile);\n if (!catalogEntry.profile) return false;\n if (dcaConfigForStrategy(cleanProfile) && !platformSupportsDca(platform)) return false;\n if (!supportedDomainsForAccount(platform, account).includes(catalogEntry.domain)) return false;\n const mode = normalizeExecutionMode(executionMode, false);\n if (!supportedExecutionModesForPlatform(platform).includes(mode)) return false;\n if (mode === \"live\") return strategyCanSwitchLive(catalogEntry);\n const allowedModes = normalizeAllowedExecutionModes(catalogEntry.allowed_execution_modes);\n if (allowedModes.length && !allowedModes.includes(mode)) return false;\n return true;\n }\n\n function strategyChoicesForAccount(platform = state.selected, account = selectedAccount(platform), executionMode = state.forms[platform]?.executionMode) {\n const choices = strategyOptions.filter((profile) => strategyAllowedForAccount(platform, account, profile, executionMode));\n const addChoice = (value) => {\n const profile = cleanStrategyProfile(value);\n if (profile && !choices.includes(profile) && strategyAllowedForAccount(platform, account, profile, executionMode)) {\n choices.push(profile);\n }\n };\n return choices;\n }\n\n function hasLiveStrategyOption(platform = state.selected, account = selectedAccount(platform)) {\n return strategyOptions.some((profile) => strategyAllowedForAccount(platform, account, profile, \"live\"));\n }\n\n function strategyLabel(profile) {\n const labels = strategyLabels[profile] || localStrategyLabels[profile];\n if (!labels) return profile;\n return state.lang === \"zh\"\n ? (labels.zh || labels.en || profile)\n : (labels.en || labels.zh || profile);\n }\n\n function strategyLabelSet(profile, item) {\n const local = localStrategyLabels[profile] || {};\n const label = String(item?.label || item?.display_name || \"\").trim();\n const labelEn = String(item?.label_en || item?.display_name_en || \"\").trim();\n const labelZh = String(item?.label_zh || item?.display_name_zh || \"\").trim();\n return {\n zh: labelZh || local.zh || label || local.en || profile,\n en: labelEn || label || local.en || labelZh || local.zh || profile,\n };\n }\n\n function modeLabel(mode) {\n return mode === \"dry_run\" ? t(\"dryRun\") : mode === \"paper\" ? t(\"paper\") : t(\"live\");\n }\n\n function normalizePluginMode(value) {\n return pluginModes.includes(value) ? value : \"none\";\n }\n\n function pluginModeLabel(mode) {\n void mode;\n return t(\"pluginModeNone\");\n }\n\n function dcaConfigForStrategy(profile) {\n const cleanProfile = cleanStrategyProfile(profile);\n const catalog = strategyCatalog[cleanProfile] || {};\n if (catalog.dca_enabled === true) {\n return {\n defaultMode: normalizeDcaMode(catalog.dca_default_mode || \"fixed\"),\n defaultBaseInvestmentUsd: cleanDisplayPositiveNumber(catalog.dca_default_base_investment_usd) || \"1000\",\n };\n }\n return dcaProfileDefaults[cleanProfile] || null;\n }\n\n function dcaSupported(profile) {\n return Boolean(dcaConfigForStrategy(profile));\n }\n\n function normalizeDcaMode(value) {\n const mode = String(value || \"\").trim().toLowerCase();\n if (mode === \"smart_dca\") return \"smart\";\n if (mode === \"fixed_dca\" || mode === \"ordinary\" || mode === \"ordinary_dca\") return \"fixed\";\n return dcaModes.includes(mode) ? mode : \"fixed\";\n }\n\n function dcaModeLabel(mode) {\n return normalizeDcaMode(mode) === \"smart\" ? t(\"dcaModeSmart\") : t(\"dcaModeFixed\");\n }\n\n function normalizeRuntimeTargetMode(value) {\n return runtimeTargetModes.includes(value) ? value : \"current\";\n }\n\n function runtimeTargetModeLabel(mode) {\n if (mode === \"enabled\") return t(\"runtimeTargetEnabled\");\n if (mode === \"disabled\") return t(\"runtimeTargetDisabled\");\n return t(\"runtimeTargetCurrent\");\n }\n\n function runtimeTargetEnabledForAccount(platform, account) {\n return cleanOptionalBoolean(currentEntryForAccount(platform, account)?.runtime_target_enabled);\n }\n\n function runtimeTargetStateForAccount(platform = state.selected, account = selectedAccount(platform)) {\n const entry = currentEntryForAccount(platform, account);\n if (!entry) return { known: false, enabled: null };\n const configured = cleanOptionalBoolean(entry.runtime_target_enabled);\n return { known: true, enabled: configured ?? true };\n }\n\n function runtimeTargetText(enabled) {\n return enabled ? t(\"runtimeTargetOn\") : t(\"runtimeTargetOff\");\n }\n\n function runtimeTargetTone(enabled) {\n return enabled ? \"enabled\" : \"disabled\";\n }\n\n function currentRuntimeTargetText(platform = state.selected, account = selectedAccount(platform)) {\n const target = runtimeTargetStateForAccount(platform, account);\n if (!target.known) return t(\"notRead\");\n return runtimeTargetText(target.enabled);\n }\n\n function currentRuntimeTargetTone(platform = state.selected, account = selectedAccount(platform)) {\n const target = runtimeTargetStateForAccount(platform, account);\n if (!target.known) return \"neutral\";\n return runtimeTargetTone(target.enabled);\n }\n\n function incomeLayerDefaultForStrategy(profile) {\n return incomeLayerDefaults[cleanStrategyProfile(profile)] || null;\n }\n\n function incomeLayerSupported(profile) {\n return Boolean(incomeLayerDefaultForStrategy(profile));\n }\n\n function normalizeIncomeLayerMode(value) {\n return incomeLayerModes.includes(value) ? value : \"current\";\n }\n\n function incomeLayerModeLabel(mode) {\n if (mode === \"enabled\") return t(\"incomeLayerEnabled\");\n if (mode === \"disabled\") return t(\"incomeLayerDisabled\");\n return t(\"incomeLayerCurrent\");\n }\n\n function optionOverlayDefaultForStrategy(profile) {\n return optionOverlayDefaults[cleanStrategyProfile(profile)] || null;\n }\n\n function optionOverlaySupported(profile) {\n return Boolean(optionOverlayDefaultForStrategy(profile));\n }\n\n function normalizeOptionOverlayMode(value) {\n return optionOverlayModes.includes(value) ? value : \"current\";\n }\n\n function optionOverlayModeLabel(mode) {\n if (mode === \"enabled\") return t(\"optionOverlayEnabled\");\n if (mode === \"disabled\") return t(\"optionOverlayDisabled\");\n return t(\"optionOverlayCurrent\");\n }\n\n function optionOverlayText(enabled) {\n return enabled ? t(\"optionOverlayOn\") : t(\"optionOverlayOff\");\n }\n\n function normalizeCashOnlyExecutionMode(value) {\n return cashOnlyExecutionModes.includes(value) ? value : \"current\";\n }\n\n function cashOnlyExecutionModeLabel(mode) {\n if (mode === \"enabled\") return t(\"cashOnlyExecutionNo\");\n if (mode === \"disabled\") return t(\"cashOnlyExecutionYes\");\n return t(\"cashOnlyExecutionCurrent\");\n }\n\n function cashOnlyExecutionText(enabled) {\n if (enabled === null) return t(\"notRead\");\n return enabled ? t(\"cashOnlyExecutionValueNo\") : t(\"cashOnlyExecutionValueYes\");\n }\n\n function platformCashOnlyExecutionDefault() {\n return true;\n }\n\n function effectiveCashOnlyExecutionForAccount(platform, account) {\n const configured = currentCashOnlyExecutionForAccount(platform, account);\n if (configured !== null) return configured;\n if (!platformSupportsMarginPolicy(platform)) return null;\n return platformCashOnlyExecutionDefault();\n }\n\n function currentCashOnlyExecutionForAccount(platform, account) {\n const entry = currentEntryForAccount(platform, account);\n if (entry) {\n const val = cleanOptionalBoolean(entry.cash_only_execution);\n if (val !== null) return val;\n }\n return platformCashOnlyExecutionDefault();\n }\n\n function currentCashOnlyExecutionText(platform = state.selected, account = selectedAccount(platform)) {\n if (!platformSupportsMarginPolicy(platform)) return t(\"notRead\");\n const entry = currentEntryForAccount(platform, account);\n if (!entry) return t(\"notRead\");\n const configured = cleanOptionalBoolean(entry.cash_only_execution);\n if (configured === null) return t(\"cashOnlyExecutionDefault\");\n return cashOnlyExecutionText(configured);\n }\n\n function currentOptionOverlayForAccount(platform, account) {\n return cleanOptionalBoolean(currentEntryForAccount(platform, account)?.option_overlay_enabled);\n }\n\n function effectiveOptionOverlayForAccount(platform, account, profile = state.forms[platform]?.strategy) {\n const configured = currentOptionOverlayForAccount(platform, account);\n if (configured !== null) return configured;\n if (!optionOverlaySupported(profile)) return null;\n return true;\n }\n\n function optionOverlayDefaultSummaryDetail(defaults) {\n if (!defaults?.families?.length) return \"\";\n return defaults.families.map((item) => {\n const family = item.family === \"income\" ? t(\"optionOverlayFamilyIncome\") : t(\"optionOverlayFamilyGrowth\");\n const ratioText = item.ratioKind === \"risk\"\n ? t(\"optionOverlayRiskRatio\").replace(\"{ratio}\", formatRatioPercent(item.ratio))\n : t(\"optionOverlayBudgetRatio\").replace(\"{ratio}\", formatRatioPercent(item.ratio));\n return `${family} ${ratioText}`;\n }).join(\" / \");\n }\n\n function optionOverlayDefaultText(profile) {\n const defaults = optionOverlayDefaultForStrategy(profile);\n if (!defaults) return t(\"optionOverlayNotSupported\");\n const detail = optionOverlayDefaultSummaryDetail(defaults);\n return detail ? t(\"optionOverlayDefault\").replace(\"{detail}\", detail) : t(\"optionOverlayDefaultSimple\");\n }\n\n function currentOptionOverlayText(platform = state.selected, account = selectedAccount(platform), profile = state.forms[platform]?.strategy) {\n const entry = currentEntryForAccount(platform, account);\n if (!entry) return t(\"notRead\");\n const configured = cleanOptionalBoolean(entry.option_overlay_enabled);\n if (!optionOverlaySupported(profile)) {\n return configured === null ? t(\"optionOverlayNotSupported\") : optionOverlayText(configured);\n }\n if (configured === null) return optionOverlayDefaultText(profile);\n return optionOverlayText(configured);\n }\n\n function currentIncomeLayerForAccount(platform, account) {\n return incomeLayerFromEntry(currentEntryForAccount(platform, account));\n }\n\n function incomeLayerFromEntry(entry) {\n return {\n enabled: cleanOptionalBoolean(entry?.income_layer_enabled),\n startUsd: cleanDisplayNumber(entry?.income_layer_start_usd),\n maxRatio: cleanDisplayRatio(entry?.income_layer_max_ratio),\n };\n }\n\n function incomeLayerFieldsConfigured(entry) {\n const current = incomeLayerFromEntry(entry);\n return current.enabled !== null || Boolean(current.startUsd || current.maxRatio);\n }\n\n function effectiveIncomeLayerForAccount(platform, account, profile = state.forms[platform]?.strategy) {\n const defaults = incomeLayerDefaultForStrategy(profile);\n if (!defaults) return null;\n const entry = currentEntryForAccount(platform, account);\n if (!entry) return null;\n const current = incomeLayerFromEntry(entry);\n if (!incomeLayerFieldsConfigured(entry)) {\n return {\n enabled: true,\n startUsd: String(defaults.startUsd),\n maxRatio: defaults.maxRatio,\n };\n }\n return {\n enabled: current.enabled ?? true,\n startUsd: current.startUsd || String(defaults.startUsd),\n maxRatio: current.maxRatio || defaults.maxRatio,\n };\n }\n\n function currentDcaForAccount(platform, account, profile = state.forms[platform]?.strategy) {\n const defaults = dcaConfigForStrategy(profile);\n if (!defaults) return { supported: false, mode: \"\", baseInvestmentUsd: \"\" };\n const entry = currentEntryForAccount(platform, account);\n return {\n supported: true,\n mode: normalizeDcaMode(entry?.dca_mode || account?.dca_mode || defaults.defaultMode),\n baseInvestmentUsd: cleanDisplayPositiveNumber(entry?.dca_base_investment_usd) ||\n cleanDisplayPositiveNumber(account?.dca_base_investment_usd) ||\n defaults.defaultBaseInvestmentUsd,\n };\n }\n\n function normalizeAccountLookupKey(value) {\n return String(value || \"\").trim().toLowerCase();\n }\n\n function collectAccountLookupCandidates(keys) {\n const candidates = new Set();\n for (const rawKey of keys) {\n const key = normalizeAccountLookupKey(rawKey);\n if (!key) continue;\n\n candidates.add(key);\n\n const compact = key.replace(/[^a-z0-9]+/g, \"\");\n if (compact) candidates.add(compact);\n\n const parts = key.split(/[^a-z0-9]+/).filter(Boolean);\n if (parts.length > 1) candidates.add(parts[parts.length - 1]);\n }\n return [...candidates];\n }\n\n function resolveCurrentEntryByKey(byPlatform, keys) {\n const candidates = new Set(collectAccountLookupCandidates(keys));\n if (!candidates.size) return null;\n\n for (const key of keys) {\n const entry = byPlatform[key];\n if (currentEntryHasState(entry)) return entry;\n }\n\n for (const [rawKey, entry] of Object.entries(byPlatform)) {\n if (!currentEntryHasState(entry)) continue;\n const rawCandidates = collectAccountLookupCandidates([rawKey]);\n const hasMatch = rawCandidates.some((candidate) => candidates.has(candidate));\n if (hasMatch) return entry;\n }\n\n return null;\n }\n\n function currentEntryForAccount(platform, account) {\n const byPlatform = state.currentStrategies[platform] || {};\n const keys = [account?.key, account?.target_name, account?.label]\n .filter(Boolean)\n .map((value) => String(value));\n const entry = resolveCurrentEntryByKey(byPlatform, keys);\n if (entry) return entry;\n const globalDefaults = window.__DEFAULT_ACCOUNT_OPTIONS__?.[platform]?.[0] || {};\n const merged = { ...globalDefaults, ...(account || {}) };\n const synth = {\n strategy_profile: \"\",\n source: \"account_defaults\",\n };\n const cashMode = merged.cash_only_execution_mode;\n if (cashMode === \"enabled\") synth.cash_only_execution = true;\n else if (cashMode === \"disabled\") synth.cash_only_execution = false;\n else if (platformSupportsMarginPolicy(platform)) synth.cash_only_execution = true;\n if (merged.min_reserved_cash_usd) synth.min_reserved_cash_usd = merged.min_reserved_cash_usd;\n if (merged.reserved_cash_ratio) synth.reserved_cash_ratio = merged.reserved_cash_ratio;\n synth.runtime_target_enabled = merged.runtime_target_enabled !== false;\n const execMode = merged.default_execution_mode || platformConfig[platform]?.default_execution_mode || \"live\";\n synth.execution_mode = execMode;\n synth.dry_run_only = execMode === \"paper\";\n return synth;\n }\n\n function currentEntryHasState(entry) {\n if (!entry || typeof entry !== \"object\" || Array.isArray(entry)) return false;\n return Boolean(\n cleanStrategyProfile(entry.strategy_profile) ||\n cleanDisplayNumber(entry.min_reserved_cash_usd ?? entry.reserved_cash_floor_usd) ||\n cleanDisplayRatio(entry.reserved_cash_ratio) ||\n cleanOptionalBoolean(entry.income_layer_enabled) !== null ||\n cleanDisplayNumber(entry.income_layer_start_usd) ||\n cleanDisplayRatio(entry.income_layer_max_ratio) ||\n cleanOptionalBoolean(entry.option_overlay_enabled) !== null ||\n cleanOptionalBoolean(entry.cash_only_execution) !== null ||\n cleanOptionalBoolean(entry.runtime_target_enabled) !== null ||\n normalizeDcaMode(entry.dca_mode || \"\") !== \"fixed\" ||\n cleanDisplayPositiveNumber(entry.dca_base_investment_usd) ||\n normalizeExecutionMode(entry.execution_mode, entry.dry_run_only),\n );\n }\n\n function currentStrategyForAccount(platform, account) {\n const entry = currentEntryForAccount(platform, account);\n return cleanStrategyProfile(entry?.strategy_profile) || \"\";\n }\n\n function currentReservePolicyForAccount(platform, account) {\n const entry = currentEntryForAccount(platform, account);\n return reservePolicyFromEntry(entry);\n }\n\n function currentPluginModeForAccount(platform, account) {\n void platform;\n return normalizePluginMode(account?.plugin_mode);\n }\n\n function reservePolicyFromEntry(entry) {\n return {\n minReservedCashUsd: cleanDisplayNumber(entry?.min_reserved_cash_usd ?? entry?.reserved_cash_floor_usd),\n reservedCashRatio: cleanDisplayRatio(entry?.reserved_cash_ratio),\n };\n }\n\n function cleanDisplayNumber(value) {\n const text = String(value ?? \"\").trim();\n if (!text || text.length > 32 || !/^(?:\\d+|\\d*\\.\\d+)$/.test(text)) return \"\";\n const numeric = Number(text);\n if (!Number.isFinite(numeric) || numeric < 0) return \"\";\n return text;\n }\n\n function cleanDisplayRatio(value) {\n const text = cleanDisplayNumber(value);\n if (!text) return \"\";\n const numeric = Number(text);\n return numeric >= 0 && numeric <= 1 ? text : \"\";\n }\n\n function cleanDisplayPositiveNumber(value) {\n const text = cleanDisplayNumber(value);\n return text && Number(text) > 0 ? text : \"\";\n }\n\n function normalizeExecutionMode(value, dryRunOnly) {\n const mode = String(value || \"\").trim().toLowerCase();\n if (mode === \"live\") return \"live\";\n if (mode === \"paper\" || mode === \"dry_run\" || mode === \"dry-run\") return \"dry_run\";\n if (dryRunOnly === true || dryRunOnly === \"true\" || dryRunOnly === \"1\" || dryRunOnly === 1) return \"dry_run\";\n if (dryRunOnly === false || dryRunOnly === \"false\" || dryRunOnly === \"0\" || dryRunOnly === 0) return \"live\";\n return \"\";\n }\n\n function cleanOptionalBoolean(value) {\n if (value === true || value === 1) return true;\n if (value === false || value === 0) return false;\n if (typeof value === \"string\") {\n const text = value.trim().toLowerCase();\n if (text === \"true\" || text === \"1\") return true;\n if (text === \"false\" || text === \"0\") return false;\n }\n return null;\n }\n\n function defaultExecutionModeForAccount(platform, account, fallback = \"live\") {\n if (platformDryRunOnly(platform)) return \"dry_run\";\n const currentMode = normalizeExecutionMode(\n currentEntryForAccount(platform, account)?.execution_mode,\n currentEntryForAccount(platform, account)?.dry_run_only,\n );\n if (currentMode) return currentMode;\n const hint = [\n account?.key,\n account?.label,\n account?.target_name,\n account?.deployment_selector,\n account?.account_scope,\n account?.service_name,\n ].join(\" \").toLowerCase();\n if (hint.split(/\\s+/).includes(\"paper\") || hint.includes(\"-paper\") || hint.includes(\"_paper\") || hint.includes(\"dry_run\") || hint.includes(\"dry-run\")) {\n return \"dry_run\";\n }\n return fallback;\n }\n\n function defaultStrategyForAccount(platform, account) {\n const currentProfile = currentStrategyForAccount(platform, account);\n if (currentProfile) return currentProfile;\n return \"\";\n }\n\n function syncStrategyForAccount(platform) {\n const account = selectedAccount(platform);\n if (!account) return;\n state.forms[platform].strategy = defaultStrategyForAccount(platform, account);\n state.forms[platform].executionMode = defaultExecutionModeForAccount(\n platform,\n account,\n );\n state.forms[platform].pluginMode = currentPluginModeForAccount(platform, account);\n syncRuntimeTargetForAccount(platform);\n syncReservePolicyForAccount(platform);\n syncIncomeLayerForAccount(platform);\n syncOptionOverlayForAccount(platform);\n syncCashOnlyExecutionForAccount(platform);\n reconcileExecutionCashPolicy(state.forms[platform], \"margin\");\n syncDcaForAccount(platform);\n }\n\n function syncRuntimeTargetForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.runtimeTargetTouched) return;\n const current = runtimeTargetEnabledForAccount(platform, selectedAccount(platform));\n form.runtimeTargetMode = current === false ? \"disabled\" : \"enabled\";\n }\n\n function syncReservePolicyForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.reservedCashTouched) return;\n const policy = currentReservePolicyForAccount(platform, selectedAccount(platform));\n const hasFloor = Boolean(policy.minReservedCashUsd);\n const hasRatio = Boolean(policy.reservedCashRatio);\n if (hasFloor && hasRatio) form.reservePolicyMode = \"max\";\n else if (hasFloor) form.reservePolicyMode = \"floor\";\n else if (hasRatio) form.reservePolicyMode = \"ratio\";\n else form.reservePolicyMode = \"none\";\n form.minReservedCashUsd = policy.minReservedCashUsd;\n form.reservedCashRatio = policy.reservedCashRatio;\n }\n\n function syncIncomeLayerForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.incomeLayerTouched) return;\n const defaults = incomeLayerDefaultForStrategy(form.strategy);\n const current = currentIncomeLayerForAccount(platform, selectedAccount(platform));\n const entry = currentEntryForAccount(platform, selectedAccount(platform));\n if (entry && incomeLayerFieldsConfigured(entry)) {\n form.incomeLayerMode = current.enabled === false ? \"disabled\" : \"enabled\";\n } else {\n form.incomeLayerMode = incomeLayerSupported(form.strategy) ? \"enabled\" : \"disabled\";\n }\n form.incomeLayerStartUsd = current.startUsd || String(defaults?.startUsd || \"\");\n form.incomeLayerMaxRatio = current.maxRatio || defaults?.maxRatio || \"\";\n }\n\n function syncOptionOverlayForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.optionOverlayTouched) return;\n const configured = normalizeOptionOverlayMode(selectedAccount(platform)?.option_overlay_mode);\n if (configured !== \"current\") {\n form.optionOverlayMode = configured;\n return;\n }\n const entry = currentEntryForAccount(platform, selectedAccount(platform));\n const rawValue = cleanOptionalBoolean(entry?.option_overlay_enabled);\n if (rawValue !== null) {\n form.optionOverlayMode = rawValue ? \"enabled\" : \"disabled\";\n return;\n }\n form.optionOverlayMode = optionOverlaySupported(form.strategy) ? \"enabled\" : \"disabled\";\n }\n\n function syncCashOnlyExecutionForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.cashOnlyExecutionTouched) return;\n const configured = normalizeCashOnlyExecutionMode(selectedAccount(platform)?.cash_only_execution_mode);\n if (configured !== \"current\") {\n form.cashOnlyExecutionMode = configured;\n return;\n }\n const entry = currentEntryForAccount(platform, selectedAccount(platform));\n const rawValue = cleanOptionalBoolean(entry?.cash_only_execution);\n if (rawValue !== null) {\n form.cashOnlyExecutionMode = rawValue ? \"enabled\" : \"disabled\";\n return;\n }\n // No explicit config — use platform default (cash-only for margin-capable platforms)\n form.cashOnlyExecutionMode = platformSupportsMarginPolicy(platform) ? \"enabled\" : \"disabled\";\n }\n\n function syncDcaForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.dcaTouched) return;\n const current = currentDcaForAccount(platform, selectedAccount(platform), form.strategy);\n form.dcaMode = current.supported ? current.mode : \"fixed\";\n form.dcaBaseInvestmentUsd = current.supported ? current.baseInvestmentUsd : \"\";\n }\n\n function ensureAccountSelection(platform) {\n const options = optionsFor(platform);\n if (!options.length) return;\n if (!options.some((option) => option.key === state.forms[platform].accountKey)) {\n state.forms[platform].accountKey = options[0].key;\n state.forms[platform].runtimeTargetTouched = false;\n state.forms[platform].reservedCashTouched = false;\n state.forms[platform].incomeLayerTouched = false;\n state.forms[platform].optionOverlayTouched = false;\n state.forms[platform].cashOnlyExecutionTouched = false;\n state.forms[platform].dcaTouched = false;\n state.forms[platform].strategy = defaultStrategyForAccount(platform, options[0]);\n state.forms[platform].pluginMode = currentPluginModeForAccount(platform, options[0]);\n syncRuntimeTargetForAccount(platform);\n syncReservePolicyForAccount(platform);\n syncIncomeLayerForAccount(platform);\n syncOptionOverlayForAccount(platform);\n syncCashOnlyExecutionForAccount(platform);\n syncDcaForAccount(platform);\n }\n }\n\n function derivedService(platform, targetName) {\n if (platform === \"longbridge\") return `longbridge-quant-${targetName.toLowerCase()}-service`;\n if (platform === \"ibkr\") return `interactive-brokers-${targetName.toLowerCase()}-service`;\n if (platform === \"schwab\") return \"charles-schwab-quant-service\";\n if (platform === \"firstrade\") return \"firstrade-quant-service\";\n if (platform === \"qmt\") return \"qmt-quant-service\";\n return \"\";\n }\n\n function accountMetaText(platform = state.selected) {\n const account = selectedAccount(platform);\n const targetName = account.target_name || account.key;\n const raw = account.service_name || derivedService(platform, targetName);\n const service = raw || (state.lang === \"zh\" ? \"无\" : \"-\");\n return t(\"targetMeta\")\n .replace(\"{target}\", targetName)\n .replace(\"{service}\", service)\n .replace(\"{domains}\", supportedDomainLabel(platform, account));\n }\n\n function hasRunnableStrategySelection(platform = state.selected) {\n const form = state.forms[platform];\n const account = selectedAccount(platform);\n return Boolean(form?.strategy && account && strategyAllowedForAccount(platform, account, form.strategy, form.executionMode));\n }\n\n function hasValidReservePolicy(platform = state.selected) {\n if (!platformSupportsReservedCashPolicy(platform)) return true;\n const form = state.forms[platform];\n const mode = normalizeReservePolicyMode(form?.reservePolicyMode);\n if (mode === \"current\" || mode === \"none\") return true;\n return Boolean(reservePolicyOverrideForForm(form, platform));\n }\n\n function hasValidExecutionCashPolicy(platform = state.selected) {\n if (!platformSupportsMarginPolicy(platform) && !platformSupportsReservedCashPolicy(platform)) return true;\n const form = state.forms[platform];\n return !executionCashPolicyConflict(form) && hasValidReservePolicy(platform);\n }\n\n function hasValidIncomeLayerPolicy(platform = state.selected) {\n const form = state.forms[platform];\n if (!incomeLayerSupported(form?.strategy)) return true;\n const mode = normalizeIncomeLayerMode(form?.incomeLayerMode);\n if (mode === \"current\" || mode === \"disabled\") return true;\n const defaults = incomeLayerDefaultForStrategy(form?.strategy);\n const startUsd = cleanDisplayNumber(form?.incomeLayerStartUsd || defaults?.startUsd);\n const maxRatio = cleanDisplayRatio(form?.incomeLayerMaxRatio || defaults?.maxRatio);\n return Boolean(startUsd && maxRatio);\n }\n\n function hasValidOptionOverlayPolicy(platform = state.selected) {\n const form = state.forms[platform];\n const mode = normalizeOptionOverlayMode(form?.optionOverlayMode);\n return mode !== \"enabled\" || optionOverlaySupported(form?.strategy);\n }\n\n function hasValidDcaPolicy(platform = state.selected) {\n const form = state.forms[platform];\n if (!dcaSupported(form?.strategy) || !platformSupportsDca(platform)) return true;\n return Boolean(dcaModes.includes(normalizeDcaMode(form?.dcaMode)) && cleanDisplayPositiveNumber(form?.dcaBaseInvestmentUsd));\n }\n\n function hasValidStrategySelection(platform = state.selected) {\n return hasRunnableStrategySelection(platform) &&\n hasValidExecutionCashPolicy(platform) &&\n hasValidIncomeLayerPolicy(platform) &&\n hasValidOptionOverlayPolicy(platform) &&\n hasValidDcaPolicy(platform);\n }\n\n function normalizeReservePolicyMode(value) {\n return reservePolicyModes.includes(value) ? value : \"current\";\n }\n\n function reservePolicyOverrideForForm(form, platform = state.selected) {\n if (!platformSupportsReservedCashPolicy(platform)) return null;\n const mode = normalizeReservePolicyMode(form?.reservePolicyMode);\n const floor = cleanDisplayNumber(form?.minReservedCashUsd);\n const ratio = cleanDisplayRatio(form?.reservedCashRatio);\n const extraVariables = {};\n if (mode === \"current\") return null;\n if (mode === \"none\") {\n extraVariables[platformMinReservedCashVariables[platform]] = \"\";\n extraVariables[platformReservedCashRatioVariables[platform]] = \"\";\n return { inputs: {}, extraVariables };\n }\n if (mode === \"ratio\") {\n if (!ratio) return null;\n extraVariables[platformMinReservedCashVariables[platform]] = \"\";\n return { inputs: { reserved_cash_ratio: ratio }, extraVariables };\n }\n if (mode === \"floor\") {\n if (!floor) return null;\n extraVariables[platformReservedCashRatioVariables[platform]] = \"\";\n return { inputs: { min_reserved_cash_usd: floor }, extraVariables };\n }\n if (mode === \"max\") {\n if (!floor || !ratio) return null;\n return { inputs: { min_reserved_cash_usd: floor, reserved_cash_ratio: ratio }, extraVariables };\n }\n return null;\n }\n\n function runtimeTargetOverrideForForm(form) {\n const mode = normalizeRuntimeTargetMode(form?.runtimeTargetMode);\n if (mode === \"current\") return null;\n return {\n inputs: { runtime_target_enabled_mode: mode },\n extraVariables: { [runtimeTargetEnabledVariable]: mode === \"enabled\" ? \"true\" : \"false\" },\n };\n }\n\n function incomeLayerOverrideForForm(form) {\n const defaults = incomeLayerDefaultForStrategy(form?.strategy);\n if (!defaults) return null;\n const mode = normalizeIncomeLayerMode(form?.incomeLayerMode);\n if (mode === \"current\") return null;\n const extraVariables = {};\n if (mode === \"disabled\") {\n extraVariables[incomeLayerEnabledVariable] = \"false\";\n extraVariables[incomeLayerStartUsdVariable] = \"\";\n extraVariables[incomeLayerMaxRatioVariable] = \"\";\n return { inputs: { income_layer_mode: mode }, extraVariables };\n }\n const startUsd = cleanDisplayNumber(form?.incomeLayerStartUsd || defaults.startUsd);\n const maxRatio = cleanDisplayRatio(form?.incomeLayerMaxRatio || defaults.maxRatio);\n if (!startUsd || !maxRatio) return null;\n extraVariables[incomeLayerEnabledVariable] = \"true\";\n extraVariables[incomeLayerStartUsdVariable] = startUsd;\n extraVariables[incomeLayerMaxRatioVariable] = maxRatio;\n return { inputs: { income_layer_mode: mode, income_layer_start_usd: startUsd, income_layer_max_ratio: maxRatio }, extraVariables };\n }\n\n function optionOverlayOverrideForForm(form) {\n const mode = normalizeOptionOverlayMode(form?.optionOverlayMode);\n if (mode === \"current\") return null;\n if (mode === \"enabled\" && !optionOverlaySupported(form?.strategy)) return null;\n return { inputs: { option_overlay_mode: mode } };\n }\n\n function cashOnlyExecutionOverrideForForm(form, platform = state.selected) {\n if (!platformSupportsMarginPolicy(platform)) return null;\n const mode = normalizeCashOnlyExecutionMode(form?.cashOnlyExecutionMode);\n if (mode === \"current\") return null;\n return { inputs: { cash_only_execution_mode: mode } };\n }\n\n function dcaOverrideForForm(form) {\n if (!dcaSupported(form?.strategy) || !platformSupportsDca(state.selected)) return null;\n const mode = normalizeDcaMode(form?.dcaMode);\n const baseInvestmentUsd = cleanDisplayPositiveNumber(form?.dcaBaseInvestmentUsd);\n if (!baseInvestmentUsd) return null;\n return { inputs: { dca_mode: mode, dca_base_investment_usd: baseInvestmentUsd } };\n }\n\n function mergeExtraVariables(inputs, extraVariables) {\n if (!extraVariables || !Object.keys(extraVariables).length) return;\n const merged = inputs.extra_variables_json ? JSON.parse(inputs.extra_variables_json) : {};\n Object.assign(merged, extraVariables);\n inputs.extra_variables_json = JSON.stringify(merged);\n }\n\n function buildInputs(platform = state.selected) {\n const form = state.forms[platform];\n const account = selectedAccount(platform);\n const targetName = account.target_name || account.key;\n const inputs = {\n platform,\n target_name: targetName,\n strategy_profile: form.strategy,\n execution_mode: form.executionMode,\n variable_scope: account.variable_scope || \"default\",\n plugin_mode: normalizePluginMode(form.pluginMode),\n service_targets_mode: \"auto\",\n apply: \"true\",\n trigger_platform_sync: \"true\",\n confirm_apply: \"APPLY_AND_SYNC\",\n platform_sync_workflow: \"sync-cloud-run-env.yml\",\n };\n for (const field of [\n \"github_environment\",\n \"deployment_selector\",\n \"account_selector\",\n \"account_scope\",\n \"service_name\",\n ]) {\n if (account[field]) inputs[field] = account[field];\n }\n const reserveOverride = platformSupportsReservedCashPolicy(platform)\n ? reservePolicyOverrideForForm(form, platform)\n : null;\n if (platformSupportsReservedCashPolicy(platform)) {\n inputs.reserved_cash_policy_mode = normalizeReservePolicyMode(form.reservePolicyMode);\n if (reserveOverride) {\n Object.assign(inputs, reserveOverride.inputs);\n mergeExtraVariables(inputs, reserveOverride.extraVariables);\n }\n }\n const runtimeTargetOverride = runtimeTargetOverrideForForm(form);\n inputs.runtime_target_enabled_mode = normalizeRuntimeTargetMode(form.runtimeTargetMode);\n if (runtimeTargetOverride) {\n Object.assign(inputs, runtimeTargetOverride.inputs);\n mergeExtraVariables(inputs, runtimeTargetOverride.extraVariables);\n }\n const incomeOverride = incomeLayerOverrideForForm(form);\n inputs.income_layer_mode = normalizeIncomeLayerMode(form.incomeLayerMode);\n if (incomeOverride) {\n Object.assign(inputs, incomeOverride.inputs);\n mergeExtraVariables(inputs, incomeOverride.extraVariables);\n }\n const optionOverlayOverride = optionOverlayOverrideForForm(form);\n inputs.option_overlay_mode = normalizeOptionOverlayMode(form.optionOverlayMode);\n if (optionOverlayOverride) {\n Object.assign(inputs, optionOverlayOverride.inputs);\n }\n const cashOnlyOverride = cashOnlyExecutionOverrideForForm(form, platform);\n if (platformSupportsMarginPolicy(platform)) {\n inputs.cash_only_execution_mode = normalizeCashOnlyExecutionMode(form.cashOnlyExecutionMode);\n if (cashOnlyOverride) {\n Object.assign(inputs, cashOnlyOverride.inputs);\n }\n }\n const dcaOverride = dcaOverrideForForm(form);\n if (dcaOverride) {\n Object.assign(inputs, dcaOverride.inputs);\n }\n return inputs;\n }\n\n function reservedCashPolicyText(inputs, platform = state.selected, account = selectedAccount(platform), fallback = t(\"unchanged\")) {\n if (inputs?.reserved_cash_policy_mode === \"none\") return t(\"reservedCashNone\");\n const floor = cleanDisplayNumber(inputs?.min_reserved_cash_usd);\n const ratio = cleanDisplayRatio(inputs?.reserved_cash_ratio);\n const currency = selectedCashCurrency(platform, account);\n const hasEffectiveFloor = Boolean(floor && !(ratio && Number(floor) === 0));\n if (!hasEffectiveFloor && !ratio) return fallback;\n if (hasEffectiveFloor && ratio) return `max(${floor} ${currency}, ${formatRatioPercent(ratio)})`;\n if (hasEffectiveFloor) return `${floor} ${currency}`;\n return formatRatioPercent(ratio);\n }\n\n function platformReservedCashDefaultText(platform = state.selected, account = selectedAccount(platform)) {\n return t(\"reservedCashDefault\").replace(\"{currency}\", selectedCashCurrency(platform, account));\n }\n\n function currentReservedCashPolicyText(platform = state.selected, account = selectedAccount(platform)) {\n const entry = currentEntryForAccount(platform, account);\n const policy = currentReservePolicyForAccount(platform, account);\n return reservedCashPolicyText(\n {\n min_reserved_cash_usd: policy.minReservedCashUsd,\n reserved_cash_ratio: policy.reservedCashRatio,\n },\n platform,\n account,\n entry ? platformReservedCashDefaultText(platform, account) : t(\"notRead\"),\n );\n }\n\n function pendingReservedCashPolicyText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n return reservedCashPolicyText(pendingReservePolicy(inputs, platform, account).inputs, platform, account, t(\"unchanged\"));\n }\n\n function pendingReservePolicy(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const current = currentReservePolicyForAccount(platform, account);\n const currentFloor = cleanDisplayNumber(current.minReservedCashUsd);\n const currentRatio = cleanDisplayRatio(current.reservedCashRatio);\n const mode = normalizeReservePolicyMode(inputs.reserved_cash_policy_mode);\n const next = {\n min_reserved_cash_usd: cleanDisplayNumber(inputs.min_reserved_cash_usd),\n reserved_cash_ratio: cleanDisplayRatio(inputs.reserved_cash_ratio),\n };\n if (mode === \"none\") {\n next.reserved_cash_policy_mode = \"none\";\n }\n const entry = currentEntryForAccount(platform, account);\n const changed = Boolean(entry && (\n next.min_reserved_cash_usd !== currentFloor ||\n next.reserved_cash_ratio !== currentRatio ||\n (mode === \"none\" && (currentFloor || currentRatio))\n ));\n return { changed, inputs: next };\n }\n\n function currentIncomeLayerText(platform = state.selected, account = selectedAccount(platform), profile = state.forms[platform]?.strategy) {\n const defaults = incomeLayerDefaultForStrategy(profile);\n if (!defaults) return t(\"incomeLayerNotSupported\");\n const entry = currentEntryForAccount(platform, account);\n if (!entry) return t(\"notRead\");\n const current = incomeLayerFromEntry(entry);\n if (!incomeLayerFieldsConfigured(entry)) {\n return t(\"incomeLayerDefault\")\n .replace(\"{start}\", formatUsd(defaults.startUsd))\n .replace(\"{ratio}\", formatRatioPercent(defaults.maxRatio));\n }\n const enabled = current.enabled ?? true;\n const startUsd = current.startUsd || String(defaults.startUsd);\n const ratio = current.maxRatio || defaults.maxRatio;\n return enabled\n ? t(\"incomeLayerOn\")\n .replace(\"{start}\", formatUsd(startUsd))\n .replace(\"{ratio}\", formatRatioPercent(ratio))\n : t(\"incomeLayerOff\");\n }\n\n function pendingIncomeLayerText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingIncomeLayer(inputs, platform, account);\n if (!pending.supported) return t(\"incomeLayerNotSupported\");\n if (!pending.changed) return t(\"unchanged\");\n if (pending.inputs.income_layer_enabled === false) return t(\"incomeLayerOff\");\n return t(\"incomeLayerOn\")\n .replace(\"{start}\", formatUsd(pending.inputs.income_layer_start_usd))\n .replace(\"{ratio}\", formatRatioPercent(pending.inputs.income_layer_max_ratio));\n }\n\n function pendingOptionOverlayText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingOptionOverlay(inputs, platform, account);\n if (!pending.supported && pending.inputs.option_overlay_enabled !== false) return t(\"optionOverlayNotSupported\");\n if (!pending.changed) return t(\"unchanged\");\n return optionOverlayText(pending.inputs.option_overlay_enabled);\n }\n\n function pendingCashOnlyExecutionText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingCashOnlyExecution(inputs, platform, account);\n if (!pending.changed) return t(\"unchanged\");\n return cashOnlyExecutionText(pending.inputs.cash_only_execution);\n }\n\n function pendingRuntimeTargetText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingRuntimeTarget(inputs, platform, account);\n if (!pending.changed) return t(\"unchanged\");\n return runtimeTargetText(pending.inputs.runtime_target_enabled);\n }\n\n function pendingRuntimeTargetTone(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingRuntimeTarget(inputs, platform, account);\n if (!pending.changed) return \"neutral\";\n return runtimeTargetTone(pending.inputs.runtime_target_enabled);\n }\n\n function currentDcaText(platform = state.selected, account = selectedAccount(platform), profile = state.forms[platform]?.strategy) {\n const current = currentDcaForAccount(platform, account, profile);\n if (!current.supported) return t(\"dcaNotSupported\");\n return t(\"dcaText\")\n .replace(\"{mode}\", dcaModeLabel(current.mode))\n .replace(\"{amount}\", formatUsd(current.baseInvestmentUsd));\n }\n\n function pendingDcaText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingDca(inputs, platform, account);\n if (!pending.supported) return t(\"dcaNotSupported\");\n if (!pending.changed) return t(\"unchanged\");\n return t(\"dcaText\")\n .replace(\"{mode}\", dcaModeLabel(pending.inputs.dca_mode))\n .replace(\"{amount}\", formatUsd(pending.inputs.dca_base_investment_usd));\n }\n\n function pendingRuntimeTarget(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const mode = normalizeRuntimeTargetMode(inputs.runtime_target_enabled_mode);\n if (mode === \"current\") {\n return {\n changed: false,\n inputs: { runtime_target_enabled: runtimeTargetEnabledForAccount(platform, account) ?? true },\n };\n }\n const current = runtimeTargetEnabledForAccount(platform, account);\n const currentEnabled = current ?? true;\n const nextEnabled = mode === \"enabled\";\n const entry = currentEntryForAccount(platform, account);\n return {\n changed: Boolean(entry && current !== null && currentEnabled !== nextEnabled),\n inputs: { runtime_target_enabled: nextEnabled },\n };\n }\n\n function pendingIncomeLayer(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const profile = cleanStrategyProfile(inputs.strategy_profile || state.forms[platform]?.strategy);\n const defaults = incomeLayerDefaultForStrategy(profile);\n if (!defaults) return { supported: false, changed: false, inputs: {} };\n const mode = normalizeIncomeLayerMode(inputs.income_layer_mode);\n const entry = currentEntryForAccount(platform, account);\n const rawCurrent = currentIncomeLayerForAccount(platform, account);\n const effective = effectiveIncomeLayerForAccount(platform, account, profile);\n const currentEnabled = effective?.enabled ?? true;\n const currentStartUsd = effective?.startUsd ?? String(defaults.startUsd);\n const currentRatio = effective?.maxRatio ?? defaults.maxRatio;\n if (mode === \"current\") {\n return {\n supported: true,\n changed: false,\n inputs: {\n income_layer_enabled: rawCurrent.enabled,\n income_layer_start_usd: rawCurrent.startUsd,\n income_layer_max_ratio: rawCurrent.maxRatio,\n },\n };\n }\n if (mode === \"disabled\") {\n if (!entry) {\n return {\n supported: true,\n changed: false,\n inputs: {\n income_layer_enabled: false,\n income_layer_start_usd: \"\",\n income_layer_max_ratio: \"\",\n },\n };\n }\n return {\n supported: true,\n changed: currentEnabled !== false || Boolean(rawCurrent.startUsd || rawCurrent.maxRatio),\n inputs: {\n income_layer_enabled: false,\n income_layer_start_usd: \"\",\n income_layer_max_ratio: \"\",\n },\n };\n }\n const nextStartUsd = cleanDisplayNumber(inputs.income_layer_start_usd || defaults.startUsd);\n const nextRatio = cleanDisplayRatio(inputs.income_layer_max_ratio || defaults.maxRatio);\n return {\n supported: true,\n changed: Boolean(entry && (currentEnabled !== true || nextStartUsd !== currentStartUsd || nextRatio !== currentRatio)),\n inputs: {\n income_layer_enabled: true,\n income_layer_start_usd: nextStartUsd,\n income_layer_max_ratio: nextRatio,\n },\n };\n }\n\n function pendingOptionOverlay(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const profile = cleanStrategyProfile(inputs.strategy_profile || state.forms[platform]?.strategy);\n const supported = optionOverlaySupported(profile);\n const mode = normalizeOptionOverlayMode(inputs.option_overlay_mode);\n const current = effectiveOptionOverlayForAccount(platform, account, profile);\n if (mode === \"current\") {\n return {\n supported,\n changed: false,\n inputs: { option_overlay_enabled: currentOptionOverlayForAccount(platform, account) },\n };\n }\n if (mode === \"enabled\") {\n return {\n supported,\n changed: supported && current !== null && current !== true,\n inputs: { option_overlay_enabled: true },\n };\n }\n return {\n supported,\n changed: current === true,\n inputs: { option_overlay_enabled: false },\n };\n }\n\n function pendingCashOnlyExecution(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const mode = normalizeCashOnlyExecutionMode(inputs.cash_only_execution_mode);\n if (!platformSupportsMarginPolicy(platform) || mode === \"current\") {\n return { changed: false, inputs: {} };\n }\n const current = effectiveCashOnlyExecutionForAccount(platform, account);\n const nextEnabled = mode === \"enabled\";\n const entry = currentEntryForAccount(platform, account);\n return {\n changed: Boolean(entry && current !== null && current !== nextEnabled),\n inputs: { cash_only_execution: nextEnabled },\n };\n }\n\n function pendingDca(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const profile = cleanStrategyProfile(inputs.strategy_profile || state.forms[platform]?.strategy);\n const defaults = dcaConfigForStrategy(profile);\n if (!defaults) return { supported: false, changed: false, inputs: {} };\n const current = currentDcaForAccount(platform, account, profile);\n const nextMode = normalizeDcaMode(inputs.dca_mode || defaults.defaultMode);\n const nextBase = cleanDisplayPositiveNumber(inputs.dca_base_investment_usd || defaults.defaultBaseInvestmentUsd);\n return {\n supported: true,\n changed: Boolean(current.mode !== nextMode || current.baseInvestmentUsd !== nextBase),\n inputs: {\n dca_mode: nextMode,\n dca_base_investment_usd: nextBase,\n },\n };\n }\n\n function pendingChangeState(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const currentProfile = currentStrategyForAccount(platform, account);\n const nextProfile = cleanStrategyProfile(inputs.strategy_profile);\n const currentEntry = currentEntryForAccount(platform, account);\n const currentMode = normalizeExecutionMode(currentEntry?.execution_mode, currentEntry?.dry_run_only);\n const currentPluginMode = currentPluginModeForAccount(platform, account);\n const nextPluginMode = normalizePluginMode(inputs.plugin_mode);\n const runtimeTarget = pendingRuntimeTarget(inputs, platform, account);\n const reserve = pendingReservePolicy(inputs, platform, account);\n const income = pendingIncomeLayer(inputs, platform, account);\n const optionOverlay = pendingOptionOverlay(inputs, platform, account);\n const cashOnly = pendingCashOnlyExecution(inputs, platform, account);\n const dca = pendingDca(inputs, platform, account);\n return {\n currentProfile,\n nextProfile,\n currentMode,\n currentPluginMode,\n nextPluginMode,\n strategyChanged: Boolean(nextProfile && ((state.forms[platform]?.strategyTouched) || (currentProfile && currentProfile !== nextProfile))),\n modeChanged: Boolean(inputs.execution_mode && currentMode && currentMode !== inputs.execution_mode),\n pluginModeChanged: Boolean(nextPluginMode && currentPluginMode && currentPluginMode !== nextPluginMode),\n runtimeTargetChanged: runtimeTarget.changed,\n reserveCashChanged: reserve.changed,\n incomeLayerChanged: income.changed,\n optionOverlayChanged: optionOverlay.changed,\n cashOnlyChanged: cashOnly.changed,\n dcaChanged: dca.changed,\n runtimeTarget,\n reserve,\n income,\n optionOverlay,\n cashOnly,\n dca,\n };\n }\n\n function hasPendingChanges(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const changes = pendingChangeState(inputs, platform, account);\n return Boolean(\n changes.strategyChanged ||\n changes.modeChanged ||\n changes.pluginModeChanged ||\n changes.runtimeTargetChanged ||\n changes.reserveCashChanged ||\n changes.incomeLayerChanged ||\n changes.optionOverlayChanged ||\n changes.cashOnlyChanged ||\n changes.dcaChanged\n );\n }\n\n function formatRatioPercent(value) {\n const numeric = Number(value);\n if (!Number.isFinite(numeric)) return String(value);\n return `${(numeric * 100).toFixed(2).replace(/\\.?0+$/, \"\")}%`;\n }\n\n function formatUsd(value) {\n const numeric = Number(value);\n if (!Number.isFinite(numeric)) return String(value);\n return `$${numeric.toLocaleString(\"en-US\", { maximumFractionDigits: 0 })}`;\n }\n\n function incomeLayerAllocationText(defaults) {\n if (!defaults?.allocations) return \"\";\n return Object.entries(defaults.allocations)\n .map(([symbol, ratio]) => `${symbol} ${formatRatioPercent(ratio)}`)\n .join(\" / \");\n }\n\n function incomeLayerDefaultMetaText(defaults) {\n if (!defaults) return t(\"incomeLayerModeMeta\");\n return t(\"incomeLayerDefaultMeta\")\n .replace(\"{start}\", formatUsd(defaults.startUsd))\n .replace(\"{ratio}\", formatRatioPercent(defaults.maxRatio));\n }\n\n function optionOverlayDefaultMetaText(defaults) {\n if (!defaults?.families?.length) return t(\"optionOverlayModeMeta\");\n const familyText = defaults.families.map((item) => {\n const family = item.family === \"income\" ? t(\"optionOverlayFamilyIncome\") : t(\"optionOverlayFamilyGrowth\");\n const ratioText = item.ratioKind === \"risk\"\n ? t(\"optionOverlayRiskRatio\").replace(\"{ratio}\", formatRatioPercent(item.ratio))\n : t(\"optionOverlayBudgetRatio\").replace(\"{ratio}\", formatRatioPercent(item.ratio));\n return `${family}: ${item.recipe}, ${formatUsd(item.startUsd)}, ${ratioText}`;\n }).join(\" / \");\n return t(\"optionOverlayDefaultMeta\").replace(\"{defaults}\", familyText);\n }\n\n function summaryRows(inputs) {\n const account = selectedAccount();\n const changes = pendingChangeState(inputs, state.selected, account);\n const currentStrategyText = changes.currentProfile ? strategyLabel(changes.currentProfile) : t(\"notRead\");\n const rows = [\n [t(\"repository\"), state.repositories[state.selected] || defaultRepositories[state.selected]],\n [t(\"selectedAccount\"), account.label],\n [t(\"currentStrategy\"), currentStrategyText],\n [t(\"selectedMarket\"), supportedDomainLabel(state.selected, account)],\n [\n t(\"currentRuntimeTarget\"),\n currentRuntimeTargetText(state.selected, account),\n \"\",\n currentRuntimeTargetTone(state.selected, account),\n ],\n [t(\"currentPluginMode\"), pluginModeLabel(changes.currentPluginMode)],\n [t(\"reservedCashPolicy\"), currentReservedCashPolicyText(state.selected, account)],\n ];\n if (platformSupportsMarginPolicy(state.selected)) {\n rows.push([t(\"currentCashOnlyExecution\"), currentCashOnlyExecutionText(state.selected, account)]);\n }\n if (incomeLayerSupported(inputs.strategy_profile)) {\n rows.push([t(\"currentIncomeLayer\"), currentIncomeLayerText(state.selected, account, inputs.strategy_profile)]);\n }\n if (optionOverlaySupported(inputs.strategy_profile) || changes.optionOverlayChanged) {\n rows.push([t(\"currentOptionOverlay\"), currentOptionOverlayText(state.selected, account, inputs.strategy_profile)]);\n }\n if (dcaSupported(inputs.strategy_profile)) {\n rows.push([t(\"currentDca\"), currentDcaText(state.selected, account, inputs.strategy_profile)]);\n }\n if (changes.reserveCashChanged) {\n rows.push([t(\"pendingReservedCashPolicy\"), pendingReservedCashPolicyText(inputs, state.selected, account), \"pending\"]);\n }\n if (changes.incomeLayerChanged) {\n rows.push([t(\"pendingIncomeLayer\"), pendingIncomeLayerText(inputs, state.selected, account), \"pending\"]);\n }\n if (changes.optionOverlayChanged) {\n rows.push([t(\"pendingOptionOverlay\"), pendingOptionOverlayText(inputs, state.selected, account), \"pending\"]);\n }\n if (changes.cashOnlyChanged) {\n rows.push([t(\"pendingCashOnlyExecution\"), pendingCashOnlyExecutionText(inputs, state.selected, account), \"pending\"]);\n }\n if (changes.dcaChanged) {\n rows.push([t(\"pendingDca\"), pendingDcaText(inputs, state.selected, account), \"pending\"]);\n }\n if (changes.modeChanged) {\n rows.push([t(\"pendingMode\"), modeLabel(inputs.execution_mode), \"pending\"]);\n }\n if (changes.pluginModeChanged) {\n rows.push([t(\"pendingPluginMode\"), pluginModeLabel(changes.nextPluginMode), \"pending\"]);\n }\n if (changes.runtimeTargetChanged) {\n rows.push([\n t(\"pendingRuntimeTarget\"),\n pendingRuntimeTargetText(inputs, state.selected, account),\n \"pending\",\n pendingRuntimeTargetTone(inputs, state.selected, account),\n ]);\n }\n if (changes.strategyChanged && changes.nextProfile) {\n rows.push([t(\"nextStrategy\"), strategyLabel(changes.nextProfile), \"pending\"]);\n }\n return rows;\n }\n\n function applyLanguage() {\n document.documentElement.lang = state.lang === \"zh\" ? \"zh-CN\" : \"en\";\n document.querySelectorAll(\"[data-i18n]\").forEach((node) => {\n node.textContent = t(node.dataset.i18n);\n });\n el(\"lang-button\").textContent = state.lang === \"zh\" ? \"EN\" : \"中\";\n }\n\n function renderRuntimeAuthorityStatus() {\n const status = String(runtimeAuthorityStatus.status || \"UNKNOWN_RUNTIME_AUTHORITY_STATUS\");\n const asOf = String(runtimeAuthorityStatus.status_as_of || \"—\");\n el(\"runtime-authority-status\").textContent = status;\n el(\"runtime-authority-as-of\").textContent = t(\"runtimeAuthorityAsOf\").replace(\"{asOf}\", asOf);\n }\n\n function renderPlatforms() {\n const strip = el(\"platform-strip\");\n strip.replaceChildren();\n const showPrivateConfig = hasPrivateConfig();\n for (const platform of Object.keys(platformMeta)) {\n ensureAccountSelection(platform);\n const meta = platformMeta[platform];\n const form = state.forms[platform];\n const account = selectedAccount(platform);\n const button = document.createElement(\"button\");\n button.className = \"platform-button\";\n button.type = \"button\";\n button.dataset.platform = platform;\n button.classList.toggle(\"active\", platform === state.selected);\n const mark = document.createElement(\"span\");\n mark.className = \"mark\";\n mark.textContent = meta.code;\n const copyNode = document.createElement(\"span\");\n copyNode.className = \"platform-copy\";\n const labelNode = document.createElement(\"strong\");\n labelNode.textContent = meta.label;\n copyNode.append(labelNode);\n if (showPrivateConfig) {\n const accountNode = document.createElement(\"span\");\n accountNode.textContent = account.label;\n const strategyNode = document.createElement(\"small\");\n strategyNode.textContent = strategyLabel(form.strategy);\n copyNode.append(accountNode, strategyNode);\n }\n button.append(mark, copyNode);\n strip.appendChild(button);\n }\n }\n\n function renderControls() {\n const platform = state.selected;\n const meta = platformMeta[platform];\n const form = state.forms[platform];\n const accounts = optionsFor(platform);\n const account = selectedAccount(platform);\n const choices = strategyChoicesForAccount(platform, account, form.executionMode);\n const currentStrategy = currentStrategyForAccount(platform, account);\n const currentStrategyBlocked = Boolean(\n currentStrategy &&\n !strategyAllowedForAccount(platform, account, currentStrategy, form.executionMode),\n );\n const accountSelect = el(\"account-select\");\n const strategySelect = el(\"strategy-select\");\n const runtimeTargetEnabledSelect = el(\"runtime-target-enabled-select\");\n const pluginModeSelect = el(\"plugin-mode-select\");\n const incomeLayerModeSelect = el(\"income-layer-mode-select\");\n const incomeLayerStartUsdInput = el(\"income-layer-start-usd-input\");\n const incomeLayerMaxRatioInput = el(\"income-layer-max-ratio-input\");\n const optionOverlayModeSelect = el(\"option-overlay-mode-select\");\n const cashOnlyExecutionModeSelect = el(\"cash-only-execution-mode-select\");\n const dcaModeSelect = el(\"dca-mode-select\");\n const dcaBaseInvestmentUsdInput = el(\"dca-base-investment-usd-input\");\n const reservePolicyModeSelect = el(\"reserve-policy-mode-select\");\n const minReservedCashInput = el(\"min-reserved-cash-input\");\n const reservedCashRatioInput = el(\"reserved-cash-ratio-input\");\n const showPrivateControls = hasPrivateConfig();\n\n el(\"switch-panel\").style.setProperty(\"--platform-color\", meta.accent);\n el(\"platform-title\").textContent = meta.label;\n el(\"quick-form\").hidden = !showPrivateControls;\n el(\"run-area\").hidden = !showPrivateControls;\n el(\"public-note\").hidden = showPrivateControls;\n el(\"public-preview\").hidden = showPrivateControls;\n el(\"public-note\").textContent = state.auth.allowed ? t(\"missingConfigNote\") : t(\"publicReadonly\");\n\n if (!showPrivateControls) {\n accountSelect.replaceChildren();\n strategySelect.replaceChildren();\n runtimeTargetEnabledSelect.replaceChildren();\n pluginModeSelect.replaceChildren();\n incomeLayerModeSelect.replaceChildren();\n optionOverlayModeSelect.replaceChildren();\n cashOnlyExecutionModeSelect.replaceChildren();\n dcaModeSelect.replaceChildren();\n reservePolicyModeSelect.replaceChildren();\n incomeLayerStartUsdInput.value = \"\";\n incomeLayerMaxRatioInput.value = \"\";\n dcaBaseInvestmentUsdInput.value = \"\";\n minReservedCashInput.value = \"\";\n reservedCashRatioInput.value = \"\";\n el(\"account-meta\").textContent = \"\";\n el(\"strategy-meta\").textContent = \"\";\n el(\"income-layer-mode-meta\").textContent = \"\";\n el(\"income-layer-start-meta\").textContent = \"\";\n el(\"income-layer-ratio-meta\").textContent = \"\";\n el(\"option-overlay-mode-meta\").textContent = \"\";\n el(\"cash-only-execution-mode-meta\").textContent = \"\";\n el(\"dca-mode-meta\").textContent = \"\";\n el(\"dca-base-meta\").textContent = \"\";\n return;\n }\n\n accountSelect.replaceChildren();\n if (accounts.length) {\n for (const account of accounts) {\n accountSelect.append(new Option(account.label, account.key, false, account.key === form.accountKey));\n }\n } else {\n accountSelect.append(new Option(t(\"noAccount\"), \"\"));\n }\n el(\"account-meta\").textContent = accounts.length ? accountMetaText(platform) : \"\";\n\n if (choices.length && !choices.includes(form.strategy) && !currentStrategyBlocked) {\n form.strategy = choices[0];\n }\n strategySelect.disabled = !choices.length;\n strategySelect.replaceChildren();\n if (currentStrategyBlocked) {\n const blockedOption = new Option(\n strategyChoiceLabel(currentStrategy, platform, account, form.executionMode),\n currentStrategy,\n true,\n currentStrategy === form.strategy,\n );\n blockedOption.disabled = true;\n strategySelect.append(blockedOption);\n }\n if (choices.length) {\n for (const strategy of choices) {\n strategySelect.append(\n new Option(strategyChoiceLabel(strategy, platform, account, form.executionMode), strategy, false, strategy === form.strategy),\n );\n }\n } else {\n strategySelect.append(new Option(t(\"noStrategy\"), \"\"));\n }\n el(\"strategy-meta\").textContent = account\n ? strategyDisplayMetaText(platform, account, form.strategy)\n : \"\";\n runtimeTargetEnabledSelect.replaceChildren();\n for (const mode of runtimeTargetModes) {\n runtimeTargetEnabledSelect.append(\n new Option(runtimeTargetModeLabel(mode), mode, false, mode === normalizeRuntimeTargetMode(form.runtimeTargetMode)),\n );\n }\n pluginModeSelect.replaceChildren();\n for (const mode of pluginModes) {\n pluginModeSelect.append(new Option(pluginModeLabel(mode), mode, false, mode === normalizePluginMode(form.pluginMode)));\n }\n const incomeDefaults = incomeLayerDefaultForStrategy(form.strategy);\n el(\"income-layer-section\").hidden = false;\n el(\"option-overlay-section\").hidden = false;\n incomeLayerModeSelect.replaceChildren();\n if (incomeDefaults) {\n incomeLayerModeSelect.disabled = false;\n for (const mode of incomeLayerModes) {\n incomeLayerModeSelect.append(new Option(incomeLayerModeLabel(mode), mode, false, mode === normalizeIncomeLayerMode(form.incomeLayerMode)));\n }\n el(\"income-layer-mode-meta\").textContent = incomeLayerDefaultMetaText(incomeDefaults);\n el(\"income-layer-start-meta\").textContent = t(\"incomeLayerStartMeta\");\n el(\"income-layer-ratio-meta\").textContent = t(\"incomeLayerAllocationMeta\").replace(\n \"{allocations}\",\n incomeLayerAllocationText(incomeDefaults),\n );\n } else {\n incomeLayerModeSelect.disabled = true;\n incomeLayerModeSelect.append(new Option(t(\"incomeLayerNotSupported\"), \"current\"));\n el(\"income-layer-mode-meta\").textContent = t(\"incomeLayerModeMeta\");\n el(\"income-layer-start-meta\").textContent = t(\"incomeLayerStartMeta\");\n el(\"income-layer-ratio-meta\").textContent = t(\"incomeLayerRatioMeta\");\n }\n const supportsMargin = platformSupportsMarginPolicy(platform);\n const supportsReserve = platformSupportsReservedCashPolicy(platform);\n if (supportsMargin) syncCashOnlyExecutionForAccount(platform);\n reconcileExecutionCashPolicy(form, \"margin\");\n const executionCashPolicyGrid = el(\"execution-cash-policy-grid\");\n const qmtPlatformCashNote = el(\"qmt-platform-cash-note\");\n const executionCashPolicyNote = el(\"execution-cash-policy-note\");\n executionCashPolicyGrid.hidden = !supportsMargin && !supportsReserve;\n qmtPlatformCashNote.hidden = supportsMargin || supportsReserve || platform !== \"qmt\";\n executionCashPolicyNote.hidden = !supportsMargin || !supportsReserve;\n\n const marginBlocksReserve = supportsMargin && supportsReserve && allowMarginExplicitlySelected(form);\n const reserveBlocksMargin = supportsMargin && supportsReserve && reserveCashOverrideActive(form);\n\n if (supportsReserve) {\n reservePolicyModeSelect.replaceChildren();\n for (const mode of reservePolicyModes) {\n reservePolicyModeSelect.append(new Option(t(`reservePolicy${mode[0].toUpperCase()}${mode.slice(1)}`), mode, false, mode === normalizeReservePolicyMode(form.reservePolicyMode)));\n }\n const reserveMode = normalizeReservePolicyMode(form.reservePolicyMode);\n el(\"min-reserved-cash-label\").textContent = t(\"minReservedCash\").replace(\n \"{currency}\",\n selectedCashCurrency(platform, account),\n );\n reservePolicyModeSelect.disabled = false;\n minReservedCashInput.disabled = reserveMode === \"current\" || reserveMode === \"none\" || reserveMode === \"ratio\";\n reservedCashRatioInput.disabled = reserveMode === \"current\" || reserveMode === \"none\" || reserveMode === \"floor\";\n minReservedCashInput.value = reserveMode === \"ratio\" || reserveMode === \"none\" ? \"\" : form.minReservedCashUsd;\n reservedCashRatioInput.value = reserveMode === \"floor\" || reserveMode === \"none\" ? \"\" : form.reservedCashRatio;\n el(\"reserve-policy-block\").classList.toggle(\"policy-block-muted\", marginBlocksReserve);\n el(\"min-reserve-block\").classList.toggle(\"policy-block-muted\", marginBlocksReserve);\n el(\"reserve-ratio-block\").classList.toggle(\"policy-block-muted\", marginBlocksReserve);\n el(\"reserve-policy-mode-meta\").textContent = marginBlocksReserve\n ? t(\"executionCashMarginBlocksReserve\")\n : t(\"reservedCashModeMeta\");\n } else {\n reservePolicyModeSelect.replaceChildren();\n minReservedCashInput.value = \"\";\n reservedCashRatioInput.value = \"\";\n }\n const incomeMode = normalizeIncomeLayerMode(form.incomeLayerMode);\n const incomeLayerInputsDisabled = !incomeDefaults || incomeMode === \"disabled\";\n incomeLayerStartUsdInput.disabled = incomeLayerInputsDisabled;\n incomeLayerMaxRatioInput.disabled = incomeLayerInputsDisabled;\n if (incomeDefaults && incomeMode !== \"disabled\" && !cleanDisplayNumber(form.incomeLayerStartUsd)) {\n form.incomeLayerStartUsd = String(incomeDefaults.startUsd);\n }\n if (incomeDefaults && incomeMode !== \"disabled\" && !cleanDisplayRatio(form.incomeLayerMaxRatio)) {\n form.incomeLayerMaxRatio = incomeDefaults.maxRatio;\n }\n incomeLayerStartUsdInput.value = incomeDefaults && incomeMode !== \"disabled\" ? form.incomeLayerStartUsd : \"\";\n incomeLayerMaxRatioInput.value = incomeDefaults && incomeMode !== \"disabled\" ? form.incomeLayerMaxRatio : \"\";\n\n const optionDefaults = optionOverlayDefaultForStrategy(form.strategy);\n optionOverlayModeSelect.replaceChildren();\n if (optionDefaults) {\n optionOverlayModeSelect.disabled = false;\n for (const mode of optionOverlayModes) {\n optionOverlayModeSelect.append(\n new Option(optionOverlayModeLabel(mode), mode, false, mode === normalizeOptionOverlayMode(form.optionOverlayMode)),\n );\n }\n el(\"option-overlay-mode-meta\").textContent = optionOverlayDefaultMetaText(optionDefaults);\n } else {\n optionOverlayModeSelect.disabled = true;\n optionOverlayModeSelect.append(new Option(t(\"optionOverlayNotSupported\"), \"current\"));\n el(\"option-overlay-mode-meta\").textContent = t(\"optionOverlayModeMeta\");\n }\n\n if (supportsMargin) {\n cashOnlyExecutionModeSelect.replaceChildren();\n for (const mode of cashOnlyExecutionModes) {\n const option = new Option(\n mode === \"enabled\" ? t(\"cashOnlyExecutionNo\") : t(\"cashOnlyExecutionYes\"),\n mode,\n false,\n mode === normalizeCashOnlyExecutionMode(form.cashOnlyExecutionMode),\n );\n cashOnlyExecutionModeSelect.append(option);\n }\n el(\"cash-only-policy-block\").classList.toggle(\"policy-block-muted\", reserveBlocksMargin);\n el(\"cash-only-execution-mode-meta\").textContent = reserveBlocksMargin\n ? t(\"executionCashReserveBlocksMargin\")\n : t(\"cashOnlyExecutionModeMeta\");\n } else {\n cashOnlyExecutionModeSelect.replaceChildren();\n el(\"cash-only-execution-mode-meta\").textContent = \"\";\n }\n\n const dcaDefaults = dcaConfigForStrategy(form.strategy);\n dcaModeSelect.replaceChildren();\n const dcaAllowed = Boolean(dcaDefaults) && platformSupportsDca(platform);\n if (dcaAllowed) {\n dcaModeSelect.disabled = false;\n for (const mode of dcaModes) {\n dcaModeSelect.append(new Option(dcaModeLabel(mode), mode, false, mode === normalizeDcaMode(form.dcaMode)));\n }\n if (!cleanDisplayPositiveNumber(form.dcaBaseInvestmentUsd)) {\n form.dcaBaseInvestmentUsd = dcaDefaults.defaultBaseInvestmentUsd;\n }\n dcaBaseInvestmentUsdInput.disabled = false;\n dcaBaseInvestmentUsdInput.value = form.dcaBaseInvestmentUsd;\n el(\"dca-mode-meta\").textContent = t(\"dcaDefaultMeta\")\n .replace(\"{mode}\", dcaModeLabel(dcaDefaults.defaultMode))\n .replace(\"{amount}\", formatUsd(dcaDefaults.defaultBaseInvestmentUsd));\n el(\"dca-base-meta\").textContent = t(\"dcaModeMeta\");\n } else {\n dcaModeSelect.disabled = true;\n dcaModeSelect.append(new Option(\n dcaDefaults && !platformSupportsDca(platform) ? t(\"dcaPlatformNotSupported\") : t(\"dcaNotSupported\"),\n \"fixed\",\n ));\n dcaBaseInvestmentUsdInput.disabled = true;\n dcaBaseInvestmentUsdInput.value = \"\";\n el(\"dca-mode-meta\").textContent = t(\"dcaModeMeta\");\n el(\"dca-base-meta\").textContent = t(\"dcaModeMeta\");\n }\n\n const supportedModes = supportedExecutionModesForPlatform(platform);\n const liveModeAvailable = supportedModes.includes(\"live\") && hasLiveStrategyOption(platform, account);\n if (!supportedModes.includes(form.executionMode)) form.executionMode = \"dry_run\";\n document.querySelectorAll(\"#mode-control [data-mode]\").forEach((button) => {\n button.disabled = !supportedModes.includes(button.dataset.mode) || (\n button.dataset.mode === \"live\" && !liveModeAvailable\n );\n button.classList.toggle(\"active\", button.dataset.mode === form.executionMode);\n });\n el(\"mode-meta\").textContent = !supportedModes.includes(\"live\")\n ? t(\"qmtDryRunOnlyNote\")\n : (!liveModeAvailable ? t(\"liveModeUnavailable\") : \"\");\n }\n\n function renderSummary() {\n const showSummary = hasPrivateConfig();\n const summaryPanel = document.querySelector(\".summary-panel\");\n const switchSurface = document.querySelector(\".switch-surface\");\n summaryPanel.hidden = !showSummary;\n switchSurface.classList.toggle(\"summary-hidden\", !showSummary);\n if (!showSummary) return;\n\n const inputs = buildInputs();\n const list = el(\"summary-list\");\n list.replaceChildren();\n document.querySelector(\".summary-head h2\").textContent = t(\"summary\");\n const currentHeading = document.createElement(\"p\");\n currentHeading.className = \"summary-section-title\";\n currentHeading.textContent = t(\"summaryCurrent\");\n list.appendChild(currentHeading);\n let pendingSectionInserted = false;\n for (const [label, value, rowClass, valueTone] of summaryRows(inputs)) {\n if (!pendingSectionInserted && rowClass === \"pending\") {\n const pendingHeading = document.createElement(\"p\");\n pendingHeading.className = \"summary-section-title\";\n pendingHeading.textContent = t(\"summaryPending\");\n list.appendChild(pendingHeading);\n pendingSectionInserted = true;\n }\n const row = document.createElement(\"div\");\n row.className = \"summary-row\";\n row.setAttribute(\"role\", \"listitem\");\n if (rowClass) row.classList.add(rowClass);\n const labelNode = document.createElement(\"div\");\n labelNode.className = \"summary-label\";\n labelNode.textContent = label;\n const valueNode = document.createElement(\"div\");\n valueNode.className = \"summary-value\";\n if (valueTone) {\n const badge = document.createElement(\"span\");\n badge.className = `summary-status ${valueTone}`;\n badge.textContent = value;\n valueNode.appendChild(badge);\n } else {\n valueNode.textContent = value;\n }\n row.append(labelNode, valueNode);\n list.appendChild(row);\n }\n\n const account = selectedAccount();\n const currentEntry = currentEntryForAccount(state.selected, account);\n const currentMode = normalizeExecutionMode(currentEntry?.execution_mode, currentEntry?.dry_run_only);\n el(\"mode-pill\").textContent = currentMode ? modeLabel(currentMode) : t(\"notRead\");\n }\n\n function setPlanCheck(id, value, tone) {\n const node = el(id);\n if (!node) return;\n node.textContent = value;\n node.dataset.tone = tone || \"neutral\";\n }\n\n function renderPlanReadiness() {\n const platform = state.selected;\n const account = selectedAccount(platform);\n const configured = hasPrivateConfig() && Boolean(account?.key);\n const runnable = configured && hasRunnableStrategySelection(platform);\n const riskValid = configured && hasValidExecutionCashPolicy(platform) &&\n hasValidIncomeLayerPolicy(platform) && hasValidOptionOverlayPolicy(platform) && hasValidDcaPolicy(platform);\n const form = state.forms[platform];\n const isNonLive = normalizeExecutionMode(form?.executionMode, false) !== \"live\";\n setPlanCheck(\n \"plan-check-account\",\n configured ? t(\"planCheckSelected\") : t(\"planCheckWaiting\"),\n configured ? \"ready\" : \"neutral\",\n );\n setPlanCheck(\n \"plan-check-strategy\",\n runnable ? t(\"planCheckValid\") : (configured ? t(\"planCheckFix\") : t(\"planCheckWaiting\")),\n runnable ? \"ready\" : (configured ? \"warning\" : \"neutral\"),\n );\n setPlanCheck(\n \"plan-check-risk\",\n riskValid ? t(\"planCheckValid\") : (configured ? t(\"planCheckFix\") : t(\"planCheckWaiting\")),\n riskValid ? \"ready\" : (configured ? \"warning\" : \"neutral\"),\n );\n setPlanCheck(\n \"plan-check-authority\",\n isNonLive ? t(\"planCheckNonLive\") : t(\"planCheckNoAuthority\"),\n isNonLive ? \"ready\" : \"warning\",\n );\n }\n\n function renderAuth() {\n const status = el(\"auth-status\");\n const loginLink = el(\"login-link\");\n const logoutButton = el(\"logout-button\");\n const signedIn = Boolean(state.auth.allowed && state.auth.login);\n\n status.hidden = !signedIn;\n status.textContent = signedIn ? t(\"signedInAs\").replace(\"{login}\", state.auth.login) : \"\";\n loginLink.hidden = signedIn;\n loginLink.href = \"/login\";\n loginLink.textContent = t(\"login\");\n logoutButton.hidden = !signedIn;\n logoutButton.textContent = t(\"logout\");\n\n const dispatch = el(\"dispatch-button\");\n const hasPrivateAccounts = state.configSource === \"private\";\n const loadingConfig = state.configSource === \"loading\";\n const hasRunnableStrategy = hasRunnableStrategySelection();\n const hasValidReserve = hasValidExecutionCashPolicy();\n const hasValidIncomeLayer = hasValidIncomeLayerPolicy();\n const hasValidOptionOverlay = hasValidOptionOverlayPolicy();\n const hasValidDca = hasValidDcaPolicy();\n const hasValidStrategy = hasRunnableStrategy &&\n hasValidReserve &&\n hasValidIncomeLayer &&\n hasValidOptionOverlay &&\n hasValidDca;\n const hasPendingChange = hasPrivateAccounts && hasValidStrategy && hasPendingChanges(buildInputs());\n dispatch.disabled = !state.auth.allowed || loadingConfig || !hasPrivateAccounts || !hasValidStrategy || !hasPendingChange;\n dispatch.textContent = state.auth.allowed\n ? (loadingConfig\n ? t(\"loadingConfig\")\n : (hasPrivateAccounts ? (hasValidStrategy ? (hasPendingChange ? t(\"runSwitch\") : t(\"noChanges\")) : t(\"configureAccounts\")) : t(\"configureAccounts\")))\n : t(\"loginToRun\");\n const note = el(\"action-note\");\n note.textContent = state.auth.allowed\n ? (loadingConfig\n ? t(\"loadingConfigNote\")\n : (hasPrivateAccounts\n ? (hasRunnableStrategy\n ? (hasValidReserve\n ? (hasValidIncomeLayer\n ? (hasValidOptionOverlay\n ? (hasValidDca ? (hasPendingChange ? t(\"readyNote\") : \"\") : t(\"invalidDcaNote\"))\n : t(\"invalidOptionOverlayNote\"))\n : t(\"invalidIncomeLayerNote\"))\n : (executionCashPolicyConflict(state.forms[state.selected])\n ? t(\"invalidExecutionCashPolicyNote\")\n : t(\"invalidReservePolicyNote\")))\n : strategyActionNoteText())\n : t(\"missingConfigNote\")))\n : t(\"readonlyNote\");\n note.classList.toggle(\n \"warning\",\n state.auth.allowed && !loadingConfig && (!hasPrivateAccounts || !hasValidStrategy),\n );\n }\n\n function renderAppVisibility() {\n document.body.classList.toggle(\"app-loading\", !state.appReady);\n el(\"boot-message\").textContent = t(state.bootMessageKey);\n }\n\n function normalizeControlPlanePayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid control plane payload\");\n const candidates = Array.isArray(payload.candidates) ? payload.candidates : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n attention: payload.attention && typeof payload.attention === \"object\" && !Array.isArray(payload.attention)\n ? {\n status: [\"research_only\", \"attention_required\", \"unavailable\"].includes(payload.attention.status)\n ? payload.attention.status\n : \"unavailable\",\n reason_codes: Array.isArray(payload.attention.reason_codes) ? payload.attention.reason_codes : [],\n }\n : { status: \"unavailable\", reason_codes: [\"control_plane_attention_missing\"] },\n candidates: candidates.filter((item) => item && typeof item === \"object\" && item.lifecycle && typeof item.lifecycle === \"object\"),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function normalizeOwnerDecisionQueue(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid owner decision queue\");\n const candidates = Array.isArray(payload.candidates) ? payload.candidates : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n candidates: candidates.filter((entry) => entry && typeof entry === \"object\"\n && entry.candidate && typeof entry.candidate === \"object\"\n && typeof entry.candidate_evidence_sha256 === \"string\"),\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function ownerDecisionEntry(candidateId) {\n return state.ownerDecisions.candidates.find((entry) => entry.candidate?.candidate_id === candidateId) || null;\n }\n\n function ownerDecisionLabel(decision) {\n return {\n approve_limited_live_canary: t(\"ownerDecisionApprove\"),\n keep_parked: t(\"ownerDecisionPark\"),\n retire_candidate: t(\"ownerDecisionRetire\"),\n }[decision] || String(decision || \"—\");\n }\n\n function controlPlaneDataStatusText(status) {\n return status === \"ready\"\n ? t(\"controlDataReady\")\n : (status === \"stale\" ? t(\"controlDataStale\") : t(\"controlDataUnavailable\"));\n }\n\n function controlPlaneAttentionText(attention) {\n const status = attention?.status || \"unavailable\";\n if (status === \"research_only\") return t(\"controlAttentionResearchOnly\");\n if (status === \"attention_required\") return t(\"controlAttentionRequired\");\n return t(\"controlAttentionUnavailable\");\n }\n\n function renderControlPlane() {\n const payload = state.controlPlane.payload;\n const summary = payload.summary || {};\n el(\"control-plane-status\").textContent = `${controlPlaneDataStatusText(payload.data_status)} · ${controlPlaneAttentionText(payload.attention)}`;\n el(\"control-plane-computed-at\").textContent = payload.computed_at\n ? t(\"controlComputedAt\").replace(\"{time}\", new Date(payload.computed_at).toLocaleString())\n : t(\"controlComputedAt\").replace(\"{time}\", \"—\");\n el(\"control-count-candidates\").textContent = String(Number(summary.candidate_count) || 0);\n el(\"control-count-deferred\").textContent = String(Number(summary.deferred) || 0);\n el(\"control-count-parked\").textContent = String(Number(summary.parked) || 0);\n el(\"control-count-owner-decision\").textContent = String(Number(summary.owner_decision_required) || 0);\n\n const notice = el(\"control-plane-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"controlLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"controlStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"controlUnavailableNotice\");\n } else if (payload.attention?.status === \"attention_required\") {\n notice.textContent = t(\"controlAttentionNotice\")\n .replace(\"{deferred}\", String(Number(summary.deferred) || 0))\n .replace(\"{parked}\", String(Number(summary.parked) || 0))\n .replace(\"{signals}\", String(payload.attention.reason_codes?.length || 0));\n } else if (payload.errors?.length) {\n notice.textContent = t(\"controlUpstreamNotice\").replace(\"{count}\", payload.errors.length);\n } else {\n notice.textContent = payload.policy?.notice || \"live 仍需所有者明确决定。\";\n }\n\n const list = el(\"control-plane-list\");\n list.replaceChildren();\n if (!payload.candidates.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"controlEmptyCandidates\");\n list.appendChild(empty);\n return;\n }\n for (const item of payload.candidates) {\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = `${item.candidate_kind || \"candidate\"} · ${domainLabel(item.domain || \"\")}`;\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = String(item.candidate_id || \"unknown\");\n const reason = document.createElement(\"p\");\n reason.className = \"health-card__reason\";\n reason.textContent = item.recommendation?.reason || t(\"controlNoRecommendation\");\n const detail = document.createElement(\"div\");\n detail.className = \"health-card__meta\";\n detail.textContent = t(\"controlStageMeta\")\n .replace(\"{stage}\", item.lifecycle?.stage || \"—\")\n .replace(\"{status}\", item.lifecycle?.status || \"unknown\")\n .replace(\"{freshness}\", item.freshness?.status || \"unknown\");\n main.append(meta, title, reason, detail);\n const stateBlock = document.createElement(\"div\");\n stateBlock.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = t(\"controlNext\");\n const stage = document.createElement(\"strong\");\n stage.textContent = item.lifecycle?.stage || \"—\";\n const recommendation = document.createElement(\"small\");\n recommendation.textContent = item.recommendation?.code || \"none\";\n stateBlock.append(label, stage, recommendation);\n const ownerEntry = ownerDecisionEntry(item.candidate_id);\n if (ownerEntry) {\n const ownerDecision = document.createElement(\"div\");\n ownerDecision.className = \"owner-decision\";\n const ownerTitle = document.createElement(\"strong\");\n ownerTitle.textContent = t(\"ownerDecisionTitle\");\n const ownerDetail = document.createElement(\"small\");\n if (ownerEntry.intent) {\n ownerDetail.textContent = t(\"ownerDecisionRecorded\")\n .replace(\"{decision}\", ownerDecisionLabel(ownerEntry.intent.decision));\n } else if (!state.auth.admin) {\n ownerDetail.textContent = t(\"ownerDecisionAdminOnly\");\n } else {\n ownerDetail.textContent = t(\"ownerDecisionReady\");\n }\n ownerDecision.append(ownerTitle, ownerDetail);\n if (!ownerEntry.intent && state.auth.admin) {\n const actions = document.createElement(\"div\");\n actions.className = \"owner-decision__actions\";\n const submitting = state.ownerDecisions.submittingCandidateId === item.candidate_id;\n for (const [decision, labelKey] of [\n [\"approve_limited_live_canary\", \"ownerDecisionApprove\"],\n [\"keep_parked\", \"ownerDecisionPark\"],\n [\"retire_candidate\", \"ownerDecisionRetire\"],\n ]) {\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.className = \"owner-decision__button\";\n button.dataset.ownerDecision = decision;\n button.dataset.candidateId = item.candidate_id;\n button.dataset.candidateEvidenceSha256 = ownerEntry.candidate_evidence_sha256;\n button.disabled = submitting;\n button.textContent = submitting ? t(\"ownerDecisionSubmitting\") : t(labelKey);\n actions.appendChild(button);\n }\n ownerDecision.appendChild(actions);\n }\n main.appendChild(ownerDecision);\n }\n card.append(main, stateBlock);\n list.appendChild(card);\n }\n }\n\n function normalizeReconciliationRecoveryPayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) {\n throw new Error(\"invalid reconciliation recovery payload\");\n }\n const recoveries = Array.isArray(payload.recoveries) ? payload.recoveries : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n recoveries: recoveries.filter((entry) => entry && typeof entry === \"object\"\n && entry.recovery && typeof entry.recovery === \"object\"\n && entry.recovery.dual_review && typeof entry.recovery.dual_review === \"object\"),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function renderReconciliationRecovery() {\n const payload = state.reconciliationRecovery.payload;\n const notice = el(\"reconciliation-recovery-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"reconciliationRecoveryLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"reconciliationRecoveryStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"reconciliationRecoveryUnavailableNotice\");\n } else if (payload.errors?.length) {\n notice.textContent = t(\"reconciliationRecoveryUpstreamNotice\").replace(\"{count}\", payload.errors.length);\n } else {\n notice.textContent = payload.policy?.notice || t(\"reconciliationRecoveryNoOrder\");\n }\n const list = el(\"reconciliation-recovery-list\");\n list.replaceChildren();\n if (!payload.recoveries.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"reconciliationRecoveryEmpty\");\n list.appendChild(empty);\n return;\n }\n for (const entry of payload.recoveries) {\n const recovery = entry.recovery || {};\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = t(\"reconciliationRecoveryMeta\")\n .replace(\"{platform}\", recovery.platform || \"unknown\")\n .replace(\"{strategy}\", recovery.strategy_profile || \"unknown\");\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = String(recovery.recovery_id || \"unknown\");\n const detail = document.createElement(\"p\");\n detail.className = \"health-card__reason\";\n detail.textContent = t(\"reconciliationRecoveryDetail\")\n .replace(\"{state}\", recovery.reconciliation_state || \"unknown\")\n .replace(\"{samples}\", String(recovery.evidence_sample_count || 0))\n .replace(\"{review}\", recovery.dual_review?.outcome || \"unknown\")\n .replace(\"{lastObserved}\", recovery.last_observed_at || \"—\");\n const status = document.createElement(\"div\");\n status.className = \"health-card__meta\";\n const ready = payload.data_status === \"ready\"\n && entry.freshness?.data_status === \"ready\"\n && recovery.readiness === \"awaiting_human_confirmation\"\n && recovery.blocker_codes?.length === 0\n && recovery.dual_review?.outcome === \"approved\"\n && recovery.dual_review?.evidence_binding_sha256 === recovery.candidate_sha256;\n if (entry.confirmation) {\n status.textContent = t(\"reconciliationRecoveryConfirmed\");\n } else if (!ready) {\n status.textContent = t(\"reconciliationRecoveryBlocked\")\n .replace(\"{blockers}\", (recovery.blocker_codes || []).join(\", \") || recovery.readiness || \"unknown\");\n } else if (!state.auth.admin) {\n status.textContent = t(\"reconciliationRecoveryAdminOnly\");\n } else {\n status.textContent = t(\"reconciliationRecoveryReady\");\n }\n main.append(meta, title, detail, status);\n if (ready && !entry.confirmation && state.auth.admin) {\n const actions = document.createElement(\"div\");\n actions.className = \"owner-decision__actions\";\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.className = \"owner-decision__button\";\n button.dataset.reconciliationRecoveryConfirm = \"true\";\n button.dataset.recoveryId = recovery.recovery_id || \"\";\n button.dataset.candidateSha256 = recovery.candidate_sha256 || \"\";\n button.dataset.dualReviewBindingSha256 = recovery.dual_review?.evidence_binding_sha256 || \"\";\n const submitting = state.reconciliationRecovery.submittingRecoveryId === recovery.recovery_id;\n button.disabled = submitting;\n button.textContent = submitting ? t(\"reconciliationRecoverySubmitting\") : t(\"reconciliationRecoveryConfirm\");\n actions.appendChild(button);\n main.appendChild(actions);\n }\n const stateBlock = document.createElement(\"div\");\n stateBlock.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = t(\"controlNext\");\n const stateText = document.createElement(\"strong\");\n stateText.textContent = entry.confirmation ? \"confirmed\" : (ready ? \"confirm\" : \"blocked\");\n const freshness = document.createElement(\"small\");\n freshness.textContent = entry.freshness?.data_status || \"unknown\";\n stateBlock.append(label, stateText, freshness);\n card.append(main, stateBlock);\n list.appendChild(card);\n }\n }\n\n const M0_RESEARCH_DISPLAY_LIMIT = 100;\n\n const m0ResearchLabels = {\n subject: {\n asset_idea: { zh: \"资产观察\", en: \"Asset idea\" },\n theme_context: { zh: \"主题背景\", en: \"Theme context\" },\n strategy_hypothesis: { zh: \"研究假设\", en: \"Research hypothesis\" },\n risk_context: { zh: \"风险背景\", en: \"Risk context\" },\n },\n state: {\n candidate: { zh: \"待验证\", en: \"Candidate\" },\n source_verification_required: { zh: \"等待来源核验\", en: \"Source verification required\" },\n deferred: { zh: \"暂缓\", en: \"Deferred\" },\n context_only: { zh: \"仅作背景\", en: \"Context only\" },\n },\n freshness: {\n fresh: { zh: \"有效\", en: \"Fresh\" },\n stale: { zh: \"已过期\", en: \"Stale\" },\n unknown: { zh: \"未知\", en: \"Unknown\" },\n },\n horizon: {\n short: { zh: \"短期\", en: \"Short\" },\n medium: { zh: \"中期\", en: \"Medium\" },\n long: { zh: \"长期\", en: \"Long\" },\n not_applicable: { zh: \"不适用\", en: \"Not applicable\" },\n },\n confidence: {\n high: { zh: \"高\", en: \"High\" },\n medium: { zh: \"中\", en: \"Medium\" },\n low: { zh: \"低\", en: \"Low\" },\n mixed: { zh: \"混合\", en: \"Mixed\" },\n no_event: { zh: \"无事件\", en: \"No event\" },\n unknown: { zh: \"未知\", en: \"Unknown\" },\n },\n style: {\n event_driven: { zh: \"事件驱动\", en: \"Event driven\" },\n long_horizon_growth: { zh: \"长期成长\", en: \"Long-horizon growth\" },\n value_quality: { zh: \"价值质量\", en: \"Value quality\" },\n macro_context: { zh: \"宏观背景\", en: \"Macro context\" },\n mixed_research: { zh: \"综合研究\", en: \"Mixed research\" },\n },\n conflict: {\n none: { zh: \"无\", en: \"None\" },\n conflict: { zh: \"存在\", en: \"Present\" },\n unavailable: { zh: \"不可用\", en: \"Unavailable\" },\n },\n drift: {\n none: { zh: \"无\", en: \"None\" },\n drift: { zh: \"存在\", en: \"Present\" },\n unavailable: { zh: \"不可用\", en: \"Unavailable\" },\n },\n };\n\n function m0ResearchLabel(group, value) {\n const entry = m0ResearchLabels[group]?.[value];\n return entry ? (state.lang === \"zh\" ? entry.zh : entry.en) : \"—\";\n }\n\n function m0ResearchTimestamp(value) {\n if (typeof value !== \"string\" || !value) return \"—\";\n const parsed = new Date(value);\n return Number.isNaN(parsed.getTime()) ? \"—\" : parsed.toLocaleString();\n }\n\n function m0ResearchHorizons(values) {\n if (!Array.isArray(values) || !values.length) return \"—\";\n const labels = values.map((value) => m0ResearchLabel(\"horizon\", value)).filter((value) => value !== \"—\");\n return labels.length ? labels.join(\" / \") : \"—\";\n }\n\n function m0ResearchEntries(subjects) {\n if (!Array.isArray(subjects)) return [];\n const entries = [];\n for (const item of subjects) {\n if (!item || typeof item !== \"object\" || !item.subject || typeof item.subject !== \"object\") continue;\n if (!Array.isArray(item.observations)) continue;\n for (const observation of item.observations) {\n if (!observation || typeof observation !== \"object\" || !observation.research_context) continue;\n entries.push({\n subject: item.subject,\n observation,\n horizonConflict: item.horizon_conflict,\n historicalStaleHorizonDrift: item.historical_stale_horizon_drift,\n });\n }\n }\n return entries;\n }\n\n function normalizeM0ResearchPayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid M0 research payload\");\n const subjects = Array.isArray(payload.subjects) ? payload.subjects : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n viewed_at: typeof payload.viewed_at === \"string\" ? payload.viewed_at : null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n subjects: subjects.filter((item) => item && typeof item === \"object\"\n && item.subject && typeof item.subject === \"object\" && Array.isArray(item.observations)),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function renderM0Research() {\n const payload = state.m0Research.payload;\n const entries = m0ResearchEntries(payload.subjects);\n const shownEntries = entries.slice(0, M0_RESEARCH_DISPLAY_LIMIT);\n const notice = el(\"m0-research-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"m0ResearchLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"m0ResearchStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"m0ResearchUnavailableNotice\");\n } else if (payload.errors?.length) {\n notice.textContent = t(\"m0ResearchUpstreamNotice\").replace(\"{count}\", String(payload.errors.length));\n } else {\n const more = entries.length > M0_RESEARCH_DISPLAY_LIMIT\n ? ` ${t(\"m0ResearchMore\").replace(\"{count}\", String(M0_RESEARCH_DISPLAY_LIMIT))}`\n : \"\";\n notice.textContent = `${t(\"m0ResearchNoOrder\")}${more}`;\n }\n\n const list = el(\"m0-research-list\");\n list.replaceChildren();\n if (!shownEntries.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"m0ResearchEmpty\");\n list.appendChild(empty);\n return;\n }\n for (const entry of shownEntries) {\n const subject = entry.subject || {};\n const observation = entry.observation || {};\n const context = observation.research_context || {};\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = t(\"m0ResearchMeta\")\n .replace(\"{kind}\", m0ResearchLabel(\"subject\", subject.kind))\n .replace(\"{viewed}\", m0ResearchTimestamp(payload.viewed_at));\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = typeof subject.identifier === \"string\" ? subject.identifier : \"—\";\n const stateFreshness = document.createElement(\"p\");\n stateFreshness.className = \"health-card__reason\";\n stateFreshness.textContent = t(\"m0ResearchStateFreshness\")\n .replace(\"{state}\", m0ResearchLabel(\"state\", context.state))\n .replace(\"{freshness}\", m0ResearchLabel(\"freshness\", observation.freshness?.status));\n const horizons = document.createElement(\"div\");\n horizons.className = \"health-card__meta\";\n horizons.textContent = t(\"m0ResearchHorizons\")\n .replace(\"{primary}\", m0ResearchLabel(\"horizon\", context.primary_horizon))\n .replace(\"{suitable}\", m0ResearchHorizons(context.suitable_horizons));\n const evidence = document.createElement(\"div\");\n evidence.className = \"health-card__meta\";\n evidence.textContent = t(\"m0ResearchEvidence\")\n .replace(\"{confidence}\", m0ResearchLabel(\"confidence\", context.source_confidence))\n .replace(\"{style}\", m0ResearchLabel(\"style\", context.source_style))\n .replace(\"{digest}\", `${shortResearchDigest(observation.source_report_digest)} / ${shortResearchDigest(observation.source_entry_digest)}`);\n const consistency = document.createElement(\"div\");\n consistency.className = \"health-card__meta\";\n consistency.textContent = t(\"m0ResearchConsistency\")\n .replace(\"{conflict}\", m0ResearchLabel(\"conflict\", entry.horizonConflict?.status))\n .replace(\"{drift}\", m0ResearchLabel(\"drift\", entry.historicalStaleHorizonDrift?.status));\n main.append(meta, title, stateFreshness, horizons, evidence, consistency);\n const status = document.createElement(\"div\");\n status.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = \"M0\";\n const freshness = document.createElement(\"strong\");\n freshness.textContent = m0ResearchLabel(\"freshness\", observation.freshness?.status);\n const stateLabel = document.createElement(\"small\");\n stateLabel.textContent = m0ResearchLabel(\"state\", context.state);\n status.append(label, freshness, stateLabel);\n card.append(main, status);\n list.appendChild(card);\n }\n }\n\n function normalizeAdaptiveSelectionPayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid adaptive selection payload\");\n const selections = Array.isArray(payload.selections) ? payload.selections : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n selections: selections.filter((item) => item && typeof item === \"object\"\n && typeof item.source_id === \"string\" && item.decision && typeof item.decision === \"object\"),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function renderAdaptiveSelection() {\n const payload = state.adaptiveSelection.payload;\n const notice = el(\"adaptive-selection-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"adaptiveSelectionLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"adaptiveSelectionStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"adaptiveSelectionUnavailableNotice\");\n } else if (payload.errors?.length) {\n notice.textContent = t(\"adaptiveSelectionUpstreamNotice\").replace(\"{count}\", String(payload.errors.length));\n } else {\n notice.textContent = payload.policy?.notice || t(\"adaptiveSelectionNoOrder\");\n }\n\n const list = el(\"adaptive-selection-list\");\n list.replaceChildren();\n if (!payload.selections.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"adaptiveSelectionEmpty\");\n list.appendChild(empty);\n return;\n }\n for (const entry of payload.selections) {\n const decision = entry.decision || {};\n const context = decision.market_context || {};\n const recommended = decision.candidates?.find((item) => item.accepted\n && item.strategy_profile === decision.recommended_strategy_profile) || null;\n const rejected = (decision.candidates || []).filter((item) => !item.accepted);\n const reasons = (recommended?.reasons?.length ? recommended.reasons : rejected.flatMap((item) => item.reasons || []))\n .slice(0, 3)\n .join(\", \");\n\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = t(\"adaptiveSelectionMeta\")\n .replace(\"{source}\", entry.source_id || \"unknown\")\n .replace(\"{domain}\", domainLabel(context.domain || \"\"))\n .replace(\"{asOf}\", context.as_of || \"—\");\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = recommended\n ? `${t(\"adaptiveSelectionRecommended\")} · ${recommended.strategy_profile}`\n : t(\"adaptiveSelectionNoCandidate\");\n const reason = document.createElement(\"p\");\n reason.className = \"health-card__reason\";\n reason.textContent = reasons\n ? t(\"adaptiveSelectionReason\").replace(\"{reasons}\", reasons)\n : t(\"adaptiveSelectionNoOrder\");\n main.append(meta, title, reason);\n const status = document.createElement(\"div\");\n status.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = \"NO ORDER\";\n const score = document.createElement(\"strong\");\n score.textContent = typeof recommended?.score === \"number\" ? recommended.score.toFixed(3) : \"—\";\n const freshness = document.createElement(\"small\");\n freshness.textContent = entry.freshness?.data_status || \"unknown\";\n status.append(label, score, freshness);\n card.append(main, status);\n list.appendChild(card);\n }\n }\n\n function normalizeExecutionEvidencePayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid execution evidence payload\");\n const deployments = Array.isArray(payload.deployments) ? payload.deployments : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n deployments: deployments.filter((item) => item && typeof item === \"object\" && item.deployment && typeof item.deployment === \"object\"),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function renderExecutionEvidence() {\n const payload = state.executionEvidence.payload;\n const notice = el(\"execution-evidence-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"executionEvidenceLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"executionEvidenceStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"executionEvidenceUnavailableNotice\");\n } else if (payload.errors?.length) {\n notice.textContent = t(\"executionEvidenceUpstreamNotice\").replace(\"{count}\", payload.errors.length);\n } else {\n notice.textContent = payload.policy?.notice || t(\"executionEvidenceNoOrder\");\n }\n\n const list = el(\"execution-evidence-list\");\n list.replaceChildren();\n if (!payload.deployments.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"executionEvidenceEmpty\");\n list.appendChild(empty);\n return;\n }\n for (const entry of payload.deployments) {\n const deployment = entry.deployment || {};\n const strategy = deployment.strategy || {};\n const target = deployment.target || {};\n const capabilities = deployment.capabilities || {};\n const evidence = deployment.evidence || {};\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = t(\"executionEvidenceMeta\")\n .replace(\"{platform}\", target.platform || \"unknown\")\n .replace(\"{environment}\", target.environment || \"unknown\")\n .replace(\"{source}\", entry.source_id || \"unknown\");\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = String(strategy.candidate_id || deployment.deployment_id || \"unknown\");\n const reason = document.createElement(\"p\");\n reason.className = \"health-card__reason\";\n reason.textContent = deployment.recommendation?.reason_code || t(\"executionEvidenceNoOrder\");\n const detail = document.createElement(\"div\");\n detail.className = \"health-card__meta\";\n detail.textContent = t(\"executionEvidenceDetail\")\n .replace(\"{strategy}\", evidence.strategy || \"unknown\")\n .replace(\"{data}\", evidence.target_data || \"unknown\")\n .replace(\"{execution}\", evidence.target_execution || \"unknown\")\n .replace(\"{shadow}\", capabilities.shadow || \"unknown\")\n .replace(\"{paper}\", capabilities.paper || \"unknown\");\n main.append(meta, title, reason, detail);\n const stateBlock = document.createElement(\"div\");\n stateBlock.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = t(\"executionEvidenceNext\");\n const recommendation = document.createElement(\"strong\");\n recommendation.textContent = deployment.recommendation?.code || \"parked\";\n const freshness = document.createElement(\"small\");\n freshness.textContent = entry.freshness?.data_status || \"unknown\";\n stateBlock.append(label, recommendation, freshness);\n card.append(main, stateBlock);\n list.appendChild(card);\n }\n }\n\n function normalizeRuntimeTargetLifecyclePayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) {\n throw new Error(\"invalid runtime target lifecycle payload\");\n }\n const targets = Array.isArray(payload.targets) ? payload.targets : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n targets: targets.filter((entry) => entry && typeof entry === \"object\"\n && entry.target && typeof entry.target === \"object\"\n && entry.target.target && typeof entry.target.target === \"object\"\n && entry.target.monitoring && typeof entry.target.monitoring === \"object\"\n && entry.target.disposition && typeof entry.target.disposition === \"object\"),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function runtimeTargetLifecycleCheckLabel(value) {\n return {\n pass: t(\"runtimeTargetLifecycleCheckPass\"),\n attention: t(\"runtimeTargetLifecycleCheckAttention\"),\n not_due: t(\"runtimeTargetLifecycleCheckNotDue\"),\n not_applicable: t(\"runtimeTargetLifecycleCheckNotApplicable\"),\n unavailable: t(\"runtimeTargetLifecycleCheckUnavailable\"),\n }[value] || t(\"runtimeTargetLifecycleCheckUnavailable\");\n }\n\n function runtimeTargetLifecycleStateLabel(value) {\n return value === \"enabled\"\n ? t(\"runtimeTargetLifecycleStateEnabled\")\n : t(\"runtimeTargetLifecycleStateDisabled\");\n }\n\n function runtimeTargetLifecycleDispositionLabel(value) {\n return {\n continue_enabled_monitoring: t(\"runtimeTargetLifecycleDispositionEnabled\"),\n continue_disabled_validation: t(\"runtimeTargetLifecycleDispositionDisabled\"),\n parked: t(\"runtimeTargetLifecycleDispositionParked\"),\n }[value] || t(\"runtimeTargetLifecycleDispositionParked\");\n }\n\n function runtimeTargetLifecycleReasonLabel(value) {\n return {\n none: t(\"runtimeTargetLifecycleReasonNone\"),\n target_intentionally_disabled: t(\"runtimeTargetLifecycleReasonDisabled\"),\n runtime_guard_attention: t(\"runtimeTargetLifecycleReasonRuntimeGuard\"),\n execution_heartbeat_attention: t(\"runtimeTargetLifecycleReasonHeartbeat\"),\n monitoring_unavailable: t(\"runtimeTargetLifecycleReasonUnavailable\"),\n }[value] || t(\"runtimeTargetLifecycleReasonUnavailable\");\n }\n\n function renderRuntimeTargetLifecycle() {\n const payload = state.runtimeTargetLifecycle.payload;\n const notice = el(\"runtime-target-lifecycle-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"runtimeTargetLifecycleLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"runtimeTargetLifecycleStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"runtimeTargetLifecycleUnavailableNotice\");\n } else if (payload.errors?.length) {\n notice.textContent = t(\"runtimeTargetLifecycleUpstreamNotice\").replace(\"{count}\", String(payload.errors.length));\n } else {\n notice.textContent = payload.policy?.notice || t(\"runtimeTargetLifecycleNoOrder\");\n }\n\n const list = el(\"runtime-target-lifecycle-list\");\n list.replaceChildren();\n if (!payload.targets.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"runtimeTargetLifecycleEmpty\");\n list.appendChild(empty);\n return;\n }\n\n const targets = [...payload.targets].sort((left, right) => {\n const leftParked = left.target?.disposition?.code === \"parked\" ? 0 : 1;\n const rightParked = right.target?.disposition?.code === \"parked\" ? 0 : 1;\n if (leftParked !== rightParked) return leftParked - rightParked;\n return String(left.target?.target_id || \"\").localeCompare(String(right.target?.target_id || \"\"));\n });\n for (const entry of targets) {\n const target = entry.target || {};\n const configuration = target.target || {};\n const monitoring = target.monitoring || {};\n const disposition = target.disposition || {};\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = t(\"runtimeTargetLifecycleMeta\")\n .replace(\"{platform}\", configuration.platform || \"unknown\")\n .replace(\"{state}\", runtimeTargetLifecycleStateLabel(configuration.configured_state))\n .replace(\"{mode}\", configuration.execution_mode || \"unknown\");\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = String(target.target_id || \"unknown\");\n const reason = document.createElement(\"p\");\n reason.className = \"health-card__reason\";\n reason.textContent = runtimeTargetLifecycleReasonLabel(disposition.reason_code);\n const detail = document.createElement(\"div\");\n detail.className = \"health-card__meta\";\n detail.textContent = t(\"runtimeTargetLifecycleDetail\")\n .replace(\"{guard}\", runtimeTargetLifecycleCheckLabel(monitoring.runtime_guard))\n .replace(\"{heartbeat}\", runtimeTargetLifecycleCheckLabel(monitoring.execution_heartbeat));\n main.append(meta, title, reason, detail);\n\n const stateBlock = document.createElement(\"div\");\n stateBlock.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = t(\"runtimeTargetLifecycleNext\");\n const current = document.createElement(\"strong\");\n current.textContent = runtimeTargetLifecycleDispositionLabel(disposition.code);\n const freshness = document.createElement(\"small\");\n freshness.textContent = entry.freshness?.data_status || \"unknown\";\n stateBlock.append(label, current, freshness);\n card.append(main, stateBlock);\n list.appendChild(card);\n }\n }\n\n function normalizeResearchTaskPayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid research task payload\");\n const tasks = Array.isArray(payload.tasks) ? payload.tasks : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n tasks: tasks.filter((item) => item && typeof item === \"object\" && item.task && typeof item.task === \"object\"),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function shortResearchDigest(value) {\n const text = String(value || \"\");\n return /^[0-9a-f]{64}$/.test(text) ? `${text.slice(0, 10)}…` : \"—\";\n }\n\n function renderResearchTasks() {\n const payload = state.researchTasks.payload;\n const notice = el(\"research-task-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"researchTaskLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"researchTaskStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"researchTaskUnavailableNotice\");\n } else if (payload.errors?.length) {\n notice.textContent = t(\"researchTaskUpstreamNotice\").replace(\"{count}\", payload.errors.length);\n } else {\n notice.textContent = payload.policy?.notice || t(\"researchTaskNoOrder\");\n }\n\n const list = el(\"research-task-list\");\n list.replaceChildren();\n if (!payload.tasks.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"researchTaskEmpty\");\n list.appendChild(empty);\n return;\n }\n for (const entry of payload.tasks) {\n const task = entry.task || {};\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = t(\"researchTaskMeta\")\n .replace(\"{type}\", task.task_type || \"research\")\n .replace(\"{domain}\", domainLabel(task.target?.domain || \"\"))\n .replace(\"{created}\", task.created_at ? new Date(task.created_at).toLocaleString() : \"—\");\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = `${task.target?.candidate_id || \"unknown\"} · ${task.task_id || \"unknown\"}`;\n const hypothesis = document.createElement(\"p\");\n hypothesis.className = \"health-card__reason\";\n hypothesis.textContent = task.experiment?.hypothesis || t(\"researchTaskNoOrder\");\n const detail = document.createElement(\"div\");\n detail.className = \"health-card__meta\";\n detail.textContent = t(\"researchTaskLimits\")\n .replace(\"{runs}\", String(task.experiment?.max_runs || \"—\"))\n .replace(\"{seconds}\", String(task.experiment?.max_wall_seconds || \"—\"))\n .replace(\"{p1}\", shortResearchDigest(task.evidence?.p1_input_digest))\n .replace(\"{p2}\", shortResearchDigest(task.evidence?.p2_config_digest))\n .replace(\"{p3}\", shortResearchDigest(task.evidence?.p3_evidence_id));\n main.append(meta, title, hypothesis, detail);\n const status = document.createElement(\"div\");\n status.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = \"NO ORDER\";\n const count = document.createElement(\"strong\");\n count.textContent = String(task.experiment?.max_runs || \"—\");\n const source = document.createElement(\"small\");\n source.textContent = entry.freshness?.data_status || \"unknown\";\n status.append(label, count, source);\n card.append(main, status);\n list.appendChild(card);\n }\n }\n\n function healthStatusLabel(status) {\n return { healthy: \"健康\", watch: \"观察\", review: \"复核\", critical: \"严重\" }[status] || \"未知\";\n }\n\n function normalizeHealthPayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid health payload\");\n const strategies = Array.isArray(payload.strategies) ? payload.strategies : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n strategies: strategies.filter((item) => item && typeof item === \"object\" && [\"healthy\", \"watch\", \"review\", \"critical\"].includes(item.status)),\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function renderHealth() {\n const payload = state.health.payload;\n const summary = payload.summary || {};\n const statusText = payload.data_status === \"ready\"\n ? \"快照已加载\"\n : (payload.data_status === \"stale\" ? \"快照已过期\" : \"等待可用快照\");\n el(\"health-status\").textContent = statusText;\n el(\"health-computed-at\").textContent = payload.computed_at\n ? `最近计算:${new Date(payload.computed_at).toLocaleString()}`\n : \"最近计算:—\";\n el(\"health-count-total\").textContent = String(Number(summary.strategy_count) || 0);\n el(\"health-count-healthy\").textContent = String(Number(summary.healthy) || 0);\n el(\"health-count-watch\").textContent = String(Number(summary.watch) || 0);\n el(\"health-count-review\").textContent = String(Number(summary.review) || 0);\n el(\"health-count-critical\").textContent = String(Number(summary.critical) || 0);\n\n const notice = el(\"health-notice\");\n if (!state.auth.allowed) {\n notice.textContent = \"登录后读取私有策略健康快照;没有快照时不会展示虚构指标。\";\n } else if (payload.data_status === \"stale\") {\n notice.textContent = \"健康快照已超过允许的新鲜度窗口;页面保留原始状态,但不会把它当作当前健康结论。\";\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = \"还没有可用的策略健康快照;当前页面保持 fail-closed 空状态。\";\n } else if (payload.errors?.length) {\n notice.textContent = `快照已加载,但有 ${payload.errors.length} 个上游提示;缺失数据不会被替换成虚构指标。`;\n } else {\n notice.textContent = \"健康不等于已批准 live;正常实盘、资金和杠杆变更仍需人工确认。\";\n }\n\n const list = el(\"health-list\");\n list.replaceChildren();\n const strategies = payload.strategies.filter((item) => state.health.filter === \"all\" || item.status === state.health.filter);\n if (!strategies.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = \"暂无可展示的策略健康快照。\";\n list.appendChild(empty);\n return;\n }\n for (const item of strategies) {\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = `${healthStatusLabel(item.status)} · ${item.domain || \"unknown\"}`;\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = String(item.profile || \"unknown\");\n const reason = document.createElement(\"p\");\n reason.className = \"health-card__reason\";\n reason.textContent = `${item.decision?.label || \"证据不足,保持研究态\"}。${item.decision?.reason || \"没有可用的机器检查结果。\"}`;\n const detail = document.createElement(\"div\");\n detail.className = \"health-card__meta\";\n detail.textContent = `阶段:${item.review?.requested_stage || \"未标记\"} · 截至:${item.as_of || \"—\"}`;\n main.append(meta, title, reason, detail);\n const scoreBlock = document.createElement(\"div\");\n scoreBlock.className = \"health-card__score\";\n const scoreLabel = document.createElement(\"small\");\n scoreLabel.textContent = \"HEALTH\";\n const score = document.createElement(\"strong\");\n score.textContent = typeof item.score === \"number\" ? item.score.toFixed(1) : \"—\";\n const decision = document.createElement(\"small\");\n decision.textContent = item.decision?.code || \"evidence_missing\";\n scoreBlock.append(scoreLabel, score, decision);\n card.append(main, scoreBlock);\n list.appendChild(card);\n }\n }\n\n function renderConsoleView() {\n const controlButton = el(\"control-plane-view-button\");\n const healthButton = el(\"health-view-button\");\n const switchButton = el(\"switch-view-button\");\n const controlVisible = state.view === \"control\";\n const healthVisible = state.view === \"health\";\n el(\"control-plane-view\").hidden = !controlVisible;\n el(\"health-view\").hidden = !healthVisible;\n el(\"switch-view\").hidden = controlVisible || healthVisible;\n controlButton.classList.toggle(\"active\", controlVisible);\n healthButton.classList.toggle(\"active\", healthVisible);\n switchButton.classList.toggle(\"active\", !controlVisible && !healthVisible);\n }\n\n function render() {\n applyLanguage();\n renderRuntimeAuthorityStatus();\n renderConsoleView();\n renderControlPlane();\n renderM0Research();\n renderAdaptiveSelection();\n renderExecutionEvidence();\n renderRuntimeTargetLifecycle();\n renderResearchTasks();\n renderHealth();\n renderPlatforms();\n renderControls();\n renderSummary();\n renderPlanReadiness();\n renderAuth();\n renderAppVisibility();\n }\n\n async function refreshSession() {\n state.bootMessageKey = \"bootSession\";\n render();\n try {\n const session = await requestJson(\"/api/session\");\n state.auth = {\n available: true,\n allowed: Boolean(session.allowed),\n admin: Boolean(session.admin),\n login: session.login || null,\n };\n } catch {\n state.auth = { available: false, allowed: false, admin: false, login: null };\n }\n if (state.auth.allowed) {\n await refreshControlPlane();\n await refreshOwnerDecisions();\n await refreshReconciliationRecovery();\n await refreshM0Research();\n await refreshAdaptiveSelection();\n await refreshExecutionEvidence();\n await refreshRuntimeTargetLifecycle();\n await refreshResearchTasks();\n await refreshHealth();\n await refreshConfig();\n } else {\n state.bootMessageKey = \"bootPublic\";\n state.appReady = true;\n render();\n }\n }\n\n async function refreshHealth() {\n if (!state.auth.allowed) {\n renderHealth();\n return;\n }\n try {\n state.health.payload = normalizeHealthPayload(await requestJson(\"/api/strategy-health\"));\n } catch {\n state.health.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { strategy_count: 0, healthy: 0, watch: 0, review: 0, critical: 0 },\n strategies: [],\n errors: [\"health_request_failed\"],\n };\n }\n renderHealth();\n }\n\n async function refreshControlPlane() {\n if (!state.auth.allowed) {\n renderControlPlane();\n return;\n }\n try {\n state.controlPlane.payload = normalizeControlPlanePayload(await requestJson(\"/api/control-plane\"));\n } catch {\n state.controlPlane.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { candidate_count: 0, deferred: 0, parked: 0, owner_decision_required: 0 },\n attention: { status: \"unavailable\", reason_codes: [\"control_plane_request_failed\"] },\n candidates: [],\n policy: { p4_p5_automation: \"not_configured\", p6_owner_decision_required: true },\n errors: [\"control_plane_request_failed\"],\n };\n }\n renderControlPlane();\n }\n\n async function refreshOwnerDecisions() {\n if (!state.auth.allowed) {\n state.ownerDecisions = { data_status: \"unavailable\", candidates: [], errors: [] };\n renderControlPlane();\n return;\n }\n try {\n state.ownerDecisions = normalizeOwnerDecisionQueue(await requestJson(\"/api/owner-decisions\"));\n } catch {\n state.ownerDecisions = {\n data_status: \"unavailable\",\n candidates: [],\n errors: [\"owner_decision_queue_request_failed\"],\n };\n }\n renderControlPlane();\n }\n\n async function refreshReconciliationRecovery() {\n if (!state.auth.allowed) {\n renderReconciliationRecovery();\n return;\n }\n try {\n state.reconciliationRecovery.payload = normalizeReconciliationRecoveryPayload(\n await requestJson(\"/api/reconciliation-recovery\"),\n );\n } catch {\n state.reconciliationRecovery.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { recovery_count: 0, awaiting_human_confirmation: 0, blocked: 0, confirmed: 0 },\n recoveries: [],\n policy: { human_confirmation_required: true, current_evidence_required: true, no_order: true, execution_authority_granted: false },\n errors: [\"reconciliation_recovery_request_failed\"],\n };\n }\n renderReconciliationRecovery();\n }\n\n async function refreshM0Research() {\n if (!state.auth.allowed) {\n renderM0Research();\n return;\n }\n try {\n state.m0Research.payload = normalizeM0ResearchPayload(await requestJson(\"/api/m0-research\"));\n } catch {\n state.m0Research.payload = {\n data_status: \"unavailable\",\n viewed_at: null,\n summary: {\n subject_count: 0,\n observation_count: 0,\n fresh_observation_count: 0,\n stale_observation_count: 0,\n unknown_observation_count: 0,\n horizon_conflict_count: 0,\n historical_stale_horizon_drift_count: 0,\n },\n subjects: [],\n policy: { authority: \"research_only\", no_order: true, permitted_next_step: \"research_validation_only\" },\n errors: [\"m0_research_request_failed\"],\n };\n }\n renderM0Research();\n }\n\n async function refreshAdaptiveSelection() {\n if (!state.auth.allowed) {\n renderAdaptiveSelection();\n return;\n }\n try {\n state.adaptiveSelection.payload = normalizeAdaptiveSelectionPayload(await requestJson(\"/api/adaptive-selection\"));\n } catch {\n state.adaptiveSelection.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { source_count: 0, decision_count: 0, candidate_count: 0, recommended_count: 0, rejected_candidate_count: 0 },\n selections: [],\n policy: { authority: \"shadow_only\", no_order: true, execution_authority_granted: false },\n errors: [\"adaptive_selection_request_failed\"],\n };\n }\n renderAdaptiveSelection();\n }\n\n async function recordOwnerDecision(button) {\n if (!state.auth.admin) return;\n const candidateId = String(button.dataset.candidateId || \"\");\n const candidateEvidenceSha256 = String(button.dataset.candidateEvidenceSha256 || \"\");\n const decision = String(button.dataset.ownerDecision || \"\");\n if (!candidateId || !candidateEvidenceSha256 || !decision) return;\n if (!window.confirm(t(\"ownerDecisionConfirm\"))) return;\n state.ownerDecisions.submittingCandidateId = candidateId;\n renderControlPlane();\n try {\n const response = await fetch(\"/api/owner-decisions\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n candidate_id: candidateId,\n decision,\n candidate_evidence_sha256: candidateEvidenceSha256,\n }),\n });\n const payload = await response.json();\n if (!response.ok || !payload.ok) throw new Error(payload.error || t(\"ownerDecisionFailed\"));\n showToast(t(\"ownerDecisionSuccess\"), { duration: 7000 });\n await refreshControlPlane();\n await refreshOwnerDecisions();\n } catch (error) {\n showToast(`${t(\"ownerDecisionFailed\")}: ${error.message}`, { duration: 12000 });\n } finally {\n delete state.ownerDecisions.submittingCandidateId;\n renderControlPlane();\n }\n }\n\n async function recordReconciliationRecoveryConfirmation(button) {\n if (!state.auth.admin) return;\n const recoveryId = String(button.dataset.recoveryId || \"\");\n const candidateSha256 = String(button.dataset.candidateSha256 || \"\");\n const dualReviewBindingSha256 = String(button.dataset.dualReviewBindingSha256 || \"\");\n if (!recoveryId || !candidateSha256 || !dualReviewBindingSha256) return;\n if (!window.confirm(t(\"reconciliationRecoveryConfirmPrompt\"))) return;\n state.reconciliationRecovery.submittingRecoveryId = recoveryId;\n renderReconciliationRecovery();\n try {\n const response = await fetch(\"/api/reconciliation-recovery-confirmations\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n recovery_id: recoveryId,\n candidate_sha256: candidateSha256,\n dual_review_binding_sha256: dualReviewBindingSha256,\n }),\n });\n const payload = await response.json();\n if (!response.ok || !payload.ok) throw new Error(payload.error || t(\"reconciliationRecoveryFailed\"));\n showToast(t(\"reconciliationRecoverySuccess\"), { duration: 9000 });\n await refreshReconciliationRecovery();\n } catch (error) {\n showToast(`${t(\"reconciliationRecoveryFailed\")}: ${error.message}`, { duration: 12000 });\n } finally {\n state.reconciliationRecovery.submittingRecoveryId = null;\n renderReconciliationRecovery();\n }\n }\n\n async function refreshExecutionEvidence() {\n if (!state.auth.allowed) {\n renderExecutionEvidence();\n return;\n }\n try {\n state.executionEvidence.payload = normalizeExecutionEvidencePayload(await requestJson(\"/api/execution-evidence\"));\n } catch {\n state.executionEvidence.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { deployment_count: 0, autonomous_shadow: 0, autonomous_paper: 0, owner_canary_decision: 0, parked: 0 },\n deployments: [],\n policy: { execution_evidence_read_only: true, p6_owner_decision_required: true, limited_live_canary_active: false },\n errors: [\"execution_evidence_request_failed\"],\n };\n }\n renderExecutionEvidence();\n }\n\n async function refreshRuntimeTargetLifecycle() {\n if (!state.auth.allowed) {\n renderRuntimeTargetLifecycle();\n return;\n }\n try {\n state.runtimeTargetLifecycle.payload = normalizeRuntimeTargetLifecyclePayload(\n await requestJson(\"/api/runtime-target-lifecycle\"),\n );\n } catch {\n state.runtimeTargetLifecycle.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { target_count: 0, enabled: 0, disabled: 0, attention: 0 },\n targets: [],\n policy: { lifecycle_status_read_only: true, no_order: true },\n errors: [\"runtime_target_lifecycle_request_failed\"],\n };\n }\n renderRuntimeTargetLifecycle();\n }\n\n async function refreshResearchTasks() {\n if (!state.auth.allowed) {\n renderResearchTasks();\n return;\n }\n try {\n state.researchTasks.payload = normalizeResearchTaskPayload(await requestJson(\"/api/research-tasks\"));\n } catch {\n state.researchTasks.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { task_count: 0 },\n tasks: [],\n policy: { research_only: true, no_order: true, size_zero_required: true, p4_p5_p6_authorized: false },\n errors: [\"research_task_request_failed\"],\n };\n }\n renderResearchTasks();\n }\n\n async function refreshStrategyProfiles() {\n state.bootMessageKey = \"bootStrategy\";\n render();\n try {\n const payload = await requestJson(\"/api/strategy-profiles\");\n applyStrategyProfiles(payload.strategyProfiles || []);\n if (payload.platformMeta) platformMeta = payload.platformMeta;\n for (const platform of Object.keys(platformMeta)) syncStrategyForAccount(platform);\n render();\n } catch {\n applyStrategyProfiles(defaultStrategyProfiles);\n for (const platform of Object.keys(platformMeta)) syncStrategyForAccount(platform);\n }\n }\n\n async function refreshConfig() {\n if (!state.auth.available || !state.auth.allowed) return;\n state.configSource = \"loading\";\n state.bootMessageKey = \"bootConfig\";\n render();\n try {\n const payload = await requestJson(\"/api/config\");\n if (payload.accountOptions) {\n applyStrategyProfiles(payload.strategyProfiles || defaultStrategyProfiles);\n state.accountOptions = normalizeAccountOptions(payload.accountOptions);\n if (payload.platformMeta) platformMeta = payload.platformMeta;\n state.repositories = normalizePlatformRepositories(payload.platformRepositories || {});\n state.currentStrategies = normalizeCurrentStrategies(payload.currentStrategies || {});\n state.configSource = \"private\";\n for (const platform of Object.keys(platformMeta)) {\n ensureAccountSelection(platform);\n syncStrategyForAccount(platform);\n }\n } else {\n state.configSource = \"default\";\n state.currentStrategies = {};\n }\n } catch (error) {\n state.configSource = \"default\";\n state.currentStrategies = {};\n if (isRequestTimeoutError(error)) {\n state.bootMessageKey = \"bootTimeout\";\n } else {\n state.bootMessageKey = \"bootPublic\";\n }\n } finally {\n state.appReady = true;\n render();\n }\n }\n\n function normalizeAccountOptions(raw) {\n const normalized = clone(defaultAccountOptions);\n for (const platform of Object.keys(platformMeta)) {\n if (!Array.isArray(raw[platform]) || !raw[platform].length) continue;\n normalized[platform] = raw[platform].map((item, index) => ({\n key: String(item.key || item.target_name || index),\n label: String(item.label || item.target_name || item.key || platform),\n target_name: String(item.target_name || item.key || \"\"),\n account_selector: item.account_selector ? String(item.account_selector) : \"\",\n deployment_selector: item.deployment_selector ? String(item.deployment_selector) : \"\",\n account_scope: item.account_scope ? String(item.account_scope) : \"\",\n service_name: item.service_name ? String(item.service_name) : \"\",\n cash_currency: item.cash_currency || item.market_currency || item.trading_currency\n ? String(item.cash_currency || item.market_currency || item.trading_currency).trim().toUpperCase()\n : \"\",\n supported_domains: normalizeSupportedDomains(platform, item),\n github_environment: item.github_environment ? String(item.github_environment) : \"\",\n variable_scope: item.variable_scope ? String(item.variable_scope) : \"\",\n plugin_mode: item.plugin_mode ? String(item.plugin_mode) : \"\",\n option_overlay_mode: item.option_overlay_mode ? normalizeOptionOverlayMode(item.option_overlay_mode) : \"\",\n cash_only_execution_mode: item.cash_only_execution_mode\n ? normalizeCashOnlyExecutionMode(item.cash_only_execution_mode)\n : \"\",\n dca_mode: item.dca_mode ? normalizeDcaMode(item.dca_mode) : \"\",\n dca_base_investment_usd: cleanDisplayPositiveNumber(item.dca_base_investment_usd),\n }));\n }\n return normalized;\n }\n\n function normalizeSupportedDomains(platform, item) {\n const raw = Array.isArray(item?.supported_domains)\n ? item.supported_domains\n : String(item?.supported_domains || \"\").split(/[\\s,;]+/);\n const cleaned = raw.map(cleanStrategyDomain).filter(Boolean);\n if (cleaned.length) return [...new Set(cleaned)];\n return inferSupportedDomains(platform, item || {});\n }\n\n function normalizeCurrentStrategies(raw) {\n const normalized = {};\n for (const platform of Object.keys(platformMeta)) {\n if (!raw[platform] || typeof raw[platform] !== \"object\" || Array.isArray(raw[platform])) continue;\n normalized[platform] = {};\n for (const [key, entry] of Object.entries(raw[platform])) {\n const profile = cleanStrategyProfile(entry?.strategy_profile);\n const minReservedCashUsd = cleanDisplayNumber(entry?.min_reserved_cash_usd ?? entry?.reserved_cash_floor_usd);\n const reservedCashRatio = cleanDisplayRatio(entry?.reserved_cash_ratio);\n const incomeLayerEnabled = cleanOptionalBoolean(entry?.income_layer_enabled);\n const incomeLayerStartUsd = cleanDisplayNumber(entry?.income_layer_start_usd);\n const incomeLayerMaxRatio = cleanDisplayRatio(entry?.income_layer_max_ratio);\n const optionOverlayEnabled = cleanOptionalBoolean(entry?.option_overlay_enabled);\n const cashOnlyExecution = cleanOptionalBoolean(entry?.cash_only_execution);\n const runtimeTargetEnabled = cleanOptionalBoolean(entry?.runtime_target_enabled);\n const dcaMode = entry?.dca_mode ? normalizeDcaMode(entry.dca_mode) : \"\";\n const dcaBaseInvestmentUsd = cleanDisplayPositiveNumber(entry?.dca_base_investment_usd);\n const executionMode = normalizeExecutionMode(entry?.execution_mode, entry?.dry_run_only);\n if (\n !profile &&\n !minReservedCashUsd &&\n !reservedCashRatio &&\n incomeLayerEnabled === null &&\n !incomeLayerStartUsd &&\n !incomeLayerMaxRatio &&\n optionOverlayEnabled === null &&\n cashOnlyExecution === null &&\n runtimeTargetEnabled === null &&\n !dcaMode &&\n !dcaBaseInvestmentUsd &&\n !executionMode\n ) continue;\n normalized[platform][String(key)] = {\n strategy_profile: profile,\n execution_mode: executionMode,\n dry_run_only: entry?.dry_run_only === true || entry?.dry_run_only === \"true\" || entry?.dry_run_only === \"1\",\n min_reserved_cash_usd: minReservedCashUsd,\n reserved_cash_ratio: reservedCashRatio,\n income_layer_enabled: incomeLayerEnabled,\n income_layer_start_usd: incomeLayerStartUsd,\n income_layer_max_ratio: incomeLayerMaxRatio,\n option_overlay_enabled: optionOverlayEnabled,\n cash_only_execution: cashOnlyExecution,\n runtime_target_enabled: runtimeTargetEnabled,\n dca_mode: dcaMode,\n dca_base_investment_usd: dcaBaseInvestmentUsd,\n source: entry?.source ? String(entry.source) : \"\",\n };\n }\n if (!Object.keys(normalized[platform]).length) delete normalized[platform];\n }\n return normalized;\n }\n\n function normalizePlatformRepositories(raw) {\n const normalized = clone(defaultRepositories);\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return normalized;\n for (const platform of Object.keys(platformMeta)) {\n const repository = String(raw[platform] || \"\").trim();\n if (/^[A-Za-z0-9_.-]+\\/[A-Za-z0-9_.-]+$/.test(repository)) {\n normalized[platform] = repository;\n }\n }\n return normalized;\n }\n\n async function dispatchSwitch() {\n if (!state.auth.allowed) return;\n showToast(t(\"dispatching\"), { duration: 0 });\n try {\n const response = await fetch(\"/api/switch\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(buildInputs()),\n });\n const payload = await response.json();\n if (!response.ok || !payload.ok) throw new Error(payload.error || t(\"dispatchFailed\"));\n showToast(t(\"dispatched\"), { duration: 4000 });\n if (payload.actions_url) window.open(payload.actions_url, \"_blank\", \"noopener,noreferrer\");\n await refreshConfig();\n } catch (error) {\n showToast(`${t(\"dispatchFailed\")}: ${error.message}`, { duration: 12000 });\n }\n }\n\n async function handleLogout() {\n await fetch(\"/api/logout\", { method: \"POST\" });\n window.location.reload();\n }\n\n function summaryText() {\n const inputs = buildInputs();\n return summaryRows(inputs).map(([label, value]) => `${label}: ${value}`).join(\"\\\n\");\n }\n\n document.querySelectorAll(\"[data-view]\").forEach((button) => button.addEventListener(\"click\", () => {\n state.view = [\"control\", \"health\", \"switch\"].includes(button.dataset.view) ? button.dataset.view : \"control\";\n renderConsoleView();\n if (state.view === \"control\") {\n refreshControlPlane();\n refreshOwnerDecisions();\n refreshReconciliationRecovery();\n refreshM0Research();\n refreshAdaptiveSelection();\n refreshExecutionEvidence();\n refreshRuntimeTargetLifecycle();\n refreshResearchTasks();\n }\n if (state.view === \"health\") refreshHealth();\n }));\n\n document.querySelectorAll(\"[data-health-filter]\").forEach((button) => button.addEventListener(\"click\", () => {\n document.querySelectorAll(\"[data-health-filter]\").forEach((node) => node.classList.remove(\"active\"));\n button.classList.add(\"active\");\n state.health.filter = button.dataset.healthFilter;\n renderHealth();\n }));\n\n el(\"control-plane-list\").addEventListener(\"click\", (event) => {\n const button = event.target.closest(\"[data-owner-decision]\");\n if (!button || button.disabled) return;\n recordOwnerDecision(button);\n });\n\n el(\"reconciliation-recovery-list\").addEventListener(\"click\", (event) => {\n const button = event.target.closest(\"[data-reconciliation-recovery-confirm]\");\n if (!button || button.disabled) return;\n recordReconciliationRecoveryConfirmation(button);\n });\n\n el(\"platform-strip\").addEventListener(\"click\", (event) => {\n const button = event.target.closest(\"[data-platform]\");\n if (!button) return;\n state.selected = button.dataset.platform;\n state.forms[state.selected].strategyTouched = false;\n render();\n });\n\n el(\"account-select\").addEventListener(\"change\", () => {\n state.forms[state.selected].accountKey = el(\"account-select\").value;\n state.forms[state.selected].runtimeTargetTouched = false;\n state.forms[state.selected].reservedCashTouched = false;\n state.forms[state.selected].incomeLayerTouched = false;\n state.forms[state.selected].optionOverlayTouched = false;\n state.forms[state.selected].cashOnlyExecutionTouched = false;\n state.forms[state.selected].dcaTouched = false;\n state.forms[state.selected].strategyTouched = false;\n syncStrategyForAccount(state.selected);\n render();\n });\n\n el(\"strategy-select\").addEventListener(\"change\", () => {\n state.forms[state.selected].strategy = el(\"strategy-select\").value;\n state.forms[state.selected].strategyTouched = true;\n state.forms[state.selected].incomeLayerTouched = false;\n state.forms[state.selected].optionOverlayTouched = false;\n state.forms[state.selected].dcaTouched = false;\n syncIncomeLayerForAccount(state.selected);\n syncOptionOverlayForAccount(state.selected);\n syncDcaForAccount(state.selected);\n render();\n });\n\n el(\"mode-control\").addEventListener(\"click\", (event) => {\n const button = event.target.closest(\"[data-mode]\");\n if (!button || button.disabled) return;\n if (!supportedExecutionModesForPlatform(state.selected).includes(button.dataset.mode)) return;\n state.forms[state.selected].executionMode = button.dataset.mode;\n render();\n });\n\n el(\"plugin-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.pluginMode = normalizePluginMode(el(\"plugin-mode-select\").value);\n render();\n });\n\n el(\"runtime-target-enabled-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.runtimeTargetMode = normalizeRuntimeTargetMode(el(\"runtime-target-enabled-select\").value);\n form.runtimeTargetTouched = form.runtimeTargetMode !== \"current\";\n render();\n });\n\n el(\"income-layer-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.incomeLayerMode = normalizeIncomeLayerMode(el(\"income-layer-mode-select\").value);\n form.incomeLayerTouched = form.incomeLayerMode !== \"current\";\n if (form.incomeLayerMode === \"current\") {\n form.incomeLayerTouched = false;\n syncIncomeLayerForAccount(state.selected);\n }\n render();\n });\n\n el(\"income-layer-start-usd-input\").addEventListener(\"input\", () => {\n const form = state.forms[state.selected];\n form.incomeLayerTouched = true;\n form.incomeLayerMode = \"enabled\";\n form.incomeLayerStartUsd = el(\"income-layer-start-usd-input\").value.trim();\n render();\n });\n\n el(\"income-layer-max-ratio-input\").addEventListener(\"input\", () => {\n const form = state.forms[state.selected];\n form.incomeLayerTouched = true;\n form.incomeLayerMode = \"enabled\";\n form.incomeLayerMaxRatio = el(\"income-layer-max-ratio-input\").value.trim();\n render();\n });\n\n el(\"option-overlay-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.optionOverlayMode = normalizeOptionOverlayMode(el(\"option-overlay-mode-select\").value);\n form.optionOverlayTouched = form.optionOverlayMode !== \"current\";\n render();\n });\n\n el(\"cash-only-execution-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.cashOnlyExecutionMode = normalizeCashOnlyExecutionMode(el(\"cash-only-execution-mode-select\").value);\n form.cashOnlyExecutionTouched = form.cashOnlyExecutionMode !== \"current\";\n if (allowMarginExplicitlySelected(form)) reconcileExecutionCashPolicy(form, \"margin\");\n else restoreReserveAfterMarginDisabled(form);\n render();\n });\n\n el(\"dca-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.dcaTouched = true;\n form.dcaMode = normalizeDcaMode(el(\"dca-mode-select\").value);\n render();\n });\n\n el(\"dca-base-investment-usd-input\").addEventListener(\"input\", () => {\n const form = state.forms[state.selected];\n form.dcaTouched = true;\n form.dcaBaseInvestmentUsd = el(\"dca-base-investment-usd-input\").value.trim();\n render();\n });\n\n el(\"reserve-policy-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.reservePolicyMode = normalizeReservePolicyMode(el(\"reserve-policy-mode-select\").value);\n form.reservedCashTouched = form.reservePolicyMode !== \"current\";\n if (form.reservePolicyMode === \"current\") syncReservePolicyForAccount(state.selected);\n reconcileExecutionCashPolicy(form, \"reserve\");\n render();\n });\n\n el(\"min-reserved-cash-input\").addEventListener(\"input\", () => {\n state.forms[state.selected].reservedCashTouched = true;\n state.forms[state.selected].minReservedCashUsd = el(\"min-reserved-cash-input\").value.trim();\n render();\n });\n\n el(\"reserved-cash-ratio-input\").addEventListener(\"input\", () => {\n state.forms[state.selected].reservedCashTouched = true;\n state.forms[state.selected].reservedCashRatio = el(\"reserved-cash-ratio-input\").value.trim();\n render();\n });\n\n el(\"copy-button\").addEventListener(\"click\", async () => {\n try {\n await navigator.clipboard.writeText(summaryText());\n showToast(t(\"copied\"), { duration: 3000 });\n } catch {\n showToast(summaryText(), { duration: 0 });\n }\n });\n\n el(\"dispatch-button\").addEventListener(\"click\", dispatchSwitch);\n el(\"logout-button\").addEventListener(\"click\", handleLogout);\n el(\"lang-button\").addEventListener(\"click\", () => {\n state.lang = state.lang === \"zh\" ? \"en\" : \"zh\";\n localStorage.setItem(\"qsl-switch-lang\", state.lang);\n render();\n });\n\n applyStrategyProfiles(defaultStrategyProfiles);\n for (const platform of Object.keys(platformMeta)) syncStrategyForAccount(platform);\n render();\n boot();\n\n async function boot() {\n try {\n await refreshStrategyProfiles();\n await refreshSession();\n } catch {\n state.auth = { available: false, allowed: false, admin: false, login: null };\n state.configSource = \"default\";\n state.currentStrategies = {};\n state.bootMessageKey = \"bootTimeout\";\n state.appReady = true;\n render();\n }\n }\n"; diff --git a/web/strategy-switch-console/index.html b/web/strategy-switch-console/index.html index 0b3cfba..f938480 100644 --- a/web/strategy-switch-console/index.html +++ b/web/strategy-switch-console/index.html @@ -88,6 +88,11 @@

只处理需要
登录后读取已验证的 Shadow 建议;它始终不下单。
+
+ 恢复核验(仅已存在实盘) +
登录后读取私有恢复核验;页面不会直接恢复运行或下单。
+
+
按需查看策略 × 平台证据覆盖
登录后读取已验证执行证据;页面不会推断 paper 或 live 状态。
diff --git a/web/strategy-switch-console/page_asset.js b/web/strategy-switch-console/page_asset.js index 0ed49d4..773b185 100644 --- a/web/strategy-switch-console/page_asset.js +++ b/web/strategy-switch-console/page_asset.js @@ -1,2 +1,2 @@ // Generated by scripts/sync_strategy_switch_page_asset.py; do not edit by hand. -export const PAGE_HTML = "\n\n\n \n \n \n QuantStrategyLab 控制台\n \n \n\n\n\n\n\n
\n
\n

QuantStrategyLab

\n

已验证证据、运行许可和所有者决策。

\n
\n
\n \n \n \n \n
\n
\n\n
\n
\n 初始化控制台\n

读取策略配置

\n

正在读取登录状态、账号配置和当前状态。

\n
\n
\n
\n\n
\n \n \n\n
\n
\n
\n 今日待处理 / 只读\n

只处理需要你决定的事。

\n

这里不是交易大屏:只显示经验证的待办、证据是否足够,以及需要人工做出的边界决定。

\n
\n
\n 等待登录\n 最近计算:—\n
\n
\n
登录后读取私有全局快照;页面不会授予实盘权限。
\n
\n
候选总数0
\n
本次延期0
\n
已停车0
\n
等待我决定0
\n
\n

需要跟进

\n
\n
\n 按需查看 M0 研究观察\n
登录后读取已验证 M0 研究台账;仅研究、无订单。
\n
\n
\n
\n 按需查看 M1 Shadow 建议\n
登录后读取已验证的 Shadow 建议;它始终不下单。
\n
\n
\n
\n 按需查看策略 × 平台证据覆盖\n
登录后读取已验证执行证据;页面不会推断 paper 或 live 状态。
\n
\n
\n
\n 按需查看平台运行状态\n
登录后读取已验证的平台运行状态;页面不会启用目标或提交订单。
\n
\n
\n
\n 按需查看研究任务队列\n
登录后读取已验证研究任务;不会运行或调参。
\n
\n
\n
\n\n \n\n \n
\n\n \n\n\n"; +export const PAGE_HTML = "\n\n\n \n \n \n QuantStrategyLab 控制台\n \n \n\n\n\n\n\n
\n
\n

QuantStrategyLab

\n

已验证证据、运行许可和所有者决策。

\n
\n
\n \n \n \n \n
\n
\n\n
\n
\n 初始化控制台\n

读取策略配置

\n

正在读取登录状态、账号配置和当前状态。

\n
\n
\n
\n\n
\n \n \n\n
\n
\n
\n 今日待处理 / 只读\n

只处理需要你决定的事。

\n

这里不是交易大屏:只显示经验证的待办、证据是否足够,以及需要人工做出的边界决定。

\n
\n
\n 等待登录\n 最近计算:—\n
\n
\n
登录后读取私有全局快照;页面不会授予实盘权限。
\n
\n
候选总数0
\n
本次延期0
\n
已停车0
\n
等待我决定0
\n
\n

需要跟进

\n
\n
\n 按需查看 M0 研究观察\n
登录后读取已验证 M0 研究台账;仅研究、无订单。
\n
\n
\n
\n 按需查看 M1 Shadow 建议\n
登录后读取已验证的 Shadow 建议;它始终不下单。
\n
\n
\n
\n 恢复核验(仅已存在实盘)\n
登录后读取私有恢复核验;页面不会直接恢复运行或下单。
\n
\n
\n
\n 按需查看策略 × 平台证据覆盖\n
登录后读取已验证执行证据;页面不会推断 paper 或 live 状态。
\n
\n
\n
\n 按需查看平台运行状态\n
登录后读取已验证的平台运行状态;页面不会启用目标或提交订单。
\n
\n
\n
\n 按需查看研究任务队列\n
登录后读取已验证研究任务;不会运行或调参。
\n
\n
\n
\n\n \n\n \n
\n\n \n\n\n"; diff --git a/web/strategy-switch-console/worker.js b/web/strategy-switch-console/worker.js index dc29147..d0956c1 100644 --- a/web/strategy-switch-console/worker.js +++ b/web/strategy-switch-console/worker.js @@ -119,6 +119,27 @@ const OWNER_DECISION_INTENT_SCHEMA_VERSION = "qsl_owner_decision_intent.v1"; const OWNER_DECISION_QUEUE_SCHEMA_VERSION = "qsl_owner_decision_queue.v1"; const OWNER_DECISION_CHOICES = ["approve_limited_live_canary", "keep_parked", "retire_candidate"]; const OWNER_DECISION_MAX_CANDIDATES = 100; +// Reconciliation recovery has a different authority boundary from P6 +// strategy promotion. It can only describe an already-authorised legacy +// target that is currently RECONCILE_ONLY; it can never create a new strategy +// release, order, broker credential, or runtime mutation from this Worker. +const RECONCILIATION_RECOVERY_SOURCE_PREFIX = "reconciliation_recovery_source:"; +const RECONCILIATION_RECOVERY_CONFIRMATION_PREFIX = "reconciliation_recovery_confirmation:"; +const RECONCILIATION_RECOVERY_CURRENT_PREFIX = "reconciliation_recovery_current:"; +const RECONCILIATION_RECOVERY_SOURCE_SCHEMA_VERSION = "qsl_reconciliation_recovery_source_snapshot.v1"; +const RECONCILIATION_RECOVERY_DASHBOARD_SCHEMA_VERSION = "qsl_reconciliation_recovery_dashboard.v1"; +const RECONCILIATION_RECOVERY_CONFIRMATION_SCHEMA_VERSION = "qsl_reconciliation_recovery_confirmation.v1"; +const RECONCILIATION_RECOVERY_MAX_SOURCES = 100; +const RECONCILIATION_RECOVERY_MAX_BODY_BYTES = 128 * 1024; +const RECONCILIATION_RECOVERY_DEFAULT_STALE_TTL_SECONDS = 30 * 60; +const RECONCILIATION_RECOVERY_MIN_SAMPLE_SEPARATION_MS = 60 * 1000; +const RECONCILIATION_RECOVERY_MAX_SAMPLE_WINDOW_MS = 15 * 60 * 1000; +const RECONCILIATION_RECOVERY_PLATFORMS = ["alpaca", "longbridge", "ibkr", "schwab", "firstrade", "qmt", "binance"]; +const RECONCILIATION_RECOVERY_ENVIRONMENTS = ["live"]; +const RECONCILIATION_RECOVERY_STATES = ["RECONCILE_ONLY"]; +const RECONCILIATION_RECOVERY_READINESS = ["blocked", "awaiting_human_confirmation"]; +const RECONCILIATION_RECOVERY_DUAL_REVIEW_OUTCOMES = ["approved", "rejected", "unavailable"]; +const RECONCILIATION_RECOVERY_MAX_REQUESTS = 100; // Execution evidence is deliberately a separate, read-only projection. A // candidate's research lifecycle is portable; a broker/data/execution result // is only meaningful for the exact target platform and lane that produced it. @@ -334,6 +355,15 @@ export default { if (url.pathname === "/api/owner-decisions" && request.method === "POST") { return await recordOwnerDecisionResponse(request, env); } + if (url.pathname === "/api/internal/sync-reconciliation-recovery-source" && request.method === "POST") { + return await syncReconciliationRecoverySourceResponse(request, env); + } + if (url.pathname === "/api/reconciliation-recovery" && request.method === "GET") { + return await reconciliationRecoveryResponse(request, env); + } + if (url.pathname === "/api/reconciliation-recovery-confirmations" && request.method === "POST") { + return await recordReconciliationRecoveryConfirmationResponse(request, env); + } if (url.pathname === "/api/internal/sync-execution-evidence-source" && request.method === "POST") { return await syncExecutionEvidenceSourceResponse(request, env); } @@ -1304,6 +1334,12 @@ async function dispatchSwitch(request, env) { const rawInput = await request.json(); const inputs = normalizeSwitchInputs(rawInput); assertSwitchIntent(inputs); + if (inputs.live_continuity_state !== "NONE") { + return json({ + ok: false, + error: "legacy continuity recovery requires the private reconciliation recovery controller; manual switch cannot restore execution", + }, 409); + } const accountConfig = await loadAccountOptionsConfig(env); const accountOption = assertConfiguredAccount(inputs, accountConfig.options); assertStrategyAllowedForAccount(inputs, accountOption, await loadStrategyProfilesConfig(env)); @@ -1894,6 +1930,350 @@ async function recordOwnerDecisionResponse(request, env) { return json({ ok: true, intent, audit_logged: auditLogged }); } +// These recovery snapshots deliberately contain only opaque target IDs, +// strategy labels, timestamps, digest bindings and stable reason codes. The +// five broker-state digests, raw receipts and account details stay inside the +// platform's private runtime. This keeps the console useful for a low +// frequency operator without turning it into a broker control plane. +async function syncReconciliationRecoverySourceResponse(request, env) { + requireDedicatedReconciliationRecoverySyncToken(request, env); + if (!hasConfigStore(env)) { + return json({ ok: false, error: "reconciliation recovery KV is not configured" }, 503); + } + let raw; + try { + raw = await readBoundedJson(request, RECONCILIATION_RECOVERY_MAX_BODY_BYTES); + } catch (error) { + return json({ ok: false, error: error.message || "invalid reconciliation recovery payload" }, error.status || 400); + } + let source; + try { + source = normalizeReconciliationRecoverySourceSnapshot(raw, "reconciliation recovery source snapshot"); + } catch (error) { + return json({ ok: false, error: error.message || "invalid reconciliation recovery payload" }, 400); + } + await writeConfigJson(env, reconciliationRecoverySourceKey(source.source_id), source); + try { + await appendAuditLog(env, { + ts: new Date().toISOString(), + login: "reconciliation-recovery-source-sync", + action: "sync_reconciliation_recovery_source", + source_id: source.source_id, + schema_version: source.schema_version, + recovery_count: source.recoveries.length, + data_status: source.data_status, + }); + } catch { + // The source is still safely stored even if the rolling convenience log + // cannot be updated. + } + return json({ + ok: true, + source_id: source.source_id, + schema_version: source.schema_version, + recovery_count: source.recoveries.length, + generated_at: source.generated_at, + }); +} + +async function reconciliationRecoveryResponse(request, env) { + const session = await readSession(request, env); + if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); + if (!hasConfigStore(env)) return json(emptyReconciliationRecoveryPayload("snapshot_unavailable")); + return json(await aggregateReconciliationRecoverySources(env)); +} + +function currentReconciliationRecoveryRequest(dashboard, recoveryId) { + if (dashboard?.data_status !== "ready") { + throw new HttpError("current reconciliation recovery evidence is not ready", 409); + } + const entry = dashboard.recoveries.find((item) => item.recovery?.recovery_id === recoveryId); + if (!entry || entry.freshness?.data_status !== "ready") { + throw new HttpError("reconciliation recovery request is not fresh", 409); + } + const recovery = entry.recovery; + if ( + recovery.readiness !== "awaiting_human_confirmation" || + recovery.reconciliation_state !== "RECONCILE_ONLY" || + recovery.blocker_codes.length || + recovery.dual_review.outcome !== "approved" || + recovery.dual_review.evidence_binding_sha256 !== recovery.candidate_sha256 || + recovery.evidence_sample_count < 2 || + recovery.dual_review.reviewer_count < 2 + ) { + throw new HttpError("reconciliation recovery request is not eligible for confirmation", 409); + } + return recovery; +} + +function normalizeReconciliationRecoveryConfirmationRequest(payload) { + const value = assertExactFields(payload, [ + "recovery_id", "candidate_sha256", "dual_review_binding_sha256", + ], "reconciliation recovery confirmation request"); + return { + recovery_id: normalizeControlPlaneIdentifier(value.recovery_id, "reconciliation recovery confirmation request.recovery_id", false), + candidate_sha256: normalizeResearchTaskDigest( + value.candidate_sha256, + "reconciliation recovery confirmation request.candidate_sha256", + ), + dual_review_binding_sha256: normalizeResearchTaskDigest( + value.dual_review_binding_sha256, + "reconciliation recovery confirmation request.dual_review_binding_sha256", + ), + }; +} + +async function calculateReconciliationRecoveryConfirmationSha256(payload) { + const material = { ...payload }; + delete material.confirmation_sha256; + const raw = new TextEncoder().encode(canonicalResearchTaskJson(material)); + const digest = await crypto.subtle.digest("SHA-256", raw); + return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join(""); +} + +async function buildReconciliationRecoveryConfirmation({ recovery, confirmedBy, confirmedAt }) { + const confirmation = { + schema_version: RECONCILIATION_RECOVERY_CONFIRMATION_SCHEMA_VERSION, + recovery_id: recovery.recovery_id, + candidate_sha256: recovery.candidate_sha256, + dual_review_binding_sha256: recovery.dual_review.evidence_binding_sha256, + confirmed_at: confirmedAt, + confirmed_by: confirmedBy, + no_order: true, + execution_authority_granted: false, + confirmation_sha256: "", + }; + confirmation.confirmation_sha256 = await calculateReconciliationRecoveryConfirmationSha256(confirmation); + return await normalizeReconciliationRecoveryConfirmation(confirmation, "reconciliation recovery confirmation"); +} + +async function normalizeReconciliationRecoveryConfirmation(payload, fieldName) { + const value = assertExactFields(payload, [ + "schema_version", "recovery_id", "candidate_sha256", "dual_review_binding_sha256", "confirmed_at", "confirmed_by", + "no_order", "execution_authority_granted", "confirmation_sha256", + ], fieldName); + if (value.schema_version !== RECONCILIATION_RECOVERY_CONFIRMATION_SCHEMA_VERSION) { + throw new Error(`${fieldName}.schema_version is unsupported`); + } + const confirmation = { + schema_version: RECONCILIATION_RECOVERY_CONFIRMATION_SCHEMA_VERSION, + recovery_id: normalizeControlPlaneIdentifier(value.recovery_id, `${fieldName}.recovery_id`, false), + candidate_sha256: normalizeResearchTaskDigest(value.candidate_sha256, `${fieldName}.candidate_sha256`), + dual_review_binding_sha256: normalizeResearchTaskDigest( + value.dual_review_binding_sha256, + `${fieldName}.dual_review_binding_sha256`, + ), + confirmed_at: normalizeResearchTaskTimestamp(value.confirmed_at, `${fieldName}.confirmed_at`), + confirmed_by: cleanGithubLogin(value.confirmed_by, `${fieldName}.confirmed_by`), + no_order: value.no_order, + execution_authority_granted: value.execution_authority_granted, + confirmation_sha256: normalizeResearchTaskDigest(value.confirmation_sha256, `${fieldName}.confirmation_sha256`), + }; + if (confirmation.no_order !== true || confirmation.execution_authority_granted !== false) { + throw new Error(`${fieldName} must remain no-order and non-executable`); + } + if (confirmation.confirmation_sha256 !== await calculateReconciliationRecoveryConfirmationSha256(confirmation)) { + throw new Error(`${fieldName}.confirmation_sha256 mismatch`); + } + return confirmation; +} + +function reconciliationRecoveryConfirmationArchiveKey(recoveryId, confirmationSha256) { + return `${RECONCILIATION_RECOVERY_CONFIRMATION_PREFIX}${recoveryId}:${confirmationSha256}`; +} + +function reconciliationRecoveryConfirmationCurrentKey(recoveryId) { + return `${RECONCILIATION_RECOVERY_CURRENT_PREFIX}${recoveryId}`; +} + +async function readReconciliationRecoveryConfirmation(env, recoveryId) { + try { + const stored = await readConfigJson(env, reconciliationRecoveryConfirmationCurrentKey(recoveryId)); + if (!stored) return { confirmation: null, error: null }; + return { + confirmation: await normalizeReconciliationRecoveryConfirmation( + stored, + reconciliationRecoveryConfirmationCurrentKey(recoveryId), + ), + error: null, + }; + } catch { + return { confirmation: null, error: "reconciliation_recovery_confirmation_invalid" }; + } +} + +async function recordReconciliationRecoveryConfirmationResponse(request, env) { + requireSameOrigin(request, { requireOrigin: true }); + const session = await readSession(request, env); + if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); + if (!session.admin) return json({ ok: false, error: "admin required" }, 403); + if (!hasConfigStore(env)) { + return json({ ok: false, error: "STRATEGY_SWITCH_CONFIG KV binding is required" }, 503); + } + let raw; + try { + raw = await readBoundedJson(request, 4 * 1024); + } catch (error) { + return json({ ok: false, error: error.message || "invalid reconciliation recovery confirmation" }, error.status || 400); + } + let requested; + try { + requested = normalizeReconciliationRecoveryConfirmationRequest(raw); + } catch (error) { + return json({ ok: false, error: error.message || "invalid reconciliation recovery confirmation" }, 400); + } + const dashboard = await aggregateReconciliationRecoverySources(env); + let recovery; + try { + recovery = currentReconciliationRecoveryRequest(dashboard, requested.recovery_id); + } catch (error) { + return json({ ok: false, error: error.message || "reconciliation recovery confirmation is unavailable" }, error.status || 409); + } + if ( + requested.candidate_sha256 !== recovery.candidate_sha256 || + requested.dual_review_binding_sha256 !== recovery.dual_review.evidence_binding_sha256 + ) { + return json({ ok: false, error: "reconciliation evidence changed; reload the review before confirming" }, 409); + } + const confirmation = await buildReconciliationRecoveryConfirmation({ + recovery, + confirmedBy: session.login, + confirmedAt: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"), + }); + await writeConfigJson( + env, + reconciliationRecoveryConfirmationArchiveKey(recovery.recovery_id, confirmation.confirmation_sha256), + confirmation, + ); + await writeConfigJson(env, reconciliationRecoveryConfirmationCurrentKey(recovery.recovery_id), confirmation); + let auditLogged = false; + try { + await appendAuditLog(env, { + ts: confirmation.confirmed_at, + login: session.login, + action: "record_reconciliation_recovery_confirmation", + recovery_id: confirmation.recovery_id, + candidate_sha256: confirmation.candidate_sha256, + dual_review_binding_sha256: confirmation.dual_review_binding_sha256, + confirmation_sha256: confirmation.confirmation_sha256, + no_order: true, + execution_authority_granted: false, + }); + auditLogged = true; + } catch { + // The immutable confirmation remains available to a future private + // controller even if the rolling audit log is unavailable. + } + return json({ ok: true, confirmation, audit_logged: auditLogged }); +} + +async function aggregateReconciliationRecoverySources(env) { + const sources = await readReconciliationRecoverySources(env); + if (!sources.length) return emptyReconciliationRecoveryPayload("snapshot_unavailable"); + const ttlSeconds = reconciliationRecoveryStaleTtlSeconds(env); + const now = Date.now(); + const recoveries = []; + const recoveryIds = new Set(); + const duplicateRecoveryIds = new Set(); + const errors = []; + const timestamps = []; + let hasReadySource = false; + let hasStaleSource = false; + let hasStaleRecoveryEvidence = false; + for (const source of sources) { + const sourceFreshness = controlPlaneSnapshotFreshness(source, ttlSeconds, now); + if (source.generated_at) timestamps.push(source.generated_at); + if (source.computed_at) timestamps.push(source.computed_at); + if (sourceFreshness.data_status === "ready") hasReadySource = true; + if (sourceFreshness.data_status === "stale") { + hasStaleSource = true; + errors.push("reconciliation_recovery_source_stale"); + } + for (const recovery of source.recoveries) { + if (recoveryIds.has(recovery.recovery_id)) { + duplicateRecoveryIds.add(recovery.recovery_id); + errors.push("reconciliation_recovery_duplicate_recovery"); + continue; + } + recoveryIds.add(recovery.recovery_id); + const freshness = reconciliationRecoveryEvidenceFreshness(recovery, sourceFreshness, ttlSeconds, now); + if (freshness.data_status === "stale") { + hasStaleRecoveryEvidence = true; + errors.push("reconciliation_recovery_evidence_stale"); + } + const result = await readReconciliationRecoveryConfirmation(env, recovery.recovery_id); + if (result.error) errors.push(result.error); + const confirmation = result.confirmation + && result.confirmation.candidate_sha256 === recovery.candidate_sha256 + && result.confirmation.dual_review_binding_sha256 === recovery.dual_review.evidence_binding_sha256 + ? result.confirmation + : null; + recoveries.push({ source_id: source.source_id, freshness, recovery, confirmation }); + } + errors.push(...source.errors); + } + const uniqueRecoveries = recoveries + .filter((entry) => !duplicateRecoveryIds.has(entry.recovery.recovery_id)) + .sort((left, right) => left.recovery.recovery_id.localeCompare(right.recovery.recovery_id)); + const dataStatus = hasStaleSource || hasStaleRecoveryEvidence + ? "stale" + : (hasReadySource ? "ready" : "unavailable"); + return { + schema_version: RECONCILIATION_RECOVERY_DASHBOARD_SCHEMA_VERSION, + generated_at: earliestControlPlaneTimestamp(timestamps), + computed_at: earliestControlPlaneTimestamp(timestamps), + data_status: dataStatus, + summary: { + recovery_count: uniqueRecoveries.length, + awaiting_human_confirmation: uniqueRecoveries.filter((entry) => + entry.freshness.data_status === "ready" && + entry.recovery.readiness === "awaiting_human_confirmation" && + !entry.confirmation, + ).length, + blocked: uniqueRecoveries.filter((entry) => entry.recovery.readiness === "blocked").length, + confirmed: uniqueRecoveries.filter((entry) => Boolean(entry.confirmation)).length, + }, + recoveries: uniqueRecoveries, + policy: { + human_confirmation_required: true, + current_evidence_required: true, + no_order: true, + execution_authority_granted: false, + notice: "恢复确认只记录与双审摘要绑定的人工意图;私有控制器仍须重新核验后才可恢复既有执行。", + }, + errors: uniqueStrings(errors), + }; +} + +async function readReconciliationRecoverySources(env) { + const store = configStore(env); + if (!store || typeof store.list !== "function") return []; + let listing; + try { + listing = await store.list({ prefix: RECONCILIATION_RECOVERY_SOURCE_PREFIX, limit: RECONCILIATION_RECOVERY_MAX_SOURCES }); + } catch { + return [emptyReconciliationRecoverySourceSnapshot("reconciliation_recovery_source_list_unavailable")]; + } + const keys = Array.isArray(listing?.keys) ? listing.keys : []; + const sources = []; + for (const entry of keys.slice(0, RECONCILIATION_RECOVERY_MAX_SOURCES)) { + const key = typeof entry?.name === "string" ? entry.name : ""; + if (!key.startsWith(RECONCILIATION_RECOVERY_SOURCE_PREFIX)) continue; + try { + const stored = await readConfigJson(env, key); + if (!stored) continue; + sources.push(normalizeReconciliationRecoverySourceSnapshot(stored, key)); + } catch { + sources.push(emptyReconciliationRecoverySourceSnapshot("reconciliation_recovery_source_invalid")); + } + } + return sources; +} + +function reconciliationRecoverySourceKey(sourceId) { + return `${RECONCILIATION_RECOVERY_SOURCE_PREFIX}${sourceId}`; +} + async function syncExecutionEvidenceSourceResponse(request, env) { requireDedicatedExecutionEvidenceSyncToken(request, env); if (!hasConfigStore(env)) { @@ -2680,6 +3060,26 @@ function controlPlaneSnapshotFreshness(snapshot, ttlSeconds, now) { return { data_status: dataStatus, age_seconds: Number.isFinite(ageSeconds) ? ageSeconds : null }; } +// A recently published source must not be able to re-advertise an old +// reconciliation candidate as current. The private controller independently +// verifies raw receipts, but the human console also fails closed here. +function reconciliationRecoveryEvidenceFreshness(recovery, sourceFreshness, ttlSeconds, now) { + const observedAt = Date.parse(recovery.last_observed_at); + const futureBeyondClockSkew = Number.isFinite(observedAt) && observedAt > now + 5 * 60 * 1000; + const evidenceAgeSeconds = futureBeyondClockSkew + ? Number.POSITIVE_INFINITY + : Number.isFinite(observedAt) + ? Math.max(0, Math.round((now - observedAt) / 1000)) + : Number.POSITIVE_INFINITY; + const ageSeconds = sourceFreshness.age_seconds === null + ? evidenceAgeSeconds + : Math.max(sourceFreshness.age_seconds, evidenceAgeSeconds); + const dataStatus = sourceFreshness.data_status === "ready" && evidenceAgeSeconds <= ttlSeconds + ? "ready" + : "stale"; + return { data_status: dataStatus, age_seconds: Number.isFinite(ageSeconds) ? ageSeconds : null }; +} + function mergeControlPlaneFreshness(candidateFreshness, sourceFreshness) { const sourceAgeSeconds = sourceFreshness.age_seconds; const candidateAge = candidateFreshness.age_seconds; @@ -2745,6 +3145,14 @@ function requireDedicatedExecutionEvidenceSyncToken(request, env) { if (token !== expected) throw new HttpError("execution evidence sync token is invalid", 401); } +function requireDedicatedReconciliationRecoverySyncToken(request, env) { + const expected = String(env.RECONCILIATION_RECOVERY_SYNC_TOKEN || ""); + if (!expected) throw new HttpError("reconciliation recovery sync token is not configured", 500); + const header = request.headers.get("Authorization") || ""; + const token = header.match(/^Bearer\s+(.+)$/i)?.[1] || ""; + if (token !== expected) throw new HttpError("reconciliation recovery sync token is invalid", 401); +} + function requireDedicatedResearchTaskSyncToken(request, env) { const expected = String(env.RESEARCH_TASK_SYNC_TOKEN || ""); if (!expected) throw new HttpError("research task sync token is not configured", 500); @@ -4116,6 +4524,115 @@ function normalizeControlPlanePolicy(value, fieldName) { }; } +function normalizeReconciliationRecoverySourceSnapshot(payload, fieldName = "reconciliation recovery source snapshot") { + const source = assertExactFields(payload, [ + "schema_version", "source_id", "generated_at", "computed_at", "data_status", "recoveries", "errors", + ], fieldName); + if (source.schema_version !== RECONCILIATION_RECOVERY_SOURCE_SCHEMA_VERSION) { + throw new Error(`${fieldName}.schema_version is unsupported`); + } + const dataStatus = cleanChoice(source.data_status, STRATEGY_HEALTH_DATA_STATUSES, `${fieldName}.data_status`); + if (!Array.isArray(source.recoveries) || source.recoveries.length > RECONCILIATION_RECOVERY_MAX_REQUESTS) { + throw new Error(`${fieldName}.recoveries must be an array with at most ${RECONCILIATION_RECOVERY_MAX_REQUESTS} items`); + } + const recoveries = []; + const seen = new Set(); + for (const [index, value] of source.recoveries.entries()) { + const recovery = normalizeReconciliationRecoveryRequest(value, `${fieldName}.recoveries[${index}]`); + if (seen.has(recovery.recovery_id)) { + throw new Error(`${fieldName}.recoveries contains duplicate recovery_id`); + } + seen.add(recovery.recovery_id); + recoveries.push(recovery); + } + if (dataStatus === "unavailable" && recoveries.length) { + throw new Error(`${fieldName}.recoveries must be empty when unavailable`); + } + return { + schema_version: RECONCILIATION_RECOVERY_SOURCE_SCHEMA_VERSION, + source_id: normalizeControlPlaneIdentifier(source.source_id, `${fieldName}.source_id`, false), + generated_at: normalizeStrategyHealthTimestamp(source.generated_at, `${fieldName}.generated_at`, true), + computed_at: normalizeStrategyHealthTimestamp(source.computed_at, `${fieldName}.computed_at`, true), + data_status: dataStatus, + recoveries, + errors: normalizeStrategyHealthErrors(source.errors), + }; +} + +function normalizeReconciliationRecoveryRequest(value, fieldName) { + const item = assertExactFields(value, [ + "recovery_id", "platform", "strategy_profile", "environment", "reconciliation_state", "readiness", + "candidate_sha256", "evidence_sample_count", "first_observed_at", "last_observed_at", "dual_review", "blocker_codes", + ], fieldName); + const dualReview = assertExactFields(item.dual_review, [ + "outcome", "reviewer_count", "evidence_binding_sha256", + ], `${fieldName}.dual_review`); + const evidenceSampleCount = Number(item.evidence_sample_count); + const reviewerCount = Number(dualReview.reviewer_count); + if (!Number.isInteger(evidenceSampleCount) || evidenceSampleCount < 0 || evidenceSampleCount > 100) { + throw new Error(`${fieldName}.evidence_sample_count must be a bounded integer`); + } + if (!Number.isInteger(reviewerCount) || reviewerCount < 0 || reviewerCount > 10) { + throw new Error(`${fieldName}.dual_review.reviewer_count must be a bounded integer`); + } + const firstObservedAt = normalizeResearchTaskTimestamp(item.first_observed_at, `${fieldName}.first_observed_at`); + const lastObservedAt = normalizeResearchTaskTimestamp(item.last_observed_at, `${fieldName}.last_observed_at`); + const observationWindowMs = Date.parse(lastObservedAt) - Date.parse(firstObservedAt); + if (observationWindowMs < 0) { + throw new Error(`${fieldName}.first_observed_at must not be after last_observed_at`); + } + if (!Array.isArray(item.blocker_codes) || item.blocker_codes.length > 20) { + throw new Error(`${fieldName}.blocker_codes must be an array with at most 20 items`); + } + const blockerCodes = normalizeStrategyHealthErrors(item.blocker_codes); + if (blockerCodes.length !== item.blocker_codes.length) { + throw new Error(`${fieldName}.blocker_codes must be unique stable codes`); + } + const recovery = { + recovery_id: normalizeControlPlaneIdentifier(item.recovery_id, `${fieldName}.recovery_id`, false), + platform: cleanChoice(item.platform, RECONCILIATION_RECOVERY_PLATFORMS, `${fieldName}.platform`), + strategy_profile: normalizeResearchTaskIdentity(item.strategy_profile, `${fieldName}.strategy_profile`), + environment: cleanChoice(item.environment, RECONCILIATION_RECOVERY_ENVIRONMENTS, `${fieldName}.environment`), + reconciliation_state: cleanChoice( + item.reconciliation_state, + RECONCILIATION_RECOVERY_STATES, + `${fieldName}.reconciliation_state`, + ), + readiness: cleanChoice(item.readiness, RECONCILIATION_RECOVERY_READINESS, `${fieldName}.readiness`), + candidate_sha256: normalizeResearchTaskDigest(item.candidate_sha256, `${fieldName}.candidate_sha256`), + evidence_sample_count: evidenceSampleCount, + first_observed_at: firstObservedAt, + last_observed_at: lastObservedAt, + dual_review: { + outcome: cleanChoice( + dualReview.outcome, + RECONCILIATION_RECOVERY_DUAL_REVIEW_OUTCOMES, + `${fieldName}.dual_review.outcome`, + ), + reviewer_count: reviewerCount, + evidence_binding_sha256: normalizeResearchTaskDigest( + dualReview.evidence_binding_sha256, + `${fieldName}.dual_review.evidence_binding_sha256`, + ), + }, + blocker_codes: blockerCodes, + }; + if (recovery.readiness === "awaiting_human_confirmation") { + if ( + recovery.evidence_sample_count < 2 || + observationWindowMs < RECONCILIATION_RECOVERY_MIN_SAMPLE_SEPARATION_MS || + observationWindowMs > RECONCILIATION_RECOVERY_MAX_SAMPLE_WINDOW_MS || + recovery.dual_review.outcome !== "approved" || + recovery.dual_review.reviewer_count < 2 || + recovery.dual_review.evidence_binding_sha256 !== recovery.candidate_sha256 || + recovery.blocker_codes.length + ) { + throw new Error(`${fieldName}.readiness requires a 1-15 minute two-sample window, bound dual approval, and no blockers`); + } + } + return recovery; +} + function normalizeExecutionEvidenceSourceSnapshot(payload, fieldName = "execution evidence source snapshot") { const source = assertExactFields(payload, [ "schema_version", "source_id", "generated_at", "computed_at", "data_status", "deployments", "errors", @@ -4349,6 +4866,45 @@ function emptyExecutionEvidencePayload(errorCode) { }; } +function emptyReconciliationRecoverySourceSnapshot(errorCode) { + return { + schema_version: RECONCILIATION_RECOVERY_SOURCE_SCHEMA_VERSION, + source_id: "unavailable", + generated_at: null, + computed_at: null, + data_status: "unavailable", + recoveries: [], + errors: [errorCode], + }; +} + +function emptyReconciliationRecoveryPayload(errorCode) { + return { + schema_version: RECONCILIATION_RECOVERY_DASHBOARD_SCHEMA_VERSION, + generated_at: null, + computed_at: null, + data_status: "unavailable", + summary: { recovery_count: 0, awaiting_human_confirmation: 0, blocked: 0, confirmed: 0 }, + recoveries: [], + policy: { + human_confirmation_required: true, + current_evidence_required: true, + no_order: true, + execution_authority_granted: false, + notice: "恢复核验尚不可用;控制台不会推断已恢复或已启用。", + }, + errors: [errorCode], + }; +} + +function reconciliationRecoveryStaleTtlSeconds(env) { + const configured = Number(env.RECONCILIATION_RECOVERY_STALE_TTL_SECONDS); + if (!Number.isFinite(configured) || configured < 300 || configured > 3600) { + return RECONCILIATION_RECOVERY_DEFAULT_STALE_TTL_SECONDS; + } + return Math.floor(configured); +} + function executionEvidenceStaleTtlSeconds(env) { const configured = Number(env.EXECUTION_EVIDENCE_STALE_TTL_SECONDS); if (!Number.isFinite(configured) || configured < 300 || configured > 604800) { @@ -6431,6 +6987,10 @@ export const __test = { calculateM0ResearchLedgerSha256, projectM0ResearchDashboardForRead, emptyM0ResearchDashboardPayload, + normalizeReconciliationRecoverySourceSnapshot, + emptyReconciliationRecoveryPayload, + calculateReconciliationRecoveryConfirmationSha256, + normalizeReconciliationRecoveryConfirmation, normalizeExecutionEvidenceSourceSnapshot, emptyExecutionEvidencePayload, calculateResearchTaskSha256, diff --git a/web/strategy-switch-console/wrangler.toml.example b/web/strategy-switch-console/wrangler.toml.example index fa2b2e9..576c439 100644 --- a/web/strategy-switch-console/wrangler.toml.example +++ b/web/strategy-switch-console/wrangler.toml.example @@ -13,6 +13,7 @@ workers_dev = true # - CONTROL_PLANE_SYNC_TOKEN # dedicated global dashboard snapshot token; never reuse dispatch token # - RESEARCH_TASK_SYNC_TOKEN # dedicated immutable research-task source token; never reuse another token # - M0_RESEARCH_SYNC_TOKEN # dedicated read-only M0 research-ledger ingress token; never reuse another token +# - RECONCILIATION_RECOVERY_SYNC_TOKEN # dedicated legacy recovery evidence ingress token; never reuse another token # - ALLOWED_GITHUB_LOGINS # - ALLOWED_GITHUB_ORGS # - STRATEGY_SWITCH_ADMIN_LOGINS