diff --git a/tests/account_diagnosis_targets_validation.mjs b/tests/account_diagnosis_targets_validation.mjs index 2a0fbf80..72448782 100644 --- a/tests/account_diagnosis_targets_validation.mjs +++ b/tests/account_diagnosis_targets_validation.mjs @@ -1,10 +1,13 @@ import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; -const workerSource = readFileSync(new URL("../web/strategy-switch-console/worker.js", import.meta.url), "utf8"); -assert.match(workerSource, /"schwab\.primary": Object\.freeze\(/); -assert.match(workerSource, /"firstrade\.primary": Object\.freeze\(/); -assert.match(workerSource, /recheckRepository: "QuantStrategyLab\/CharlesSchwabPlatform"/); -assert.match(workerSource, /recheckRepository: "QuantStrategyLab\/FirstradePlatform"/); -assert.match(workerSource, /recheckWorkflow: "runtime-target-lifecycle\.yml"/); +const diagnosisTargetsSource = [ + readFileSync(new URL("../web/strategy-switch-console/worker.js", import.meta.url), "utf8"), + readFileSync(new URL("../web/strategy-switch-console/application/account_diagnosis/constants.js", import.meta.url), "utf8"), +].join("\n"); +assert.match(diagnosisTargetsSource, /"schwab\.primary": Object\.freeze\(/); +assert.match(diagnosisTargetsSource, /"firstrade\.primary": Object\.freeze\(/); +assert.match(diagnosisTargetsSource, /recheckRepository: "QuantStrategyLab\/CharlesSchwabPlatform"/); +assert.match(diagnosisTargetsSource, /recheckRepository: "QuantStrategyLab\/FirstradePlatform"/); +assert.match(diagnosisTargetsSource, /recheckWorkflow: "runtime-target-lifecycle\.yml"/); console.log("account_diagnosis_targets_validation: PASS (schwab.primary + firstrade.primary admitted)"); diff --git a/tests/account_facts_readonly_extract_validation.mjs b/tests/account_facts_readonly_extract_validation.mjs index 5f09c66c..64bc5c6c 100644 --- a/tests/account_facts_readonly_extract_validation.mjs +++ b/tests/account_facts_readonly_extract_validation.mjs @@ -11,20 +11,26 @@ const workerSource = await readFile( new URL("../web/strategy-switch-console/worker.js", import.meta.url), "utf8", ); +const supportSource = await readFile( + new URL("../web/strategy-switch-console/application/worker/runtime_support.js", import.meta.url), + "utf8", +); const readSource = await readFile( new URL("../web/strategy-switch-console/application/account_facts/read.js", import.meta.url), "utf8", ); +const wiredSource = `${workerSource}\n${supportSource}`; assert.equal( - workerSource.includes('from "./application/account_facts/read.js"'), + workerSource.includes('from "./application/account_facts/read.js"') + || supportSource.includes('from "../account_facts/read.js"') + || supportSource.includes("from '../account_facts/read.js'"), true, - "Worker imports the B10-a account-facts read module", + "Worker stack imports the B10-a account-facts read module", ); -assert.match(workerSource, /\/api\/account-facts"/); -assert.match(workerSource, /\/api\/account-facts\/history"/); -assert.match(workerSource, /async function syncAccountFactsResponse/); -assert.equal( +assert.match(wiredSource, /\/api\/account-facts"/); +assert.match(wiredSource, /\/api\/account-facts\/history"/); +assert.match(supportSource, /async function syncAccountFactsResponse/);assert.equal( /transactionSync/.test(readSource), false, "read module must not open Durable Object transactions", diff --git a/tests/account_facts_sync_extract_validation.mjs b/tests/account_facts_sync_extract_validation.mjs index f1cb96a1..82cfc391 100644 --- a/tests/account_facts_sync_extract_validation.mjs +++ b/tests/account_facts_sync_extract_validation.mjs @@ -6,19 +6,26 @@ const workerSource = await readFile( new URL("../web/strategy-switch-console/worker.js", import.meta.url), "utf8", ); +const supportSource = await readFile( + new URL("../web/strategy-switch-console/application/worker/runtime_support.js", import.meta.url), + "utf8", +); const syncSource = await readFile( new URL("../web/strategy-switch-console/application/account_facts/sync.js", import.meta.url), "utf8", ); +const wiredSource = `${workerSource}\n${supportSource}`; assert.equal( - workerSource.includes('from "./application/account_facts/sync.js"'), + workerSource.includes('from "./application/account_facts/sync.js"') + || supportSource.includes('from "../account_facts/sync.js"') + || supportSource.includes("from '../account_facts/sync.js'"), true, - "Worker imports the B10-b account-facts sync module", + "Worker stack imports the B10-b account-facts sync module", ); -assert.match(workerSource, /\/api\/account-facts\/sync"/); -assert.match(workerSource, /async function syncAccountFactsResponse/); -assert.match(workerSource, /syncAccountFactsResponseImpl/); +assert.match(wiredSource, /\/api\/account-facts\/sync"/); +assert.match(supportSource, /async function syncAccountFactsResponse/); +assert.match(wiredSource, /syncAccountFactsResponseImpl/); const syncSourceNoComments = syncSource.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, ""); assert.equal( /transactionSync/.test(syncSourceNoComments), @@ -40,11 +47,11 @@ assert.equal( true, "sync still issues account_facts_put via injected repository", ); -assert.match(workerSource, /async function syncAccountPeriodReturnResponse/); +assert.match(wiredSource, /async function syncAccountPeriodReturnResponse|syncAccountPeriodReturnResponse/); assert.equal( /syncAccountPeriodReturnResponse/.test(syncSource), false, - "B10-b smallest slice: period-return sync stays in worker", + "B10-b smallest slice: period-return sync stays outside sync.js", ); function json(payload, status = 200) { diff --git a/tests/account_facts_validation.mjs b/tests/account_facts_validation.mjs index 14f18d3a..4e506654 100644 --- a/tests/account_facts_validation.mjs +++ b/tests/account_facts_validation.mjs @@ -1960,12 +1960,15 @@ for (const platform of ["longbridge", "ibkr", "schwab", "firstrade"]) { } const workerSource = readFileSync(join(root, "web/strategy-switch-console/worker.js"), "utf8"); -assert.match(workerSource, /account_facts_put/); -assert.match(workerSource, /account_facts_history_read|account_facts_daily/); -assert.match(workerSource, /account_facts_stale_write|decideAccountFactsPut/); -assert.doesNotMatch(workerSource, /writeConfigJson\(env, accountFactsStorageKey/); -assert.doesNotMatch(workerSource, /account_facts:\$\{platform\}/); -assert.match(workerSource, /\/api\/account-facts\/history/); +const supportSource = readFileSync(join(root, "web/strategy-switch-console/application/worker/runtime_support.js"), "utf8"); +const durableSource = readFileSync(join(root, "web/strategy-switch-console/application/runtime_instances/durable_object.js"), "utf8"); +const wiredSource = `${workerSource}\n${supportSource}\n${durableSource}`; +assert.match(wiredSource, /account_facts_put/); +assert.match(wiredSource, /account_facts_history_read|account_facts_daily/); +assert.match(wiredSource, /account_facts_stale_write|decideAccountFactsPut/); +assert.doesNotMatch(wiredSource, /writeConfigJson\(env, accountFactsStorageKey/); +assert.doesNotMatch(wiredSource, /account_facts:\$\{platform\}/); +assert.match(wiredSource, /\/api\/account-facts\/history/); await mf.dispose(); console.log("account_facts_validation ok"); diff --git a/tests/console_gap_contract_validation.mjs b/tests/console_gap_contract_validation.mjs index 4cde582e..172dbe2f 100644 --- a/tests/console_gap_contract_validation.mjs +++ b/tests/console_gap_contract_validation.mjs @@ -130,7 +130,14 @@ for (const reason of controlReasons.filter(r => r !== "available")) { } const controlSource = readFileSync(new URL("../web/strategy-switch-console/application/console_api/run_controls.js", import.meta.url), "utf8"); const workerSource = readFileSync(new URL("../web/strategy-switch-console/worker.js", import.meta.url), "utf8"); +const supportSource = readFileSync(new URL("../web/strategy-switch-console/application/worker/runtime_support.js", import.meta.url), "utf8"); +const profileStateSources = [workerSource, supportSource] + .map((source) => { + const start = source.indexOf(" profileState: async () => {"); + return start < 0 ? "" : source.slice(start).split("\n });")[0]; + }) + .join("\n"); const reasons = [...controlSource.matchAll(/(?:blocked\(|reason: )"([a-z_]+)"/g), - ...workerSource.slice(workerSource.indexOf(" profileState: async () => {")).split("\n });")[0].matchAll(/reason: "([a-z_]+)"/g)].map(m => m[1]); + ...profileStateSources.matchAll(/reason: "([a-z_]+)"/g)].map(m => m[1]); assert.deepEqual([...new Set(reasons)].sort(), [...controlReasons].sort(), "handoff reasons cover every current control gate"); console.log("console_gap_contract_validation: PASS (M0 12, currency 7, ten identities, failure/unknown/control semantics)"); diff --git a/tests/overview_fx_proxy_validation.mjs b/tests/overview_fx_proxy_validation.mjs index e839856e..5fda7a90 100644 --- a/tests/overview_fx_proxy_validation.mjs +++ b/tests/overview_fx_proxy_validation.mjs @@ -14,14 +14,24 @@ const workerSource = await fs.readFile( new URL("../web/strategy-switch-console/worker.js", import.meta.url), "utf8", ); +const supportSource = await fs.readFile( + new URL("../web/strategy-switch-console/application/worker/runtime_support.js", import.meta.url), + "utf8", +); const fxSource = await fs.readFile( new URL("../web/strategy-switch-console/infrastructure/fx/overview_fx.js", import.meta.url), "utf8", ); -assert.match(workerSource, /connect-src 'self'/, "CSP stays same-origin; Frankfurter is not widened into connect-src"); +assert.match(supportSource, /connect-src 'self'/, "CSP stays same-origin; Frankfurter is not widened into connect-src"); assert.equal(fxSource.includes("api.frankfurter.dev"), true, "FX module retains the fixed Frankfurter upstream"); -assert.equal(workerSource.includes("infrastructure/fx/overview_fx.js"), true, "Worker imports the FX module"); -assert.equal(/connect-src[^;]*frankfurter/.test(workerSource), false, "browser CSP must not allow frankfurter"); +assert.equal( + workerSource.includes("infrastructure/fx/overview_fx.js") + || supportSource.includes("infrastructure/fx/overview_fx.js"), + true, + "Worker stack imports the FX module", +); +assert.equal(/connect-src[^;]*frankfurter/.test(supportSource), false, "browser CSP must not allow frankfurter"); +assert.equal(/connect-src[^;]*frankfurter/.test(workerSource), false, "facade CSP must not allow frankfurter"); __test.resetOverviewFxCache(); const frankfurterBody = JSON.stringify({ diff --git a/tests/owner_decision_extract_validation.mjs b/tests/owner_decision_extract_validation.mjs index 2b5884fe..9a831ead 100644 --- a/tests/owner_decision_extract_validation.mjs +++ b/tests/owner_decision_extract_validation.mjs @@ -9,21 +9,28 @@ const workerSource = await readFile( new URL("../web/strategy-switch-console/worker.js", import.meta.url), "utf8", ); +const supportSource = await readFile( + new URL("../web/strategy-switch-console/application/worker/runtime_support.js", import.meta.url), + "utf8", +); const moduleSource = await readFile( new URL("../web/strategy-switch-console/application/approval/owner_decisions.js", import.meta.url), "utf8", ); +const wiredSource = `${workerSource}\n${supportSource}`; assert.equal( - workerSource.includes('from "./application/approval/owner_decisions.js"'), + workerSource.includes('from "./application/approval/owner_decisions.js"') + || supportSource.includes('from "../approval/owner_decisions.js"') + || supportSource.includes("from '../approval/owner_decisions.js'"), true, - "Worker imports the B10-c owner-decision module", + "Worker stack imports the B10-c owner-decision module", ); -assert.match(workerSource, /\/api\/owner-decisions"/); -assert.match(workerSource, /ownerDecisionQueueResponseImpl/); -assert.match(workerSource, /recordOwnerDecisionResponseImpl/); -assert.match(workerSource, /async function ownerDecisionQueueResponse/); -assert.match(workerSource, /async function recordOwnerDecisionResponse/); +assert.match(wiredSource, /\/api\/owner-decisions"/); +assert.match(wiredSource, /ownerDecisionQueueResponseImpl/); +assert.match(wiredSource, /recordOwnerDecisionResponseImpl/); +assert.match(supportSource, /async function ownerDecisionQueueResponse/); +assert.match(supportSource, /async function recordOwnerDecisionResponse/); const moduleNoComments = moduleSource.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, ""); assert.equal( diff --git a/tests/reconciliation_material_presentation_validation.mjs b/tests/reconciliation_material_presentation_validation.mjs index e4005310..b8d568df 100644 --- a/tests/reconciliation_material_presentation_validation.mjs +++ b/tests/reconciliation_material_presentation_validation.mjs @@ -16,10 +16,20 @@ import { recoveryMaterialPresentation, currentReconciliationObservationPresentat // Exercise the production aggregation/freshness functions with in-memory inputs. // No Worker, storage adapter, KV, server, or network is instantiated. const workerText = readFileSync(new URL("../web/strategy-switch-console/worker.js", import.meta.url), "utf8"); +const runtimeSupportText = readFileSync( + new URL("../web/strategy-switch-console/application/worker/runtime_support.js", import.meta.url), + "utf8", +); function productionFunction(name, dependencies = {}) { - const start = workerText.search(new RegExp(`(?:async )?function ${name}\\(`)); + const pattern = new RegExp(`(?:export )?(?:async )?function ${name}\\(`); + let source = workerText; + let start = source.search(pattern); + if (start < 0) { + source = runtimeSupportText; + start = source.search(pattern); + } assert.ok(start >= 0, name); - const code = workerText.slice(start, workerText.indexOf("\n}", start) + 2); + const code = source.slice(start, source.indexOf("\n}", start) + 2).replace(/^export\s+/, ""); return new Function(...Object.keys(dependencies), `return (${code});`)(...Object.values(dependencies)); } const now = Date.parse("2001-10-17T04:05:00Z"); diff --git a/tests/research_promotion_application_extract_validation.mjs b/tests/research_promotion_application_extract_validation.mjs index ae5a25c3..da4f2609 100644 --- a/tests/research_promotion_application_extract_validation.mjs +++ b/tests/research_promotion_application_extract_validation.mjs @@ -9,26 +9,36 @@ const workerSource = await readFile( new URL("../web/strategy-switch-console/worker.js", import.meta.url), "utf8", ); +const supportSource = await readFile( + new URL("../web/strategy-switch-console/application/worker/runtime_support.js", import.meta.url), + "utf8", +); +const durableSource = await readFile( + new URL("../web/strategy-switch-console/application/runtime_instances/durable_object.js", import.meta.url), + "utf8", +); const moduleSource = await readFile( new URL("../web/strategy-switch-console/application/research_promotion/applications.js", import.meta.url), "utf8", ); +const wiredSource = `${workerSource}\n${supportSource}`; assert.equal( - workerSource.includes('from "./application/research_promotion/applications.js"'), + workerSource.includes('from "./application/research_promotion/applications.js"') + || supportSource.includes('from "../research_promotion/applications.js"') + || supportSource.includes("from '../research_promotion/applications.js'"), true, - "Worker imports the B10-d research-promotion applications module", + "Worker stack imports the B10-d research-promotion applications module", ); -assert.match(workerSource, /\/api\/research-promotion-applications"/); -assert.match(workerSource, /\/api\/internal\/research-promotion-application\//); -assert.match(workerSource, /researchPromotionApplicationResponseImpl/); -assert.match(workerSource, /researchPromotionApplicationInternalResponseImpl/); -assert.match(workerSource, /async function researchPromotionApplicationResponse/); -assert.match(workerSource, /async function researchPromotionApplicationInternalResponse/); -assert.match(workerSource, /function applicationIdFromPath/); -assert.match(workerSource, /function validateV7PaperApplicationReadback/); -assert.match(workerSource, /CREATE TABLE IF NOT EXISTS research_promotion_applications/); - +assert.match(wiredSource, /\/api\/research-promotion-applications"/); +assert.match(wiredSource, /\/api\/internal\/research-promotion-application\//); +assert.match(wiredSource, /researchPromotionApplicationResponseImpl/); +assert.match(wiredSource, /researchPromotionApplicationInternalResponseImpl/); +assert.match(supportSource, /async function researchPromotionApplicationResponse/); +assert.match(supportSource, /async function researchPromotionApplicationInternalResponse/); +assert.match(supportSource, /function applicationIdFromPath|applicationIdFromPath/); +assert.match(supportSource, /function validateV7PaperApplicationReadback|validateV7PaperApplicationReadback/); +assert.match(durableSource, /CREATE TABLE IF NOT EXISTS research_promotion_applications/); const moduleNoComments = moduleSource.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, ""); assert.equal( /transactionSync/.test(moduleNoComments), diff --git a/tests/runtime_instances_worker_validation.mjs b/tests/runtime_instances_worker_validation.mjs index f76ae151..ade8e349 100644 --- a/tests/runtime_instances_worker_validation.mjs +++ b/tests/runtime_instances_worker_validation.mjs @@ -147,7 +147,10 @@ try { mf = new Miniflare({ ...options, durableObjectsPersist: false, durableObjects: { STRATEGY_SWITCH_RUNTIME_INSTANCES: { className: "FailingRuntimeInstances", useSQLite: true } }, script: source + `\nexport class FailingRuntimeInstances extends RuntimeInstances { - constructor(ctx) { super(ctx); this.sql.exec("CREATE TRIGGER IF NOT EXISTS fail_history BEFORE INSERT ON instance_history WHEN NEW.revision = 2 BEGIN SELECT RAISE(ABORT, 'fixture rollback'); END"); } + constructor(ctx, env) { + super(ctx, env); + this.sql.exec("CREATE TRIGGER IF NOT EXISTS fail_history BEFORE INSERT ON instance_history WHEN NEW.revision = 2 BEGIN SELECT RAISE(ABORT, 'fixture rollback'); END"); + } }`, }); assert.equal((await call({ action: "initialize", expected_revision: 0, confirm: "IMPORT_EXISTING_CONFIG" })).status, 200); diff --git a/tests/ux1_research_worker_validation.mjs b/tests/ux1_research_worker_validation.mjs index f8fc4ad6..b096dbbb 100644 --- a/tests/ux1_research_worker_validation.mjs +++ b/tests/ux1_research_worker_validation.mjs @@ -569,8 +569,15 @@ try { assert.equal(__test.ux1CalculatorDeadlineMs, 30000); assert.equal(__test.ux1CalculatorMaxBytes, 65536); - const workerSource = await readFile(new URL("../web/strategy-switch-console/worker.js", import.meta.url), "utf8"); - const previewFn = workerSource.slice(workerSource.indexOf("async function ux1PreviewPost"), workerSource.indexOf("async function ux1IntentPost")); + // Implementation lives in application/ux1/research.js; worker.js only imports and routes. + const ux1ResearchSource = await readFile( + new URL("../web/strategy-switch-console/application/ux1/research.js", import.meta.url), + "utf8", + ); + const previewStart = ux1ResearchSource.indexOf("async function ux1PreviewPost"); + const intentStart = ux1ResearchSource.indexOf("async function ux1IntentPost"); + assert.equal(previewStart >= 0 && intentStart > previewStart, true); + const previewFn = ux1ResearchSource.slice(previewStart, intentStart); assert.equal(previewFn.includes("readUx1CalculatorExchange(binding, canonicalJson(calculatorRequest))"), true); assert.equal(previewFn.includes("deadlineMs"), false); assert.equal(previewFn.includes("maxBytes"), false); diff --git a/web/strategy-switch-console/application/account_diagnosis/constants.js b/web/strategy-switch-console/application/account_diagnosis/constants.js new file mode 100644 index 00000000..b784fb3c --- /dev/null +++ b/web/strategy-switch-console/application/account_diagnosis/constants.js @@ -0,0 +1,62 @@ +export const ACCOUNT_DIAGNOSIS_REQUEST_PATH = "/api/account-diagnosis"; +export const ACCOUNT_DIAGNOSIS_INTERNAL_PREFIX = "/api/internal/account-diagnosis/"; +export const ACCOUNT_DIAGNOSIS_TARGET_ID = "binance.crypto_live_pool_rotation"; +export const ACCOUNT_DIAGNOSIS_TARGETS = Object.freeze({ + [ACCOUNT_DIAGNOSIS_TARGET_ID]: Object.freeze({ + platform: "binance", + recheckRepository: "QuantStrategyLab/BinancePlatform", + recheckWorkflow: "runtime-target-lifecycle.yml", + }), + "longbridge.paper": Object.freeze({ + platform: "longbridge", + recheckRepository: "QuantStrategyLab/LongBridgePlatform", + recheckWorkflow: "runtime-target-lifecycle.yml", + }), + "longbridge.hk": Object.freeze({ + platform: "longbridge", + recheckRepository: "QuantStrategyLab/LongBridgePlatform", + recheckWorkflow: "runtime-target-lifecycle.yml", + }), + "longbridge.sg": Object.freeze({ + platform: "longbridge", + recheckRepository: "QuantStrategyLab/LongBridgePlatform", + recheckWorkflow: "runtime-target-lifecycle.yml", + }), + "schwab.primary": Object.freeze({ + platform: "schwab", + recheckRepository: "QuantStrategyLab/CharlesSchwabPlatform", + recheckWorkflow: "runtime-target-lifecycle.yml", + }), + "firstrade.primary": Object.freeze({ + platform: "firstrade", + recheckRepository: "QuantStrategyLab/FirstradePlatform", + recheckWorkflow: "runtime-target-lifecycle.yml", + }), +}); +export const ACCOUNT_DIAGNOSIS_TRIGGERS = ["incident", "manual_check"]; +export const ACCOUNT_DIAGNOSIS_STATUSES = ["queued", "running", "succeeded", "failed", "unknown"]; +export const ACCOUNT_DIAGNOSIS_REASON_CODES = ["diagnosis_ready", "capacity_unavailable", "codex_unavailable", "invalid_response"]; +export const ACCOUNT_DIAGNOSIS_MAX_BODY_BYTES = 16 * 1024; +export const ACCOUNT_DIAGNOSIS_SUMMARY_MAX_CHARS = 600; +export const ACCOUNT_DIAGNOSIS_DEDUPE_WINDOW_MS = 24 * 60 * 60 * 1000; +export const ACCOUNT_DIAGNOSIS_AUTO_ENABLED_ENV = "ACCOUNT_DIAGNOSIS_AUTO_ENABLED"; +export const ACCOUNT_DIAGNOSIS_DO_ACTIONS = new Set([ + "diagnosis_create", "diagnosis_latest", "diagnosis_read", "diagnosis_mark_dispatch", "diagnosis_claim", "diagnosis_result", "diagnosis_mark_recheck", "diagnosis_native_intent", "diagnosis_native_save", "diagnosis_native_cancel", +]); +/** Stable 409/qualification reason codes for /api/account-diagnosis (P0-08). */ +export const ACCOUNT_DIAGNOSIS_CONFLICT_REASON_TEXT = Object.freeze({ + account_diagnosis_config_unavailable: "账户选项配置不可用,无法绑定诊断身份。", + account_diagnosis_account_not_found: "未找到匹配的账户选项。", + account_diagnosis_account_not_unique: "账户选项不唯一,无法安全绑定诊断身份。", + account_diagnosis_target_not_admitted: "该 target 未纳入账户诊断准入清单。", + account_diagnosis_identity_conflict: "target 与账户身份冲突或不唯一。", + account_diagnosis_lifecycle_store_unavailable: "生命周期配置存储不可用。", + account_diagnosis_lifecycle_match_missing: "缺少匹配的生命周期观测来源。", + account_diagnosis_lifecycle_match_ambiguous: "生命周期观测来源不唯一。", + account_diagnosis_source_unavailable: "生命周期来源不可用(非 ready/stale)。", + account_diagnosis_observation_missing: "缺少观测时间,无法确认诊断资格。", + account_diagnosis_incident_requires_attention: "incident 触发需要已确认的监测关注项。", + account_diagnosis_native_binding_conflict: "诊断原生任务身份或材料绑定冲突。", + account_diagnosis_workflow_binding_conflict: "诊断工作流运行绑定冲突。", + account_diagnosis_schema_incompatible: "诊断上下文 schema 不兼容。", +}); diff --git a/web/strategy-switch-console/application/account_diagnosis/helpers.js b/web/strategy-switch-console/application/account_diagnosis/helpers.js new file mode 100644 index 00000000..e88aea1f --- /dev/null +++ b/web/strategy-switch-console/application/account_diagnosis/helpers.js @@ -0,0 +1,40 @@ +import { HttpError } from "../http_error.js"; +import { ACCOUNT_DIAGNOSIS_CONFLICT_REASON_TEXT } from "./constants.js"; + +export function accountDiagnosisConflict(reasonCode, status = 409) { + const reason = ACCOUNT_DIAGNOSIS_CONFLICT_REASON_TEXT[reasonCode] || ""; + return new HttpError(reasonCode, status, { reason_code: reasonCode, reason }); +} +export function accountDiagnosisErrorPayload(error) { + const reason_code = String(error?.reason_code || error?.message || "account_diagnosis_unavailable"); + const payload = { ok: false, error: reason_code, reason_code }; + const reason = String(error?.reason || ACCOUNT_DIAGNOSIS_CONFLICT_REASON_TEXT[reason_code] || "").trim(); + if (reason) payload.reason = reason; + return payload; +} +export function accountDiagnosisTaskFromRow(row) { + if (!row) return null; + return { + request_id: row.request_id, + platform: row.platform, + key: row.account_key, + target_id: row.target_id, + trigger: row.trigger, + observed_at: row.observed_at, + checks: JSON.parse(row.checks_json), + state_fingerprint: row.state_fingerprint, + status: row.status, + native: row.native_json ? JSON.parse(row.native_json) : null, + backend_kind: row.native_json ? "native" : "legacy", + workflow_run_id: row.workflow_run_id || null, + workflow_run_attempt: row.workflow_run_attempt || null, + job_id: row.job_id || null, + summary: row.summary || "", + reason_code: row.reason_code || "", + dispatch_state: row.dispatch_state, + recheck_dispatch_state: row.recheck_dispatch_state, + auto_source: row.auto_source || "manual", + created_at: row.created_at, + updated_at: row.updated_at, + }; +} diff --git a/web/strategy-switch-console/application/auth/constants.js b/web/strategy-switch-console/application/auth/constants.js new file mode 100644 index 00000000..c9476bba --- /dev/null +++ b/web/strategy-switch-console/application/auth/constants.js @@ -0,0 +1,6 @@ +export const SESSION_COOKIE = "qsl_switch_session"; +export const OAUTH_STATE_COOKIE = "qsl_switch_oauth_state"; +export const SESSION_TTL_SECONDS = 8 * 60 * 60; +export const AUTH_CONFIG_KEY = "auth_config"; +export const GITHUB_LOGIN_REQUEST_TIMEOUT_MS = 20000; +export const GITHUB_API_TIMEOUT_MS = 8000; diff --git a/web/strategy-switch-console/application/auth/cookies.js b/web/strategy-switch-console/application/auth/cookies.js new file mode 100644 index 00000000..de4a7f56 --- /dev/null +++ b/web/strategy-switch-console/application/auth/cookies.js @@ -0,0 +1,47 @@ +import { OAUTH_STATE_COOKIE } from "./constants.js"; + +export function randomToken() { + const bytes = new Uint8Array(24); + crypto.getRandomValues(bytes); + return base64UrlEncodeBytes(bytes); +} + +export function base64UrlEncodeJson(value) { + return base64UrlEncodeBytes(new TextEncoder().encode(JSON.stringify(value))); +} + +export function base64UrlEncodeBytes(bytes) { + let binary = ""; + for (const byte of bytes) binary += String.fromCharCode(byte); + return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", ""); +} + +export function base64UrlDecode(value) { + const base64 = value.replaceAll("-", "+").replaceAll("_", "/").padEnd(Math.ceil(value.length / 4) * 4, "="); + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index); + return new TextDecoder().decode(bytes); +} + +export function parseCookies(header) { + const result = {}; + for (const part of header.split(";")) { + const [name, ...rest] = part.trim().split("="); + if (!name) continue; + result[name] = decodeURIComponent(rest.join("=")); + } + return result; +} + +export function cookie(name, value, maxAge) { + return `${name}=${encodeURIComponent(value)}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=${maxAge}`; +} + +export function clearCookie(name) { + return `${name}=; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=0`; +} + +export function clearOAuthCookie() { + return { "Set-Cookie": clearCookie(OAUTH_STATE_COOKIE) }; +} diff --git a/web/strategy-switch-console/application/auth/fetch.js b/web/strategy-switch-console/application/auth/fetch.js new file mode 100644 index 00000000..b1802e22 --- /dev/null +++ b/web/strategy-switch-console/application/auth/fetch.js @@ -0,0 +1,14 @@ +import { GITHUB_API_TIMEOUT_MS } from "./constants.js"; + +export async function fetchWithTimeout(resource, init = {}, timeoutMs = GITHUB_API_TIMEOUT_MS, fetchImpl = fetch) { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), timeoutMs); + try { + return await fetchImpl(resource, { ...init, signal: controller.signal }); + } catch (error) { + if (error?.name === "AbortError") throw new Error("GitHub request timed out"); + throw error; + } finally { + clearTimeout(timeoutId); + } +} diff --git a/web/strategy-switch-console/application/auth/github.js b/web/strategy-switch-console/application/auth/github.js new file mode 100644 index 00000000..f991d80b --- /dev/null +++ b/web/strategy-switch-console/application/auth/github.js @@ -0,0 +1,31 @@ +import { fetchWithTimeout } from "./fetch.js"; +import { GITHUB_API_TIMEOUT_MS } from "./constants.js"; +import { cleanGithubOrg, uniqueStrings } from "./identity_lists.js"; + +export function githubHeaders(token) { + return { + Authorization: `Bearer ${token}`, + Accept: "application/vnd.github+json", + "Content-Type": "application/json", + "User-Agent": "QuantRuntimeSettings-StrategySwitchConsole", + "X-GitHub-Api-Version": "2022-11-28", + }; +} + +export async function fetchGithubOrgLogins(token, timeoutMs = GITHUB_API_TIMEOUT_MS) { + const orgs = []; + for (let page = 1; page <= 5; page += 1) { + const response = await fetchWithTimeout(`https://api.github.com/user/orgs?per_page=100&page=${page}`, { + headers: githubHeaders(token), + }, timeoutMs); + if (!response.ok) return orgs; + const payload = await response.json(); + if (!Array.isArray(payload) || !payload.length) break; + for (const org of payload) { + const login = cleanGithubOrg(org?.login || "", "github org"); + if (login) orgs.push(login); + } + if (payload.length < 100) break; + } + return uniqueStrings(orgs); +} diff --git a/web/strategy-switch-console/application/auth/identity_lists.js b/web/strategy-switch-console/application/auth/identity_lists.js new file mode 100644 index 00000000..77e3e6df --- /dev/null +++ b/web/strategy-switch-console/application/auth/identity_lists.js @@ -0,0 +1,48 @@ +export function parseLoginList(value, fieldName) { + return normalizeLoginList(value, fieldName); +} + +export function normalizeLoginList(value, fieldName) { + const items = Array.isArray(value) ? value : String(value || "").split(/[\s,]+/); + if (items.length > 80) throw new Error(`${fieldName} supports at most 80 logins`); + return uniqueStrings(items.map((item) => cleanGithubLogin(item, fieldName)).filter(Boolean)); +} + +export function parseOrgList(value, fieldName) { + return normalizeOrgList(value, fieldName); +} + +export function normalizeOrgList(value, fieldName) { + const items = Array.isArray(value) ? value : String(value || "").split(/[\s,]+/); + if (items.length > 80) throw new Error(`${fieldName} supports at most 80 orgs`); + return uniqueStrings(items.map((item) => cleanGithubOrg(item, fieldName)).filter(Boolean)); +} + +export function cleanGithubLogin(value, fieldName) { + const login = String(value || "").trim().toLowerCase(); + if (!login) return ""; + if ( + login.length > 39 || + !/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(login) || + login.includes("--") + ) { + throw new Error(`${fieldName} contains an invalid GitHub login`); + } + return login; +} + +export function cleanGithubOrg(value, fieldName) { + return cleanGithubLogin(value, fieldName); +} + +export function uniqueStrings(items) { + const result = []; + const seen = new Set(); + for (const item of items) { + const text = String(item || "").trim().toLowerCase(); + if (!text || seen.has(text)) continue; + seen.add(text); + result.push(text); + } + return result; +} diff --git a/web/strategy-switch-console/application/auth/login.js b/web/strategy-switch-console/application/auth/login.js new file mode 100644 index 00000000..4d862453 --- /dev/null +++ b/web/strategy-switch-console/application/auth/login.js @@ -0,0 +1,137 @@ +import { SESSION_COOKIE, OAUTH_STATE_COOKIE, GITHUB_LOGIN_REQUEST_TIMEOUT_MS, SESSION_TTL_SECONDS } from "./constants.js"; +import { requireEnv } from "./require_env.js"; +import { randomToken, cookie, clearOAuthCookie, parseCookies, clearCookie } from "./cookies.js"; +import { redirect, html } from "../http_response.js"; +import { renderMessage, escapeHtml } from "../html_message.js"; +import { fetchWithTimeout } from "./fetch.js"; +import { githubHeaders, fetchGithubOrgLogins } from "./github.js"; +import { loadAuthConfig, isAllowedPrincipal, authorizedOrgLogins } from "./principals.js"; +import { makeSession } from "./session.js"; + +export async function startLogin(request, env) { + requireEnv(env, "GITHUB_CLIENT_ID"); + const url = new URL(request.url); + const state = randomToken(); + const authorizeUrl = new URL("https://github.com/login/oauth/authorize"); + authorizeUrl.searchParams.set("client_id", env.GITHUB_CLIENT_ID); + authorizeUrl.searchParams.set("redirect_uri", `${url.origin}/callback`); + authorizeUrl.searchParams.set("scope", "read:user read:org"); + authorizeUrl.searchParams.set("state", state); + return redirect(authorizeUrl.toString(), { + "Set-Cookie": cookie(OAUTH_STATE_COOKIE, state, 600), + }); +} + +export async function finishLogin(request, env) { + requireEnv(env, "GITHUB_CLIENT_ID"); + requireEnv(env, "GITHUB_CLIENT_SECRET"); + requireEnv(env, "SESSION_SECRET"); + + const url = new URL(request.url); + const code = url.searchParams.get("code") || ""; + const state = url.searchParams.get("state") || ""; + const cookies = parseCookies(request.headers.get("Cookie") || ""); + if (!code || !state || cookies[OAUTH_STATE_COOKIE] !== state) { + return html(renderMessage("登录失败", "OAuth state 校验失败,请重新登录。"), 400, clearOAuthCookie()); + } + + const tokenStage = await runLoginGithubStage("token_exchange", () => fetchWithTimeout("https://github.com/login/oauth/access_token", { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify({ + client_id: env.GITHUB_CLIENT_ID, + client_secret: env.GITHUB_CLIENT_SECRET, + code, + redirect_uri: `${url.origin}/callback`, + }), + }, GITHUB_LOGIN_REQUEST_TIMEOUT_MS)); + if (tokenStage.timedOut) return loginTimeoutResponse("token_exchange"); + const tokenResponse = tokenStage.value; + const tokenPayload = await tokenResponse.json(); + const tokenPayloadIsObject = tokenPayload !== null && typeof tokenPayload === "object"; + if (!tokenResponse.ok || !tokenPayloadIsObject || !tokenPayload.access_token) { + const status = Number.isSafeInteger(tokenResponse?.status) ? tokenResponse.status : null; + const allowedOauthErrors = new Set([ + "incorrect_client_credentials", + "redirect_uri_mismatch", + "bad_verification_code", + "unverified_user_email", + ]); + const errorCategory = tokenPayloadIsObject && typeof tokenPayload.error === "string" + && allowedOauthErrors.has(tokenPayload.error) + ? tokenPayload.error + : "unknown"; + console.warn("github_token_exchange_failed", { + http_status: status, + error_category: errorCategory, + }); + const statusLabel = status === null ? "不可用" : String(status); + return html( + renderMessage("登录失败", `GitHub 凭证交换未成功(HTTP ${statusLabel},类别:${errorCategory})。请从登录入口重新开始。`), + 502, + clearOAuthCookie(), + ); + } + + const userStage = await runLoginGithubStage("user_lookup", () => fetchWithTimeout("https://api.github.com/user", { + headers: githubHeaders(tokenPayload.access_token), + }, GITHUB_LOGIN_REQUEST_TIMEOUT_MS)); + if (userStage.timedOut) return loginTimeoutResponse("user_lookup"); + const userResponse = userStage.value; + const user = await userResponse.json(); + const login = String(user.login || "").toLowerCase(); + if (!userResponse.ok || !login) { + return html(renderMessage("登录失败", "无法读取 GitHub 用户。"), 502, clearOAuthCookie()); + } + + const authConfig = await loadAuthConfig(env); + const orgStage = await runLoginGithubStage("organization_lookup", () => fetchGithubOrgLogins( + tokenPayload.access_token, + GITHUB_LOGIN_REQUEST_TIMEOUT_MS, + )); + if (orgStage.timedOut) return loginTimeoutResponse("organization_lookup"); + const orgLogins = orgStage.value; + if (!isAllowedPrincipal(login, orgLogins, authConfig)) { + return html(renderMessage("没有权限", `${login} 不在允许登录名单或组织中。`), 403, clearOAuthCookie()); + } + + const session = await makeSession(login, authorizedOrgLogins(orgLogins, authConfig), env); + return redirect("/", { + "Set-Cookie": [ + cookie(SESSION_COOKIE, session, SESSION_TTL_SECONDS), + clearCookie(OAUTH_STATE_COOKIE), + ], + }); +} + +export async function runLoginGithubStage(stage, operation) { + const startedAt = Date.now(); + try { + return { timedOut: false, value: await operation() }; + } catch (error) { + if (error?.message !== "GitHub request timed out") throw error; + console.warn("github_login_request_timed_out", { + stage, + timeout_ms: GITHUB_LOGIN_REQUEST_TIMEOUT_MS, + elapsed_ms: Math.max(0, Date.now() - startedAt), + }); + return { timedOut: true }; + } +} + +export function loginTimeoutResponse(stage) { + const stageLabels = { + token_exchange: "登录凭证交换", + user_lookup: "读取用户身份", + organization_lookup: "核对组织权限", + }; + const stageLabel = stageLabels[stage] || "登录请求处理"; + return html( + renderMessage("登录超时", `${stageLabel}超时,请从登录入口重新开始。此回调链接已失效,请勿复用。`), + 504, + clearOAuthCookie(), + ); +} diff --git a/web/strategy-switch-console/application/auth/principals.js b/web/strategy-switch-console/application/auth/principals.js new file mode 100644 index 00000000..ccdf4bbd --- /dev/null +++ b/web/strategy-switch-console/application/auth/principals.js @@ -0,0 +1,88 @@ +import { + AUTH_CONFIG_KEY, +} from "./constants.js"; +import { hasConfigStore, readConfigJson } from "../config_kv.js"; +import { uniqueStrings, parseLoginList, parseOrgList, normalizeLoginList, normalizeOrgList, cleanGithubLogin, cleanGithubOrg } from "./identity_lists.js"; + +export async function loadAuthConfig(env) { + const bootstrapAdmins = parseLoginList(env.STRATEGY_SWITCH_ADMIN_LOGINS || "", "STRATEGY_SWITCH_ADMIN_LOGINS"); + const bootstrapAdminOrgs = parseOrgList(env.STRATEGY_SWITCH_ADMIN_ORGS || "", "STRATEGY_SWITCH_ADMIN_ORGS"); + const envAllowed = parseLoginList( + env.ALLOWED_GITHUB_LOGINS || env.ALLOWED_GITHUB_LOGIN || "", + "ALLOWED_GITHUB_LOGINS", + ); + const envAllowedOrgs = parseOrgList( + env.ALLOWED_GITHUB_ORGS || env.ALLOWED_GITHUB_ORG || "", + "ALLOWED_GITHUB_ORGS", + ); + let storedAllowed = []; + let storedAllowedOrgs = []; + let storedAdmins = []; + let storedAdminOrgs = []; + let source = "secret"; + if (hasConfigStore(env)) { + const stored = await readConfigJson(env, AUTH_CONFIG_KEY); + if (stored) { + const normalized = normalizeAuthConfigPayload(stored, AUTH_CONFIG_KEY); + storedAllowed = normalized.allowed_logins; + storedAllowedOrgs = normalized.allowed_orgs; + storedAdmins = normalized.admin_logins; + storedAdminOrgs = normalized.admin_orgs; + source = "kv"; + } + } + const adminLogins = uniqueStrings([...bootstrapAdmins, ...storedAdmins]); + const adminOrgs = uniqueStrings([...bootstrapAdminOrgs, ...storedAdminOrgs]); + const allowedLogins = uniqueStrings([...envAllowed, ...storedAllowed, ...adminLogins]); + const allowedOrgs = uniqueStrings([...envAllowedOrgs, ...storedAllowedOrgs]); + return { + allowed_logins: allowedLogins, + allowed_orgs: allowedOrgs, + admin_logins: adminLogins, + admin_orgs: adminOrgs, + bootstrap_admin_logins: bootstrapAdmins, + bootstrap_admin_orgs: bootstrapAdminOrgs, + env_allowed_logins: envAllowed, + env_allowed_orgs: envAllowedOrgs, + source, + kv_available: hasConfigStore(env), + }; +} + +export function normalizeAuthConfigPayload(payload, fieldName) { + if (!payload || Array.isArray(payload) || typeof payload !== "object") { + throw new Error(`${fieldName} must be an object`); + } + return { + allowed_logins: normalizeLoginList(payload.allowed_logins || [], `${fieldName}.allowed_logins`), + allowed_orgs: normalizeOrgList(payload.allowed_orgs || [], `${fieldName}.allowed_orgs`), + admin_logins: normalizeLoginList(payload.admin_logins || [], `${fieldName}.admin_logins`), + admin_orgs: normalizeOrgList(payload.admin_orgs || [], `${fieldName}.admin_orgs`), + }; +} +export function isAdminLogin(login, orgLogins, authConfig) { + return authConfig.admin_logins.includes(String(login || "").toLowerCase()); +} + +export function isAdminPrincipal(login, orgLogins, authConfig) { + return isAdminLogin(login, orgLogins, authConfig) || hasOrgMatch(orgLogins, authConfig.admin_orgs); +} + +export function isAllowedPrincipal(login, orgLogins, authConfig) { + const normalizedLogin = String(login || "").toLowerCase(); + return ( + authConfig.allowed_logins.includes(normalizedLogin) || + hasOrgMatch(orgLogins, authConfig.allowed_orgs) || + isAdminPrincipal(normalizedLogin, orgLogins, authConfig) + ); +} + +export function authorizedOrgLogins(orgLogins, authConfig) { + const authorized = new Set([...authConfig.allowed_orgs, ...authConfig.admin_orgs]); + return uniqueStrings(orgLogins).filter((org) => authorized.has(org)); +} + +export function hasOrgMatch(orgLogins, configuredOrgs) { + const orgs = new Set(uniqueStrings(orgLogins)); + return configuredOrgs.some((org) => orgs.has(String(org || "").toLowerCase())); +} diff --git a/web/strategy-switch-console/application/auth/require_env.js b/web/strategy-switch-console/application/auth/require_env.js new file mode 100644 index 00000000..0d580969 --- /dev/null +++ b/web/strategy-switch-console/application/auth/require_env.js @@ -0,0 +1,3 @@ +export function requireEnv(env, name) { + if (!env[name]) throw new Error(`${name} is not configured`); +} diff --git a/web/strategy-switch-console/application/auth/session.js b/web/strategy-switch-console/application/auth/session.js new file mode 100644 index 00000000..a5404099 --- /dev/null +++ b/web/strategy-switch-console/application/auth/session.js @@ -0,0 +1,47 @@ +import { HttpError } from "../http_error.js"; +import { + SESSION_COOKIE, + SESSION_TTL_SECONDS, + AUTH_CONFIG_KEY, +} from "./constants.js"; +import { parseCookies, base64UrlEncodeJson, base64UrlEncodeBytes, base64UrlDecode } from "./cookies.js"; +import { loadAuthConfig, isAllowedPrincipal, isAdminPrincipal } from "./principals.js"; +import { normalizeOrgList, uniqueStrings } from "./identity_lists.js"; + +export async function makeSession(login, orgs, env) { + const payload = base64UrlEncodeJson({ + login, + orgs: uniqueStrings(orgs), + exp: Math.floor(Date.now() / 1000) + SESSION_TTL_SECONDS, + }); + const signature = await hmac(payload, env.SESSION_SECRET); + return `${payload}.${signature}`; +} + +export async function readSession(request, env) { + if (!env.SESSION_SECRET) return null; + const cookies = parseCookies(request.headers.get("Cookie") || ""); + const token = cookies[SESSION_COOKIE]; + if (!token || !token.includes(".")) return null; + const [payload, signature] = token.split(".", 2); + const expected = await hmac(payload, env.SESSION_SECRET); + if (signature !== expected) return null; + const session = JSON.parse(base64UrlDecode(payload)); + if (!session.exp || session.exp < Math.floor(Date.now() / 1000)) return null; + const login = String(session.login || "").toLowerCase(); + const orgs = normalizeOrgList(session.orgs || [], "session.orgs"); + const authConfig = await loadAuthConfig(env); + const admin = isAdminPrincipal(login, orgs, authConfig); + return { login, orgs, allowed: isAllowedPrincipal(login, orgs, authConfig), admin }; +} +export async function hmac(value, secret) { + const key = await crypto.subtle.importKey( + "raw", + new TextEncoder().encode(secret), + { name: "HMAC", hash: "SHA-256" }, + false, + ["sign"], + ); + const signature = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(value)); + return base64UrlEncodeBytes(new Uint8Array(signature)); +} diff --git a/web/strategy-switch-console/application/config_kv.js b/web/strategy-switch-console/application/config_kv.js new file mode 100644 index 00000000..2d1ac53a --- /dev/null +++ b/web/strategy-switch-console/application/config_kv.js @@ -0,0 +1,31 @@ +import { indexedKey, directoryCommand } from "../kv_directory.js"; + +export function configStore(env) { + const store = env.STRATEGY_SWITCH_CONFIG; + if (!store || typeof store.get !== "function" || typeof store.put !== "function") return null; + return store; +} + +export function hasConfigStore(env) { + return Boolean(configStore(env)); +} + +export async function readConfigJson(env, key) { + const store = configStore(env); + if (!store) return null; + const text = await store.get(key); + if (!text) return null; + try { + return JSON.parse(text); + } catch (error) { + throw new Error(`STRATEGY_SWITCH_CONFIG.${key} must be valid JSON`); + } +} + +export async function writeConfigJson(env, key, value) { + const store = configStore(env); + if (!store) throw new Error("STRATEGY_SWITCH_CONFIG KV binding is required"); + const text = JSON.stringify(value, null, 2); + if (indexedKey(key)) await directoryCommand(env, { action: "put", key, value: text }); + else await store.put(key, text); +} diff --git a/web/strategy-switch-console/application/html_message.js b/web/strategy-switch-console/application/html_message.js new file mode 100644 index 00000000..b31c30f7 --- /dev/null +++ b/web/strategy-switch-console/application/html_message.js @@ -0,0 +1,13 @@ +export function renderMessage(title, message) { + return `${escapeHtml(title)} + +

${escapeHtml(title)}

${escapeHtml(message)}

返回控制台

`; +} + +export function escapeHtml(value) { + return String(value) + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """); +} diff --git a/web/strategy-switch-console/application/http_error.js b/web/strategy-switch-console/application/http_error.js new file mode 100644 index 00000000..85f572f1 --- /dev/null +++ b/web/strategy-switch-console/application/http_error.js @@ -0,0 +1,8 @@ +export class HttpError extends Error { + constructor(message, status, details = null) { + super(message); + this.status = status; + this.reason_code = (details && details.reason_code) || message; + this.reason = (details && details.reason) || ""; + } +} diff --git a/web/strategy-switch-console/application/http_request.js b/web/strategy-switch-console/application/http_request.js new file mode 100644 index 00000000..07f1d765 --- /dev/null +++ b/web/strategy-switch-console/application/http_request.js @@ -0,0 +1,10 @@ +import { HttpError } from "./http_error.js"; + +export function requireSameOrigin(request, options = {}) { + const origin = request.headers.get("Origin"); + if (!origin) { + if (options.requireOrigin) throw new HttpError("Origin header is required", 403); + return; + } + if (origin !== new URL(request.url).origin) throw new HttpError("cross-origin request rejected", 403); +} diff --git a/web/strategy-switch-console/application/http_response.js b/web/strategy-switch-console/application/http_response.js new file mode 100644 index 00000000..9d579811 --- /dev/null +++ b/web/strategy-switch-console/application/http_response.js @@ -0,0 +1,45 @@ +import { SECURITY_HEADERS } from "./security_headers.js"; + +export function json(payload, status = 200, headers = {}) { + return new Response(JSON.stringify(payload, null, 2), { + status, + headers: responseHeaders({ + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "no-store", + }, headers), + }); +} + +export function html(body, status = 200, headers = {}) { + return new Response(body, { + status, + headers: responseHeaders({ + "Content-Type": "text/html; charset=utf-8", + "Cache-Control": "no-store", + }, headers), + }); +} + +export function redirect(location, headers = {}) { + return new Response(null, { + status: 302, + headers: responseHeaders({ Location: location }, headers), + }); +} + +export function responseHeaders(base = {}, extra = {}) { + const headers = new Headers(SECURITY_HEADERS); + appendHeaderEntries(headers, base); + appendHeaderEntries(headers, extra); + return headers; +} + +export function appendHeaderEntries(headers, values) { + for (const [name, value] of Object.entries(values)) { + if (Array.isArray(value)) { + for (const item of value) headers.append(name, item); + } else { + headers.set(name, value); + } + } +} diff --git a/web/strategy-switch-console/application/risk_envelope.js b/web/strategy-switch-console/application/risk_envelope.js new file mode 100644 index 00000000..0ed58c96 --- /dev/null +++ b/web/strategy-switch-console/application/risk_envelope.js @@ -0,0 +1,90 @@ +import { RESEARCH_PROMOTION_RISK_PROFILES } from "./risk_envelope_profiles.js"; + +export const RISK_ENVELOPE_PREFERENCE_META = { + CAPITAL_PRESERVATION: { + short_zh: "保全", + short_en: "Preserve", + composer_mdd_multiple: 1.0, + promotion_size_scale: 0.5, + }, + BALANCED_COMPOUNDING: { + short_zh: "均衡", + short_en: "Balance", + composer_mdd_multiple: 1.25, + promotion_size_scale: 0.75, + }, + GROWTH_COMPOUNDING: { + short_zh: "增长", + short_en: "Growth", + composer_mdd_multiple: 1.5, + promotion_size_scale: 1.0, + }, +}; + +/** Read-only console view: preference / capital band / status lamp. No KV writes. */ +export function buildRiskEnvelopeView({ riskPreference = null, equityUsd = null, status = null } = {}) { + const preferenceRaw = String(riskPreference || "").trim().toUpperCase(); + const preference = RESEARCH_PROMOTION_RISK_PROFILES.includes(preferenceRaw) ? preferenceRaw : null; + const meta = preference ? RISK_ENVELOPE_PREFERENCE_META[preference] : null; + const hasEquity = equityUsd != null && Number.isFinite(Number(equityUsd)); + // Equity injection is not wired yet — band/status/capital_scale stay unknown. + const source = hasEquity ? "reconciled_equity" : "design_preview"; + const statusId = hasEquity && status ? String(status) : "unknown"; + const statusLabels = { + normal: { zh: "正常", en: "Normal" }, + stepped_down: { zh: "已降档", en: "Stepped down" }, + new_risk_blocked: { zh: "禁止新增风险", en: "New risk blocked" }, + unknown: { zh: "待对账权益注入", en: "Awaiting reconciled equity" }, + }; + const statusLabel = statusLabels[statusId] || statusLabels.unknown; + return { + schema: "qsl.risk_envelope_view.v1", + source, + preference: preference + ? { + id: preference, + label_zh: meta.short_zh, + label_en: meta.short_en, + } + : { + id: "unknown", + label_zh: "未设定", + label_en: "Unset", + }, + capital_band: { + id: "unknown", + label_zh: "待对账权益注入", + label_en: "Awaiting reconciled equity", + }, + status: { + id: statusId, + label_zh: statusLabel.zh, + label_en: statusLabel.en, + }, + scales: { + composer_mdd_multiple: meta ? meta.composer_mdd_multiple : null, + promotion_size_scale: meta ? meta.promotion_size_scale : null, + capital_scale: null, + vol_scale: null, + dd_scale: null, + }, + detail: { + dual_scale_note_zh: + "双口径:Composer 相对无杠杆基准 MDD 天花板为 1.00 / 1.25 / 1.50;晋级仓位缩放为 0.50 / 0.75 / 1.00(仅新晋级/材料变更)。资金信封 combined_scale = capital_scale × vol_scale × dd_scale(各因子 ≤1),由系统按权益/波动/回撤计算,禁止自动升档。", + dual_scale_note_en: + "Dual scale: Composer unlevered-benchmark MDD caps are 1.00 / 1.25 / 1.50; promotion size scales are 0.50 / 0.75 / 1.00 (new promotion / material change only). Envelope combined_scale = capital_scale × vol_scale × dd_scale (each ≤1), system-computed from equity/vol/drawdown; auto step-up is forbidden.", + }, + live_authority_granted: false, + }; +} + +export function attachRiskEnvelopeView(ticket) { + if (!ticket || typeof ticket !== "object") return ticket; + return { + ...ticket, + risk_envelope_view: buildRiskEnvelopeView({ + riskPreference: ticket.suggested_risk_profile, + equityUsd: null, + }), + }; +} diff --git a/web/strategy-switch-console/application/risk_envelope_profiles.js b/web/strategy-switch-console/application/risk_envelope_profiles.js new file mode 100644 index 00000000..b26c4030 --- /dev/null +++ b/web/strategy-switch-console/application/risk_envelope_profiles.js @@ -0,0 +1,5 @@ +export const RESEARCH_PROMOTION_RISK_PROFILES = [ + "CAPITAL_PRESERVATION", + "BALANCED_COMPOUNDING", + "GROWTH_COMPOUNDING", +]; diff --git a/web/strategy-switch-console/application/runtime_instances/command.js b/web/strategy-switch-console/application/runtime_instances/command.js new file mode 100644 index 00000000..8488a552 --- /dev/null +++ b/web/strategy-switch-console/application/runtime_instances/command.js @@ -0,0 +1,58 @@ +import { HttpError } from "../http_error.js"; + +export function hasRuntimeInstanceStore(env) { + return env.STRATEGY_SWITCH_RUNTIME_INSTANCES !== undefined && env.STRATEGY_SWITCH_RUNTIME_INSTANCES !== null; +} + +export async function runtimeInstanceCommand(env, command) { + const namespace = env.STRATEGY_SWITCH_RUNTIME_INSTANCES; + if (!namespace?.idFromName || !namespace?.get) throw new HttpError("runtime_instances_not_bound", 503); + try { + const stub = namespace.get(namespace.idFromName("runtime-instances")); + const response = await stub.fetch("https://runtime-instances/", command ? { + method: "POST", body: JSON.stringify(command), + } : {}); + let result; + try { + result = await response.json(); + } catch { + throw new HttpError("runtime_instances_unavailable", response.status || 503); + } + if (!response.ok) throw new HttpError(result.error || "runtime_instances_unavailable", response.status); + return result; + } catch (error) { + if (error instanceof HttpError) throw error; + if (error && typeof error.status === "number") { + throw new HttpError(error.message || "runtime_instances_unavailable", error.status); + } + throw new HttpError("runtime_instances_unavailable", 503); + } +} + +export function runtimeInstanceAccountOptions(instances) { + const options = {}; + for (const instance of instances) { + if (instance.kind !== "existing") continue; + (options[instance.platform] ||= []).push(instance.config); + } + return options; +} + +export function runtimeInstanceConflict(instances, platform, config, exceptKey = null) { + const selectors = (value) => String(value || "").split(",").map((item) => item.trim().toLowerCase()).filter(Boolean); + for (const item of instances) { + if (item.platform !== platform || item.key === exceptKey) continue; + const other = item.config; + if (item.key === config.key || other.target_name === config.target_name || + (other.service_name && other.service_name === config.service_name) || + selectors(other.account_selector).some((value) => selectors(config.account_selector).includes(value))) { + throw new HttpError("runtime_instance_identity_conflict", 409); + } + } +} + +export function runtimeInstanceFields(raw, allowed) { + if (!raw || typeof raw !== "object" || Array.isArray(raw) || Object.keys(raw).some((key) => !allowed.includes(key))) { + throw new HttpError("invalid_runtime_instance_fields", 400); + } +} diff --git a/web/strategy-switch-console/application/runtime_instances/durable_object.js b/web/strategy-switch-console/application/runtime_instances/durable_object.js new file mode 100644 index 00000000..50312870 --- /dev/null +++ b/web/strategy-switch-console/application/runtime_instances/durable_object.js @@ -0,0 +1,3171 @@ +import { dateOnlyExternalFlow, externalFlowEnabled, initializeExternalFlow, normalizeExternalFlow, putExternalFlow, readExternalFlow, readExternalFlowCoverage } from "../account_facts/external_flow.js"; +import { initializeExecutions, normalizeExecutions, putExecutions, readExecutions } from "../account_facts/executions.js"; +import { DeskError } from "../desk/http.js"; +import { DeskReadbackStore, readbackTargets } from "../desk/readback.js"; +import { HttpError } from "../http_error.js"; +import { KvDirectory, DIRECTORY_PATH } from "../../kv_directory.js"; +import { initializeSecretRotation, durableRotationCommand } from "../../secretRotation.js"; +import { initializeCredentialMaintenance } from "../../credentialMaintenance.js"; +import { runtimeInstanceAccountOptions } from "./command.js"; +import { ux1MutateJob, ux1SelectSlot, ux1SlotPayload } from "../ux1/research.js"; +import { + ACCOUNT_DIAGNOSIS_DEDUPE_WINDOW_MS, + ACCOUNT_DIAGNOSIS_DO_ACTIONS, + ACCOUNT_DIAGNOSIS_STATUSES, +} from "../account_diagnosis/constants.js"; +import { accountDiagnosisConflict, accountDiagnosisTaskFromRow } from "../account_diagnosis/helpers.js"; +import { researchPromotionApplicationFromRow } from "./promotion_application_row.js"; +import { + CycleHealthError, + canonicalCycleJson, compareCycleInstants, cycleCoverageComplete, + cycleHealthAttemptKey, cycleHealthFaultCategory, cycleHealthFaultTime, normalizeCycleInstant, +} from "../../runtime_cycle_health.js"; +import { CredentialMaintenanceError, credentialMaintenanceCommand } from "../../credentialMaintenance.js"; +import { RotationError } from "../../secretRotationStepUp.js"; + +import { accountFactsIdentityDates } from "../account_facts/read.js"; +import { runtimeInstanceConflict } from "./command.js"; +import { deskErrorResponse } from "../desk/http.js"; +import { binanceDetailCommand } from "../../binance_detail_archive.js"; +import { + ACCOUNT_FACTS_FUTURE_SKEW_MS, + ACCOUNT_FACTS_HISTORY_MAX_DAYS, + ACCOUNT_FACTS_RETURN_UNAVAILABLE, + ACCOUNT_FACTS_STALE_MS, + ACCOUNT_FACTS_SUPPORTED_PLATFORMS, + AccountFactsError, + FIRSTRADE_ACCOUNT_FACTS_PLATFORM, + IBKR_ACCOUNT_FACTS_PLATFORM, + SCHWAB_ACCOUNT_FACTS_PLATFORM, + canonicalizeAccountFactsHistory, + decideAccountFactsDailyUpsert, + decideAccountFactsPut, + historyRetentionCutoff, + normalizeAccountFactsHistoricalPayload, + normalizeAccountFactsHistoryPayload, + normalizeIbkrPeriodReturn, + publicIbkrPeriodReturn, + validFirstradeAccountId, +} from "../../account_facts.js"; +import { + BINANCE_WALLET_HISTORY_MAX_DAYS, + normalizeBinanceWalletHistoryEntry, +} from "../../binance_account_facts.js"; +import { + MARKET_BENCHMARK_SCHEMA, + MarketBenchmarkError, + normalizeMarketBenchmarkSyncPayload, + publicMarketBenchmarkReadModel, +} from "../../infrastructure/market_benchmark/market_benchmark.js"; +import { BROKER_ENVIRONMENTS } from "../../account_options_schema.js"; +import { RISK_TIER_POLICY_BINDINGS } from "../console_api/risk_tiers.js"; +import { + ACCOUNT_SETTINGS_PROFILE_APPLICATION_REQUEST_KEY, + AUDIT_EVENTS_MAX_ROWS, + HK_STOP_REQUEST_ID, + HK_STOP_TARGET_ID, + RISK_PROFILE_PREFERENCES, + SUPPORTED_PLATFORMS, + accountSettingsIdentity, + accountSettingsIdentityIsComplete, + accountSettingsProfileName, + applyAccountSettingOverrides, + canonicalResearchTaskJson, + deskTargetReadback, + hkStopDigestOrEmpty, + hkStopIdentityComplete, + hkStopSourceIdentity, + isDcaProfile, + json, + normalizeControlPlaneIdentifier, + promotionMaterialReviewBinding, + promotionReviewBindingsMatch, + readRiskProfileLegacy, + riskProfileBindingTargets, + riskProfileScopeId, + runtimeControlIdentityIsComplete, + runtimeDailyCalendarDate, + supportedBrokerEnvironmentsForPlatform, + updateAccountOptionsDefaultStrategy, + HUMAN_DECISION_DO_ACTIONS, + ACCOUNT_SETTINGS_DO_ACTIONS, + ACCOUNT_FACTS_DO_ACTIONS, + MARKET_BENCHMARK_DO_ACTIONS, + V7_PAPER_APPLICATION_DO_ACTIONS, + RUNTIME_CONTROL_DO_ACTIONS, + HK_STOP_DO_ACTIONS, + CYCLE_HEALTH_HISTORY_PAGE_SIZE, + CYCLE_HEALTH_SUMMARY_SCHEMA_VERSION, +} from '../worker/runtime_support.js'; + +export class RuntimeInstances { + constructor(ctx, env) { + this.directory = new KvDirectory(ctx.storage, env?.STRATEGY_SWITCH_CONFIG, env); + this.storage = ctx.storage; + this.sql = ctx.storage.sql; + initializeExecutions(this.sql); + this.externalFlowEnabled = externalFlowEnabled(env); + initializeExternalFlow(this.sql); + initializeCredentialMaintenance(this.sql); + initializeSecretRotation(this.sql); + this.rotationEnv = env; + this.deskReadbacks = new DeskReadbackStore(ctx.storage, async (id, actor) => { + const state = this.read(); + const target = readbackTargets({ options: runtimeInstanceAccountOptions(state.instances), revision: state.revision }).find(target => target.id === id); + if (!target) throw new DeskError("not_found", 404); + let observed = null; + try { observed = this.readAccountSettings({ platform: target.platform, key: target.key, legacy: await readRiskProfileLegacy(env) }); } catch { /* Degrade saved independently. */ } + return deskTargetReadback(env, target, actor, observed); + }); + // Native intervals remain in the existing private account read-model store. + this.sql.exec(`CREATE TABLE IF NOT EXISTS account_period_return ( + account_key TEXT PRIMARY KEY, payload_json TEXT NOT NULL + )`); + this.sql.exec(`CREATE TABLE IF NOT EXISTS account_daily_native_return ( + account_key TEXT NOT NULL, source_binding_id TEXT NOT NULL, currency TEXT NOT NULL, + observation_date TEXT NOT NULL, payload_json TEXT NOT NULL, + PRIMARY KEY(account_key, source_binding_id, currency, observation_date) + )`); + // Dormant: only an independent protected provisioning step may seed admission. + // There is deliberately no ingress/admin command that writes an admission. + this.sql.exec(`CREATE TABLE IF NOT EXISTS runtime_cycle_health_source ( + source_id TEXT PRIMARY KEY, target_id TEXT NOT NULL UNIQUE, binding_fingerprint TEXT NOT NULL, + configuration_sha256 TEXT NOT NULL, authority_revision INTEGER NOT NULL, + required_from TEXT NOT NULL, observed_at TEXT, observation_json TEXT, + observation_sha256 TEXT, received_at TEXT, checkpoint_revision INTEGER + )`); + this.sql.exec(`CREATE TABLE IF NOT EXISTS runtime_cycle_health_checkpoint ( + target_id TEXT NOT NULL, binding_fingerprint TEXT NOT NULL, source_id TEXT NOT NULL, + revision INTEGER NOT NULL, payload_json TEXT NOT NULL, + PRIMARY KEY (target_id, binding_fingerprint) + )`); + this.sql.exec(`CREATE TABLE IF NOT EXISTS runtime_cycle_health_incident ( + target_id TEXT NOT NULL, binding_fingerprint TEXT NOT NULL, source_id TEXT NOT NULL, + cycle_id TEXT NOT NULL, cycle_json TEXT NOT NULL, category TEXT NOT NULL, + active_category TEXT, latest_fault_at TEXT NOT NULL, attempt_count INTEGER NOT NULL, + resolution_history_json TEXT NOT NULL, + PRIMARY KEY (target_id, binding_fingerprint, source_id, cycle_id) + )`); + this.sql.exec(`CREATE TABLE IF NOT EXISTS runtime_cycle_health_attempt ( + history_id INTEGER PRIMARY KEY AUTOINCREMENT, + target_id TEXT NOT NULL, binding_fingerprint TEXT NOT NULL, source_id TEXT NOT NULL, + cycle_id TEXT NOT NULL, attempt_key TEXT NOT NULL, fault_at TEXT NOT NULL, + category TEXT NOT NULL, configuration_sha256 TEXT NOT NULL, cycle_json TEXT NOT NULL, + resolution_ref TEXT, + UNIQUE (target_id, binding_fingerprint, source_id, attempt_key) + )`); + this.sql.exec("CREATE INDEX IF NOT EXISTS runtime_cycle_health_history_page ON runtime_cycle_health_attempt (target_id, source_id, history_id)"); + this.sql.exec("CREATE TABLE IF NOT EXISTS instance_state (id INTEGER PRIMARY KEY CHECK (id = 1), revision INTEGER NOT NULL, payload TEXT NOT NULL)"); + this.sql.exec("CREATE TABLE IF NOT EXISTS instance_history (revision INTEGER PRIMARY KEY, entry TEXT NOT NULL)"); + this.sql.exec(`CREATE TABLE IF NOT EXISTS account_diagnosis_tasks ( + request_id TEXT PRIMARY KEY, + platform TEXT NOT NULL, + account_key TEXT NOT NULL, + target_id TEXT NOT NULL, + trigger TEXT NOT NULL, + observed_at TEXT NOT NULL, + checks_json TEXT NOT NULL, + state_fingerprint TEXT NOT NULL, + status TEXT NOT NULL, + workflow_run_id TEXT, + workflow_run_attempt TEXT, + job_id TEXT, + summary TEXT NOT NULL, + reason_code TEXT NOT NULL, + dispatch_state TEXT NOT NULL, + recheck_dispatch_state TEXT NOT NULL, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + )`); + try { this.sql.exec("ALTER TABLE account_diagnosis_tasks ADD COLUMN auto_source TEXT NOT NULL DEFAULT 'manual'"); } catch { /* already present */ } + try { this.sql.exec("ALTER TABLE account_diagnosis_tasks ADD COLUMN native_json TEXT"); } catch { /* already present */ } + this.sql.exec("CREATE INDEX IF NOT EXISTS account_diagnosis_tasks_fingerprint ON account_diagnosis_tasks (target_id, state_fingerprint, updated_at)"); + this.sql.exec(`CREATE TABLE IF NOT EXISTS research_promotion_applications ( + application_id TEXT PRIMARY KEY, + ticket_id TEXT NOT NULL, + status TEXT NOT NULL, + dispatch_state TEXT NOT NULL, + claim_token TEXT NOT NULL, + platform_id TEXT NOT NULL, + account_key TEXT NOT NULL, + account_scope TEXT NOT NULL, + account_selector TEXT NOT NULL, + service_name TEXT NOT NULL, + broker_environment TEXT NOT NULL, + expected_revision INTEGER NOT NULL, + expected_strategy_profile TEXT NOT NULL, + strategy_profile TEXT NOT NULL, + candidate_id TEXT NOT NULL, + config_sha256 TEXT NOT NULL, + source_commit TEXT NOT NULL, + approved_ues_revision TEXT NOT NULL, + desired_state TEXT NOT NULL, + workflow_run_id TEXT, + workflow_run_attempt TEXT, + reason_code TEXT NOT NULL, + readback_json TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + )`); + // A rejected application is a terminal record, but an explicitly + // re-qualified user request may create a new attempt. Keep history while + // deduplicating all still-active/uncertain attempts. + this.sql.exec("DROP INDEX IF EXISTS research_promotion_applications_ticket_account"); + this.sql.exec("CREATE INDEX IF NOT EXISTS research_promotion_applications_ticket_account_idx ON research_promotion_applications (ticket_id, platform_id, account_key, status)"); + this.sql.exec(`CREATE TABLE IF NOT EXISTS ux1_research_slot ( + login TEXT PRIMARY KEY, + revision INTEGER NOT NULL, + draft_json TEXT NOT NULL, + fingerprint TEXT NOT NULL, + preview_json TEXT, + intent_json TEXT, + job_json TEXT + )`); + try { + this.sql.exec("ALTER TABLE ux1_research_slot ADD COLUMN job_json TEXT"); + } catch { + /* already migrated */ + } + this.sql.exec(`CREATE TABLE IF NOT EXISTS human_decision_material ( + kind TEXT NOT NULL, + subject_id TEXT NOT NULL, + source_id TEXT NOT NULL, + material_key TEXT NOT NULL, + summary_json TEXT NOT NULL, + eligible INTEGER NOT NULL, + expires_at_ms INTEGER, + legacy_state TEXT NOT NULL, + legacy_block TEXT NOT NULL, + updated_at TEXT NOT NULL, + PRIMARY KEY (kind, subject_id) + )`); + this.sql.exec(`CREATE TABLE IF NOT EXISTS human_decision_record ( + request_key TEXT PRIMARY KEY, + kind TEXT NOT NULL, + subject_id TEXT NOT NULL, + material_key TEXT NOT NULL, + action TEXT NOT NULL, + actor TEXT NOT NULL, + decided_at TEXT NOT NULL, + target_json TEXT NOT NULL, + payload_json TEXT NOT NULL + )`); + this.sql.exec("CREATE INDEX IF NOT EXISTS human_decision_record_subject ON human_decision_record (kind, subject_id, material_key)"); + this.sql.exec(`CREATE TABLE IF NOT EXISTS human_decision_source_material ( + kind TEXT NOT NULL, + subject_id TEXT NOT NULL, + source_id TEXT NOT NULL, + material_key TEXT NOT NULL, + summary_json TEXT NOT NULL, + eligible INTEGER NOT NULL, + expires_at_ms INTEGER, + updated_at TEXT NOT NULL, + PRIMARY KEY (kind, subject_id, source_id) + )`); + this.sql.exec(`CREATE TABLE IF NOT EXISTS risk_profile_authority ( + id INTEGER PRIMARY KEY CHECK (id = 1), + revision INTEGER NOT NULL, + imported INTEGER NOT NULL, + bindings_json TEXT NOT NULL + )`); + this.sql.exec(`CREATE TABLE IF NOT EXISTS account_setting_draft ( + platform TEXT NOT NULL, + account_key TEXT NOT NULL, + revision INTEGER NOT NULL, + identity_json TEXT NOT NULL, + overrides_json TEXT NOT NULL, + updated_at TEXT NOT NULL, + PRIMARY KEY (platform, account_key) + )`); + this.sql.exec(`CREATE TABLE IF NOT EXISTS account_setting_declaration ( + platform TEXT NOT NULL, account_key TEXT NOT NULL, revision INTEGER NOT NULL, + identity_json TEXT NOT NULL, declaration_json TEXT NOT NULL, + PRIMARY KEY (platform, account_key) + )`); + this.sql.exec(`CREATE TABLE IF NOT EXISTS account_setting_profile_application ( + request_key TEXT PRIMARY KEY, + platform TEXT NOT NULL, + account_key TEXT NOT NULL, + identity_json TEXT NOT NULL, + instance_revision INTEGER NOT NULL, + draft_revision INTEGER NOT NULL, + old_profile TEXT NOT NULL, + new_profile TEXT NOT NULL, + approved_by TEXT NOT NULL, + approved_at TEXT NOT NULL, + status TEXT NOT NULL, + workflow_run_id TEXT, + workflow_run_attempt TEXT, + claimed_at TEXT + )`); + this.sql.exec("CREATE INDEX IF NOT EXISTS account_setting_profile_application_target ON account_setting_profile_application (platform, account_key, status)"); + this.sql.exec(`CREATE TABLE IF NOT EXISTS account_facts_observation ( + platform TEXT NOT NULL, + account_key TEXT NOT NULL, + payload_json TEXT NOT NULL, + observed_finished_at TEXT NOT NULL, + observation_date TEXT NOT NULL, + target_id TEXT NOT NULL, + source_binding_id TEXT NOT NULL, + account_scope TEXT NOT NULL, + updated_at TEXT NOT NULL, + PRIMARY KEY (platform, account_key) + )`); + this.sql.exec(`CREATE TABLE IF NOT EXISTS account_facts_daily ( + platform TEXT NOT NULL, + account_key TEXT NOT NULL, + observation_date TEXT NOT NULL, + payload_json TEXT NOT NULL, + observed_finished_at TEXT NOT NULL, + target_id TEXT NOT NULL, + source_binding_id TEXT NOT NULL, + account_scope TEXT NOT NULL, + updated_at TEXT NOT NULL, + PRIMARY KEY (platform, account_key, observation_date) + )`); + this.sql.exec("CREATE INDEX IF NOT EXISTS account_facts_daily_account ON account_facts_daily (platform, account_key, observation_date)"); + this.sql.exec(`CREATE TABLE IF NOT EXISTS market_benchmark_daily ( + series_id TEXT NOT NULL, + observation_date TEXT NOT NULL, + close TEXT NOT NULL, + source TEXT, + updated_at TEXT NOT NULL, + PRIMARY KEY (series_id, observation_date) + )`); + this.sql.exec("CREATE INDEX IF NOT EXISTS market_benchmark_daily_date ON market_benchmark_daily (observation_date)"); + this.sql.exec(`CREATE TABLE IF NOT EXISTS market_benchmark_revision_ledger ( + proposal_sha256 TEXT NOT NULL, + entry_index INTEGER NOT NULL, + series_id_sha256 TEXT NOT NULL, + observation_date TEXT NOT NULL, + prior_close_sha256 TEXT NOT NULL, + new_close_sha256 TEXT NOT NULL, + candidate_source TEXT NOT NULL, + reason TEXT NOT NULL, + applied_at TEXT NOT NULL, + PRIMARY KEY (proposal_sha256, entry_index) + )`); + this.sql.exec("CREATE INDEX IF NOT EXISTS market_benchmark_revision_ledger_date ON market_benchmark_revision_ledger (observation_date)"); + this.sql.exec(`CREATE TABLE IF NOT EXISTS hk_stop_request ( + request_id TEXT PRIMARY KEY, + target_id TEXT NOT NULL, + identity_json TEXT NOT NULL, + source_revision INTEGER NOT NULL, + action TEXT NOT NULL, + requested_at TEXT NOT NULL, + phase TEXT NOT NULL, + dispatch_result TEXT, + workflow_run_id TEXT, + workflow_run_attempt TEXT, + updated_at TEXT NOT NULL + )`); + this.sql.exec("CREATE INDEX IF NOT EXISTS hk_stop_request_target ON hk_stop_request (target_id, requested_at)"); + for (const column of ["source_identity_sha256", "runtime_identity_sha256", "result_json", "stopped_at"]) { + try { this.sql.exec(`ALTER TABLE hk_stop_request ADD COLUMN ${column} TEXT`); } catch { /* already present */ } + } + } + + emptyCycleHealthState(targetId, bindingId) { + return { + schema_version: CYCLE_HEALTH_SUMMARY_SCHEMA_VERSION, + target_id: targetId, source_binding_id: bindingId, + incident_count: 0, fault_attempt_count: 0, unresolved_count: 0, + highest_severity: null, active_category: null, latest_fault_at: null, + resolution_history_count: 0, history_complete: false, + execution_authority_granted: false, + }; + } + + cycleHealthHistoryCursor(sourceId, targetId, bindingFingerprint, throughId, afterId) { + return [sourceId, targetId, bindingFingerprint, throughId, afterId].join(":"); + } + + cycleHealthHistoryPage(sourceId, targetId, bindingFingerprint, cursor = null) { + let throughId, afterId; + if (cursor) { + const parts = cursor.split(":"); + if (parts.length !== 5 || parts[0] !== sourceId || parts[1] !== targetId || parts[2] !== bindingFingerprint) { + throw new HttpError("cycle_health_history_cursor_mismatch", 400); + } + throughId = Number(parts[3]); afterId = Number(parts[4]); + if (!Number.isSafeInteger(throughId) || throughId < 0 || !Number.isSafeInteger(afterId) || afterId < 0 || afterId > throughId) { + throw new HttpError("cycle_health_history_cursor_invalid", 400); + } + } else { + throughId = Number(this.sql.exec("SELECT COALESCE(MAX(history_id), 0) AS highwater FROM runtime_cycle_health_attempt WHERE target_id = ? AND source_id = ?", targetId, sourceId).toArray()[0]?.highwater || 0); + afterId = 0; + } + const rows = this.sql.exec(`SELECT a.history_id, a.binding_fingerprint, a.cycle_id, a.attempt_key, a.fault_at, + a.category, a.configuration_sha256, a.cycle_json, a.resolution_ref, i.resolution_history_json + FROM runtime_cycle_health_attempt a JOIN runtime_cycle_health_incident i + ON i.target_id = a.target_id AND i.binding_fingerprint = a.binding_fingerprint + AND i.source_id = a.source_id AND i.cycle_id = a.cycle_id + WHERE a.target_id = ? AND a.source_id = ? AND a.history_id > ? AND a.history_id <= ? + ORDER BY a.history_id ASC LIMIT ?`, targetId, sourceId, afterId, throughId, CYCLE_HEALTH_HISTORY_PAGE_SIZE + 1).toArray(); + const hasMore = rows.length > CYCLE_HEALTH_HISTORY_PAGE_SIZE; + const selected = rows.slice(0, CYCLE_HEALTH_HISTORY_PAGE_SIZE); + const items = selected.map(row => { + let cycle, resolutionHistory; + try { cycle = JSON.parse(row.cycle_json); resolutionHistory = JSON.parse(row.resolution_history_json); } + catch { throw new HttpError("cycle_health_history_stored_invalid", 409); } + return { + history_id: row.history_id, binding_fingerprint: row.binding_fingerprint, + continuity: row.binding_fingerprint === bindingFingerprint ? "same_binding" : "unconfirmed", + cycle_id: row.cycle_id, attempt_key: row.attempt_key, fault_at: row.fault_at, + category: row.category, configuration_sha256: row.configuration_sha256, + cycle, resolution_ref: row.resolution_ref, resolution_history: resolutionHistory, + }; + }); + const nextCursor = hasMore && selected.length + ? this.cycleHealthHistoryCursor(sourceId, targetId, bindingFingerprint, throughId, selected.at(-1).history_id) + : null; + return { + items, snapshot_through_id: throughId, complete: !hasMore, + next_cursor: nextCursor, + }; + } + + cycleHealthRecordAttempt({ targetId, bindingFingerprint, sourceId, configurationSha, cycle, resolutionRef = null, resolutionHistory = null, state }) { + const category = cycleHealthFaultCategory(cycle); + const attemptKey = cycleHealthAttemptKey(cycle); + const cycleJson = canonicalCycleJson(cycle); + const oldAttempt = this.sql.exec(`SELECT cycle_json FROM runtime_cycle_health_attempt + WHERE target_id = ? AND binding_fingerprint = ? AND source_id = ? AND attempt_key = ?`, + targetId, bindingFingerprint, sourceId, attemptKey).toArray()[0]; + if (oldAttempt) { + if (oldAttempt.cycle_json !== cycleJson) throw new HttpError("cycle_health_fault_receipt_conflict", 409); + return { state, inserted: false }; + } + if (!category) return { state, inserted: false }; + const faultAt = cycleHealthFaultTime(cycle); + const prior = this.sql.exec(`SELECT * FROM runtime_cycle_health_incident + WHERE target_id = ? AND binding_fingerprint = ? AND source_id = ? AND cycle_id = ?`, + targetId, bindingFingerprint, sourceId, cycle.cycle_id).toArray()[0] || null; + let incidentCycle = cycle, incidentCategory = category, latestFaultAt = faultAt; + let activeCategory = resolutionRef === null ? category : null; + let attemptCount = 1; + let history = resolutionHistory || []; + if (prior) { + let oldCycle; + try { oldCycle = JSON.parse(prior.cycle_json); history = JSON.parse(prior.resolution_history_json); } + catch { throw new HttpError("cycle_health_checkpoint_invalid", 409); } + const oldRank = prior.category === "execution_uncertainty" ? 2 : 1; + const newRank = category === "execution_uncertainty" ? 2 : 1; + const timeOrder = compareCycleInstants(faultAt, prior.latest_fault_at); + if (newRank > oldRank || (newRank === oldRank && (timeOrder > 0 || (timeOrder === 0 && attemptKey > cycleHealthAttemptKey(oldCycle))))) { + incidentCycle = cycle; + } else { + incidentCycle = oldCycle; + } + incidentCategory = newRank > oldRank ? category : prior.category; + latestFaultAt = timeOrder > 0 ? faultAt : prior.latest_fault_at; + attemptCount = prior.attempt_count + 1; + if (resolutionRef === null) { + const priorActiveRank = prior.active_category === "execution_uncertainty" ? 2 : prior.active_category === "operational" ? 1 : 0; + activeCategory = newRank > priorActiveRank ? category : prior.active_category; + } else activeCategory = prior.active_category; + } + this.sql.exec(`INSERT INTO runtime_cycle_health_attempt ( + target_id, binding_fingerprint, source_id, cycle_id, attempt_key, fault_at, + category, configuration_sha256, cycle_json, resolution_ref + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, targetId, bindingFingerprint, sourceId, + cycle.cycle_id, attemptKey, faultAt, category, configurationSha, cycleJson, resolutionRef); + this.sql.exec(`INSERT INTO runtime_cycle_health_incident ( + target_id, binding_fingerprint, source_id, cycle_id, cycle_json, category, + active_category, latest_fault_at, attempt_count, resolution_history_json + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(target_id, binding_fingerprint, source_id, cycle_id) DO UPDATE SET + cycle_json = excluded.cycle_json, category = excluded.category, + active_category = excluded.active_category, latest_fault_at = excluded.latest_fault_at, + attempt_count = excluded.attempt_count, resolution_history_json = excluded.resolution_history_json`, + targetId, bindingFingerprint, sourceId, cycle.cycle_id, canonicalCycleJson(incidentCycle), incidentCategory, + activeCategory, latestFaultAt, attemptCount, JSON.stringify(history)); + const next = { ...state }; + if (!prior) next.incident_count += 1; + next.fault_attempt_count += 1; + if (activeCategory !== null && (!prior || prior.active_category === null)) next.unresolved_count += 1; + if (!prior && Array.isArray(history)) next.resolution_history_count += history.length; + if (activeCategory !== null && !next.active_category) next.active_category = activeCategory; + const stateRank = next.highest_severity === "execution_uncertainty" ? 2 : next.highest_severity === "operational" ? 1 : 0; + const incidentRank = incidentCategory === "execution_uncertainty" ? 2 : 1; + if (incidentRank > stateRank) next.highest_severity = incidentCategory; + const activeRank = next.active_category === "execution_uncertainty" ? 2 : next.active_category === "operational" ? 1 : 0; + const nextActiveRank = activeCategory === "execution_uncertainty" ? 2 : activeCategory === "operational" ? 1 : 0; + if (nextActiveRank > activeRank) next.active_category = activeCategory; + if (!next.latest_fault_at || compareCycleInstants(faultAt, next.latest_fault_at) > 0) next.latest_fault_at = faultAt; + next.history_complete = false; + return { state: next, inserted: true }; + } + + migrateCycleHealthCheckpoint(row) { + let checkpoint; + try { checkpoint = JSON.parse(row.payload_json); } catch { throw new HttpError("cycle_health_checkpoint_invalid", 409); } + if (checkpoint?.state?.schema_version === CYCLE_HEALTH_SUMMARY_SCHEMA_VERSION) return checkpoint; + const oldState = checkpoint?.state; + if (!oldState || !Array.isArray(oldState.incidents)) throw new HttpError("cycle_health_checkpoint_invalid", 409); + let state = this.emptyCycleHealthState(row.target_id, oldState.source_binding_id); + for (const incident of oldState.incidents) { + if (!incident || !Array.isArray(incident.fault_attempts) || !incident.fault_attempts.length) throw new HttpError("cycle_health_checkpoint_invalid", 409); + for (const attempt of incident.fault_attempts) { + if (!attempt?.cycle || typeof attempt.configuration_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(attempt.configuration_sha256) + || (attempt.resolution_ref !== null && (typeof attempt.resolution_ref !== "string" || !/^[a-f0-9]{64}$/.test(attempt.resolution_ref)))) { + throw new HttpError("cycle_health_checkpoint_invalid", 409); + } + state = this.cycleHealthRecordAttempt({ targetId: row.target_id, bindingFingerprint: row.binding_fingerprint, + sourceId: row.source_id, configurationSha: attempt.configuration_sha256, cycle: attempt.cycle, + resolutionRef: attempt.resolution_ref, resolutionHistory: incident.resolution_history || [], state }).state; + } + } + checkpoint.state = state; + checkpoint.schema_version = "qsl_runtime_cycle_health_checkpoint_state.v2"; + checkpoint.covered_through = null; + checkpoint.coverage_complete = false; + checkpoint.coverage_incomplete_reason = "legacy_watermark_unavailable"; + const source = this.sql.exec("SELECT binding_fingerprint, configuration_sha256, required_from, observed_at, observation_json FROM runtime_cycle_health_source WHERE source_id = ?", row.source_id).toArray()[0]; + if (row.binding_fingerprint === source?.binding_fingerprint && checkpoint.baseline_established === true + && checkpoint.configuration_sha256 === source.configuration_sha256 && checkpoint.required_from === source.required_from + && source.observation_json && source.observed_at) { + try { + const observation = JSON.parse(source.observation_json); + const coverage = observation?.targets?.[0]?.cycle_health?.coverage; + if (observation.computed_at === source.observed_at && compareCycleInstants(coverage?.through, observation.computed_at) <= 0 + && cycleCoverageComplete(coverage, { since: source.required_from, through: source.required_from })) { + checkpoint.covered_through = coverage.through; + checkpoint.coverage_complete = true; + checkpoint.coverage_incomplete_reason = null; + } + } catch { /* Keep legacy coverage unproven when the stored source record is invalid. */ } + } + this.sql.exec("UPDATE runtime_cycle_health_checkpoint SET payload_json = ? WHERE target_id = ? AND binding_fingerprint = ? AND source_id = ? AND revision = ?", + JSON.stringify(checkpoint), row.target_id, row.binding_fingerprint, row.source_id, row.revision); + return checkpoint; + } + + cycleHealthCommand(command) { + const rows = [...this.sql.exec("SELECT * FROM runtime_cycle_health_source WHERE source_id = ?", command.source_id)]; + const a = rows[0]; + if (!a || a.target_id !== command.target_id || a.binding_fingerprint !== command.binding_fingerprint + || !/^[a-f0-9]{64}$/.test(a.configuration_sha256) + || !Number.isSafeInteger(a.authority_revision) || a.authority_revision < 1) throw new HttpError("cycle_health_source_not_admitted", 409); + try { normalizeCycleInstant(a.required_from); } catch { throw new HttpError("cycle_health_baseline_not_admitted", 409); } + if (command.action === "cycle_health_admission") return { + authority_revision: a.authority_revision, configuration_sha256: a.configuration_sha256, required_from: a.required_from, + }; + if (command.authority_revision !== a.authority_revision || command.configuration_sha256 !== a.configuration_sha256 + || command.required_from !== a.required_from) throw new HttpError("cycle_health_admission_changed", 409); + let partitions = [...this.sql.exec("SELECT * FROM runtime_cycle_health_checkpoint WHERE target_id = ? AND source_id = ?", a.target_id, a.source_id)]; + const owner = [...this.sql.exec("SELECT source_id FROM runtime_cycle_health_checkpoint WHERE target_id = ? AND binding_fingerprint = ?", a.target_id, a.binding_fingerprint)][0]; + if (owner && owner.source_id !== a.source_id) throw new HttpError("cycle_health_source_partition_conflict", 409); + partitions = partitions.map(row => ({ ...row, checkpoint: this.migrateCycleHealthCheckpoint(row) })); + const row = partitions.find(p => p.binding_fingerprint === a.binding_fingerprint) || null; + const previous = row?.checkpoint || null; + const priorPartitions = partitions.filter(p => p.binding_fingerprint !== a.binding_fingerprint).map(p => ({ + checkpoint_revision: p.revision, checkpoint: p.checkpoint, + })); + if (row && a.checkpoint_revision !== row.revision) throw new HttpError("cycle_health_checkpoint_revision_conflict", 409); + const state = previous?.state || this.emptyCycleHealthState(a.target_id, command.source_binding_id); + const admissionTransition = previous && (previous.configuration_sha256 !== a.configuration_sha256 || previous.required_from !== a.required_from); + const effectiveCheckpoint = admissionTransition ? { + ...previous, baseline_established: false, covered_through: null, coverage_complete: false, + coverage_incomplete_reason: "admission_changed", + } : previous; + const result = (status, checkpoint = previous, revision = row?.revision || 0) => ({ + ok: true, schema_version: "qsl_runtime_cycle_health_checkpoint.v2", status, + source_id: a.source_id, target_id: a.target_id, authority_revision: a.authority_revision, + configuration_sha256: a.configuration_sha256, source_binding_id: command.source_binding_id, + checkpoint_revision: revision, observed_at: a.observed_at, observation_sha256: a.observation_sha256, + received_at: a.received_at, required_from: a.required_from, checkpoint, + covered_through: checkpoint?.covered_through || null, + coverage_complete: checkpoint?.coverage_complete === true, + coverage_incomplete_reason: checkpoint?.coverage_incomplete_reason || null, + ...(command.action === "cycle_health_read" + ? { history_page: this.cycleHealthHistoryPage(a.source_id, a.target_id, a.binding_fingerprint, command.history_cursor || null) } + : {}), + prior_partitions: priorPartitions, continuity: priorPartitions.length ? "unconfirmed" : "same_binding", + adopted: false, no_order: true, execution_authority_granted: false, + }); + if (command.action === "cycle_health_read") { + const r = result(!previous ? "uninitialized" : state.unresolved_count || priorPartitions.length ? "blocked" + : !effectiveCheckpoint.baseline_established || effectiveCheckpoint.coverage_complete !== true ? "incomplete" : "complete", effectiveCheckpoint); + if (a.observed_at && (Date.now() - Date.parse(a.observed_at) > ACCOUNT_FACTS_STALE_MS || Date.parse(a.observed_at) - Date.now() > ACCOUNT_FACTS_FUTURE_SKEW_MS)) r.status = "stale"; + if (previous && (previous.configuration_sha256 !== a.configuration_sha256 || previous.required_from !== a.required_from)) r.status = "incomplete"; + if (priorPartitions.length || state.unresolved_count) r.status = "blocked"; + return r; + } + if (command.action !== "cycle_health_put") throw new HttpError("unsupported_cycle_health_action", 400); + const packet = command.observation; + if (packet.targets[0].cycle_health.configuration_sha256 !== a.configuration_sha256) throw new HttpError("cycle_health_configuration_not_admitted", 409); + const order = a.observed_at ? compareCycleInstants(packet.computed_at, a.observed_at) : 1; + if (order < 0) throw new HttpError("cycle_health_stale_observation", 409); + if (order === 0) { + if (command.observation_json !== a.observation_json || command.observation_sha256 !== a.observation_sha256) throw new HttpError("cycle_health_observation_conflict", 409); + return { ...result("unchanged"), schema_version: "qsl_runtime_cycle_health_ack.v1", result: "unchanged" }; + } + const h = packet.targets[0].cycle_health; + const transition = previous && (previous.configuration_sha256 !== a.configuration_sha256 || previous.required_from !== a.required_from); + const expectedCoverageFrom = !transition && previous?.covered_through ? previous.covered_through : a.required_from; + const scanComplete = cycleCoverageComplete(h.coverage); + const coverageContinuous = scanComplete + && compareCycleInstants(h.coverage.from, expectedCoverageFrom) <= 0 + && compareCycleInstants(h.coverage.through, expectedCoverageFrom) >= 0; + const coveredThrough = coverageContinuous ? h.coverage.through : transition ? null : previous?.covered_through || null; + const incompleteReason = coverageContinuous ? null : !scanComplete ? "scan_incomplete" + : compareCycleInstants(h.coverage.from, expectedCoverageFrom) > 0 ? "coverage_gap" : "coverage_not_advanced"; + let nextState = structuredClone(state); + for (const cycle of h.cycles) { + nextState = this.cycleHealthRecordAttempt({ targetId: a.target_id, bindingFingerprint: a.binding_fingerprint, + sourceId: a.source_id, configurationSha: h.configuration_sha256, cycle, state: nextState }).state; + } + const baselineEstablished = coverageContinuous + ? (!previous || transition || previous.baseline_established || !previous.covered_through) + : Boolean(previous && !transition && previous.baseline_established); + const checkpoint = { + schema_version: "qsl_runtime_cycle_health_checkpoint_state.v2", + configuration_sha256: a.configuration_sha256, required_from: a.required_from, + baseline_established: baselineEstablished, + covered_through: coveredThrough, coverage_complete: coverageContinuous, + coverage_incomplete_reason: incompleteReason, + state: nextState, + }; + const revision = (row?.revision || 0) + 1; + this.sql.exec("UPDATE runtime_cycle_health_source SET observed_at = ?, observation_json = ?, observation_sha256 = ?, received_at = ?, checkpoint_revision = ? WHERE source_id = ?", + packet.computed_at, command.observation_json, command.observation_sha256, command.received_at, revision, a.source_id); + this.sql.exec(`INSERT INTO runtime_cycle_health_checkpoint (target_id, binding_fingerprint, source_id, revision, payload_json) + VALUES (?, ?, ?, ?, ?) ON CONFLICT(target_id, binding_fingerprint) DO UPDATE SET revision = excluded.revision, payload_json = excluded.payload_json`, + a.target_id, a.binding_fingerprint, a.source_id, revision, JSON.stringify(checkpoint)); + a.observed_at = packet.computed_at; a.observation_sha256 = command.observation_sha256; a.received_at = command.received_at; + return { ...result("stored", checkpoint, revision), schema_version: "qsl_runtime_cycle_health_ack.v1", result: "stored" }; + } + + cycleHealthProvisionCommand(command) { + const { source_id: sourceId, target_id: targetId, binding_fingerprint: bindingFingerprint, + configuration_sha256: configurationSha, required_from: requiredFrom, + expected_authority_revision: expectedRevision } = command || {}; + if (targetId !== "longbridge.paper" || !/^[a-f0-9]{64}$/.test(bindingFingerprint || "") + || !/^[a-f0-9]{64}$/.test(configurationSha || "") + || !Number.isSafeInteger(expectedRevision) || expectedRevision < 0) { + throw new HttpError("cycle_health_admission_request_invalid", 400); + } + const normalizedSourceId = normalizeControlPlaneIdentifier(sourceId, "source_id", false); + let normalizedRequiredFrom; + try { normalizedRequiredFrom = normalizeCycleInstant(requiredFrom); } + catch { throw new HttpError("cycle_health_baseline_not_admitted", 400); } + const current = [...this.sql.exec("SELECT * FROM runtime_cycle_health_source WHERE target_id = ?", targetId)][0] || null; + const sourceOwner = [...this.sql.exec("SELECT * FROM runtime_cycle_health_source WHERE source_id = ?", normalizedSourceId)][0] || null; + if (sourceOwner && sourceOwner.target_id !== targetId) throw new HttpError("cycle_health_source_target_conflict", 409); + if (current && current.source_id !== normalizedSourceId) throw new HttpError("cycle_health_source_id_change_requires_support", 409); + if (current?.binding_fingerprint !== bindingFingerprint + && [...this.sql.exec("SELECT 1 AS found FROM runtime_cycle_health_checkpoint WHERE target_id = ? AND binding_fingerprint = ?", targetId, bindingFingerprint)][0]) { + throw new HttpError("cycle_health_binding_partition_reuse_requires_support", 409); + } + const currentRevision = current?.authority_revision || 0; + const sameAdmission = current && current.binding_fingerprint === bindingFingerprint + && current.configuration_sha256 === configurationSha && current.required_from === normalizedRequiredFrom; + if (sameAdmission) { + if (expectedRevision > currentRevision || expectedRevision < currentRevision - 1) { + throw new HttpError("cycle_health_admission_revision_conflict", 409); + } + return { + ok: true, schema_version: "qsl_runtime_cycle_health_admission.v1", result: "unchanged", + source_id: normalizedSourceId, target_id: targetId, authority_revision: currentRevision, + configuration_sha256: configurationSha, required_from: normalizedRequiredFrom, + status: "admission_unchanged", adopted: false, no_order: true, + execution_authority_granted: false, + }; + } + if (expectedRevision !== currentRevision) throw new HttpError("cycle_health_admission_revision_conflict", 409); + if (current && compareCycleInstants(normalizedRequiredFrom, current.required_from) > 0) { + throw new HttpError("cycle_health_baseline_range_shrink_rejected", 409); + } + const nextRevision = currentRevision + 1; + if (!Number.isSafeInteger(nextRevision)) throw new HttpError("cycle_health_admission_revision_exhausted", 409); + if (!current) { + this.sql.exec(`INSERT INTO runtime_cycle_health_source ( + source_id, target_id, binding_fingerprint, configuration_sha256, + authority_revision, required_from, observed_at, observation_json, + observation_sha256, received_at, checkpoint_revision + ) VALUES (?, ?, ?, ?, ?, ?, NULL, NULL, NULL, NULL, NULL)`, + normalizedSourceId, targetId, bindingFingerprint, configurationSha, nextRevision, normalizedRequiredFrom); + } else { + const bindingChanged = current.binding_fingerprint !== bindingFingerprint; + this.sql.exec(`UPDATE runtime_cycle_health_source SET binding_fingerprint = ?, configuration_sha256 = ?, + authority_revision = ?, required_from = ?, observed_at = ?, observation_json = ?, observation_sha256 = ?, + received_at = ?, checkpoint_revision = ? WHERE source_id = ? AND authority_revision = ?`, + bindingFingerprint, configurationSha, nextRevision, normalizedRequiredFrom, + bindingChanged ? null : current.observed_at, + bindingChanged ? null : current.observation_json, + bindingChanged ? null : current.observation_sha256, + bindingChanged ? null : current.received_at, + bindingChanged ? null : current.checkpoint_revision, + normalizedSourceId, currentRevision); + if (this.sql.exec("SELECT changes() AS changed").toArray()[0]?.changed !== 1) { + throw new HttpError("cycle_health_admission_revision_conflict", 409); + } + } + return { + ok: true, schema_version: "qsl_runtime_cycle_health_admission.v1", result: "stored", + source_id: normalizedSourceId, target_id: targetId, authority_revision: nextRevision, + configuration_sha256: configurationSha, required_from: normalizedRequiredFrom, + status: "admission_configured", adopted: false, no_order: true, + execution_authority_granted: false, + }; + } + + accountFactsCommand(command) { + if (!command || typeof command.action !== "string") throw new HttpError("unsupported_account_facts_action", 400); + if (command.action === "account_daily_native_return_research_read") return this.researchNativeDailyReturns(command); + if (["binance_detail_put", "binance_detail_read"].includes(command.action)) return binanceDetailCommand(this.sql, command); + if (command.action === "binance_wallet_history_put" || command.action === "binance_wallet_history_read") { + return this.binanceWalletHistoryCommand(command); + } + if (command.action === "account_period_return_put" || command.action === "account_period_return_read") return this.accountPeriodReturnCommand(command); + if (["account_executions_put", "account_executions_read"].includes(command.action)) { + const context = this.accountFactsStorageContext(command); + if (context.platform !== "ibkr") throw new HttpError("invalid_execution_platform", 400); + return command.action === "account_executions_read" + ? { ok: true, executions: readExecutions(this.sql, context) } + : { ok: true, ...putExecutions(this.sql, context, command.payload) }; + } + if (command.action === "account_external_flow_put") { + const context = this.accountFactsStorageContext(command); + const flow = normalizeExternalFlow(command.externalFlow); + if (!this.externalFlowEnabled && !dateOnlyExternalFlow(flow)) throw new HttpError("external_flow_disabled", 409); + const source = { ibkr: "ibkr_flex", schwab: "schwab_transactions", longbridge: "longbridge_cashflow" }[context.platform]; + if (!source || flow.windows.some(w => w.source !== source)) throw new HttpError("external_flow_source_mismatch", 409); + const stored = putExternalFlow(this.sql, context, flow); + const readback = readExternalFlow(this.sql, context); + const readbackVerified = flow.windows.every(w => readback?.windows.some(saved => JSON.stringify(saved) === JSON.stringify(w))); + if (!readbackVerified) throw new HttpError("external_flow_readback_failed", 503); + return { ok: true, ...stored, readback_verified: true }; + } + if (command.action === "account_facts_put") return this.putAccountFactsObservation(command); + if (command.action === "account_facts_history_put") return this.putAccountFactsHistoricalObservation(command); + if (command.action === "account_facts_read") return this.readAccountFactsObservation(command); + if (command.action === "account_facts_list") return this.listAccountFactsObservations(command); + if (command.action === "account_facts_history_read") return this.readAccountFactsDailyHistory(command); + throw new HttpError("unsupported_account_facts_action", 400); + } + + + marketBenchmarkCommand(command) { + if (!command || typeof command.action !== "string") throw new HttpError("unsupported_market_benchmark_action", 400); + if (command.action === "market_benchmark_read") return this.readMarketBenchmarkPoints(command); + if (command.action === "market_benchmark_put") return this.putMarketBenchmarkPoints(command); + if (command.action === "market_benchmark_revise") return this.reviseMarketBenchmarkPoints(command); + throw new HttpError("unsupported_market_benchmark_action", 400); + } + + readMarketBenchmarkPoints(command = {}) { + const rows = this.sql.exec( + `SELECT series_id, observation_date, close, source FROM market_benchmark_daily + ORDER BY series_id ASC, observation_date ASC`, + ).toArray(); + const points = rows.map((row) => ({ + seriesId: row.series_id, + observationDate: row.observation_date, + close: row.close, + ...(row.source ? { source: row.source } : {}), + })); + if (command.performance === true) return { ok: true, points }; + return { ok: true, ...publicMarketBenchmarkReadModel(points) }; + } + + putMarketBenchmarkPoints(command) { + let normalized; + try { + normalized = normalizeMarketBenchmarkSyncPayload(command.payload || command); + } catch (error) { + if (error instanceof MarketBenchmarkError) throw new HttpError(error.code, error.status || 400); + throw error; + } + const updatedAt = new Date().toISOString(); + let written = 0; + let unchanged = 0; + for (const point of normalized.points) { + const existing = this.sql.exec( + `SELECT close, source FROM market_benchmark_daily WHERE series_id = ? AND observation_date = ?`, + point.seriesId, + point.observationDate, + ).toArray()[0]; + if (existing && existing.close === point.close + && (existing.source || null) === (point.source || null)) { + unchanged += 1; + continue; + } + if (existing && existing.close !== point.close) { + throw new HttpError("market_benchmark_point_conflict", 409); + } + this.sql.exec( + `INSERT INTO market_benchmark_daily (series_id, observation_date, close, source, updated_at) + VALUES (?, ?, ?, ?, ?) + ON CONFLICT(series_id, observation_date) DO UPDATE SET + close = excluded.close, + source = excluded.source, + updated_at = excluded.updated_at`, + point.seriesId, + point.observationDate, + point.close, + point.source || null, + updatedAt, + ); + written += 1; + } + return { + ok: true, + stored: true, + schema_version: MARKET_BENCHMARK_SCHEMA, + inception_date: this.readMarketBenchmarkPoints().inception_date, + written, + unchanged, + points: normalized.points.length, + }; + } + + reviseMarketBenchmarkPoints(command) { + const appliedAt = typeof command.applied_at === "string" && !Number.isNaN(Date.parse(command.applied_at)) + ? command.applied_at + : new Date().toISOString(); + // Plan is validated by the HTTP layer (hashes + concrete updates); DO only applies. + const plan = command.plan; + if (!plan || !Array.isArray(plan.updates) || !Array.isArray(plan.ledger_entries)) { + throw new HttpError("benchmark_revision_plan_required", 400); + } + if (plan.overwrites !== plan.updates.length || plan.deletes !== 0) { + throw new HttpError("benchmark_revision_plan_invalid", 400); + } + const existingProposal = this.sql.exec( + `SELECT proposal_sha256 FROM market_benchmark_revision_ledger WHERE proposal_sha256 = ? LIMIT 1`, + plan.proposal_sha256, + ).toArray()[0]; + if (existingProposal) throw new HttpError("benchmark_revision_already_applied", 409); + let written = 0; + for (const point of plan.updates) { + const existing = this.sql.exec( + `SELECT close, source FROM market_benchmark_daily WHERE series_id = ? AND observation_date = ?`, + point.seriesId, + point.observationDate, + ).toArray()[0]; + if (!existing) throw new HttpError("benchmark_revision_target_missing", 404); + if (existing.close === point.close) throw new HttpError("benchmark_revision_noop", 400); + this.sql.exec( + `UPDATE market_benchmark_daily SET close = ?, source = ?, updated_at = ? WHERE series_id = ? AND observation_date = ?`, + point.close, + point.source || null, + appliedAt, + point.seriesId, + point.observationDate, + ); + written += 1; + } + for (const entry of plan.ledger_entries) { + this.sql.exec( + `INSERT INTO market_benchmark_revision_ledger ( + proposal_sha256, entry_index, series_id_sha256, observation_date, + prior_close_sha256, new_close_sha256, candidate_source, reason, applied_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + entry.proposal_sha256, + entry.entry_index, + entry.series_id_sha256, + entry.observation_date, + entry.prior_close_sha256, + entry.new_close_sha256, + entry.candidate_source, + entry.reason, + entry.applied_at || appliedAt, + ); + } + return { + ok: true, + applied: true, + proposal_sha256: plan.proposal_sha256, + overwrites: written, + deletes: 0, + ledger_entries: plan.ledger_entries.length, + }; + } + + + binanceWalletHistoryContext(command) { + const accountKey = command?.account_key; + const sourceBindingId = command?.source_binding_id; + if (typeof accountKey !== "string" || !accountKey + || typeof sourceBindingId !== "string" || !/^[a-f0-9]{64}$/.test(sourceBindingId) + || command.storage_account_key !== `${accountKey}:${sourceBindingId}` + || typeof command.target_id !== "string" || !command.target_id + || typeof command.account_scope !== "string" || !command.account_scope) { + throw new HttpError("invalid_binance_wallet_history_account", 400); + } + return { + account_key: command.storage_account_key, + target_id: command.target_id, + account_scope: command.account_scope, + source_binding_id: sourceBindingId, + binding: { + target_id: command.target_id, + account_scope: command.account_scope, + source_binding: { kind: "binance_readonly_scope_revision", id: sourceBindingId }, + }, + }; + } + + binanceWalletHistoryCommand(command) { + const context = this.binanceWalletHistoryContext(command); + if (command.action === "binance_wallet_history_read") { + const rows = this.sql.exec( + `SELECT payload_json, target_id, source_binding_id, account_scope FROM account_facts_daily + WHERE platform = 'binance' AND account_key = ? ORDER BY observation_date ASC`, + context.account_key, + ).toArray(); + const days = []; + for (const row of rows) { + if (row.target_id !== context.target_id || row.source_binding_id !== context.source_binding_id + || row.account_scope !== context.account_scope) { + throw new HttpError("binance_wallet_history_identity_mismatch", 409); + } + let payload; + try { payload = JSON.parse(row.payload_json); } + catch { throw new HttpError("binance_wallet_history_stored_invalid", 409); } + try { days.push(normalizeBinanceWalletHistoryEntry(payload, context.binding)); } + catch { throw new HttpError("binance_wallet_history_stored_invalid", 409); } + } + return { ok: true, days }; + } + + let history; + try { history = normalizeBinanceWalletHistoryEntry(command.history, context.binding); } + catch { throw new HttpError("invalid_binance_wallet_history", 400); } + const row = this.sql.exec( + `SELECT payload_json, target_id, source_binding_id, account_scope FROM account_facts_daily + WHERE platform = 'binance' AND account_key = ? AND observation_date = ?`, + context.account_key, + history.observation_date, + ).toArray()[0]; + let existing = null; + if (row) { + if (row.target_id !== context.target_id || row.source_binding_id !== context.source_binding_id + || row.account_scope !== context.account_scope) { + throw new HttpError("binance_wallet_history_identity_mismatch", 409); + } + try { existing = normalizeBinanceWalletHistoryEntry(JSON.parse(row.payload_json), context.binding); } + catch { throw new HttpError("binance_wallet_history_stored_invalid", 409); } + } + if (existing) { + const existingTime = Date.parse(existing.report_observed_finished_at); + const incomingTime = Date.parse(history.report_observed_finished_at); + if (incomingTime < existingTime) return { ok: true, unchanged: true }; + if (incomingTime === existingTime) { + if (JSON.stringify(existing) !== JSON.stringify(history)) { + throw new HttpError("binance_wallet_history_conflict", 409); + } + return { ok: true, unchanged: true }; + } + } + const updatedAt = new Date().toISOString(); + this.sql.exec( + `INSERT INTO account_facts_daily ( + platform, account_key, observation_date, payload_json, observed_finished_at, + target_id, source_binding_id, account_scope, updated_at + ) VALUES ('binance', ?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(platform, account_key, observation_date) DO UPDATE SET + payload_json = excluded.payload_json, + observed_finished_at = excluded.observed_finished_at, + target_id = excluded.target_id, + source_binding_id = excluded.source_binding_id, + account_scope = excluded.account_scope, + updated_at = excluded.updated_at`, + context.account_key, + history.observation_date, + JSON.stringify(history), + history.report_observed_finished_at, + context.target_id, + context.source_binding_id, + context.account_scope, + updatedAt, + ); + const latestDate = this.sql.exec( + "SELECT MAX(observation_date) AS latest_date FROM account_facts_daily WHERE platform = 'binance' AND account_key = ?", + context.account_key, + ).toArray()[0]?.latest_date; + const cutoff = historyRetentionCutoff(latestDate, BINANCE_WALLET_HISTORY_MAX_DAYS); + if (cutoff) { + this.sql.exec("DELETE FROM account_facts_daily WHERE platform = 'binance' AND account_key = ? AND observation_date < ?", + context.account_key, cutoff); + } + return { ok: true, unchanged: false }; + } + + accountPeriodReturnCommand(command) { + const context = this.accountFactsStorageContext(command); + if (context.platform !== "ibkr") throw new HttpError("invalid_account_period_return", 400); + const binding = { ...context, source_binding: { id: context.source_binding_id } }; + const sourceIds = [context.source_binding_id, ...(context.history_source_binding_ids || [])]; + const sources = new Set(sourceIds); + const readStored = (payloadJson) => { + const payload = JSON.parse(payloadJson); + if (!sources.has(payload?.source_binding_id)) throw new HttpError("account_period_return_source_unqualified", 409); + return normalizeIbkrPeriodReturn(payload, { + binding: { ...binding, source_binding: { id: payload.source_binding_id } }, enforceFresh: false, + }); + }; + const existing = this.sql.exec("SELECT payload_json FROM account_period_return WHERE account_key = ?", context.account_key).toArray()[0]; + let prior = null; + if (existing) { + try { prior = readStored(existing.payload_json); } + catch { /* Keep the latest interval unavailable; independently qualify daily rows below. */ } + } + if (command.action === "account_period_return_read") { + const dailyByDay = new Map(); + let conflictingDailyValues = false; + const conflictingNav = new Set(); + const navByDay = new Map(); + const addDaily = (row) => { + const key = `${row.currency}:${row.period.to}`; + const old = dailyByDay.get(key); + if (old && Number(old.value) !== Number(row.value)) { + conflictingDailyValues = true; + return; + } + if (row.nav_change) { + const nav = canonicalResearchTaskJson(row.nav_change); + if (navByDay.has(key) && navByDay.get(key) !== nav) conflictingNav.add(key); + else navByDay.set(key, nav); + } + if (!old || row.source_binding_id === context.source_binding_id + || (old.source_binding_id !== context.source_binding_id + && Date.parse(row.observed_at) > Date.parse(old.observed_at))) dailyByDay.set(key, row); + }; + const rows = this.sql.exec(`SELECT payload_json FROM account_daily_native_return + WHERE account_key = ? AND source_binding_id IN (${sourceIds.map(() => "?").join(",")}) + ORDER BY observation_date DESC LIMIT ?`, context.account_key, ...sourceIds, 366 * sourceIds.length); + for (const stored of rows) { + let row; + try { row = readStored(stored.payload_json); } catch { continue; } + addDaily(row); + } + if (prior && prior.period.from === prior.period.to) addDaily(prior); + if (conflictingDailyValues) return { ok: true, return: { + ...ACCOUNT_FACTS_RETURN_UNAVAILABLE, reason: "native_return_history_conflict", + } }; + const daily = [...dailyByDay.values()].sort((a, b) => b.period.to.localeCompare(a.period.to)).slice(0, 366); + const latest = prior ? publicIbkrPeriodReturn(prior) : { ...ACCOUNT_FACTS_RETURN_UNAVAILABLE }; + if (prior && prior.period.from === prior.period.to && conflictingNav.has(`${prior.currency}:${prior.period.to}`)) delete latest.nav_change; + return { ok: true, return: { ...latest, + daily: daily.sort((a, b) => a.period.to.localeCompare(b.period.to)).map(row => { + const projected = publicIbkrPeriodReturn(row); + if (conflictingNav.has(`${row.currency}:${row.period.to}`)) delete projected.nav_change; + return projected; + }) } }; + } + const incoming = normalizeIbkrPeriodReturn(command.payload, { binding }); + const historical = command.historical === true; + if (historical && (incoming.period.from !== incoming.period.to || Date.now() - Date.parse(incoming.period.to + "T00:00:00Z") > 366 * 86400000)) throw new HttpError("invalid_account_daily_return", 400); + if (prior && !historical) { + if (incoming.period.to < prior.period.to || Date.parse(incoming.observed_at) < Date.parse(prior.observed_at)) throw new HttpError("account_period_return_stale_write", 409); + if (incoming.observed_at === prior.observed_at && canonicalResearchTaskJson(incoming) !== canonicalResearchTaskJson(prior)) throw new HttpError("account_period_return_conflict", 409); + } + if (incoming.period.from === incoming.period.to) { + const oldDay = this.sql.exec("SELECT payload_json FROM account_daily_native_return WHERE account_key = ? AND source_binding_id = ? AND currency = ? AND observation_date = ?", context.account_key, context.source_binding_id, incoming.currency, incoming.period.to).toArray()[0]; + if (oldDay) { + const old = normalizeIbkrPeriodReturn(JSON.parse(oldDay.payload_json), { binding, enforceFresh: false }); + if (Date.parse(incoming.observed_at) < Date.parse(old.observed_at)) throw new HttpError("account_daily_return_stale_write", 409); + if (incoming.observed_at === old.observed_at && canonicalResearchTaskJson(incoming) !== canonicalResearchTaskJson(old)) throw new HttpError("account_daily_return_conflict", 409); + } + this.sql.exec("INSERT INTO account_daily_native_return (account_key, source_binding_id, currency, observation_date, payload_json) VALUES (?, ?, ?, ?, ?) ON CONFLICT(account_key, source_binding_id, currency, observation_date) DO UPDATE SET payload_json = excluded.payload_json", context.account_key, context.source_binding_id, incoming.currency, incoming.period.to, JSON.stringify(incoming)); + } + if (historical && prior && (incoming.period.to < prior.period.to || Date.parse(incoming.observed_at) < Date.parse(prior.observed_at))) return { ok: true, unchanged: false }; + if (prior && canonicalResearchTaskJson(incoming) === canonicalResearchTaskJson(prior)) return { ok: true, unchanged: true }; + this.sql.exec("INSERT INTO account_period_return (account_key, payload_json) VALUES (?, ?) ON CONFLICT(account_key) DO UPDATE SET payload_json = excluded.payload_json", context.account_key, JSON.stringify(incoming)); + return { ok: true, unchanged: false }; + } + + // Internal only: a future authorized caller must supply verified account and + // source bindings. The browser/session and publisher routes do not call this. + researchNativeDailyReturns(command) { + const context = this.accountFactsStorageContext(command); + const from = runtimeDailyCalendarDate(command.from), to = runtimeDailyCalendarDate(command.to); + const start = Date.parse(`${from}T00:00:00Z`), end = Date.parse(`${to}T00:00:00Z`); + if (context.platform !== "ibkr" || !/^[a-f0-9]{64}$/.test(context.source_binding_id) + || !context.account_scope || !context.account_selector || !context.target_id + || typeof command.currency !== "string" || !/^[A-Z]{3}$/.test(command.currency) + || !from || !to || end < start || (end - start) / 86400000 >= 366) + throw new HttpError("invalid_native_return_research_range", 400); + const sources = new Set([context.source_binding_id, ...(context.history_source_binding_ids || [])]); + let stored; + try { + stored = this.sql.exec(`SELECT source_binding_id, currency, observation_date, payload_json + FROM account_daily_native_return WHERE account_key = ? AND currency = ? AND observation_date >= ? AND observation_date <= ? + ORDER BY observation_date, source_binding_id LIMIT ?`, context.account_key, command.currency, from, to, 366 * 65 + 1).toArray(); + } catch { throw new HttpError("native_return_read_failed", 503); } + if (stored.length > 366 * 65) throw new HttpError("native_return_read_limit_exceeded", 503); + const byDate = new Map(); + const navByDate = new Map(); + const decimal = text => { + const [whole, fraction = ""] = text.split("."); + return { number: BigInt(whole + fraction), scale: fraction.length }; + }; + const decimalEqual = (left, right) => { + const a = decimal(left), b = decimal(right); + return a.number * 10n ** BigInt(b.scale) === b.number * 10n ** BigInt(a.scale); + }; + const navFields = ["startingValue", "endingValue", "depositsWithdrawals", "internalCashTransfers", "assetTransfers", "linkingAdjustments"]; + for (const item of stored) { + const date = runtimeDailyCalendarDate(item.observation_date); + if (!date) return { ok: false, error: "native_return_record_invalid", date: null }; + if (!sources.has(item.source_binding_id)) return { ok: false, error: "native_return_source_unqualified", date }; + let raw; + try { + raw = JSON.parse(item.payload_json); + const validated = normalizeIbkrPeriodReturn(raw, { binding: { ...context, source_binding: { id: item.source_binding_id } }, enforceFresh: false }); + if (validated.currency !== item.currency || validated.period.from !== date || validated.period.to !== date) throw Error(); + } catch { return { ok: false, error: "native_return_record_invalid", date }; } + const previous = byDate.get(date); + if (previous) { + if (!decimalEqual(previous.value, raw.value)) + return { ok: false, error: "native_return_history_conflict", date }; + } + if (raw.nav_change) { + const priorNav = navByDate.get(date), nav = raw.nav_change; + if (priorNav && (!decimalEqual(priorNav.amount, nav.amount) + || navFields.some(field => !decimalEqual(priorNav.components[field], nav.components[field])))) + return { ok: false, error: "native_return_nav_conflict", date }; + navByDate.set(date, nav); + } + // SQL order is deterministic; prefer the currently configured source. + if (!previous || raw.source_binding_id === context.source_binding_id) byDate.set(date, raw); + } + const rows = [...byDate.values()]; + const datesWithoutRecords = []; + for (let day = start; day <= end; day += 86400000) { + const date = new Date(day).toISOString().slice(0, 10); + if (!byDate.has(date)) datesWithoutRecords.push(date); + } + return { ok: true, status: "ok", currency: command.currency, from, to, rows, + source_row_count: stored.length, duplicate_row_count: stored.length - rows.length, + returned_from: rows[0]?.period.to || null, returned_to: rows.at(-1)?.period.to || null, + dates_without_records: datesWithoutRecords, calendar_qualification: "unknown", published_at: null, historical_pit: "unknown" }; + } + + accountFactsStorageContext(command) { + if (!ACCOUNT_FACTS_SUPPORTED_PLATFORMS.includes(command?.platform) + || typeof command.account_key !== "string" || !command.account_key + || typeof command.account_scope !== "string" + || typeof command.account_selector !== "string" + || typeof command.target_id !== "string" + || typeof command.source_binding_id !== "string") { + throw new HttpError("invalid_account_facts_account", 400); + } + const historyIds = command.history_source_binding_ids; + if (historyIds !== undefined && (!["account_facts_history_read", "account_facts_history_put", "account_period_return_read", "account_period_return_put", "account_daily_native_return_research_read", "account_executions_read", "account_executions_put"].includes(command.action) + || !Array.isArray(historyIds) || historyIds.length < 1 || historyIds.length > 64 + || new Set(historyIds).size !== historyIds.length + || historyIds.some(id => typeof id !== "string" || !/^[a-f0-9]{64}$/.test(id) + || id === command.source_binding_id))) throw new HttpError("invalid_account_facts_history_sources", 400); + const brokerAccountId = command.platform === FIRSTRADE_ACCOUNT_FACTS_PLATFORM ? command.broker_account_id : null; + if (command.platform === FIRSTRADE_ACCOUNT_FACTS_PLATFORM && !validFirstradeAccountId(brokerAccountId)) { + throw new HttpError("invalid_account_facts_account", 400); + } + const brokerAccountHash = command.platform === SCHWAB_ACCOUNT_FACTS_PLATFORM + ? command.broker_account_hash + : null; + if (command.platform === SCHWAB_ACCOUNT_FACTS_PLATFORM + && (typeof brokerAccountHash !== "string" || !brokerAccountHash || brokerAccountHash.length > 256)) { + throw new HttpError("invalid_account_facts_account", 400); + } + return { + platform: command.platform, + ...(command.platform === FIRSTRADE_ACCOUNT_FACTS_PLATFORM ? { broker_account_id: brokerAccountId } : {}), + account_key: command.account_key, + account_scope: command.account_scope, + account_selector: command.account_selector, + target_id: command.target_id, + source_binding_id: command.source_binding_id, + ...(historyIds ? { history_source_binding_ids: [...historyIds] } : {}), + ...(command.platform === SCHWAB_ACCOUNT_FACTS_PLATFORM + ? { broker_account_hash: brokerAccountHash } + : {}), + }; + } + + validateAccountFactsStoragePayload(payload, context, { historical = false } = {}) { + try { + const normalize = historical ? normalizeAccountFactsHistoricalPayload : normalizeAccountFactsHistoryPayload; + return normalize(payload, { + expectedPlatform: context.platform, + expectedTargetId: context.target_id, + expectedBindingId: context.source_binding_id, + expectedOptionScope: context.account_scope, + expectedAccountSelector: context.platform === IBKR_ACCOUNT_FACTS_PLATFORM + ? context.account_selector + : null, + expectedBrokerAccountHash: context.platform === SCHWAB_ACCOUNT_FACTS_PLATFORM + ? context.broker_account_hash + : null, + expectedBrokerAccountId: context.platform === FIRSTRADE_ACCOUNT_FACTS_PLATFORM ? context.broker_account_id : null, + enforceObservationWindow: false, + }); + } catch (error) { + if (error instanceof AccountFactsError) throw new HttpError(error.code, error.status || 400); + throw error; + } + } + + validatePriorAccountFactsForAccount(row, context) { + if (!row) return null; + if (row.platform !== context.platform || row.account_key !== context.account_key + || row.account_scope !== context.account_scope || row.target_id !== context.target_id) { + throw new HttpError("account_facts_identity_mismatch", 409); + } + const priorBindingId = row.payload?.source_binding?.id; + if (typeof priorBindingId !== "string") throw new HttpError("account_facts_stored_invalid", 409); + const priorContext = { ...context, source_binding_id: priorBindingId }; + const payload = this.validateAccountFactsStoragePayload(row.payload, priorContext); + if (row.source_binding_id !== priorBindingId + || payload.target_id !== row.target_id + || payload.source_binding.id !== row.source_binding_id) { + throw new HttpError("account_facts_identity_mismatch", 409); + } + return { ...row, payload }; + } + + readAccountFactsRow(platform, accountKey, context = null) { + const row = this.sql.exec( + "SELECT payload_json, observed_finished_at, observation_date, target_id, source_binding_id, account_scope FROM account_facts_observation WHERE platform = ? AND account_key = ?", + platform, + accountKey, + ).toArray()[0]; + if (!row) return null; + let payload; + try { payload = JSON.parse(row.payload_json); } catch { throw new HttpError("account_facts_stored_invalid", 409); } + if (context) { + if (platform !== context.platform || accountKey !== context.account_key + || row.target_id !== context.target_id + || row.source_binding_id !== context.source_binding_id + || row.account_scope !== context.account_scope) { + throw new HttpError("account_facts_identity_mismatch", 409); + } + payload = this.validateAccountFactsStoragePayload(payload, context); + } + return { + platform, + account_key: accountKey, + payload, + observed_finished_at: row.observed_finished_at, + observation_date: row.observation_date, + target_id: row.target_id, + source_binding_id: row.source_binding_id, + account_scope: row.account_scope, + }; + } + + putAccountFactsObservation(command) { + const context = this.accountFactsStorageContext(command); + if (!command.history || typeof command.history !== "object" || Array.isArray(command.history)) { + throw new HttpError("invalid_account_facts_history", 400); + } + let history; + try { + history = this.validateAccountFactsStoragePayload(command.history, context); + } catch (error) { + if (error instanceof AccountFactsError) throw new HttpError(error.code, error.status || 400); + throw error; + } + const existingRow = this.validatePriorAccountFactsForAccount( + this.readAccountFactsRow(command.platform, command.account_key), + context, + ); + const decision = decideAccountFactsPut(existingRow?.payload || null, history); + if (decision.action === "reject") throw new HttpError(decision.reason, decision.status || 409); + let flowChanged = false; + if (command.externalFlow !== undefined) { + if (!this.externalFlowEnabled) throw new HttpError("external_flow_disabled", 409); + const flow = normalizeExternalFlow(command.externalFlow); + const source = { ibkr: "ibkr_flex", schwab: "schwab_transactions", longbridge: "longbridge_cashflow" }[context.platform]; + const currencies = new Set(history.broker_reported_balances.map(b => b.currency)); + if (flow.windows.some(w => w.source !== source || !currencies.has(w.currency) + || Date.parse(w.period_end) > Date.parse(history.observed_finished_at))) { + throw new HttpError("external_flow_observation_mismatch", 409); + } + flowChanged = !putExternalFlow(this.sql, context, flow).unchanged; + } + if (decision.action === "keep") { + // Latest table unchanged; still ensure the accepted day is retained. + this.upsertAccountFactsDaily(context, history); + return { + ok: true, + stored: true, + unchanged: !flowChanged, + platform: command.platform, + account_key: command.account_key, + observation_date: existingRow.observation_date, + observed_finished_at: existingRow.observed_finished_at, + }; + } + const updatedAt = new Date().toISOString(); + const payloadJson = JSON.stringify(history); + this.sql.exec( + `INSERT INTO account_facts_observation ( + platform, account_key, payload_json, observed_finished_at, observation_date, + target_id, source_binding_id, account_scope, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(platform, account_key) DO UPDATE SET + payload_json = excluded.payload_json, + observed_finished_at = excluded.observed_finished_at, + observation_date = excluded.observation_date, + target_id = excluded.target_id, + source_binding_id = excluded.source_binding_id, + account_scope = excluded.account_scope, + updated_at = excluded.updated_at`, + command.platform, + command.account_key, + payloadJson, + history.observed_finished_at, + history.observation_date, + history.target_id, + history.source_binding.id, + command.account_scope, + updatedAt, + ); + this.upsertAccountFactsDaily(context, history); + return { + ok: true, + stored: true, + unchanged: false, + platform: command.platform, + account_key: command.account_key, + observation_date: history.observation_date, + observed_finished_at: history.observed_finished_at, + }; + } + + putAccountFactsHistoricalObservation(command) { + const context = this.accountFactsStorageContext(command); + const sourceId = command.history?.source_binding?.id; + if (![context.source_binding_id, ...(context.history_source_binding_ids || [])].includes(sourceId)) { + throw new HttpError("account_facts_binding_unmatched", 409); + } + const history = this.validateAccountFactsStoragePayload(command.history, + { ...context, source_binding_id: sourceId }, { historical: true }); + // Deliberately bypass latest observation and external-flow writes. Reuse + // daily storage with replacement and retention deletion disabled. + return { ok: true, stored: true, ...this.upsertAccountFactsDaily(context, history, { appendOnly: true }) }; + } + + readAccountFactsDailyRow(platform, accountKey, observationDate, context = null) { + const row = this.sql.exec( + `SELECT payload_json, observed_finished_at, observation_date, target_id, source_binding_id, account_scope + FROM account_facts_daily WHERE platform = ? AND account_key = ? AND observation_date = ?`, + platform, + accountKey, + observationDate, + ).toArray()[0]; + if (!row) return null; + let payload; + try { payload = JSON.parse(row.payload_json); } catch { throw new HttpError("account_facts_stored_invalid", 409); } + if (context) { + if (platform !== context.platform || accountKey !== context.account_key + || row.target_id !== context.target_id + || row.source_binding_id !== context.source_binding_id + || row.account_scope !== context.account_scope) { + throw new HttpError("account_facts_identity_mismatch", 409); + } + payload = this.validateAccountFactsStoragePayload(payload, context); + } + return { + platform, + account_key: accountKey, + observation_date: row.observation_date, + payload, + observed_finished_at: row.observed_finished_at, + target_id: row.target_id, + source_binding_id: row.source_binding_id, + account_scope: row.account_scope, + }; + } + + upsertAccountFactsDaily(context, history, { appendOnly = false } = {}) { + const { platform, account_key: accountKey, account_scope: accountScope } = context; + const existing = this.validatePriorAccountFactsForAccount( + this.readAccountFactsDailyRow(platform, accountKey, history.observation_date), + context, + ); + if (appendOnly && existing) { + if (existing.observed_finished_at !== history.observed_finished_at + || canonicalizeAccountFactsHistory(existing.payload) !== canonicalizeAccountFactsHistory(history)) { + throw new HttpError("account_facts_daily_conflict", 409); + } + return { unchanged: true }; + } + let decision; + try { + decision = decideAccountFactsDailyUpsert(existing?.payload || null, history); + } catch (error) { + if (error instanceof AccountFactsError) throw new HttpError(error.code, error.status || 400); + throw error; + } + if (decision.action === "reject") throw new HttpError(decision.reason, decision.status || 409); + if (decision.action === "skip" || decision.action === "keep") { + this.pruneAccountFactsDaily(platform, accountKey, history.observation_date); + return { unchanged: true }; + } + const updatedAt = new Date().toISOString(); + this.sql.exec( + `INSERT INTO account_facts_daily ( + platform, account_key, observation_date, payload_json, observed_finished_at, + target_id, source_binding_id, account_scope, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + ${appendOnly ? "" : `ON CONFLICT(platform, account_key, observation_date) DO UPDATE SET + payload_json = excluded.payload_json, + observed_finished_at = excluded.observed_finished_at, + target_id = excluded.target_id, + source_binding_id = excluded.source_binding_id, + account_scope = excluded.account_scope, + updated_at = excluded.updated_at`}`, + platform, + accountKey, + history.observation_date, + JSON.stringify(history), + history.observed_finished_at, + history.target_id, + history.source_binding.id, + accountScope, + updatedAt, + ); + if (!appendOnly) this.pruneAccountFactsDaily(platform, accountKey, history.observation_date); + return { unchanged: false }; + } + + pruneAccountFactsDaily(platform, accountKey, latestDate) { + const cutoff = historyRetentionCutoff(latestDate, ACCOUNT_FACTS_HISTORY_MAX_DAYS); + if (!cutoff) return; + this.sql.exec( + "DELETE FROM account_facts_daily WHERE platform = ? AND account_key = ? AND observation_date < ?", + platform, + accountKey, + cutoff, + ); + } + + readAccountFactsDailyHistory(command) { + const context = this.accountFactsStorageContext(command); + const { platform, account_key: accountKey } = context; + if (command.projection === "external-flow-coverage") { + return { ok: true, ...readExternalFlowCoverage(this.sql, context) }; + } + if (command.projection !== undefined) { + const dates = accountFactsIdentityDates(command.from, command.to); + if (command.projection !== "identity" || !dates) { + throw new HttpError("invalid_account_facts_identity_query", 400); + } + // Deliberately exclude payload_json: identity inspection must not read assets. + const rows = this.sql.exec( + `SELECT observation_date, observed_finished_at, target_id, source_binding_id, account_scope + FROM account_facts_daily WHERE platform = ? AND account_key = ? + AND observation_date >= ? AND observation_date <= ? ORDER BY observation_date ASC`, + platform, accountKey, dates[0], dates.at(-1), + ).toArray(); + const same = (value, expected) => typeof value === "string" && value.length > 0 && value.length <= 512 + ? value === expected : null; + return { ok: true, records: dates.map(date => { + const matches = rows.filter(row => row.observation_date === date); + const row = matches.length === 1 ? matches[0] : null; + const source = row?.source_binding_id; + return { + observation_date: date, observed_finished_at: row?.observed_finished_at ?? null, + target_id_matches: same(row?.target_id, context.target_id), + source_binding_id_matches: same(source, context.source_binding_id), + account_scope_matches: same(row?.account_scope, context.account_scope), + record_revision: null, + row_present: matches.length > 1 ? null : Boolean(row), + source_binding_id_trusted: typeof source === "string" && source.length > 0 && source.length <= 512 + ? [context.source_binding_id, ...(context.history_source_binding_ids || [])].includes(source) : null, + stored_source_binding_id: typeof source === "string" && /^[a-f0-9]{64}$/.test(source) ? source : null, + }; + }) }; + } + const rows = this.sql.exec( + `SELECT payload_json, observed_finished_at, observation_date, target_id, source_binding_id, account_scope + FROM account_facts_daily WHERE platform = ? AND account_key = ? ORDER BY observation_date ASC`, + platform, + accountKey, + ).toArray(); + const days = []; + for (const row of rows) { + // Same-account history may use an explicitly qualified prior source. + // Current observations and ordinary sync still require the current source. + if (row.target_id !== context.target_id + || ![context.source_binding_id, ...(context.history_source_binding_ids || [])].includes(row.source_binding_id) + || row.account_scope !== context.account_scope) continue; + let payload; + try { payload = JSON.parse(row.payload_json); } catch { throw new HttpError("account_facts_stored_invalid", 409); } + payload = this.validateAccountFactsStoragePayload(payload, { ...context, source_binding_id: row.source_binding_id }); + days.push({ + observation_date: row.observation_date, + payload, + observed_finished_at: row.observed_finished_at, + target_id: row.target_id, + source_binding_id: row.source_binding_id, + account_scope: row.account_scope, + }); + } + return { ok: true, days, + externalFlow: readExternalFlow(this.sql, context, { dateOnly: !this.externalFlowEnabled }), + }; + } + + readAccountFactsObservation(command) { + const context = this.accountFactsStorageContext(command); + const row = this.readAccountFactsRow(context.platform, context.account_key, context); + return { ok: true, observation: row }; + } + + listAccountFactsObservations(command) { + const wanted = Array.isArray(command.accounts) ? command.accounts : []; + const observations = []; + for (const item of wanted) { + if (!item || typeof item.platform !== "string" || typeof item.account_key !== "string") continue; + const context = this.accountFactsStorageContext(item); + try { + const row = this.readAccountFactsRow(context.platform, context.account_key, context); + if (row) observations.push(row); + } catch (error) { + // A trusted binding rotation makes older rows ineligible for display. + // Keep them stored for audit, but treat them as absent until refreshed. + if (!(error instanceof HttpError) || error.message !== "account_facts_identity_mismatch") throw error; + } + } + return { ok: true, observations }; + } + + accountSettingsCommand(command) { + if (!command || typeof command.actor !== "string" || !command.actor) throw new HttpError("runtime_instance_actor_required", 400); + if (command.action === "risk_profile_read") return this.readRiskProfileAuthority(command.legacy); + if (command.action === "risk_profile_replace") return this.replaceRiskProfileAuthority(command); + if (command.action === "risk_profile_set") return this.setRiskProfileAuthority(command); + if (command.action === "account_declarations_read") { + const state = this.read(); + if (!state.initialized) throw new HttpError("runtime_instances_not_initialized", 409); + return { declarations: state.instances.filter(item => item.kind === "existing").map(item => ({ + platform: item.platform, key: item.key, binding_environment: accountSettingsIdentity(item).broker_environment, + ...this.accountSettingDeclaration(item, accountSettingsIdentity(item)), + })) }; + } + if (command.action === "account_settings_read") return this.readAccountSettings(command); + if (command.action === "account_settings_save") return this.saveAccountSettings(command); + if (command.action === "account_settings_profile_application_lookup") return this.readAccountSettingsProfileApplication(command); + if (command.action === "account_settings_profile_application_approve") return this.approveAccountSettingsProfileApplication(command); + if (command.action === "account_settings_profile_application_claim") return this.claimAccountSettingsProfileApplication(command); + throw new HttpError("unsupported_account_settings_action", 400); + } + + ensureRiskProfileAuthority(legacy) { + const row = this.sql.exec("SELECT revision, imported, bindings_json FROM risk_profile_authority WHERE id = 1").toArray()[0]; + if (row) return row; + if (legacy?.state === "blocked") throw new HttpError("risk_profile_bindings_invalid", 409); + const bindings = legacy?.state === "valid" && Array.isArray(legacy.bindings) ? legacy.bindings : []; + const payload = JSON.stringify(bindings); + this.sql.exec("INSERT INTO risk_profile_authority (id, revision, imported, bindings_json) VALUES (1, 0, 1, ?)", payload); + return { revision: 0, imported: 1, bindings_json: payload }; + } + + storedRiskBindings(row) { + let bindings; + try { bindings = JSON.parse(row.bindings_json); } catch { throw new HttpError("risk_profile_bindings_invalid", 409); } + if (!Array.isArray(bindings)) throw new HttpError("risk_profile_bindings_invalid", 409); + return bindings; + } + + assertStoredRiskBinding(binding, accountOptions) { + if (!binding || Array.isArray(binding) || typeof binding !== "object") throw new HttpError("invalid_risk_profile_binding", 400); + if (binding.no_order !== true || binding.execution_authority_granted !== false) throw new HttpError("risk_profile_execution_authority_rejected", 400); + if (binding.scope_id !== riskProfileScopeId(binding.platform, binding.target_name)) throw new HttpError("invalid_risk_profile_binding", 400); + if (!RISK_PROFILE_PREFERENCES.includes(binding.profile_selection?.risk_preference)) throw new HttpError("invalid_risk_profile_binding", 400); + const policy = RISK_TIER_POLICY_BINDINGS[binding.profile_selection?.risk_preference]; + if (!policy || binding.profile_selection?.profile_id !== policy.profile_id) { + throw new HttpError("risk_profile_policy_binding_invalid", 409); + } + if (!riskProfileBindingTargets(accountOptions).some((target) => target.platform === binding.platform && target.target_name === binding.target_name)) { + throw new HttpError("risk_profile_binding_target_not_configured", 409); + } + } + + writeRiskProfileAuthority(row, bindings) { + const current = this.storedRiskBindings(row); + const same = canonicalResearchTaskJson(current) === canonicalResearchTaskJson(bindings); + const revision = same ? Number(row.revision) : Number(row.revision) + 1; + if (!same) this.sql.exec("UPDATE risk_profile_authority SET revision = ?, bindings_json = ? WHERE id = 1", revision, JSON.stringify(bindings)); + const state = this.read(); + return { + ok: true, + revision, + bindings, + account_options: runtimeInstanceAccountOptions(state.instances), + instance_revision: state.revision, + initialized: state.initialized, + no_order: true, + execution_authority_granted: false, + }; + } + + readRiskProfileAuthority(legacy) { + const row = this.ensureRiskProfileAuthority(legacy); + const state = this.read(); + return { + ok: true, + revision: Number(row.revision), + bindings: this.storedRiskBindings(row), + account_options: runtimeInstanceAccountOptions(state.instances), + instance_revision: state.revision, + initialized: state.initialized, + no_order: true, + execution_authority_granted: false, + }; + } + + requireRiskRevision(command, row) { + if (!Number.isSafeInteger(command.expected_revision) || command.expected_revision !== Number(row.revision)) { + throw new HttpError("risk_profile_revision_conflict", 409); + } + } + + replaceRiskProfileAuthority(command) { + const row = this.ensureRiskProfileAuthority(command.legacy); + this.requireRiskRevision(command, row); + const options = runtimeInstanceAccountOptions(this.read().instances); + if (!Array.isArray(command.bindings)) throw new HttpError("invalid_risk_profile_binding", 400); + for (const binding of command.bindings) this.assertStoredRiskBinding(binding, options); + const bindings = [...command.bindings].sort((left, right) => left.scope_id.localeCompare(right.scope_id)); + return this.writeRiskProfileAuthority(row, bindings); + } + + setRiskProfileAuthority(command) { + const row = this.ensureRiskProfileAuthority(command.legacy); + this.requireRiskRevision(command, row); + const options = runtimeInstanceAccountOptions(this.read().instances); + if (!riskProfileBindingTargets(options).some((target) => target.platform === command.platform && target.target_name === command.target_name)) { + throw new HttpError("risk_profile_binding_target_not_configured", 409); + } + const scopeId = riskProfileScopeId(command.platform, command.target_name); + const current = this.storedRiskBindings(row).filter((binding) => binding.scope_id !== scopeId); + if (command.risk_change === "clear") return this.writeRiskProfileAuthority(row, current); + if (command.risk_change !== "set") throw new HttpError("invalid_risk_profile_change", 400); + this.assertStoredRiskBinding(command.binding, options); + if (command.binding.scope_id !== scopeId) throw new HttpError("invalid_risk_profile_binding", 400); + current.push(command.binding); + current.sort((left, right) => left.scope_id.localeCompare(right.scope_id)); + return this.writeRiskProfileAuthority(row, current); + } + + existingAccountInstance(platform, key) { + const state = this.read(); + if (!state.initialized) throw new HttpError("runtime_instances_not_initialized", 409); + const item = state.instances.find((candidate) => candidate.kind === "existing" && candidate.platform === platform && candidate.key === key); + if (!item) throw new HttpError("account_settings_not_found", 404); + return { state, item, identity: accountSettingsIdentity(item) }; + } + + accountSettingDraftView(item, identity) { + const row = this.sql.exec( + "SELECT revision, identity_json, overrides_json FROM account_setting_draft WHERE platform = ? AND account_key = ?", + item.platform, item.key, + ).toArray()[0]; + if (!row) return { status: "empty", revision: 0, identity: null, current_identity: identity, overrides: {} }; + let storedIdentity = null; + let overrides = {}; + try { + storedIdentity = JSON.parse(row.identity_json); + overrides = JSON.parse(row.overrides_json); + } catch { + throw new HttpError("account_settings_draft_invalid", 409); + } + const conflict = canonicalResearchTaskJson(storedIdentity) !== canonicalResearchTaskJson(identity); + return { + status: conflict ? "identity_conflict" : "current", + revision: Number(row.revision), + identity: storedIdentity, + current_identity: identity, + overrides: overrides && typeof overrides === "object" && !Array.isArray(overrides) ? overrides : {}, + }; + } + + accountSettingDeclaration(item, identity) { + const row = this.sql.exec("SELECT revision, identity_json, declaration_json FROM account_setting_declaration WHERE platform = ? AND account_key = ?", item.platform, item.key).toArray()[0]; + if (!row) return { status: "empty", revision: 0, environment: null, account_type: null, source: "user_declared" }; + const matches = canonicalResearchTaskJson(JSON.parse(row.identity_json)) === canonicalResearchTaskJson(identity); + const value = JSON.parse(row.declaration_json); + return { status: matches ? "current" : "identity_conflict", revision: Number(row.revision), + environment: matches ? value.environment : null, account_type: matches ? value.account_type : null, source: "user_declared" }; + } + + saveAccountSettingDeclaration(item, identity, command) { + if (!command.identity || canonicalResearchTaskJson(command.identity) !== canonicalResearchTaskJson(identity)) throw new HttpError("account_settings_identity_conflict", 409); + const current = this.accountSettingDeclaration(item, identity); + if (!Number.isSafeInteger(command.expected_declaration_revision) || command.expected_declaration_revision !== current.revision) throw new HttpError("account_settings_declaration_revision_conflict", 409); + if (current.status === "identity_conflict") throw new HttpError("account_settings_identity_conflict", 409); + const value = command.declaration; + if (!value || Array.isArray(value) || typeof value !== "object" || Object.keys(value).some(k => !["environment", "account_type"].includes(k)) + || ![null, "live", "paper"].includes(value.environment) || ![null, "cash", "margin"].includes(value.account_type)) throw new HttpError("invalid_account_settings_declaration", 400); + if (current.status === "current" && current.environment === value.environment && current.account_type === value.account_type) return; + this.sql.exec("INSERT INTO account_setting_declaration (platform, account_key, revision, identity_json, declaration_json) VALUES (?, ?, ?, ?, ?) ON CONFLICT (platform, account_key) DO UPDATE SET revision = excluded.revision, identity_json = excluded.identity_json, declaration_json = excluded.declaration_json", item.platform, item.key, current.revision + 1, JSON.stringify(identity), JSON.stringify(value)); + } + + readAccountSettings(command) { + const { state, item, identity } = this.existingAccountInstance(command.platform, command.key); + const risk = this.readRiskProfileAuthority(command.legacy); + const scopeId = riskProfileScopeId(item.platform, identity.target_name); + const binding = risk.bindings.find((entry) => entry.scope_id === scopeId) || null; + return { + ok: true, + found: true, + platform: item.platform, + key: item.key, + instance_revision: state.revision, + enabled: item.enabled, + identity, + config: item.config, + draft: this.accountSettingDraftView(item, identity), + declaration: this.accountSettingDeclaration(item, identity), + risk: { + revision: risk.revision, + scope_id: scopeId, + preference: binding?.profile_selection?.risk_preference || null, + binding, + }, + bindings: risk.bindings, + account_options: risk.account_options, + no_order: true, + execution_authority_granted: false, + }; + } + + saveAccountSettingDraft(item, identity, command) { + if (!command.identity || Array.isArray(command.identity) || typeof command.identity !== "object") { + throw new HttpError("account_settings_identity_required", 400); + } + if (canonicalResearchTaskJson(command.identity) !== canonicalResearchTaskJson(identity)) { + throw new HttpError("account_settings_identity_conflict", 409); + } + if (!Number.isSafeInteger(command.expected_draft_revision) || command.expected_draft_revision < 0) { + throw new HttpError("account_settings_draft_revision_conflict", 409); + } + const row = this.sql.exec( + "SELECT revision, identity_json, overrides_json FROM account_setting_draft WHERE platform = ? AND account_key = ?", + item.platform, item.key, + ).toArray()[0]; + const now = new Date().toISOString(); + if (!row) { + if (command.expected_draft_revision !== 0) throw new HttpError("account_settings_draft_revision_conflict", 409); + const overrides = applyAccountSettingOverrides({}, command.overrides); + this.sql.exec( + "INSERT INTO account_setting_draft (platform, account_key, revision, identity_json, overrides_json, updated_at) VALUES (?, ?, 1, ?, ?, ?)", + item.platform, item.key, JSON.stringify(identity), JSON.stringify(overrides), now, + ); + return; + } + if (command.expected_draft_revision !== Number(row.revision)) throw new HttpError("account_settings_draft_revision_conflict", 409); + const storedIdentity = JSON.parse(row.identity_json); + const conflict = canonicalResearchTaskJson(storedIdentity) !== canonicalResearchTaskJson(identity); + if (conflict && command.acknowledge_identity_conflict !== true) throw new HttpError("account_settings_identity_conflict", 409); + const storedOverrides = JSON.parse(row.overrides_json); + const base = conflict ? {} : storedOverrides; + const overrides = applyAccountSettingOverrides(base, command.overrides); + if (!conflict && canonicalResearchTaskJson(overrides) === canonicalResearchTaskJson(storedOverrides)) return; + this.sql.exec( + "UPDATE account_setting_draft SET revision = ?, identity_json = ?, overrides_json = ?, updated_at = ? WHERE platform = ? AND account_key = ?", + Number(row.revision) + 1, JSON.stringify(identity), JSON.stringify(overrides), now, item.platform, item.key, + ); + } + + saveAccountSettings(command) { + const { item, identity } = this.existingAccountInstance(command.platform, command.key); + const hasDraft = Object.prototype.hasOwnProperty.call(command, "overrides"); + const hasRisk = Object.prototype.hasOwnProperty.call(command, "risk_change"); + const hasDeclaration = Object.prototype.hasOwnProperty.call(command, "declaration"); + if (!hasDraft && !hasRisk && !hasDeclaration) throw new HttpError("account_settings_change_required", 400); + if (hasDeclaration) this.saveAccountSettingDeclaration(item, identity, command); + if (hasDraft) this.saveAccountSettingDraft(item, identity, command); + if (hasRisk) { + if (!command.identity || Array.isArray(command.identity) || typeof command.identity !== "object") { + throw new HttpError("account_settings_identity_required", 400); + } + if (canonicalResearchTaskJson(command.identity) !== canonicalResearchTaskJson(identity)) { + throw new HttpError("account_settings_identity_conflict", 409); + } + this.setRiskProfileAuthority({ + ...command, + platform: identity.platform, + target_name: identity.target_name, + expected_revision: command.expected_risk_revision, + }); + } + return { ...this.readAccountSettings(command), adopted: false }; + } + + accountSettingsProfileApplicationFromRow(row) { + if (!row) return null; + let identity; + try { identity = JSON.parse(row.identity_json); } catch { throw new HttpError("account_settings_profile_application_invalid", 409); } + return { + request_key: row.request_key, + platform: row.platform, + key: row.account_key, + identity, + instance_revision: Number(row.instance_revision), + draft_revision: Number(row.draft_revision), + old_profile: row.old_profile, + new_profile: row.new_profile, + approved_by: row.approved_by, + approved_at: row.approved_at, + status: row.status, + workflow_run_id: row.workflow_run_id || null, + workflow_run_attempt: row.workflow_run_attempt || null, + claimed_at: row.claimed_at || null, + runtime_applied: false, + variables_updated: false, + no_order: true, + execution_authority_granted: false, + }; + } + + readAccountSettingsProfileApplication(command) { + const requestKey = String(command.request_key || ""); + if (!ACCOUNT_SETTINGS_PROFILE_APPLICATION_REQUEST_KEY.test(requestKey)) { + throw new HttpError("account_settings_profile_application_request_invalid", 400); + } + const row = this.sql.exec("SELECT * FROM account_setting_profile_application WHERE request_key = ?", requestKey).toArray()[0]; + return { ok: true, application: this.accountSettingsProfileApplicationFromRow(row) }; + } + + approveAccountSettingsProfileApplication(command) { + const requestKey = String(command.request_key || ""); + if (!ACCOUNT_SETTINGS_PROFILE_APPLICATION_REQUEST_KEY.test(requestKey)) { + throw new HttpError("account_settings_profile_application_request_invalid", 400); + } + if (!SUPPORTED_PLATFORMS.includes(command.platform) || typeof command.key !== "string" || !command.key) { + throw new HttpError("account_settings_account_required", 400); + } + if (!Number.isSafeInteger(command.expected_instance_revision) || command.expected_instance_revision < 0 + || !Number.isSafeInteger(command.expected_draft_revision) || command.expected_draft_revision < 0) { + throw new HttpError("account_settings_profile_application_revision_invalid", 400); + } + const identity = command.identity; + if (!accountSettingsIdentityIsComplete(identity)) throw new HttpError("account_settings_identity_required", 400); + const identityJson = canonicalResearchTaskJson(identity); + const oldProfile = accountSettingsProfileName(command.old_profile); + const newProfile = accountSettingsProfileName(command.new_profile); + if (!oldProfile || !newProfile || oldProfile === newProfile) throw new HttpError("account_settings_profile_application_profile_invalid", 400); + + const existing = this.sql.exec("SELECT * FROM account_setting_profile_application WHERE request_key = ?", requestKey).toArray()[0]; + if (existing) { + const sameRequest = existing.platform === command.platform + && existing.account_key === command.key + && existing.identity_json === identityJson + && Number(existing.instance_revision) === command.expected_instance_revision + && Number(existing.draft_revision) === command.expected_draft_revision + && existing.old_profile === oldProfile + && existing.new_profile === newProfile + && existing.approved_by === command.actor; + if (!sameRequest) throw new HttpError("account_settings_profile_application_conflict", 409); + return { ok: true, replayed: true, application: this.accountSettingsProfileApplicationFromRow(existing) }; + } + + if (command.current_profile !== oldProfile || command.runtime_target_enabled !== false) { + throw new HttpError("account_settings_profile_application_runtime_conflict", 409); + } + const { state, item, identity: currentIdentity } = this.existingAccountInstance(command.platform, command.key); + if (state.revision !== command.expected_instance_revision || identityJson !== canonicalResearchTaskJson(currentIdentity)) { + throw new HttpError("account_settings_profile_application_identity_conflict", 409); + } + const draft = this.accountSettingDraftView(item, currentIdentity); + if (draft.status !== "current" || draft.revision !== command.expected_draft_revision + || canonicalResearchTaskJson(draft.identity) !== identityJson + || Object.keys(draft.overrides).length !== 1 + || draft.overrides.strategy_profile !== newProfile) { + throw new HttpError("account_settings_profile_application_draft_conflict", 409); + } + if (isDcaProfile(oldProfile) || isDcaProfile(newProfile)) { + throw new HttpError("account_settings_profile_application_profile_only_required", 409); + } + + const priorApprovals = this.sql.exec( + "SELECT request_key, identity_json, instance_revision, draft_revision, old_profile, new_profile FROM account_setting_profile_application WHERE platform = ? AND account_key = ? AND status = 'approved'", + command.platform, command.key, + ).toArray(); + for (const prior of priorApprovals) { + const stale = prior.identity_json !== identityJson + || Number(prior.instance_revision) !== state.revision + || Number(prior.draft_revision) !== draft.revision + || prior.old_profile !== oldProfile + || prior.new_profile !== newProfile; + if (stale) { + this.sql.exec( + "UPDATE account_setting_profile_application SET status = 'invalidated' WHERE request_key = ? AND status = 'approved'", + prior.request_key, + ); + } + } + const active = this.sql.exec( + "SELECT request_key FROM account_setting_profile_application WHERE platform = ? AND account_key = ? AND status IN ('approved', 'claimed') LIMIT 1", + command.platform, command.key, + ).toArray()[0]; + if (active) throw new HttpError("account_settings_profile_application_pending", 409); + + const approvedAt = new Date().toISOString(); + this.sql.exec( + `INSERT INTO account_setting_profile_application + (request_key, platform, account_key, identity_json, instance_revision, draft_revision, + old_profile, new_profile, approved_by, approved_at, status, workflow_run_id, + workflow_run_attempt, claimed_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'approved', NULL, NULL, NULL)`, + requestKey, command.platform, command.key, identityJson, command.expected_instance_revision, + command.expected_draft_revision, oldProfile, newProfile, command.actor, approvedAt, + ); + const row = this.sql.exec("SELECT * FROM account_setting_profile_application WHERE request_key = ?", requestKey).toArray()[0]; + return { ok: true, replayed: false, application: this.accountSettingsProfileApplicationFromRow(row) }; + } + + claimAccountSettingsProfileApplication(command) { + const requestKey = String(command.request_key || ""); + if (!ACCOUNT_SETTINGS_PROFILE_APPLICATION_REQUEST_KEY.test(requestKey)) { + throw new HttpError("account_settings_profile_application_request_invalid", 400); + } + const row = this.sql.exec("SELECT * FROM account_setting_profile_application WHERE request_key = ?", requestKey).toArray()[0]; + if (!row) throw new HttpError("account_settings_profile_application_not_found", 404); + const workflowRunId = String(command.workflow_run_id || ""); + const workflowRunAttempt = String(command.workflow_run_attempt || ""); + if (!/^\d{1,20}$/.test(workflowRunId) || !/^[1-9]\d{0,5}$/.test(workflowRunAttempt)) { + throw new HttpError("account_settings_profile_application_run_invalid", 400); + } + const identityJson = accountSettingsIdentityIsComplete(command.identity) + ? canonicalResearchTaskJson(command.identity) + : ""; + const safeResult = { no_order: true, execution_authority_granted: false, runtime_applied: false, variables_updated: false }; + const boundRequest = identityJson === row.identity_json + && Number(command.expected_instance_revision) === Number(row.instance_revision) + && Number(command.expected_draft_revision) === Number(row.draft_revision) + && command.old_profile === row.old_profile + && command.new_profile === row.new_profile + && command.current_profile === row.old_profile + && command.runtime_target_enabled === false; + if (!boundRequest) throw new HttpError("account_settings_profile_application_conflict", 409); + if (row.status === "claimed") { + if (row.workflow_run_id !== workflowRunId || row.workflow_run_attempt !== workflowRunAttempt) { + throw new HttpError("account_settings_profile_application_already_claimed", 409); + } + return { ok: true, claimed: false, replayed: true, application: this.accountSettingsProfileApplicationFromRow(row), ...safeResult }; + } + if (row.status !== "approved") throw new HttpError("account_settings_profile_application_conflict", 409); + if (!Number.isSafeInteger(command.expected_instance_revision) || !Number.isSafeInteger(command.expected_draft_revision)) { + throw new HttpError("account_settings_profile_application_revision_invalid", 409); + } + const { state, item, identity: currentIdentity } = this.existingAccountInstance(row.platform, row.account_key); + if (state.revision !== Number(row.instance_revision) + || canonicalResearchTaskJson(currentIdentity) !== row.identity_json) { + throw new HttpError("account_settings_profile_application_identity_conflict", 409); + } + const draft = this.accountSettingDraftView(item, currentIdentity); + if (draft.status !== "current" || draft.revision !== Number(row.draft_revision) + || canonicalResearchTaskJson(draft.identity) !== row.identity_json + || Object.keys(draft.overrides).length !== 1 + || draft.overrides.strategy_profile !== row.new_profile) { + throw new HttpError("account_settings_profile_application_draft_conflict", 409); + } + const claimedAt = new Date().toISOString(); + this.sql.exec( + `UPDATE account_setting_profile_application + SET status = 'claimed', workflow_run_id = ?, workflow_run_attempt = ?, claimed_at = ? + WHERE request_key = ? AND status = 'approved'`, + workflowRunId, workflowRunAttempt, claimedAt, requestKey, + ); + const claimedRow = this.sql.exec("SELECT * FROM account_setting_profile_application WHERE request_key = ?", requestKey).toArray()[0]; + return { ok: true, claimed: true, replayed: false, application: this.accountSettingsProfileApplicationFromRow(claimedRow), ...safeResult }; + } + + runtimeControlCommand(command) { + const platform = command?.platform; + const key = command?.key; + if (!["ibkr", "schwab"].includes(platform) || typeof key !== "string" || !key) throw new HttpError("runtime_control_target_invalid", 400); + const found = this.existingAccountInstance(platform, key); + const identity = found.identity; + if (!runtimeControlIdentityIsComplete(identity)) throw new HttpError("runtime_control_identity_incomplete", 409); + const targetId = `${platform}/${identity.target_name}`; + const latest = () => { + const row = this.sql.exec("SELECT * FROM hk_stop_request WHERE target_id = ? ORDER BY requested_at DESC, rowid DESC LIMIT 1", targetId).toArray()[0]; + return row ? { ...this.hkStopPublic(row), result_json: row.result_json || null } : null; + }; + if (command.action === "runtime_control_read") return { ok: true, record: latest() }; + const requestId = String(command.request_id || ""); + if (!HK_STOP_REQUEST_ID.test(requestId)) throw new HttpError("runtime_control_request_invalid", 400); + const stored = this.hkStopStored(requestId); + if (command.action === "runtime_control_claim") { + if (!["enable", "disable"].includes(command.operation) || !Number.isSafeInteger(command.source_revision) + || found.state.revision !== command.source_revision + || canonicalResearchTaskJson(identity) !== canonicalResearchTaskJson(command.identity)) throw new HttpError("runtime_control_identity_conflict", 409); + if (stored) { + if (stored.target_id !== targetId || stored.action !== command.operation || stored.identity_json !== canonicalResearchTaskJson(identity)) throw new HttpError("runtime_control_identity_conflict", 409); + return { ok: true, state: "replay", record: stored }; + } + const open = this.hkStopPublic(this.sql.exec( + "SELECT * FROM hk_stop_request WHERE target_id = ? AND phase IN ('reserved', 'unknown', 'accepted', 'partial') ORDER BY requested_at ASC LIMIT 1", targetId, + ).toArray()[0]); + if (open) return { ok: true, state: "blocked", record: open }; + const now = new Date().toISOString(); + this.sql.exec(`INSERT INTO hk_stop_request + (request_id, target_id, identity_json, source_revision, action, requested_at, phase, updated_at) + VALUES (?, ?, ?, ?, ?, ?, 'reserved', ?)`, requestId, targetId, canonicalResearchTaskJson(identity), command.source_revision, command.operation, now, now); + return { ok: true, state: "reserved", record: this.hkStopStored(requestId) }; + } + if (!stored || stored.target_id !== targetId || stored.identity_json !== canonicalResearchTaskJson(identity)) throw new HttpError("runtime_control_identity_conflict", 409); + if (command.action === "runtime_control_record") { + if (!["accepted", "unknown", "rejected"].includes(command.phase)) throw new HttpError("runtime_control_result_invalid", 400); + if (command.run_id != null && !/^[1-9][0-9]*$/.test(String(command.run_id))) throw new HttpError("runtime_control_result_invalid", 400); + if (stored.workflow_run_id && command.run_id && String(stored.workflow_run_id) !== String(command.run_id)) throw new HttpError("runtime_control_result_invalid", 409); + if (stored.phase === "reserved") this.sql.exec( + "UPDATE hk_stop_request SET phase = ?, dispatch_result = ?, workflow_run_id = ?, updated_at = ? WHERE request_id = ? AND phase = 'reserved'", + command.phase, command.phase, command.run_id || null, new Date().toISOString(), requestId, + ); + else if (stored.phase === "unknown" && !stored.workflow_run_id && command.run_id) this.sql.exec( + "UPDATE hk_stop_request SET workflow_run_id = ?, updated_at = ? WHERE request_id = ? AND phase = 'unknown' AND workflow_run_id IS NULL", + String(command.run_id), new Date().toISOString(), requestId, + ); + return { ok: true, record: this.hkStopStored(requestId) }; + } + if (command.action === "runtime_control_capture") { + if (!stored.workflow_run_id || String(stored.workflow_run_id) !== String(command.run_id) + || !/^[1-9][0-9]*$/.test(String(command.platform_run_id || ""))) throw new HttpError("runtime_control_result_invalid", 400); + let details = {}; + try { details = JSON.parse(latest()?.result_json || "{}"); } catch { throw new HttpError("runtime_control_result_invalid", 409); } + if (details.platform_run_id && details.platform_run_id !== String(command.platform_run_id)) throw new HttpError("runtime_control_result_invalid", 409); + details.platform_run_id = String(command.platform_run_id); + if (["platform_result_unavailable", "platform_dispatch_unverified"].includes(command.reason)) details.reason = command.reason; + this.sql.exec("UPDATE hk_stop_request SET result_json = ?, updated_at = ? WHERE request_id = ?", JSON.stringify(details), new Date().toISOString(), requestId); + return { ok: true, record: latest() }; + } + if (command.action === "runtime_control_begin_check") { + let details = {}; + try { details = JSON.parse(latest()?.result_json || "{}"); } catch { throw new HttpError("runtime_control_result_invalid", 409); } + if (!["unknown", "accepted", "partial"].includes(stored.phase) || !details.platform_run_id) throw new HttpError("runtime_control_check_unavailable", 409); + if (details.check_id && !details.check_complete) return { ok: true, state: "replay", record: latest() }; + if (!HK_STOP_REQUEST_ID.test(String(command.check_id || ""))) throw new HttpError("runtime_control_request_invalid", 400); + Object.assign(details, { check_id: command.check_id, check_run_id: null, check_complete: false, check_requested_at: new Date().toISOString() }); + this.sql.exec("UPDATE hk_stop_request SET result_json = ?, updated_at = ? WHERE request_id = ?", JSON.stringify(details), new Date().toISOString(), requestId); + return { ok: true, state: "reserved", record: latest() }; + } + if (command.action === "runtime_control_record_check") { + let details = {}; + try { details = JSON.parse(latest()?.result_json || "{}"); } catch { throw new HttpError("runtime_control_result_invalid", 409); } + if (!details.check_id || details.check_id !== command.check_id) throw new HttpError("runtime_control_result_invalid", 409); + if (command.run_id != null && !/^[1-9][0-9]*$/.test(String(command.run_id))) throw new HttpError("runtime_control_result_invalid", 400); + if (details.check_run_id && command.run_id && details.check_run_id !== String(command.run_id)) throw new HttpError("runtime_control_result_invalid", 409); + if (command.run_id) details.check_run_id = String(command.run_id); + if (command.complete === true) details.check_complete = true; + if (command.reason === "readback_dispatch_rejected") details.reason = command.reason; + this.sql.exec("UPDATE hk_stop_request SET result_json = ?, updated_at = ? WHERE request_id = ?", JSON.stringify(details), new Date().toISOString(), requestId); + return { ok: true, record: latest() }; + } + if (command.action === "runtime_control_resolve") { + if (!["applied", "partial", "protected", "rejected"].includes(command.phase) || !command.run_id + || String(stored.workflow_run_id) !== String(command.run_id)) throw new HttpError("runtime_control_result_invalid", 400); + if (command.check_id) { + let check = {}; + try { check = JSON.parse(latest()?.result_json || "{}"); } catch { throw new HttpError("runtime_control_result_invalid", 409); } + if (check.check_id !== command.check_id || check.check_run_id !== String(command.check_run_id)) throw new HttpError("runtime_control_result_invalid", 409); + } + const reason = ["runtime_control_workflow_permission_denied", "runtime_control_workflow_unavailable", "runtime_control_target_unsupported", "stop_source_permission_denied", "stop_source_not_accessible", "platform_dispatch_rejected", "platform_result_unavailable", "platform_dispatch_unverified"].includes(command.reason) ? command.reason : null; + let details = {}; + try { details = JSON.parse(latest()?.result_json || "{}"); } catch { throw new HttpError("runtime_control_result_invalid", 409); } + if (["accepted", "unknown", "partial"].includes(stored.phase)) this.sql.exec( + "UPDATE hk_stop_request SET phase = ?, result_json = ?, updated_at = ? WHERE request_id = ?", command.phase, JSON.stringify({ ...details, reason }), new Date().toISOString(), requestId, + ); + return { ok: true, record: latest() }; + } + throw new HttpError("runtime_control_action_invalid", 400); + } + + hkStopCommand(command) { + if (command?.action === "hk_stop_read") return { ok: true, record: this.hkStopOpen() || this.hkStopLatest() }; + if (command?.action === "hk_stop_claim") return this.hkStopClaim(command); + if (command?.action === "hk_stop_record") return this.hkStopRecord(command); + if (command?.action === "hk_stop_accept_result") return this.hkStopAcceptResult(command); + throw new HttpError("unsupported_hk_stop_action", 400); + } + + hkStopPublic(row) { + if (!row) return null; + return { + target_id: row.target_id, + request_id: row.request_id, + identity_json: row.identity_json, + source_revision: Number(row.source_revision), + action: row.action, + requested_at: row.requested_at, + phase: row.phase, + dispatch_result: row.dispatch_result || null, + workflow_run_id: row.workflow_run_id || null, + workflow_run_attempt: row.workflow_run_attempt || null, + source_identity_sha256: row.source_identity_sha256 || null, + runtime_identity_sha256: row.runtime_identity_sha256 || null, + updated_at: row.updated_at, + }; + } + + hkStopStored(requestId) { + return this.hkStopPublic(this.sql.exec("SELECT * FROM hk_stop_request WHERE request_id = ?", requestId).toArray()[0]); + } + + hkStopOpen() { + return this.hkStopPublic(this.sql.exec( + "SELECT * FROM hk_stop_request WHERE target_id = ? AND phase IN ('reserved', 'unknown', 'accepted', 'stopped') ORDER BY requested_at ASC", + HK_STOP_TARGET_ID, + ).toArray()[0]); + } + + hkStopLatest() { + return this.hkStopPublic(this.sql.exec( + "SELECT * FROM hk_stop_request WHERE target_id = ? ORDER BY requested_at DESC", + HK_STOP_TARGET_ID, + ).toArray()[0]); + } + + hkStopLiveIdentity(sourceRevision, identity) { + let found; + try { + found = this.existingAccountInstance("longbridge", "hk"); + } catch { + throw new HttpError("hk_stop_source_conflict", 409); + } + if (found.state.revision !== sourceRevision) throw new HttpError("hk_stop_source_conflict", 409); + const live = hkStopSourceIdentity(found.item.config, found.item); + if (!hkStopIdentityComplete(live) || canonicalResearchTaskJson(live) !== canonicalResearchTaskJson(identity)) { + throw new HttpError("hk_stop_source_conflict", 409); + } + return live; + } + + hkStopClaim(command) { + const requestId = String(command.request_id || "").toLowerCase(); + if (!HK_STOP_REQUEST_ID.test(requestId)) throw new HttpError("invalid_hk_stop_request_id", 400); + if (command.action_name !== "stop") throw new HttpError("hk_stop_action_conflict", 409); + const identity = command.identity; + if (!identity || Array.isArray(identity) || typeof identity !== "object") throw new HttpError("hk_stop_identity_incomplete", 400); + if (!Number.isSafeInteger(command.source_revision)) throw new HttpError("hk_stop_source_conflict", 409); + const storedIdentity = canonicalResearchTaskJson(identity); + const existing = this.hkStopStored(requestId); + if (existing) { + if (existing.action !== "stop" || existing.identity_json !== storedIdentity) { + throw new HttpError("hk_stop_identity_conflict", 409); + } + return { ok: true, state: "replay", record: existing }; + } + const open = this.hkStopOpen(); + if (open) { + if (open.identity_json !== storedIdentity) throw new HttpError("hk_stop_identity_conflict", 409); + return { ok: true, state: "blocked", record: open }; + } + this.hkStopLiveIdentity(command.source_revision, identity); + const sourceHash = hkStopDigestOrEmpty(command.source_identity_sha256); + const runtimeHash = hkStopDigestOrEmpty(command.runtime_identity_sha256); + if ((sourceHash === "") !== (runtimeHash === "")) throw new HttpError("hk_stop_identity_incomplete", 400); + const now = new Date().toISOString(); + this.sql.exec( + `INSERT INTO hk_stop_request ( + request_id, target_id, identity_json, source_revision, action, requested_at, phase, dispatch_result, workflow_run_id, workflow_run_attempt, source_identity_sha256, runtime_identity_sha256, updated_at + ) VALUES (?, ?, ?, ?, 'stop', ?, 'reserved', NULL, NULL, NULL, ?, ?, ?)`, + requestId, HK_STOP_TARGET_ID, storedIdentity, command.source_revision, now, sourceHash || null, runtimeHash || null, now, + ); + return { ok: true, state: "reserved", record: this.hkStopStored(requestId) }; + } + + hkStopRecord(command) { + const requestId = String(command.request_id || "").toLowerCase(); + const phase = command.phase; + if (!HK_STOP_REQUEST_ID.test(requestId) || !["accepted", "rejected", "unknown"].includes(phase)) { + throw new HttpError("hk_stop_result_rejected", 400); + } + const existing = this.hkStopStored(requestId); + if (!existing) throw new HttpError("hk_stop_request_conflict", 409); + if (existing.phase === "stopped" || existing.phase !== "reserved") return { ok: true, recorded: false, record: existing }; + const now = new Date().toISOString(); + this.sql.exec( + "UPDATE hk_stop_request SET phase = ?, dispatch_result = ?, updated_at = ? WHERE request_id = ? AND phase = 'reserved'", + phase, phase, now, requestId, + ); + return { ok: true, recorded: true, record: this.hkStopStored(requestId) }; + } + + hkStopAcceptResult(command) { + const result = command?.result; + const now = Date.parse(command?.now); + if (!result?.producer || !result.readback) throw new HttpError("hk_stop_result_rejected", 400); + const requestId = String(result?.request_id || "").toLowerCase(); + if (!HK_STOP_REQUEST_ID.test(requestId) || !Number.isFinite(now)) throw new HttpError("hk_stop_result_rejected", 400); + const row = this.sql.exec("SELECT * FROM hk_stop_request WHERE request_id = ?", requestId).toArray()[0]; + if (!row) throw new HttpError("hk_stop_request_conflict", 409); + if (!row.source_identity_sha256 || !row.runtime_identity_sha256) { + throw new HttpError("hk_stop_result_unlinked", 409); + } + const requestedAt = Date.parse(row.requested_at); + const observedAt = Date.parse(result.observed_at); + if (!Number.isFinite(requestedAt) || !Number.isFinite(observedAt)) throw new HttpError("hk_stop_result_rejected", 400); + if (result.source_revision !== Number(row.source_revision) + || result.source_identity_sha256 !== row.source_identity_sha256 + || result.runtime_identity_sha256 !== row.runtime_identity_sha256 + || result.target_id !== row.target_id) { + throw new HttpError("hk_stop_identity_conflict", 409); + } + let live; + try { + live = this.existingAccountInstance("longbridge", "hk"); + } catch { + throw new HttpError("hk_stop_identity_conflict", 409); + } + const current = hkStopSourceIdentity(live.item.config, live.item); + if (!hkStopIdentityComplete(current) || canonicalResearchTaskJson(current) !== row.identity_json) { + throw new HttpError("hk_stop_identity_conflict", 409); + } + if (observedAt < requestedAt || observedAt > now + 60000) throw new HttpError("hk_stop_result_stale", 409); + const canonical = canonicalResearchTaskJson(result); + const sameRun = row.workflow_run_id === result.producer.run_id && String(row.workflow_run_attempt) === String(result.producer.run_attempt); + if (row.phase === "stopped") { + if (row.result_json === canonical && sameRun) return { ok: true, replayed: true, record: this.hkStopStored(requestId) }; + throw new HttpError("hk_stop_result_conflict", 409); + } + if (!["reserved", "accepted", "unknown"].includes(row.phase)) throw new HttpError("hk_stop_result_conflict", 409); + if (now - requestedAt > 24 * 60 * 60 * 1000) throw new HttpError("hk_stop_result_stale", 409); + const stamp = new Date(now).toISOString(); + this.sql.exec( + `UPDATE hk_stop_request + SET phase = 'stopped', workflow_run_id = ?, workflow_run_attempt = ?, result_json = ?, stopped_at = ?, updated_at = ? + WHERE request_id = ? AND phase IN ('reserved', 'accepted', 'unknown')`, + result.producer.run_id, String(result.producer.run_attempt), canonical, stamp, stamp, requestId, + ); + const stored = this.hkStopStored(requestId); + if (stored?.phase !== "stopped") throw new HttpError("hk_stop_result_conflict", 409); + return { ok: true, replayed: false, record: stored }; + } + + read() { + const row = this.sql.exec("SELECT revision, payload FROM instance_state WHERE id = 1").toArray()[0]; + return row ? { revision: row.revision, instances: JSON.parse(row.payload), initialized: true } : { revision: 0, instances: [], initialized: false }; + } + + response(state) { + return { + ok: true, ...state, account_options: runtimeInstanceAccountOptions(state.instances), + history: this.sql.exec("SELECT entry FROM instance_history ORDER BY revision DESC LIMIT 50").toArray().map((row) => JSON.parse(row.entry)), + no_order: true, execution_authority_granted: false, + }; + } + + async alarm() { await this.deskReadbacks.alarm(); } + + async fetch(request) { + try { + if (new URL(request.url).pathname === DIRECTORY_PATH) { + if (request.method !== "POST") throw new HttpError("method_not_allowed", 405); + const result = await this.directory.command(await request.json()); + return json(result, result.ok ? 200 : 503); + } + if (request.method === "GET") return json(this.response(this.read())); + if (request.method !== "POST") throw new HttpError("method_not_allowed", 405); + let command; + try { command = await request.json(); } catch { throw new HttpError("invalid_runtime_instance_request", 400); } + if (["desk_readback_start", "desk_readback_get", "desk_readback_fresh"].includes(command?.action)) { + try { return json(await this.deskReadbacks.command(command)); } + catch (error) { return deskErrorResponse(error); } + } + if (typeof command?.action === "string" && command.action.startsWith("rotation_")) { + return json(await durableRotationCommand(this.storage, this.sql, this.rotationEnv, command)); + } + if (["credential_maintenance_read", "credential_maintenance_save", "credential_maintenance_claim", "credential_maintenance_ack"].includes(command?.action)) { + return json(this.storage.transactionSync(() => credentialMaintenanceCommand(this.sql, command))); + } + if (ACCOUNT_DIAGNOSIS_DO_ACTIONS.has(command?.action)) { + return json(await this.accountDiagnosisCommand(command)); + } + if (["console_decision_history", "console_settings_history", "console_account_metadata", "console_audit_events"].includes(command?.action)) { + return json(this.consoleReadCommand(command)); + } + if (HUMAN_DECISION_DO_ACTIONS.has(command?.action)) { + return json(this.humanDecisionCommand(command)); + } + if (V7_PAPER_APPLICATION_DO_ACTIONS.has(command?.action)) { + return json(await this.applicationCommand(command)); + } + if (typeof command?.action === "string" && command.action.startsWith("ux1_")) { + return json(this.ux1ResearchCommand(command)); + } + if (["cycle_health_admission", "cycle_health_put", "cycle_health_read", "cycle_health_provision"].includes(command?.action)) { + if (command.action === "cycle_health_provision") { + return json(this.storage.transactionSync(() => this.cycleHealthProvisionCommand(command))); + } + return json(this.storage.transactionSync(() => this.cycleHealthCommand(command))); + } + if (ACCOUNT_SETTINGS_DO_ACTIONS.has(command?.action)) { + return json(this.storage.transactionSync(() => this.accountSettingsCommand(command))); + } + if (ACCOUNT_FACTS_DO_ACTIONS.has(command?.action)) { + const result = this.storage.transactionSync(() => this.accountFactsCommand(command)); + return json(result, command.action === "account_daily_native_return_research_read" && !result.ok ? 409 : 200); + } + if (MARKET_BENCHMARK_DO_ACTIONS.has(command?.action)) { + return json(this.storage.transactionSync(() => this.marketBenchmarkCommand(command))); + } + if (RUNTIME_CONTROL_DO_ACTIONS.has(command?.action)) { + return json(this.storage.transactionSync(() => this.runtimeControlCommand(command))); + } + if (HK_STOP_DO_ACTIONS.has(command?.action)) { + return json(this.storage.transactionSync(() => this.hkStopCommand(command))); + } + // No awaits or external I/O inside this SQLite transaction. Every state + // change, version advance, and history row commits together or rolls back. + return json(this.storage.transactionSync(() => { + const state = this.read(); + if (!command || typeof command.actor !== "string" || !command.actor) throw new HttpError("runtime_instance_actor_required", 400); + if (command.action !== "sync_defaults") { + if (!Number.isSafeInteger(command.expected_revision) || command.expected_revision < 0) throw new HttpError("runtime_instance_revision_required", 400); + if (command.expected_revision !== state.revision) throw new HttpError("runtime_instance_revision_conflict", 409); + } + const ts = new Date().toISOString(); + let changed = true; + let before = null; + let after = null; + if (command.action === "initialize") { + if (state.initialized) throw new HttpError("runtime_instances_already_initialized", 409); + for (const [platform, options] of Object.entries(command.account_options)) { + for (const config of options) { + if (state.instances.some((item) => item.platform === platform && (item.key === config.key || item.config.target_name === config.target_name))) throw new HttpError("runtime_instance_identity_conflict", 409); + state.instances.push({ platform, key: config.key, kind: "existing", config, enabled: null, platform_applied: null, application_status: "unknown", retirement_status: "none", created_at: ts, updated_at: ts }); + } + } + state.initialized = true; + } else { + if (!state.initialized) throw new HttpError("runtime_instances_not_initialized", 409); + const item = state.instances.find((candidate) => candidate.platform === command.platform && candidate.key === command.key); + if (command.action === "create") { + if (state.instances.length >= 120) throw new HttpError("runtime_instance_limit_reached", 409); + runtimeInstanceConflict(state.instances, command.platform, command.config); + after = { platform: command.platform, key: command.config.key, kind: "draft", config: command.config, enabled: false, platform_applied: false, application_status: "unapplied", retirement_status: "none", created_at: ts, updated_at: ts }; + state.instances.push(after); + } else if (command.action === "edit") { + if (!item || item.kind !== "draft" || item.retirement_status !== "none") throw new HttpError("runtime_instance_draft_edit_required", 409); + if (item.key !== command.config.key) throw new HttpError("runtime_instance_key_immutable", 409); + runtimeInstanceConflict(state.instances, command.platform, command.config, item.key); + before = structuredClone(item); + item.config = command.config; + item.updated_at = ts; + after = item; + } else if (command.action === "request_retirement") { + if (!item) throw new HttpError("runtime_instance_not_found", 404); + before = structuredClone(item); + changed = item.retirement_status !== "requested"; + item.retirement_status = "requested"; + if (changed) item.updated_at = ts; + after = item; + } else if (command.action === "set_broker_environment") { + if (!item || item.kind !== "existing") throw new HttpError("runtime_instance_existing_required", 409); + if (!BROKER_ENVIRONMENTS.includes(command.broker_environment) + || !supportedBrokerEnvironmentsForPlatform(command.platform).includes(command.broker_environment)) { + throw new HttpError("invalid_broker_environment", 400); + } + before = structuredClone(item); + changed = item.config.broker_environment !== command.broker_environment; + if (changed) { + item.config = { ...item.config, broker_environment: command.broker_environment }; + item.updated_at = ts; + } + after = item; + } else if (command.action === "sync_defaults") { + const current = runtimeInstanceAccountOptions(state.instances); + const update = updateAccountOptionsDefaultStrategy(current, command.inputs); + changed = update.changed; + for (const instance of state.instances) { + if (instance.kind !== "existing") continue; + const config = update.options[instance.platform]?.find((option) => option.key === instance.key); + if (config) { + const defaults = {}; + for (const field of ["default_execution_mode", "default_strategy_profile"]) { + if (instance.config[field] !== undefined) defaults[field] = instance.config[field]; + } + instance.config = { ...config, ...defaults }; + } + } + } else { + throw new HttpError("unsupported_runtime_instance_action", 400); + } + } + if (changed) { + state.revision += 1; + this.sql.exec("INSERT INTO instance_state (id, revision, payload) VALUES (1, ?, ?) ON CONFLICT(id) DO UPDATE SET revision = excluded.revision, payload = excluded.payload", state.revision, JSON.stringify(state.instances)); + try { + this.sql.exec("INSERT INTO instance_history (revision, entry) VALUES (?, ?)", state.revision, JSON.stringify({ revision: state.revision, ts, login: command.actor, action: command.action, before, after })); + } catch (error) { + // Fixture / storage aborts must surface as revision conflicts, not DO crashes. + throw new HttpError(error?.message || "runtime_instance_transaction_failed", 409); + } + } + return this.response(state); + })); + } catch (error) { + return json({ ok: false, error: error instanceof HttpError || error instanceof CycleHealthError || error instanceof CredentialMaintenanceError || error instanceof RotationError ? error.message : "runtime_instance_transaction_failed" }, error instanceof HttpError || error instanceof CycleHealthError || error instanceof CredentialMaintenanceError || error instanceof RotationError ? error.status : 409); + } + } + + accountDiagnosisCommand(command) { + return this.storage.transactionSync(() => { + if (!command || typeof command.actor !== "string" || !command.actor) { + throw new HttpError("runtime_instance_actor_required", 400); + } + if (command.action === "diagnosis_create") { + const autoSource = command.auto_source === "source_sync" ? "source_sync" : "manual"; + // Unclosed work keeps its original obligation regardless of age or fingerprint. + const unresolved = this.sql.exec("SELECT * FROM account_diagnosis_tasks WHERE target_id = ? AND status IN ('queued', 'running', 'unknown') ORDER BY updated_at DESC LIMIT 1", command.target_id).toArray()[0]; + if (unresolved) return { ok: true, created: false, task: accountDiagnosisTaskFromRow(unresolved) }; + const existing = autoSource === "source_sync" + ? this.sql.exec( + "SELECT * FROM account_diagnosis_tasks WHERE target_id = ? AND auto_source = ? AND updated_at >= ? ORDER BY updated_at DESC LIMIT 1", + command.target_id, autoSource, new Date(Number(command.now) - ACCOUNT_DIAGNOSIS_DEDUPE_WINDOW_MS).toISOString(), + ).toArray()[0] + : this.sql.exec( + "SELECT * FROM account_diagnosis_tasks WHERE target_id = ? AND state_fingerprint = ? ORDER BY updated_at DESC LIMIT 1", + command.target_id, command.state_fingerprint, + ).toArray()[0]; + const existingAge = existing ? Date.parse(existing.updated_at) : Number.NaN; + const existingIsRecent = Number.isFinite(existingAge) + && existingAge >= Number(command.now) - ACCOUNT_DIAGNOSIS_DEDUPE_WINDOW_MS; + if (existing && existingIsRecent) return { ok: true, created: false, task: accountDiagnosisTaskFromRow(existing) }; + const task = { + request_id: command.request_id, + platform: command.platform, + key: command.key, + target_id: command.target_id, + trigger: command.trigger, + observed_at: command.observed_at, + checks: command.checks, + state_fingerprint: command.state_fingerprint, + status: "queued", + workflow_run_id: null, + workflow_run_attempt: null, + job_id: null, + summary: "", + reason_code: "", + dispatch_state: "pending", + recheck_dispatch_state: "not_requested", + created_at: command.created_at, + updated_at: command.created_at, + }; + this.sql.exec( + `INSERT INTO account_diagnosis_tasks + (request_id, platform, account_key, target_id, trigger, observed_at, checks_json, state_fingerprint, + status, workflow_run_id, workflow_run_attempt, job_id, summary, reason_code, dispatch_state, + recheck_dispatch_state, created_at, updated_at, auto_source) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + task.request_id, task.platform, task.key, task.target_id, task.trigger, task.observed_at, + JSON.stringify(task.checks), task.state_fingerprint, task.status, null, null, null, + task.summary, task.reason_code, task.dispatch_state, task.recheck_dispatch_state, + task.created_at, task.updated_at, autoSource, + ); + return { ok: true, created: true, task }; + } + if (command.action === "diagnosis_latest") { + const row = this.sql.exec( + "SELECT * FROM account_diagnosis_tasks WHERE platform = ? AND account_key = ? ORDER BY updated_at DESC LIMIT 1", + command.platform, command.key, + ).toArray()[0]; + return { ok: true, task: accountDiagnosisTaskFromRow(row) }; + } + const requestId = command.request_id; + const row = this.sql.exec("SELECT * FROM account_diagnosis_tasks WHERE request_id = ?", requestId).toArray()[0]; + if (!row) throw new HttpError("account_diagnosis_request_not_found", 404); + if (command.action === "diagnosis_read") return { ok: true, task: accountDiagnosisTaskFromRow(row) }; + if (command.action === "diagnosis_native_intent") { + if (row.native_json) { + const old = JSON.parse(row.native_json); + if (canonicalResearchTaskJson(old.binding) !== canonicalResearchTaskJson(command.native.binding) + || old.request_sha256 !== command.native.request_sha256) throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); + return { ok: true, created: false, task: accountDiagnosisTaskFromRow(row) }; + } + if (row.workflow_run_id || row.workflow_run_attempt || row.status !== "queued" || row.dispatch_state !== "pending") { + throw new HttpError("account_diagnosis_legacy_retired", 409); + } + this.sql.exec("UPDATE account_diagnosis_tasks SET native_json = ?, updated_at = ? WHERE request_id = ?", + JSON.stringify(command.native), command.updated_at, requestId); + return { ok: true, created: true, task: accountDiagnosisTaskFromRow(this.sql.exec("SELECT * FROM account_diagnosis_tasks WHERE request_id = ?", requestId).toArray()[0]) }; + } + if (command.action === "diagnosis_native_cancel") { + if (!row.native_json) throw new HttpError("account_diagnosis_legacy_retired", 409); + const native = JSON.parse(row.native_json); + // The first durable terminal receipt or cancellation wins atomically. + if (native.receipt) return { ok: true, send_cancel: false, task: accountDiagnosisTaskFromRow(row) }; + const send = Boolean(native.task_id && !native.cancel_sent); + native.cancel_requested = true; + native.cancel_sent = native.cancel_sent || send; + native.phase = "hold"; + this.sql.exec("UPDATE account_diagnosis_tasks SET native_json = ?, status = 'unknown', summary = '', reason_code = '', recheck_dispatch_state = 'not_requested', updated_at = ? WHERE request_id = ?", + JSON.stringify(native), command.updated_at, requestId); + return { ok: true, send_cancel: send, task: accountDiagnosisTaskFromRow(this.sql.exec("SELECT * FROM account_diagnosis_tasks WHERE request_id = ?", requestId).toArray()[0]) }; + } + if (command.action === "diagnosis_native_save") { + if (!row.native_json) throw new HttpError("account_diagnosis_legacy_retired", 409); + const old = JSON.parse(row.native_json), next = command.native; + for (const field of ["version", "binding", "request", "input_sha256", "request_sha256", "operation_key", "deadline"]) { + if (canonicalResearchTaskJson(old[field]) !== canonicalResearchTaskJson(next[field])) throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); + } + if ((old.task_id && old.task_id !== next.task_id) || (old.task_deadline !== null && old.task_deadline !== next.task_deadline) + || (old.claim && canonicalResearchTaskJson(old.claim) !== canonicalResearchTaskJson(next.claim)) + || (old.cancel_sent && !next.cancel_sent) || (old.cancel_requested && !next.cancel_requested) + || !ACCOUNT_DIAGNOSIS_STATUSES.includes(command.status)) { + throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); + } + if (old.receipt) { + if (old.receipt !== next.receipt) throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); + return { ok: true, changed: false, task: accountDiagnosisTaskFromRow(row) }; + } + if (command.status === "succeeded" && (old.cancel_requested || old.cancel_sent + || Date.now() / 1000 >= Math.min(old.deadline, next.task_deadline ?? Infinity))) { + throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); + } + const recheck = command.status === "succeeded" ? "pending" : row.recheck_dispatch_state; + this.sql.exec("UPDATE account_diagnosis_tasks SET native_json = ?, status = ?, summary = ?, reason_code = ?, dispatch_state = 'sent', recheck_dispatch_state = ?, updated_at = ? WHERE request_id = ?", + JSON.stringify(next), command.status, command.summary, command.reason_code, recheck, command.updated_at, requestId); + return { ok: true, changed: true, task: accountDiagnosisTaskFromRow(this.sql.exec("SELECT * FROM account_diagnosis_tasks WHERE request_id = ?", requestId).toArray()[0]) }; + } + if (command.action === "diagnosis_mark_dispatch") { + if (row.dispatch_state !== "pending") return { ok: true, changed: false, task: accountDiagnosisTaskFromRow(row) }; + if (command.dispatch_state === "unknown" && row.native_json) { + const native = { ...JSON.parse(row.native_json), phase: "hold" }; + this.sql.exec("UPDATE account_diagnosis_tasks SET dispatch_state = 'unknown', status = 'unknown', native_json = ?, updated_at = ? WHERE request_id = ?", + JSON.stringify(native), command.updated_at, requestId); + } else { + this.sql.exec("UPDATE account_diagnosis_tasks SET dispatch_state = ?, updated_at = ? WHERE request_id = ?", command.dispatch_state, command.updated_at, requestId); + } + return { ok: true, changed: true, task: accountDiagnosisTaskFromRow(this.sql.exec("SELECT * FROM account_diagnosis_tasks WHERE request_id = ?", requestId).toArray()[0]) }; + } + if (command.action === "diagnosis_claim" || command.action === "diagnosis_result") { + throw new HttpError("account_diagnosis_legacy_retired", 409); + } + if (command.action === "diagnosis_mark_recheck") { + if (row.recheck_dispatch_state !== "pending") return { ok: true, changed: false, task: accountDiagnosisTaskFromRow(row) }; + this.sql.exec("UPDATE account_diagnosis_tasks SET recheck_dispatch_state = ?, updated_at = ? WHERE request_id = ?", command.recheck_dispatch_state, command.updated_at, requestId); + return { ok: true, changed: true, task: accountDiagnosisTaskFromRow(this.sql.exec("SELECT * FROM account_diagnosis_tasks WHERE request_id = ?", requestId).toArray()[0]) }; + } + throw new HttpError("unsupported_account_diagnosis_action", 400); + }); + } + + consoleReadCommand(command) { + if (!command.actor) throw new HttpError("runtime_instance_actor_required", 400); + if (command.action === "console_audit_events") { + const limit = command.limit; + if (!Number.isSafeInteger(limit) || limit < 1 || limit > AUDIT_EVENTS_MAX_ROWS) throw new HttpError("invalid_pagination", 400); + const { snapshot, before } = command; + // Normalize time once, and use the same binary id tie-break as the HTTP merge. + return { rows: this.sql.exec(` + WITH events AS ( + SELECT 'decision' AS source, kind, subject_id, material_key AS revision, + action, actor, strftime('%Y-%m-%dT%H:%M:%fZ', decided_at) AS occurred_at, + request_key AS ref, 'decided' AS status, 'durable_human_decision:' || request_key AS event_id + FROM human_decision_record + UNION ALL + SELECT 'promotion_application', 'promotion', ticket_id, '', status, '', + strftime('%Y-%m-%dT%H:%M:%fZ', updated_at), application_id, status, + 'research_promotion_application:' || application_id FROM research_promotion_applications + UNION ALL + SELECT 'account_settings_application', 'account_settings', platform || ':' || account_key, '', + status, approved_by, strftime('%Y-%m-%dT%H:%M:%fZ', approved_at), request_key, status, + 'account_settings_application:' || request_key FROM account_setting_profile_application + UNION ALL + SELECT 'credential_rotation', 'credential', 'ibkr_flex', '', action, actor, + occurred_at, operation_id, outcome, 'credential_rotation:' || event_id + FROM credential_rotation_audit + ) SELECT * FROM events WHERE occurred_at <= ? + AND (? IS NULL OR occurred_at < ? OR (occurred_at = ? AND event_id > ?)) + ORDER BY occurred_at DESC, event_id ASC LIMIT ?`, snapshot, + before?.at ?? null, before?.at ?? null, before?.at ?? null, before?.id ?? null, limit).toArray() }; + } + if (command.action === "console_decision_history") { + const { limit, offset } = command; + if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100 || !Number.isSafeInteger(offset) || offset < 0 || offset > 100000) throw new HttpError("invalid_pagination", 400); + return { rows: this.sql.exec("SELECT request_key, kind, subject_id, material_key, action, actor, decided_at, target_json, json_extract(payload_json, '$.ticket.confirmation_execution_mode') AS confirmation_execution_mode FROM human_decision_record ORDER BY decided_at DESC, request_key DESC LIMIT ? OFFSET ?", limit + 1, offset).toArray() }; + } + if (command.action === "console_settings_history") { + this.existingAccountInstance(command.platform, command.key); + return { approvals: this.sql.exec("SELECT request_key, status, identity_json, instance_revision, old_profile, new_profile, approved_at, approved_by, draft_revision FROM account_setting_profile_application WHERE platform = ? AND account_key = ? ORDER BY approved_at DESC", command.platform, command.key).toArray() }; + } + const drafts = this.sql.exec("SELECT platform, account_key, overrides_json, updated_at FROM account_setting_draft").toArray(); + const approvals = this.sql.exec("SELECT platform, account_key, MAX(approved_at) AS approved_at FROM account_setting_profile_application GROUP BY platform, account_key").toArray(); + return { rows: this.read().instances.map(item => { + const draft = drafts.find(row => row.platform === item.platform && row.account_key === item.key); + const approval = approvals.find(row => row.platform === item.platform && row.account_key === item.key); + return { platform: item.platform, account_key: item.key, + pending_draft_count: draft ? new Set(Object.keys(JSON.parse(draft.overrides_json)).map(field => field.startsWith("dca_") ? "dca" : field.startsWith("reserved_") ? "cash" : field)).size : 0, + last_change_at: [draft?.updated_at, approval?.approved_at, item.updated_at].filter(Boolean).sort().at(-1) || null, + }; + }) }; + } + + humanDecisionCommand(command) { + return this.storage.transactionSync(() => { + if (!command || typeof command.actor !== "string" || !command.actor) { + throw new HttpError("runtime_instance_actor_required", 400); + } + if (command.action === "owner_sync_materials") return this.syncHumanDecisionMaterials(command, "owner"); + if (command.action === "recovery_sync_materials") return this.syncHumanDecisionMaterials(command, "recovery"); + if (command.action === "promotion_sync_materials") return this.syncHumanDecisionMaterials(command, "promotion"); + if (command.action === "owner_decide") return this.writeHumanDecision(command, "owner"); + if (command.action === "recovery_decide") return this.writeHumanDecision(command, "recovery"); + if (command.action === "promotion_decide") return this.writeHumanDecision(command, "promotion"); + if (command.action === "owner_read") return this.readHumanDecision(command, "owner"); + if (command.action === "recovery_read") return this.readHumanDecision(command, "recovery"); + if (command.action === "promotion_read") return this.readHumanDecision(command, "promotion"); + throw new HttpError("unsupported_human_decision_action", 400); + }); + } + + humanDecisionMaterialRow(kind, subjectId) { + return this.sql.exec( + "SELECT * FROM human_decision_material WHERE kind = ? AND subject_id = ?", + kind, subjectId, + ).toArray()[0] || null; + } + + humanDecisionTerminalRow(kind, subjectId, materialKey) { + return this.sql.exec( + "SELECT * FROM human_decision_record WHERE kind = ? AND subject_id = ? AND material_key = ? ORDER BY decided_at ASC LIMIT 1", + kind, subjectId, materialKey, + ).toArray()[0] || null; + } + + humanDecisionRecordPayload(row) { + let payload = {}; + try { + payload = JSON.parse(row.payload_json); + } catch { + throw new HttpError("human_decision_record_invalid", 409); + } + return { + request_key: row.request_key, + kind: row.kind, + subject_id: row.subject_id, + material_key: row.material_key, + action: row.action, + actor: row.actor, + decided_at: row.decided_at, + target_json: row.target_json, + payload, + }; + } + + humanDecisionSourceRow(kind, subjectId, sourceId) { + return this.sql.exec( + "SELECT * FROM human_decision_source_material WHERE kind = ? AND subject_id = ? AND source_id = ?", + kind, subjectId, sourceId, + ).toArray()[0] || null; + } + + upsertHumanDecisionSource(kind, subjectId, sourceId, item, eligible, updatedAt) { + if (this.humanDecisionSourceRow(kind, subjectId, sourceId)) { + this.sql.exec( + `UPDATE human_decision_source_material + SET material_key = ?, summary_json = ?, eligible = ?, expires_at_ms = ?, updated_at = ? + WHERE kind = ? AND subject_id = ? AND source_id = ?`, + item.material_key, item.summary_json || "{}", eligible, item.expires_at_ms ?? null, updatedAt, kind, subjectId, sourceId, + ); + return; + } + this.sql.exec( + `INSERT INTO human_decision_source_material + (kind, subject_id, source_id, material_key, summary_json, eligible, expires_at_ms, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + kind, subjectId, sourceId, item.material_key, item.summary_json || "{}", eligible, item.expires_at_ms ?? null, updatedAt, + ); + } + + recomputeHumanDecisionSubject(kind, subjectId, updatedAt) { + const sources = this.sql.exec( + "SELECT * FROM human_decision_source_material WHERE kind = ? AND subject_id = ?", + kind, subjectId, + ).toArray(); + let projection = this.humanDecisionMaterialRow(kind, subjectId); + if (!projection && !sources.length) return; + if (!projection) { + this.insertHumanDecisionMaterial(kind, subjectId, sources[0].source_id, sources[0], 0, { state: "none", block: "" }, updatedAt); + projection = this.humanDecisionMaterialRow(kind, subjectId); + } + if (projection.legacy_state === "blocked") { + this.sql.exec( + "UPDATE human_decision_material SET eligible = 0, updated_at = ? WHERE kind = ? AND subject_id = ?", + updatedAt, kind, subjectId, + ); + return; + } + if (sources.length !== 1) { + const keys = new Set(sources.map((row) => row.material_key)); + const materialKey = keys.size === 1 ? sources[0].material_key : ""; + this.sql.exec( + "UPDATE human_decision_material SET source_id = ?, material_key = ?, eligible = 0, updated_at = ? WHERE kind = ? AND subject_id = ?", + "", materialKey, updatedAt, kind, subjectId, + ); + return; + } + const only = sources[0]; + this.sql.exec( + `UPDATE human_decision_material + SET source_id = ?, material_key = ?, summary_json = ?, eligible = ?, expires_at_ms = ?, updated_at = ? + WHERE kind = ? AND subject_id = ?`, + only.source_id, only.material_key, only.summary_json, only.eligible, only.expires_at_ms, updatedAt, kind, subjectId, + ); + } + + insertHumanDecisionMaterial(kind, subjectId, sourceId, item, eligible, legacyState, updatedAt) { + this.sql.exec( + `INSERT INTO human_decision_material + (kind, subject_id, source_id, material_key, summary_json, eligible, expires_at_ms, legacy_state, legacy_block, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + kind, subjectId, sourceId, item.material_key, item.summary_json || "{}", eligible, + item.expires_at_ms ?? null, legacyState.state, legacyState.block, updatedAt, + ); + } + + insertHumanDecisionRecord(record) { + this.sql.exec( + `INSERT INTO human_decision_record + (request_key, kind, subject_id, material_key, action, actor, decided_at, target_json, payload_json) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + record.request_key, record.kind, record.subject_id, record.material_key, record.action, + record.actor, record.decided_at, record.target_json, record.payload_json, + ); + } + + importHumanDecisionLegacy(kind, subjectId, item) { + const legacy = item.legacy; + if (!legacy) return { state: "none", block: "" }; + const record = legacy.record; + if (legacy.blocked || !record || record.material_key !== item.material_key || !record.action || !record.actor || !record.decided_at || !record.payload || typeof record.payload !== "object") { + return { state: "blocked", block: "unexplained_legacy" }; + } + this.insertHumanDecisionRecord({ + request_key: `legacy:${kind}:${subjectId}:${item.material_key}`, + kind, + subject_id: subjectId, + material_key: item.material_key, + action: record.action, + actor: record.actor, + decided_at: record.decided_at, + target_json: record.target_json || "{}", + payload_json: JSON.stringify(record.payload), + }); + return { state: "imported", block: "" }; + } + + syncHumanDecisionMaterials(command, kind) { + const sourceId = String(command.source_id || ""); + const updatedAt = String(command.updated_at || ""); + const materials = Array.isArray(command.materials) ? command.materials : []; + const seen = new Set(); + const affected = new Set(); + let terminal = false; + let terminalState = ""; + let identityConflict = false; + let legacyBlocked = false; + for (const item of materials) { + const subjectId = String(item.subject_id || ""); + if (!subjectId || seen.has(subjectId)) continue; + seen.add(subjectId); + const sourceRow = this.humanDecisionSourceRow(kind, subjectId, sourceId); + const incomingTerminal = this.humanDecisionTerminalRow(kind, subjectId, item.material_key); + if ( + kind === "promotion" && sourceRow && sourceRow.material_key !== item.material_key && !incomingTerminal + && !this.humanDecisionTerminalRow(kind, subjectId, sourceRow.material_key) + ) { + identityConflict = true; + continue; + } + if (kind === "promotion" && incomingTerminal) { + terminal = true; + terminalState = incomingTerminal.action === "accept" ? "human_accepted" : "human_rejected"; + } + let projection = this.humanDecisionMaterialRow(kind, subjectId); + if (!projection) { + const legacyState = this.importHumanDecisionLegacy(kind, subjectId, item); + this.insertHumanDecisionMaterial(kind, subjectId, sourceId, item, 0, legacyState, updatedAt); + if (legacyState.state === "blocked") legacyBlocked = true; + const imported = this.humanDecisionTerminalRow(kind, subjectId, item.material_key); + if (imported && kind === "promotion") { + terminal = true; + terminalState = imported.action === "accept" ? "human_accepted" : "human_rejected"; + } + projection = this.humanDecisionMaterialRow(kind, subjectId); + } else if (projection.legacy_state === "blocked") { + legacyBlocked = true; + } + const blocked = projection?.legacy_state === "blocked"; + this.upsertHumanDecisionSource(kind, subjectId, sourceId, item, blocked ? 0 : (item.eligible ? 1 : 0), updatedAt); + affected.add(subjectId); + } + if (command.replace_source) { + const rows = this.sql.exec( + "SELECT subject_id FROM human_decision_source_material WHERE kind = ? AND source_id = ?", + kind, sourceId, + ).toArray(); + for (const row of rows) { + if (seen.has(row.subject_id)) continue; + this.sql.exec( + "DELETE FROM human_decision_source_material WHERE kind = ? AND subject_id = ? AND source_id = ?", + kind, row.subject_id, sourceId, + ); + affected.add(row.subject_id); + } + } + for (const subjectId of command.duplicate_subject_ids || []) { + if (typeof subjectId !== "string" || !subjectId) continue; + this.sql.exec( + "UPDATE human_decision_source_material SET eligible = 0, updated_at = ? WHERE kind = ? AND subject_id = ? AND source_id = ?", + updatedAt, kind, subjectId, sourceId, + ); + affected.add(subjectId); + } + for (const subjectId of affected) this.recomputeHumanDecisionSubject(kind, subjectId, updatedAt); + return { ok: true, terminal, terminal_state: terminalState, identity_conflict: identityConflict, legacy_blocked: legacyBlocked }; + } + + writeHumanDecision(command, kind) { + if (kind === "owner" && command.action_name === "retire_candidate") { + throw new HttpError("owner_retirement_proposal_required", 409); + } + const subjectId = String(command.subject_id || ""); + const materialKey = String(command.material_key || ""); + const actionName = String(command.action_name || ""); + const requestKey = String(command.request_key || ""); + const targetJson = String(command.target_json || "{}"); + if (!subjectId || !materialKey || !actionName || !requestKey) throw new HttpError("human_decision_request_invalid", 400); + let row = this.humanDecisionMaterialRow(kind, subjectId); + if (!row && command.legacy) { + const legacyState = this.importHumanDecisionLegacy(kind, subjectId, { material_key: materialKey, legacy: command.legacy }); + this.insertHumanDecisionMaterial(kind, subjectId, String(command.source_id || kind), { + material_key: materialKey, + summary_json: command.summary_json || "{}", + expires_at_ms: command.expires_at_ms ?? null, + }, legacyState.state === "blocked" ? 0 : 1, legacyState, String(command.decided_at || "")); + row = this.humanDecisionMaterialRow(kind, subjectId); + } + if (!row) throw new HttpError("human_decision_material_unavailable", 409); + if (row.legacy_state === "blocked") throw new HttpError("human_decision_legacy_blocked", 409); + if (row.material_key !== materialKey) throw new HttpError("human_decision_material_conflict", 409); + if (Number(row.eligible) !== 1) throw new HttpError("human_decision_material_ineligible", 409); + if (row.expires_at_ms != null && Number(command.now_ms) >= Number(row.expires_at_ms)) { + throw new HttpError("human_decision_material_expired", 409); + } + if (kind === "promotion" && command.review_binding !== undefined + && !promotionReviewBindingsMatch(command.review_binding, promotionMaterialReviewBinding(row))) { + throw new HttpError("human_decision_review_material_conflict", 409); + } + const assertReview = terminal => { + if (kind === "promotion" && command.review_binding !== undefined + && !promotionReviewBindingsMatch(command.review_binding, this.humanDecisionRecordPayload(terminal).payload?.review_binding)) { + throw new HttpError("human_decision_review_binding_unqualified", 409); + } + }; + const byRequest = this.sql.exec("SELECT * FROM human_decision_record WHERE request_key = ?", requestKey).toArray()[0]; + if (byRequest) { + if (byRequest.kind !== kind || byRequest.subject_id !== subjectId || byRequest.material_key !== materialKey || byRequest.action !== actionName || byRequest.target_json !== targetJson) { + throw new HttpError("human_decision_conflict", 409); + } + assertReview(byRequest); + return { ok: true, replayed: true, record: this.humanDecisionRecordPayload(byRequest) }; + } + const terminal = this.humanDecisionTerminalRow(kind, subjectId, materialKey); + if (terminal) { + if (terminal.action !== actionName || terminal.target_json !== targetJson) throw new HttpError("human_decision_conflict", 409); + assertReview(terminal); + return { ok: true, replayed: true, record: this.humanDecisionRecordPayload(terminal) }; + } + const payload = command.payload && typeof command.payload === "object" ? structuredClone(command.payload) : {}; + if (kind === "promotion" && actionName === "accept") { + const instances = this.read(); + if (instances.initialized) { + const preflightRevision = Number(command.preflight_revision); + if (!Number.isSafeInteger(preflightRevision) || preflightRevision !== instances.revision) { + throw new HttpError("runtime_instance_revision_conflict", 409); + } + payload.account_revision = instances.revision; + } else { + payload.account_revision = null; + } + } + const record = { + request_key: requestKey, + kind, + subject_id: subjectId, + material_key: materialKey, + action: actionName, + actor: String(command.actor || ""), + decided_at: String(command.decided_at || ""), + target_json: targetJson, + payload_json: JSON.stringify(payload), + }; + this.insertHumanDecisionRecord(record); + return { ok: true, replayed: false, record: this.humanDecisionRecordPayload(this.humanDecisionTerminalRow(kind, subjectId, materialKey)) }; + } + + readHumanDecision(command, kind) { + const subjectId = String(command.subject_id || ""); + const now = Number.isFinite(Number(command.now_ms)) ? Number(command.now_ms) : Date.now(); + const row = this.humanDecisionMaterialRow(kind, subjectId); + const expiry = (material) => material.expires_at_ms == null ? null : new Date(Number(material.expires_at_ms)).toISOString(); + const expired = (material) => material.expires_at_ms != null && now >= Number(material.expires_at_ms); + // Historical receipt lookup deliberately does not substitute the current material. + // It grants no eligibility or authority to act on the current material. + if (command.exact_material_key !== undefined) { + const materialKey = command.exact_material_key; + if (!/^[0-9a-f]{64}$/.test(materialKey) || !subjectId || subjectId.length > 256) { + throw new HttpError("human_decision_lookup_invalid", 400); + } + const terminal = this.humanDecisionTerminalRow(kind, subjectId, materialKey); + return { + ok: true, + exact_material_key: materialKey, + decision: terminal ? this.humanDecisionRecordPayload(terminal) : null, + current_material: row ? { + matches: row.material_key === materialKey, + eligible: Number(row.eligible) === 1 && !expired(row), + expired: expired(row), + expires_at: expiry(row), + blocked: row.legacy_state === "blocked", + ...(kind === "promotion" ? { review_binding: promotionMaterialReviewBinding(row) } : {}), + } : null, + }; + } + if (!row) return { ok: true, migrated: false, blocked: false, eligible: false, expired: false, expires_at: null, material_key: null, decision: null }; + const terminal = this.humanDecisionTerminalRow(kind, subjectId, row.material_key); + return { + ok: true, + migrated: true, + blocked: row.legacy_state === "blocked", + eligible: Number(row.eligible) === 1 && !expired(row), + expired: expired(row), + expires_at: expiry(row), + material_key: row.material_key, + ...(kind === "promotion" ? { review_binding: promotionMaterialReviewBinding(row) } : {}), + decision: terminal ? this.humanDecisionRecordPayload(terminal) : null, + }; + } + + applicationCommand(command) { + return this.storage.transactionSync(() => { + if (!command || typeof command.actor !== "string" || !command.actor) { + throw new HttpError("runtime_instance_actor_required", 400); + } + if (command.action === "application_create") { + const currentMaterial = this.humanDecisionMaterialRow("promotion", command.ticket_id); + if (!currentMaterial || currentMaterial.legacy_state === "blocked" || Number(currentMaterial.eligible) !== 1 || currentMaterial.material_key !== command.promotion_material_key) { + throw new HttpError("research candidate is not human_accepted", 409); + } + const decisionRow = this.humanDecisionTerminalRow("promotion", command.ticket_id, currentMaterial.material_key); + if (!decisionRow || decisionRow.action !== "accept") throw new HttpError("research candidate is not human_accepted", 409); + let decisionPayload = {}; + try { decisionPayload = JSON.parse(decisionRow.payload_json); } catch { throw new HttpError("research candidate is not human_accepted", 409); } + const selected = decisionPayload.selected_account || {}; + if (selected.platform !== command.platform_id || selected.key !== command.account_key) { + throw new HttpError("promotion_account_binding_conflict", 409); + } + const instances = this.read(); + if (!instances.initialized || instances.revision !== Number(command.expected_revision) || !Number.isSafeInteger(decisionPayload.account_revision) || decisionPayload.account_revision !== instances.revision) { + throw new HttpError("runtime_instance_revision_conflict", 409); + } + const existing = this.sql.exec( + "SELECT * FROM research_promotion_applications WHERE ticket_id = ? AND platform_id = ? AND account_key = ? AND status <> 'rejected' ORDER BY created_at DESC LIMIT 1", + command.ticket_id, command.platform_id, command.account_key, + ).toArray()[0]; + if (existing) return { ok: true, created: false, application: researchPromotionApplicationFromRow(existing) }; + const application = { + application_id: command.application_id, + ticket_id: command.ticket_id, + status: "approved", + dispatch_state: "pending", + claim_token: command.claim_token, + platform_id: command.platform_id, + account_key: command.account_key, + account_scope: command.account_scope, + account_selector: command.account_selector, + service_name: command.service_name, + broker_environment: command.broker_environment, + expected_revision: command.expected_revision, + expected_strategy_profile: command.expected_strategy_profile, + strategy_profile: command.strategy_profile, + candidate_id: command.candidate_id, + config_sha256: command.config_sha256, + source_commit: command.source_commit, + approved_ues_revision: command.approved_ues_revision, + desired_state: "paused", + workflow_run_id: null, + workflow_run_attempt: null, + reason_code: "", + readback: null, + created_at: command.created_at, + updated_at: command.created_at, + }; + this.sql.exec( + `INSERT INTO research_promotion_applications + (application_id, ticket_id, status, dispatch_state, claim_token, platform_id, account_key, + account_scope, account_selector, service_name, broker_environment, expected_revision, + expected_strategy_profile, strategy_profile, candidate_id, config_sha256, source_commit, + approved_ues_revision, desired_state, workflow_run_id, workflow_run_attempt, reason_code, + readback_json, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + application.application_id, application.ticket_id, application.status, application.dispatch_state, + application.claim_token, application.platform_id, application.account_key, application.account_scope, + application.account_selector, application.service_name, application.broker_environment, + application.expected_revision, application.expected_strategy_profile, application.strategy_profile, + application.candidate_id, application.config_sha256, application.source_commit, + application.approved_ues_revision, application.desired_state, null, null, application.reason_code, + null, application.created_at, application.updated_at, + ); + return { ok: true, created: true, application }; + } + if (command.action === "application_latest") { + const row = this.sql.exec( + "SELECT * FROM research_promotion_applications WHERE ticket_id = ? ORDER BY created_at DESC LIMIT 1", + command.ticket_id, + ).toArray()[0]; + return { ok: true, application: researchPromotionApplicationFromRow(row) }; + } + const applicationId = command.application_id; + const row = this.sql.exec("SELECT * FROM research_promotion_applications WHERE application_id = ?", applicationId).toArray()[0]; + if (!row) throw new HttpError("research_promotion_application_not_found", 404); + if (command.action === "application_read") return { ok: true, application: researchPromotionApplicationFromRow(row) }; + if (command.action === "application_mark_dispatch") { + if (row.status !== "approved" || row.dispatch_state !== "pending") return { ok: true, changed: false, application: researchPromotionApplicationFromRow(row) }; + this.sql.exec("UPDATE research_promotion_applications SET dispatch_state = ?, updated_at = ? WHERE application_id = ?", command.dispatch_state, command.updated_at, applicationId); + return { ok: true, changed: true, application: researchPromotionApplicationFromRow(this.sql.exec("SELECT * FROM research_promotion_applications WHERE application_id = ?", applicationId).toArray()[0]) }; + } + if (command.action === "application_claim") { + const state = this.read(); + const instance = state.instances.find((item) => item.kind === "existing" + && item.platform === row.platform_id && item.key === row.account_key); + const config = instance?.config || {}; + const bindingStillMatches = state.revision === Number(row.expected_revision) + && String(config.default_strategy_profile || "") === row.expected_strategy_profile + && String(config.broker_environment || "") === row.broker_environment + && String(config.account_selector || "") === row.account_selector + && String(config.service_name || "") === row.service_name; + const claimed = row.status === "approved" && ["pending", "sent", "unknown"].includes(row.dispatch_state) + && row.claim_token === command.claim_token && bindingStillMatches; + if (claimed) { + this.sql.exec( + "UPDATE research_promotion_applications SET status = 'claimed', workflow_run_id = ?, workflow_run_attempt = ?, updated_at = ? WHERE application_id = ? AND status = 'approved'", + command.workflow_run_id, command.workflow_run_attempt, command.updated_at, applicationId, + ); + } + return { ok: true, claimed, reason: claimed ? "" : (bindingStillMatches ? "already_claimed_or_invalid_token" : "account_changed") }; + } + if (command.action === "application_result") { + const sameRun = row.workflow_run_id === command.workflow_run_id + && row.workflow_run_attempt === command.workflow_run_attempt + && row.claim_token === command.claim_token; + if (["applied_paused", "rejected", "uncertain"].includes(row.status)) { + if (!sameRun) throw new HttpError("research_promotion_application_workflow_binding_conflict", 409); + return { ok: true, changed: false, application: researchPromotionApplicationFromRow(row) }; + } + if (row.status !== "claimed" || !sameRun) throw new HttpError("research_promotion_application_workflow_binding_conflict", 409); + let effectiveStatus = command.status; + let effectiveReasonCode = command.reason_code; + if (command.status === "applied_paused") { + const state = this.read(); + const instance = state.instances.find((item) => item.kind === "existing" + && item.platform === row.platform_id && item.key === row.account_key); + const config = instance?.config || {}; + const bindingStillMatches = state.revision === Number(row.expected_revision) + && String(config.default_strategy_profile || "") === row.expected_strategy_profile + && String(config.broker_environment || "") === row.broker_environment + && String(config.account_selector || "") === row.account_selector + && String(config.service_name || "") === row.service_name; + if (!bindingStillMatches) { + effectiveStatus = "uncertain"; + effectiveReasonCode = "account_changed"; + } else { + const before = structuredClone(instance); + instance.config = { ...instance.config, default_strategy_profile: row.strategy_profile }; + instance.enabled = false; + instance.platform_applied = true; + instance.application_status = "applied_paused"; + instance.updated_at = command.updated_at; + state.revision += 1; + this.sql.exec("INSERT INTO instance_state (id, revision, payload) VALUES (1, ?, ?) ON CONFLICT(id) DO UPDATE SET revision = excluded.revision, payload = excluded.payload", state.revision, JSON.stringify(state.instances)); + this.sql.exec("INSERT INTO instance_history (revision, entry) VALUES (?, ?)", state.revision, JSON.stringify({ + revision: state.revision, + ts: command.updated_at, + login: command.actor, + action: "research_promotion_application_applied_paused", + before, + after: instance, + })); + } + } + this.sql.exec( + `UPDATE research_promotion_applications + SET status = ?, reason_code = ?, readback_json = ?, updated_at = ? + WHERE application_id = ?`, + effectiveStatus, effectiveReasonCode, command.readback ? JSON.stringify(command.readback) : null, + command.updated_at, applicationId, + ); + return { ok: true, changed: true, application: researchPromotionApplicationFromRow(this.sql.exec("SELECT * FROM research_promotion_applications WHERE application_id = ?", applicationId).toArray()[0]) }; + } + throw new HttpError("unsupported_research_promotion_application_action", 400); + }); + } + + ux1ResearchCommand(command) { + return this.storage.transactionSync(() => { + if (["ux1_begin_job", "ux1_record_dispatch", "ux1_lookup_job", "ux1_claim_job", "ux1_finish_job"].includes(command?.action)) { + return ux1MutateJob(this.sql, command); + } + const login = String(command?.login || ""); + if (!/^[a-z0-9-]{1,39}$/.test(login)) throw new HttpError("ux1_login_invalid", 400); + const row = ux1SelectSlot(this.sql, login); + const found = ux1SlotPayload(row); + if (command.action === "ux1_read") return found; + if (command.action === "ux1_save") { + if (!Number.isSafeInteger(command.expected_revision) || command.expected_revision < 0) throw new HttpError("ux1_revision_required", 400); + if ((row ? Number(row.revision) : 0) !== command.expected_revision) throw new HttpError("ux1_revision_conflict", 409); + if (typeof command.draft_json !== "string" || typeof command.fingerprint !== "string" || command.draft_json.length > 16384) { + throw new HttpError("ux1_invalid_body", 400); + } + if (row && row.draft_json === command.draft_json && row.fingerprint === command.fingerprint) return { ...found, unchanged: true }; + const revision = (row ? Number(row.revision) : 0) + 1; + this.sql.exec( + `INSERT INTO ux1_research_slot (login, revision, draft_json, fingerprint, preview_json, intent_json) + VALUES (?, ?, ?, ?, NULL, NULL) + ON CONFLICT(login) DO UPDATE SET revision = excluded.revision, draft_json = excluded.draft_json, + fingerprint = excluded.fingerprint, preview_json = NULL, intent_json = NULL`, + login, revision, command.draft_json, command.fingerprint, + ); + return { ...ux1SlotPayload(ux1SelectSlot(this.sql, login)), unchanged: false }; + } + if (command.action === "ux1_store_preview") { + if (!row || Number(row.revision) !== command.expected_revision || row.fingerprint !== command.fingerprint) throw new HttpError("ux1_preview_conflict", 409); + if (typeof command.preview_json !== "string" || command.preview_json.length > 65536) throw new HttpError("ux1_invalid_body", 400); + this.sql.exec("UPDATE ux1_research_slot SET preview_json = ? WHERE login = ? AND revision = ?", command.preview_json, login, Number(row.revision)); + return { ...found, preview_json: command.preview_json }; + } + if (command.action === "ux1_save_intent") { + if (!row || Number(row.revision) !== command.expected_revision || row.fingerprint !== command.fingerprint || !row.preview_json) { + throw new HttpError("ux1_intent_conflict", 409); + } + if (row.intent_json) { + const existing = JSON.parse(row.intent_json); + if (existing.revision === Number(row.revision) && existing.fingerprint === row.fingerprint) return { ...found, duplicate: true }; + } + if (typeof command.intent_json !== "string" || command.intent_json.length > 4096) throw new HttpError("ux1_invalid_body", 400); + this.sql.exec("UPDATE ux1_research_slot SET intent_json = ? WHERE login = ? AND revision = ?", command.intent_json, login, Number(row.revision)); + return { ...found, intent_json: command.intent_json, duplicate: false }; + } + if (command.action === "ux1_clear_preview") { + if (!row) return found; + if (Number(row.revision) !== command.expected_revision || row.fingerprint !== command.fingerprint) return found; + this.sql.exec("UPDATE ux1_research_slot SET preview_json = NULL WHERE login = ? AND revision = ? AND fingerprint = ?", login, Number(row.revision), row.fingerprint); + return { ...found, preview_json: null }; + } + throw new HttpError("unsupported_ux1_action", 400); + }); + } +} diff --git a/web/strategy-switch-console/application/runtime_instances/promotion_application_row.js b/web/strategy-switch-console/application/runtime_instances/promotion_application_row.js new file mode 100644 index 00000000..ed802f03 --- /dev/null +++ b/web/strategy-switch-console/application/runtime_instances/promotion_application_row.js @@ -0,0 +1,30 @@ +export function researchPromotionApplicationFromRow(row) { + if (!row) return null; + return { + application_id: row.application_id, + ticket_id: row.ticket_id, + status: row.status, + dispatch_state: row.dispatch_state, + claim_token: row.claim_token, + platform_id: row.platform_id, + account_key: row.account_key, + account_scope: row.account_scope, + account_selector: row.account_selector, + service_name: row.service_name, + broker_environment: row.broker_environment, + expected_revision: Number(row.expected_revision), + expected_strategy_profile: row.expected_strategy_profile, + strategy_profile: row.strategy_profile, + candidate_id: row.candidate_id, + config_sha256: row.config_sha256, + source_commit: row.source_commit, + approved_ues_revision: row.approved_ues_revision, + desired_state: row.desired_state, + workflow_run_id: row.workflow_run_id || null, + workflow_run_attempt: row.workflow_run_attempt || null, + reason_code: row.reason_code || "", + readback: row.readback_json ? JSON.parse(row.readback_json) : null, + created_at: row.created_at, + updated_at: row.updated_at, + }; +} diff --git a/web/strategy-switch-console/application/security_headers.js b/web/strategy-switch-console/application/security_headers.js new file mode 100644 index 00000000..632d18fb --- /dev/null +++ b/web/strategy-switch-console/application/security_headers.js @@ -0,0 +1,18 @@ +export const SECURITY_HEADERS = { + "Content-Security-Policy": [ + "default-src 'self'", + "base-uri 'none'", + "object-src 'none'", + "frame-ancestors 'none'", + "frame-src https://fred.stlouisfed.org/graph/graph-landing.php", + "form-action 'self'", + "img-src 'self' data:", + "connect-src 'self'", + "script-src 'self'", + "style-src 'self'", + ].join("; "), + "Permissions-Policy": "camera=(), microphone=(), geolocation=(), payment=(), usb=()", + "Referrer-Policy": "no-referrer", + "X-Content-Type-Options": "nosniff", + "X-Frame-Options": "DENY", +}; diff --git a/web/strategy-switch-console/application/ux1/constants.js b/web/strategy-switch-console/application/ux1/constants.js new file mode 100644 index 00000000..b135b83a --- /dev/null +++ b/web/strategy-switch-console/application/ux1/constants.js @@ -0,0 +1,17 @@ +export const UX1_CALCULATOR_DEADLINE_MS = 30000; +export const UX1_CALCULATOR_MAX_BYTES = 65536; +export const UX1_RESEARCH_REPOSITORY = "QuantStrategyLab/UsEquityStrategies"; +export const UX1_RESEARCH_WORKFLOW_FILE = "ux1-research-preview.yml"; +export const UX1_RESEARCH_WORKFLOW_PATH = ".github/workflows/ux1-research-preview.yml"; +export const UX1_JOB_BODY_MAX_BYTES = 80 * 1024; +export const UX1_GITHUB_DEADLINE_MS = 8000; +export const UX1_GITHUB_MAX_BYTES = 65536; +export const UX1_JOB_UNKNOWN_AFTER_MS = 30 * 60 * 1000; +export const UX1_PUBLIC_JOB_REASONS = new Set([ + "dispatch_rejected", + "dispatch_unconfirmed", + "input_unavailable", + "calculator_failed", + "check_required", + "config_changed", +]); diff --git a/web/strategy-switch-console/application/ux1/research.js b/web/strategy-switch-console/application/ux1/research.js new file mode 100644 index 00000000..f792269f --- /dev/null +++ b/web/strategy-switch-console/application/ux1/research.js @@ -0,0 +1,934 @@ +import { + UX1_DRAFT_SCHEMA, + UX1_EVIDENCE_HASH_KEYS, + UX1_STUDY, + Ux1InputError, + assertUx1DraftPost, + assertUx1IntentPost, + assertUx1PreviewPost, + buildUx1PreviewRequest, + canonicalJson, + classifyUx1Draft, + defaultUx1Draft, + localUx1Preview, + normalizeUx1Draft, + projectUx1CalculatorResult, + ux1CustomDraft, + ux1Fingerprint, + ux1IntentAllowed, + ux1IntentRecord, + ux1ReceiptId, +} from "../../ux1_research_contract.js"; +import { HttpError } from "../http_error.js"; +import { json } from "../http_response.js"; +import { requireSameOrigin } from "../http_request.js"; +import { githubHeaders } from "../auth/github.js"; +import { readSession } from "../auth/session.js"; +import { hasRuntimeInstanceStore, runtimeInstanceCommand } from "../runtime_instances/command.js"; +import { + UX1_CALCULATOR_DEADLINE_MS, + UX1_CALCULATOR_MAX_BYTES, + UX1_RESEARCH_REPOSITORY, + UX1_RESEARCH_WORKFLOW_FILE, + UX1_RESEARCH_WORKFLOW_PATH, + UX1_JOB_BODY_MAX_BYTES, + UX1_GITHUB_DEADLINE_MS, + UX1_GITHUB_MAX_BYTES, + UX1_JOB_UNKNOWN_AFTER_MS, + UX1_PUBLIC_JOB_REASONS, +} from "./constants.js"; + +let ux1ModuleEpoch = ""; + +export function ux1RuntimeEpoch(env) { + const configured = env?.UX1_RUNTIME_EPOCH; + if (typeof configured === "string" && /^[a-f0-9]{32}$/.test(configured)) return configured; + if (!ux1ModuleEpoch) { + const bytes = new Uint8Array(16); + crypto.getRandomValues(bytes); + ux1ModuleEpoch = [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join(""); + } + return ux1ModuleEpoch; +} + +function ux1PreviewMatchesEpoch(preview, env) { + return Boolean(preview && preview.runtime_epoch === ux1RuntimeEpoch(env)); +} + +function ux1ExpectedEvidenceMatches(preview, env) { + if (!preview.evidence) return !["computed", "no_advantage", "no_action"].includes(preview.status); + if (!env.UX1_EXPECTED_EVIDENCE_HASHES) return true; + let expected; + try { expected = JSON.parse(env.UX1_EXPECTED_EVIDENCE_HASHES); } catch { return false; } + return expected && typeof expected === "object" && !Array.isArray(expected) + && Object.keys(expected).length === UX1_EVIDENCE_HASH_KEYS.length + && UX1_EVIDENCE_HASH_KEYS.every((key) => /^[a-f0-9]{64}$/.test(expected[key]) && preview.evidence[key] === expected[key]); +} + +export function ux1PreviewBindingStale(preview, env) { + if (!preview) return false; + if (!ux1PreviewMatchesEpoch(preview, env)) return true; + if (!preview.evidence || !env?.UX1_EXPECTED_EVIDENCE_HASHES) return false; + return !ux1ExpectedEvidenceMatches(preview, env); +} + +export function presentUx1Preview(preview, env) { + if (!preview) return null; + if (!ux1PreviewBindingStale(preview, env)) return preview; + return { + ...preview, + status: "stale", + stale: true, + actionable: false, + recompute_required: true, + }; +} + +function ux1Flags() { + return { + schema_version: UX1_DRAFT_SCHEMA, + study: UX1_STUDY, + account_connection: "native_not_observed", + native_observed: false, + credential_access_unavailable: true, + research_only: true, + no_order: true, + execution_authority_granted: false, + workflow_dispatched: false, + runtime_mutated: false, + d1_native: "NATIVE_PENDING_EXTERNAL", + d1_status: "PARTIAL", + }; +} + +function ux1StatePayload(env, { revision, fingerprint, draft, preview, intent, job = null, duplicate = false, unchanged = false }) { + const publicJob = projectUx1Job(job, Date.now(), revision, env); + const waiting = Boolean(publicJob && ["queued", "running", "unknown"].includes(publicJob.status)); + let shown = waiting ? null : presentUx1Preview(preview, env); + if (shown && ux1TerminalJobConfigStale(job, env)) { + shown = { ...shown, status: "stale", stale: true, actionable: false, recompute_required: true }; + } + return { + ok: true, + ...ux1Flags(), + research_workflow_dispatched: job?.dispatch_state === "dispatched", + revision, + fingerprint, + custom_draft: ux1CustomDraft(draft), + draft, + preview: shown, + preview_stale: Boolean(shown?.stale), + intent: waiting ? null : intent, + job: publicJob, + duplicate, + unchanged, + }; +} + +export async function requireUx1Session(request, env) { + const session = await readSession(request, env); + if (!session) throw new HttpError("login required", 401); + if (!session.allowed) throw new HttpError("allowlist required", 403); + return session; +} + +async function readUx1Body(request) { + const text = await request.text(); + if (text.length > 16384) throw new Ux1InputError("ux1_body_too_large"); + try { + return JSON.parse(text); + } catch { + throw new Ux1InputError("ux1_invalid_json"); + } +} + +function parseUx1Slot(result) { + if (!result?.found) return null; + return { + revision: result.revision, + fingerprint: result.fingerprint, + draft: JSON.parse(result.draft_json), + preview: result.preview_json ? JSON.parse(result.preview_json) : null, + intent: result.intent_json ? JSON.parse(result.intent_json) : null, + job: result.job_json ? JSON.parse(result.job_json) : null, + }; +} + +async function ux1ReadSlot(env, login) { + if (!hasRuntimeInstanceStore(env)) throw new HttpError("ux1_research_store_not_bound", 503); + return parseUx1Slot(await runtimeInstanceCommand(env, { action: "ux1_read", login })); +} + +export async function ux1DraftGet(request, env) { + const session = await requireUx1Session(request, env); + const slot = await ux1ReadSlot(env, session.login); + if (!slot) { + const draft = defaultUx1Draft(); + return json(ux1StatePayload(env, { revision: 0, fingerprint: await ux1Fingerprint(draft), draft, preview: null, intent: null })); + } + return json(ux1StatePayload(env, slot)); +} + +export async function ux1DraftPost(request, env) { + requireSameOrigin(request, { requireOrigin: true }); + const session = await requireUx1Session(request, env); + const parsed = assertUx1DraftPost(await readUx1Body(request)); + if (!hasRuntimeInstanceStore(env)) throw new HttpError("ux1_research_store_not_bound", 503); + const saved = await runtimeInstanceCommand(env, { + action: "ux1_save", + login: session.login, + expected_revision: parsed.expected_revision, + draft_json: canonicalJson(parsed.draft), + fingerprint: await ux1Fingerprint(parsed.draft), + }); + return json(ux1StatePayload(env, { ...parseUx1Slot({ ...saved, found: true }), unchanged: Boolean(saved.unchanged) })); +} + +function ux1AbortError() { + return new DOMException("The operation was aborted", "AbortError"); +} + +function abandonUx1Reader(reader) { + if (!reader) return; + try { + const canceling = reader.cancel(); + if (canceling && typeof canceling.catch === "function") canceling.catch(() => {}); + } catch { + /* cancel failed before returning a promise */ + } +} + +function abandonUx1Response(response) { + if (!response?.body) return; + try { + abandonUx1Reader(response.body.getReader()); + } catch { + /* body already locked or disturbed */ + } +} + +export async function readLimitedUtf8Body(response, maxBytes, controller, abortPromise) { + if (!response?.body) return ""; + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let received = 0; + let text = ""; + const stopReader = () => { + if (!controller.signal.aborted) controller.abort(); + abandonUx1Reader(reader); + }; + const onAbort = () => { abandonUx1Reader(reader); }; + controller.signal.addEventListener("abort", onAbort); + try { + while (true) { + if (controller.signal.aborted) throw ux1AbortError(); + const readPromise = reader.read(); + readPromise.catch(() => {}); + const next = await Promise.race([readPromise, abortPromise]); + if (next.done) break; + const bytes = next.value?.byteLength ?? 0; + received += bytes; + if (received > maxBytes) { + stopReader(); + throw new Error("ux1_calculator_body_limit"); + } + text += decoder.decode(next.value, { stream: true }); + } + text += decoder.decode(); + return text; + } catch (error) { + stopReader(); + throw error; + } finally { + controller.signal.removeEventListener("abort", onAbort); + } +} + +export async function readUx1CalculatorExchange(binding, body, limits) { + const deadlineMs = Number.isFinite(limits?.deadlineMs) ? limits.deadlineMs : UX1_CALCULATOR_DEADLINE_MS; + const maxBytes = Number.isFinite(limits?.maxBytes) ? limits.maxBytes : UX1_CALCULATOR_MAX_BYTES; + const controller = new AbortController(); + let rejectAbort = () => {}; + const abortPromise = new Promise((_, reject) => { rejectAbort = reject; }); + abortPromise.catch(() => {}); + const onAbort = () => rejectAbort(ux1AbortError()); + controller.signal.addEventListener("abort", onAbort); + const timer = setTimeout(() => controller.abort(), deadlineMs); + const pending = Promise.resolve().then(() => binding.fetch("https://ux1-research-calculator/preview", { + method: "POST", + headers: { "content-type": "application/json" }, + body, + signal: controller.signal, + })); + let accepted = false; + pending.then((response) => { + if (accepted || !controller.signal.aborted) return; + abandonUx1Response(response); + }, () => {}); + try { + const response = await Promise.race([pending, abortPromise]); + accepted = true; + const text = await readLimitedUtf8Body(response, maxBytes, controller, abortPromise); + return { status: response.status, ok: response.ok, text }; + } finally { + clearTimeout(timer); + controller.signal.removeEventListener("abort", onAbort); + } +} + +async function ux1ClearStoredPreview(env, login, slot) { + await runtimeInstanceCommand(env, { + action: "ux1_clear_preview", login, + expected_revision: slot.revision, + fingerprint: slot.fingerprint, + }); +} + +export async function ux1PreviewPost(request, env) { + requireSameOrigin(request, { requireOrigin: true }); + const session = await requireUx1Session(request, env); + const parsed = assertUx1PreviewPost(await readUx1Body(request)); + const slot = await ux1ReadSlot(env, session.login); + if (!slot || slot.revision !== parsed.expected_revision) throw new HttpError("ux1_preview_conflict", 409); + const draft = normalizeUx1Draft(slot.draft); + const fingerprint = await ux1Fingerprint(draft); + if (fingerprint !== slot.fingerprint) throw new HttpError("ux1_preview_conflict", 409); + if (ux1StoredJobBlocks(slot.job)) return json(ux1StatePayload(env, slot)); + const previewMode = ux1PreviewMode(env); + if (previewMode === "rejected") throw new HttpError("ux1_preview_mode_rejected", 400); + const gate = classifyUx1Draft(draft); + let preview; + if (gate.kind === "missing_input" || gate.kind === "unsupported_scope") { + preview = localUx1Preview({ draft, fingerprint, status: gate.kind, reasons: gate.reasons }); + } else if (previewMode === "github_actions") { + return await ux1DispatchPreview(env, session, slot, draft, fingerprint); + } else { + const binding = env.UX1_RESEARCH_CALCULATOR; + if (!binding || typeof binding.fetch !== "function") { + await ux1ClearStoredPreview(env, session.login, slot); + return json({ + ok: false, + error: "calculator_not_connected", + status: "calculator_not_connected", + recompute_required: true, + ...ux1Flags(), + revision: slot.revision, + fingerprint, + custom_draft: ux1CustomDraft(draft), + draft, + preview: null, + decision_preview: null, + historical_execution_check: null, + }, 503); + } + const calculatorRequest = buildUx1PreviewRequest(draft); + let exchange; + try { + exchange = await readUx1CalculatorExchange(binding, canonicalJson(calculatorRequest)); + } catch { + await ux1ClearStoredPreview(env, session.login, slot); + throw new HttpError("calculator_failed", 502); + } + const calculatorText = exchange.text; + if (exchange.status === 429) { + let busy = false; + try { busy = JSON.parse(calculatorText).error === "calculator_busy"; } catch { busy = false; } + if (busy) throw new HttpError("calculator_busy", 429); + } + if (!exchange.ok) { + await ux1ClearStoredPreview(env, session.login, slot); + throw new HttpError("calculator_failed", 502); + } + let raw; + try { + raw = JSON.parse(calculatorText); + } catch { + await ux1ClearStoredPreview(env, session.login, slot); + throw new HttpError("calculator_failed", 502); + } + try { + preview = projectUx1CalculatorResult(raw, calculatorRequest, draft, fingerprint); + if (!ux1ExpectedEvidenceMatches(preview, env)) throw new Ux1InputError("calculator_result_rejected", 502); + } catch (error) { + await ux1ClearStoredPreview(env, session.login, slot); + if (error instanceof Ux1InputError) throw new HttpError(error.message, error.status || 502); + throw new HttpError("calculator_result_rejected", 502); + } + } + preview = { ...preview, runtime_epoch: ux1RuntimeEpoch(env) }; + const stored = await runtimeInstanceCommand(env, { + action: "ux1_store_preview", + login: session.login, + expected_revision: slot.revision, + fingerprint, + preview_json: canonicalJson(preview), + }); + return json(ux1StatePayload(env, parseUx1Slot({ ...stored, found: true }))); +} + +export async function ux1IntentPost(request, env) { + requireSameOrigin(request, { requireOrigin: true }); + const session = await requireUx1Session(request, env); + const parsed = assertUx1IntentPost(await readUx1Body(request)); + const slot = await ux1ReadSlot(env, session.login); + if (!slot || slot.revision !== parsed.expected_revision || slot.fingerprint !== parsed.fingerprint) { + throw new HttpError("ux1_intent_conflict", 409); + } + if (ux1StoredJobBlocks(slot.job)) throw new HttpError("ux1_job_active", 409); + if (ux1PreviewBindingStale(slot.preview, env) || ux1TerminalJobConfigStale(slot.job, env)) throw new HttpError("ux1_preview_stale", 409); + if (!ux1IntentAllowed(slot.preview) || slot.preview.fingerprint !== parsed.fingerprint) { + throw new HttpError("ux1_intent_conflict", 409); + } + const intent = ux1IntentRecord({ + login: session.login, + revision: slot.revision, + fingerprint: slot.fingerprint, + receiptId: await ux1ReceiptId(session.login, slot.revision, slot.fingerprint), + }); + const stored = await runtimeInstanceCommand(env, { + action: "ux1_save_intent", + login: session.login, + expected_revision: slot.revision, + fingerprint: slot.fingerprint, + intent_json: canonicalJson(intent), + }); + return json(ux1StatePayload(env, { ...parseUx1Slot({ ...stored, found: true }), duplicate: Boolean(stored.duplicate) })); +} + +function ux1PreviewMode(env) { + const mode = String(env?.UX1_PREVIEW_MODE ?? "").trim(); + if (!mode) return "local"; + if (mode === "github_actions") return "github_actions"; + return "rejected"; +} + +function ux1EvidenceRecord(value) { + if (!value || typeof value !== "object" || Array.isArray(value)) return null; + if (Object.keys(value).length !== UX1_EVIDENCE_HASH_KEYS.length) return null; + const evidence = {}; + for (const key of UX1_EVIDENCE_HASH_KEYS) { + if (typeof value[key] !== "string" || !/^[a-f0-9]{64}$/.test(value[key])) return null; + evidence[key] = value[key]; + } + return evidence; +} + +function ux1AsyncConfig(env) { + const ues = String(env?.UX1_UES_REVISION || ""); + const epoch = String(env?.UX1_RUNTIME_EPOCH || ""); + const token = String(env?.UX1_RESEARCH_JOB_TOKEN || ""); + if (!/^[a-f0-9]{40}$/.test(ues) || !/^[a-f0-9]{32}$/.test(epoch) || token.length < 16 || token.length > 256) return null; + return ux1EvidenceRecord(ux1ParseJsonObject(env?.UX1_EXPECTED_EVIDENCE_HASHES)) + ? { ues, epoch, evidence: ux1EvidenceRecord(ux1ParseJsonObject(env.UX1_EXPECTED_EVIDENCE_HASHES)) } + : null; +} + +function ux1ParseJsonObject(text) { + if (typeof text !== "string" || !text) return null; + try { + const parsed = JSON.parse(text); + return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null; + } catch { + return null; + } +} + +function ux1StoredJobBlocks(job) { + return Boolean(job && (job.status === "queued" || job.status === "running" || job.status === "unknown")); +} + +function ux1PublicJobReason(reason) { + return UX1_PUBLIC_JOB_REASONS.has(reason) ? reason : null; +} + +function ux1JobConfigStale(job, env) { + const config = ux1AsyncConfig(env); + if (!config || !job) return false; + if (job.runtime_epoch !== config.epoch || job.ues_revision !== config.ues) return true; + return UX1_EVIDENCE_HASH_KEYS.some((key) => job.evidence?.[key] !== config.evidence[key]); +} + +function ux1TerminalJobConfigStale(job, env) { + return Boolean(job && !ux1StoredJobBlocks(job) && ux1JobConfigStale(job, env)); +} + +function ux1JobBindingMatchesConfig(job, env) { + return Boolean(job && ux1AsyncConfig(env) && !ux1JobConfigStale(job, env)); +} + +export function projectUx1Job(job, now, slotRevision, env) { + if (!job || typeof job !== "object") return null; + const created = Date.parse(job.created_at); + const age = Number.isFinite(created) ? now - created : 0; + let status = job.status; + let reason = job.reason || null; + if (ux1StoredJobBlocks(job) && age >= UX1_JOB_UNKNOWN_AFTER_MS) { + status = "unknown"; + reason = "check_required"; + } else if (!ux1StoredJobBlocks(job) && (job.revision !== slotRevision || ux1JobConfigStale(job, env))) { + status = "superseded"; + reason = "config_changed"; + } + return { + request_id: job.request_id, + status, + dispatch_state: job.dispatch_state, + created_at: job.created_at, + updated_at: job.updated_at, + reason: ux1PublicJobReason(reason), + }; +} + +function ux1BearerMatches(header, secret) { + const match = /^Bearer ([!-~]{16,256})$/.exec(String(header || "")); + if (!match || typeof secret !== "string" || secret.length < 16 || secret.length > 256) return false; + const left = new TextEncoder().encode(match[1]); + const right = new TextEncoder().encode(secret); + let diff = left.length ^ right.length; + const length = Math.max(left.length, right.length); + for (let index = 0; index < length; index += 1) diff |= (left[index] || 0) ^ (right[index] || 0); + return diff === 0; +} + +export function ux1ExactKeys(value, keys) { + return Boolean(value && typeof value === "object" && !Array.isArray(value) + && Object.keys(value).length === keys.length + && keys.every((key) => Object.hasOwn(value, key))); +} + +function ux1RequestIdOk(value) { + return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value); +} + +function ux1PositiveInt(value) { + return typeof value === "string" && /^[1-9][0-9]{0,18}$/.test(value); +} + +async function ux1Digest(value) { + const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonicalJson(value))); + return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join(""); +} + +async function readUx1LimitedJson(request, maxBytes) { + const controller = new AbortController(); + let rejectAbort = () => {}; + const abortPromise = new Promise((_, reject) => { rejectAbort = reject; }); + abortPromise.catch(() => {}); + const onAbort = () => rejectAbort(ux1AbortError()); + controller.signal.addEventListener("abort", onAbort); + const timer = setTimeout(() => controller.abort(), UX1_GITHUB_DEADLINE_MS); + try { + const text = await readLimitedUtf8Body(request, maxBytes, controller, abortPromise); + try { + return JSON.parse(text); + } catch { + throw new HttpError("ux1_invalid_body", 400); + } + } catch (error) { + if (error instanceof HttpError) throw error; + if (String(error?.message || "") === "ux1_calculator_body_limit") throw new HttpError("ux1_body_too_large", 400); + throw new HttpError("ux1_invalid_body", 400); + } finally { + clearTimeout(timer); + controller.signal.removeEventListener("abort", onAbort); + } +} + +async function ux1GithubExchange(url, init) { + const controller = new AbortController(); + let rejectAbort = () => {}; + const abortPromise = new Promise((_, reject) => { rejectAbort = reject; }); + abortPromise.catch(() => {}); + const onAbort = () => rejectAbort(ux1AbortError()); + controller.signal.addEventListener("abort", onAbort); + const timer = setTimeout(() => controller.abort(), UX1_GITHUB_DEADLINE_MS); + const pending = Promise.resolve().then(() => fetch(url, { ...init, redirect: "manual", signal: controller.signal })); + let accepted = false; + pending.then((response) => { + if (accepted || !controller.signal.aborted) return; + abandonUx1Response(response); + }, () => {}); + try { + const response = await Promise.race([pending, abortPromise]); + accepted = true; + const text = await readLimitedUtf8Body(response, UX1_GITHUB_MAX_BYTES, controller, abortPromise); + return { status: response.status, ok: response.ok, text }; + } finally { + clearTimeout(timer); + controller.signal.removeEventListener("abort", onAbort); + } +} + +async function ux1DispatchResearchJob(env, requestId) { + if (!env.RUNTIME_SETTINGS_DISPATCH_TOKEN) return "unknown"; + try { + const exchange = await ux1GithubExchange( + `https://api.github.com/repos/${UX1_RESEARCH_REPOSITORY}/actions/workflows/${UX1_RESEARCH_WORKFLOW_FILE}/dispatches`, + { + method: "POST", + headers: githubHeaders(env.RUNTIME_SETTINGS_DISPATCH_TOKEN), + body: JSON.stringify({ ref: "main", inputs: { ux1_request_id: requestId } }), + }, + ); + if (exchange.status >= 200 && exchange.status < 300) return "dispatched"; + if (exchange.status >= 400 && exchange.status < 500) return "rejected"; + return "unknown"; + } catch { + return "unknown"; + } +} + +async function ux1DispatchPreview(env, session, slot, draft, fingerprint) { + const config = ux1AsyncConfig(env); + if (!config || !env.RUNTIME_SETTINGS_DISPATCH_TOKEN) { + await ux1ClearStoredPreview(env, session.login, slot); + return json({ + ok: false, + error: "ux1_async_not_connected", + status: "ux1_async_not_connected", + recompute_required: true, + ...ux1Flags(), + research_workflow_dispatched: false, + revision: slot.revision, + fingerprint, + custom_draft: ux1CustomDraft(draft), + draft, + preview: null, + job: projectUx1Job(slot.job, Date.now(), slot.revision, env), + decision_preview: null, + historical_execution_check: null, + }, 503); + } + const begun = await runtimeInstanceCommand(env, { + action: "ux1_begin_job", + login: session.login, + revision: slot.revision, + fingerprint, + epoch: config.epoch, + ues: config.ues, + evidence: config.evidence, + request: buildUx1PreviewRequest(draft), + }); + const job = JSON.parse(begun.job_json); + if (begun.created) { + const outcome = await ux1DispatchResearchJob(env, job.request_id); + await runtimeInstanceCommand(env, { + action: "ux1_record_dispatch", + login: session.login, + request_id: job.request_id, + outcome, + }); + } + return json(ux1StatePayload(env, await ux1ReadSlot(env, session.login))); +} + +export function ux1RunMatches(run, job, runId, runAttempt) { + if (!run || typeof run !== "object" || Array.isArray(run) || !job) return false; + const repository = run.repository; + const headRepository = run.head_repository; + if (!repository || typeof repository !== "object" || !headRepository || typeof headRepository !== "object") return false; + return String(run.id) === runId + && run.event === "workflow_dispatch" + && run.path === UX1_RESEARCH_WORKFLOW_PATH + && run.head_sha === job.ues_revision + && run.display_title === `UX1 preview ${job.request_id}` + && String(run.run_attempt) === runAttempt + && run.head_branch === "main" + && repository.full_name === UX1_RESEARCH_REPOSITORY + && headRepository.full_name === UX1_RESEARCH_REPOSITORY + && repository.fork !== true + && headRepository.fork !== true; +} + +async function ux1FetchRun(env, runId) { + let exchange; + try { + exchange = await ux1GithubExchange( + `https://api.github.com/repos/${UX1_RESEARCH_REPOSITORY}/actions/runs/${runId}`, + { method: "GET", headers: githubHeaders(env.RUNTIME_SETTINGS_DISPATCH_TOKEN) }, + ); + } catch { + throw new HttpError("ux1_run_unverified", 502); + } + if (!exchange.ok) throw new HttpError("ux1_run_unverified", 502); + try { + return JSON.parse(exchange.text); + } catch { + throw new HttpError("ux1_run_unverified", 502); + } +} + +function ux1ClaimDenied(requestId, runId, runAttempt) { + return json({ ok: true, claimed: false, request_id: requestId, run_id: runId, run_attempt: runAttempt }); +} + +export async function ux1JobClaimPost(request, env) { + if (!ux1BearerMatches(request.headers.get("Authorization"), env.UX1_RESEARCH_JOB_TOKEN)) { + throw new HttpError("ux1_job_unauthorized", 401); + } + const body = await readUx1LimitedJson(request, UX1_JOB_BODY_MAX_BYTES); + if (!ux1ExactKeys(body, ["request_id", "run_id", "run_attempt"]) || !ux1RequestIdOk(body.request_id) || !ux1PositiveInt(body.run_id) || !ux1PositiveInt(body.run_attempt)) { + throw new HttpError("ux1_invalid_body", 400); + } + const looked = await runtimeInstanceCommand(env, { action: "ux1_lookup_job", request_id: body.request_id }); + if (!looked.found) throw new HttpError("ux1_job_not_found", 404); + const job = JSON.parse(looked.job_json); + if (job.claimed_run_id || (job.status !== "queued" && job.status !== "unknown")) { + return ux1ClaimDenied(body.request_id, body.run_id, body.run_attempt); + } + const draft = { + objective: job.request?.objective, + research_case_id: job.request?.research_case_id, + advanced_settings: job.request?.advanced_settings, + }; + try { + if (!job.request || canonicalJson(buildUx1PreviewRequest(draft)) !== canonicalJson(job.request) || await ux1Fingerprint(draft) !== job.fingerprint) { + throw new HttpError("ux1_run_unverified", 502); + } + } catch (error) { + if (error instanceof HttpError) throw error; + throw new HttpError("ux1_run_unverified", 502); + } + const run = await ux1FetchRun(env, body.run_id); + if (!ux1RunMatches(run, job, body.run_id, body.run_attempt)) throw new HttpError("ux1_run_unverified", 502); + const claimed = await runtimeInstanceCommand(env, { + action: "ux1_claim_job", + request_id: body.request_id, + run_id: body.run_id, + run_attempt: body.run_attempt, + }); + if (!claimed.claimed) return ux1ClaimDenied(body.request_id, body.run_id, body.run_attempt); + return json({ + ok: true, + claimed: true, + request_id: body.request_id, + run_id: body.run_id, + run_attempt: body.run_attempt, + request: claimed.request, + }); +} + +export async function ux1JobResultPost(request, env) { + if (!ux1BearerMatches(request.headers.get("Authorization"), env.UX1_RESEARCH_JOB_TOKEN)) { + throw new HttpError("ux1_job_unauthorized", 401); + } + const body = await readUx1LimitedJson(request, UX1_JOB_BODY_MAX_BYTES); + if (!ux1ExactKeys(body, ["request_id", "run_id", "run_attempt", "result", "error_code"]) || !ux1RequestIdOk(body.request_id) || !ux1PositiveInt(body.run_id) || !ux1PositiveInt(body.run_attempt)) { + throw new HttpError("ux1_invalid_body", 400); + } + if (!(body.error_code === null || body.error_code === "input_unavailable" || body.error_code === "calculator_failed")) { + throw new HttpError("ux1_invalid_body", 400); + } + if (body.error_code === null ? body.result === null : body.result !== null) throw new HttpError("ux1_invalid_body", 400); + const looked = await runtimeInstanceCommand(env, { action: "ux1_lookup_job", request_id: body.request_id }); + if (!looked.found) throw new HttpError("ux1_job_not_found", 404); + const job = JSON.parse(looked.job_json); + if (job.claimed_run_id !== body.run_id || job.claimed_run_attempt !== body.run_attempt) { + throw new HttpError("ux1_result_conflict", 409); + } + let previewJson = null; + if (!body.error_code) { + const draft = { + objective: job.request?.objective, + research_case_id: job.request?.research_case_id, + advanced_settings: job.request?.advanced_settings, + }; + try { + if (canonicalJson(buildUx1PreviewRequest(draft)) !== canonicalJson(job.request)) throw new Ux1InputError("calculator_result_rejected", 502); + const preview = projectUx1CalculatorResult(body.result, job.request, draft, job.fingerprint); + const evidence = { evidence: Object.fromEntries(UX1_EVIDENCE_HASH_KEYS.map((key) => [key, preview.evidence?.[key]])) }; + if (!preview.evidence || !ux1ExpectedEvidenceMatches(evidence, { UX1_EXPECTED_EVIDENCE_HASHES: JSON.stringify(job.evidence) })) { + throw new Ux1InputError("calculator_result_rejected", 502); + } + if (ux1JobBindingMatchesConfig(job, env)) previewJson = canonicalJson({ ...preview, runtime_epoch: job.runtime_epoch }); + } catch (error) { + if (error instanceof Ux1InputError && error.message === "calculator_failed") throw new HttpError("calculator_failed", 502); + throw new HttpError("calculator_result_rejected", 502); + } + } + const finished = await runtimeInstanceCommand(env, { + action: "ux1_finish_job", + request_id: body.request_id, + run_id: body.run_id, + run_attempt: body.run_attempt, + error_code: body.error_code, + preview_json: previewJson, + terminal_digest: await ux1Digest({ error_code: body.error_code, result: body.result }), + }); + return json({ ok: true, accepted: true, duplicate: Boolean(finished.duplicate) }); +} + +export function ux1SelectSlot(sql, login) { + return sql.exec( + "SELECT revision, draft_json, fingerprint, preview_json, intent_json, job_json FROM ux1_research_slot WHERE login = ?", + login, + ).toArray()[0] || null; +} + +export function ux1SlotPayload(row, extra = {}) { + return { + ok: true, + found: Boolean(row), + revision: row ? Number(row.revision) : 0, + draft_json: row?.draft_json || null, + fingerprint: row?.fingerprint || null, + preview_json: row?.preview_json || null, + intent_json: row?.intent_json || null, + job_json: row?.job_json || null, + ...extra, + }; +} + +function ux1ParseStoredJob(text) { + if (!text) return null; + try { + const job = JSON.parse(text); + return job && typeof job === "object" && !Array.isArray(job) ? job : null; + } catch { + return null; + } +} + +function ux1SelectJob(sql, requestId) { + return sql.exec( + `SELECT login, revision, draft_json, fingerprint, preview_json, intent_json, job_json + FROM ux1_research_slot WHERE json_extract(job_json, '$.request_id') = ?`, + requestId, + ).toArray()[0] || null; +} + +function ux1WriteJob(sql, login, job) { + const text = JSON.stringify(job); + if (text.length > 65536) throw new HttpError("ux1_invalid_body", 400); + sql.exec("UPDATE ux1_research_slot SET job_json = ? WHERE login = ?", text, login); + return text; +} + +function ux1SameSucceededBinding(job, command, row) { + const evidence = ux1EvidenceRecord(command.evidence); + if (!job || job.status !== "succeeded" || !evidence || !row) return false; + return job.revision === Number(row.revision) + && job.fingerprint === row.fingerprint + && job.revision === command.revision + && job.fingerprint === command.fingerprint + && job.runtime_epoch === command.epoch + && job.ues_revision === command.ues + && UX1_EVIDENCE_HASH_KEYS.every((key) => job.evidence?.[key] === evidence[key]); +} + +export function ux1MutateJob(sql, command) { + if (command.action === "ux1_lookup_job") { + if (!ux1RequestIdOk(command.request_id)) throw new HttpError("ux1_invalid_body", 400); + const row = ux1SelectJob(sql, command.request_id); + return row ? ux1SlotPayload(row) : { ok: true, found: false }; + } + if (command.action === "ux1_claim_job" || command.action === "ux1_finish_job") { + if (!ux1RequestIdOk(command.request_id) || !ux1PositiveInt(command.run_id) || !ux1PositiveInt(command.run_attempt)) { + throw new HttpError("ux1_invalid_body", 400); + } + const row = ux1SelectJob(sql, command.request_id); + if (!row) throw new HttpError("ux1_job_not_found", 404); + const job = ux1ParseStoredJob(row.job_json); + if (!job) throw new HttpError("ux1_job_not_found", 404); + const now = new Date().toISOString(); + if (command.action === "ux1_claim_job") { + if (job.claimed_run_id || (job.status !== "queued" && job.status !== "unknown")) { + return { ok: true, claimed: false }; + } + job.status = "running"; + job.claimed_run_id = command.run_id; + job.claimed_run_attempt = command.run_attempt; + job.updated_at = now; + ux1WriteJob(sql, row.login, job); + return { ok: true, claimed: true, request: job.request }; + } + if (job.claimed_run_id !== command.run_id || job.claimed_run_attempt !== command.run_attempt) { + throw new HttpError("ux1_result_conflict", 409); + } + if (job.terminal_digest) { + if (job.terminal_digest === command.terminal_digest) return { ok: true, duplicate: true }; + throw new HttpError("ux1_result_conflict", 409); + } + if (job.status !== "running") throw new HttpError("ux1_result_conflict", 409); + if (typeof command.terminal_digest !== "string" || !/^[a-f0-9]{64}$/.test(command.terminal_digest)) { + throw new HttpError("ux1_invalid_body", 400); + } + job.status = command.error_code ? "failed" : "succeeded"; + job.reason = command.error_code || null; + job.terminal_digest = command.terminal_digest; + job.updated_at = now; + ux1WriteJob(sql, row.login, job); + const sameSlot = Number(row.revision) === job.revision && row.fingerprint === job.fingerprint; + if (command.error_code && sameSlot) { + sql.exec( + "UPDATE ux1_research_slot SET preview_json = NULL, intent_json = NULL WHERE login = ? AND revision = ? AND fingerprint = ?", + row.login, Number(row.revision), row.fingerprint, + ); + } else if (!command.error_code && sameSlot && typeof command.preview_json === "string" && command.preview_json.length <= 65536) { + sql.exec( + "UPDATE ux1_research_slot SET preview_json = ? WHERE login = ? AND revision = ? AND fingerprint = ?", + command.preview_json, row.login, Number(row.revision), row.fingerprint, + ); + } + return { ok: true, duplicate: false }; + } + const login = String(command?.login || ""); + if (!/^[a-z0-9-]{1,39}$/.test(login)) throw new HttpError("ux1_login_invalid", 400); + const row = ux1SelectSlot(sql, login); + if (!row) throw new HttpError("ux1_preview_conflict", 409); + const job = ux1ParseStoredJob(row.job_json); + if (command.action === "ux1_begin_job") { + if (ux1StoredJobBlocks(job)) return { ...ux1SlotPayload(row), created: false }; + if (ux1SameSucceededBinding(job, command, row)) return { ...ux1SlotPayload(row), created: false }; + const evidence = ux1EvidenceRecord(command.evidence); + if (!evidence || command.revision !== Number(row.revision) || command.fingerprint !== row.fingerprint) { + throw new HttpError("ux1_preview_conflict", 409); + } + if (!/^[a-f0-9]{32}$/.test(String(command.epoch || "")) || !/^[a-f0-9]{40}$/.test(String(command.ues || ""))) { + throw new HttpError("ux1_invalid_body", 400); + } + if (!command.request || command.request.schema !== "qsl.ux1.preview_request.v1" || JSON.stringify(command.request).length > 16384) { + throw new HttpError("ux1_invalid_body", 400); + } + const now = new Date().toISOString(); + const next = { + request_id: crypto.randomUUID(), + status: "queued", + dispatch_state: "pending", + revision: Number(row.revision), + fingerprint: row.fingerprint, + runtime_epoch: command.epoch, + ues_revision: command.ues, + evidence, + request: command.request, + created_at: now, + updated_at: now, + claimed_run_id: null, + claimed_run_attempt: null, + reason: null, + terminal_digest: null, + }; + const jobJson = ux1WriteJob(sql, login, next); + sql.exec("UPDATE ux1_research_slot SET preview_json = NULL, intent_json = NULL WHERE login = ?", login); + return { ...ux1SlotPayload(row), preview_json: null, intent_json: null, job_json: jobJson, created: true }; + } + if (command.action === "ux1_record_dispatch") { + if (!job || job.request_id !== command.request_id || job.dispatch_state !== "pending") { + return { ...ux1SlotPayload(row), recorded: false }; + } + if (command.outcome === "dispatched") job.dispatch_state = "dispatched"; + else if (command.outcome === "rejected") { + job.dispatch_state = "rejected"; + if (job.status === "queued") job.status = "failed"; + job.reason = "dispatch_rejected"; + } else { + job.dispatch_state = "unknown"; + if (job.status === "queued") job.status = "unknown"; + job.reason = "dispatch_unconfirmed"; + } + job.updated_at = new Date().toISOString(); + const jobJson = ux1WriteJob(sql, login, job); + return { ...ux1SlotPayload(row), job_json: jobJson, recorded: true }; + } + throw new HttpError("unsupported_ux1_action", 400); +} diff --git a/web/strategy-switch-console/application/worker/runtime_support.js b/web/strategy-switch-console/application/worker/runtime_support.js new file mode 100644 index 00000000..ed10dc49 --- /dev/null +++ b/web/strategy-switch-console/application/worker/runtime_support.js @@ -0,0 +1,13981 @@ +import { performanceBenchmarkResponse } from "../console_api/performance_benchmarks.js"; +import { binanceDetailCommand } from "../../binance_detail_archive.js"; +import { createDeskAuth, DeskError, deskErrorResponse } from "../desk/http.js"; +import { DeskReadbackStore, deskReadbackResponse, buildReadback, readbackTargets } from "../desk/readback.js"; +import { deskCredentialsResponse } from "../desk/credentials.js"; +import { deskSwitchTargetsResponse } from "../desk/switch_targets.js"; +import { auditPageQuery, auditEventInPage, auditEventOrder, auditNextCursor } from "../console_api/audit_pagination.js"; +import { runtimeReadbackProjection } from "../console_api/runtime_readback.js"; +import { initialEnrollmentBindingsMatch, initialEnrollmentDigest } from "../console_api/initial_enrollment.js"; +import { readNotificationLog } from "../console_api/notification_receipts.js"; +import { readRuntimeControlReceipt } from "../console_api/runtime_control_receipts.js"; +import { KvDirectory, currentStrategiesSignature, DIRECTORY_PATH, directoryCommand, directoryList, directoryStatus, indexedKey, maintainDirectory } from "../../kv_directory.js"; +import { sourceListFailure } from "../console_api/read_failures.js"; +import { strategyTargetExecution, accountExecutionEvidence } from "../console_api/strategy_execution.js"; +import { performanceResponse } from "../console_api/performance_routes.js"; +import { consoleApiResponse } from "../console_api/routes.js"; +import { runControls } from "../console_api/run_controls.js"; +import { decorateQueue, decisionFields, actorAlias, pageQuery, shortId } from "../console_api/presentation.js"; +import { inbox } from "../console_api/inbox.js"; +import { + DECISION_ACTIONS, DECISION_REVIEW_SCHEMA_VERSION, decisionCategoryPart, decisionCurrentEligibility, + decisionExpiry, decisionExpiryState, decisionKind, decisionOperationId, decisionOperationReference, + decisionOperationSha256, decisionRevision, decisionWriteCategory, + operationIdFor, operationStatusFromApplication, +} from "../console_api/decision_contract.js"; +import { RISK_TIER_POLICY_BINDINGS, riskTierBindingView, riskTierPolicyCatalog } from "../console_api/risk_tiers.js"; +import { recoveryMaterialPresentation, currentReconciliationObservationPresentation } from "../console_api/reconciliation_presentation.js"; +// deploy: 2026-06-30 — config driven by platform-config.json +import { initializeSecretRotation, durableRotationCommand, credentialRotationResponse } from "../../secretRotation.js"; +import { RotationError } from "../../secretRotationStepUp.js"; +import { CredentialMaintenanceError, initializeCredentialMaintenance, credentialMaintenanceCommand } from "../../credentialMaintenance.js"; +import { V2_ASSETS, V2_PAGE_HTML, V2_BUILD_ID } from "../../v2_asset_map.js"; +import { CycleHealthError, canonicalCycleJson, cycleHealthSha256, normalizeCycleHealth, normalizeCycleInstant, compareCycleInstants, cycleCoverageComplete, cycleHealthAttemptKey, cycleHealthFaultCategory, cycleHealthFaultTime } from "../../runtime_cycle_health.js"; +import { RUNTIME_DAILY_TARGET, RUNTIME_DAILY_TARGETS, RUNTIME_DAILY_TIMEZONE, runtimeDailyBusinessDate, runtimeDailyTarget, runtimeDailyBoundAccountKey, runtimeDailyRunIssue, parseRuntimeDailyTargetKey } from "../../runtime_daily_contract.js"; +import { + BINANCE_FACTS_KEY, BINANCE_FACTS_MAX_BYTES, BinanceFactsError, + normalizeBinanceFactsBinding, binanceFactsOptionMatches, assertBinanceFactsSourceBinding, + normalizeBinanceAccountFacts, projectBinanceAccountFacts, + binanceWalletHistoryEntry, binanceWalletHistoryStorageKey, normalizeBinanceWalletHistoryEntry, + projectBinanceWalletHistoryFromSources, + assertBinanceFactsHistoryBindings, BINANCE_WALLET_HISTORY_MAX_DAYS, +} from "../../binance_account_facts.js"; +import { DEFAULT_STRATEGY_PROFILES } from "../../strategy_profiles_asset.js"; +import { canonicalStrategyProfile, sameStrategyProfile } from "../../strategy_identity.js"; +import { + DCA_SUPPORTED_PLATFORMS, + DEFAULT_VARIABLE_SCOPES as DEFAULT_VARIABLE_SCOPE, + PLATFORM_REPOSITORIES, + DOMAIN_LABELS, + PLATFORM_MIN_RESERVED_CASH_VARIABLES, + PLATFORM_RESERVED_CASH_RATIO_VARIABLES, + PLATFORM_CONFIG, + PLATFORM_META, + DEFAULT_ACCOUNT_OPTIONS, + FALLBACK_INCOME_LAYER_DEFAULTS, + FALLBACK_OPTION_OVERLAY_DEFAULTS, + DCA_PROFILE_DEFAULTS, + RUNTIME_CATALOG_PROJECTION, + STRATEGY_FEATURES, + PLATFORM_REPOSITORY_ENV_KEYS, + OBSERVABILITY_PLATFORMS, +} from "../../config.js"; +import { + DEFAULT_PLATFORM_REPOSITORIES, + PLATFORM_REPOSITORY_ENV, + PLATFORM_CASH_ONLY_EXECUTION_VARIABLES_MAP as PLATFORM_CASH_ONLY_EXECUTION_VARIABLES, + OBSERVABILITY_PLATFORM_IDS, + resolvePlatformRepositories, + isDcaSupportedPlatform, +} from "../../platform_registry.js"; +import { assertLiveSwitchAllowed, catalogVersionMetadata, isLiveSwitchAllowed } from "../../catalog.js"; +import { + BROKER_ENVIRONMENTS, + normalizeAccountOptionsPayload as normalizeAccountOptionsSchemaPayload, + parseAccountOptionsJson as parseAccountOptionsSchemaJson, +} from "../../account_options_schema.js"; +import { + normalizeIbkrPeriodReturn, + publicIbkrPeriodReturn, + ACCOUNT_FACTS_BINDINGS_KEY, + ACCOUNT_FACTS_HISTORY_MAX_DAYS, + ACCOUNT_FACTS_MAX_BODY_BYTES, + ACCOUNT_FACTS_STALE_MS, + ACCOUNT_FACTS_FUTURE_SKEW_MS, + ACCOUNT_FACTS_SUPPORTED_PLATFORMS, + ACCOUNT_FACTS_RETURN_UNAVAILABLE, + IBKR_ACCOUNT_FACTS_PLATFORM, + SCHWAB_ACCOUNT_FACTS_PLATFORM, + FIRSTRADE_ACCOUNT_FACTS_PLATFORM, + validFirstradeAccountId, + accountFactsOptionMatchesBinding, + accountFactsPlatformForHistory, + accountFactsReadModelEnabled, + accountFactsScopesMatchForPlatform, + buildAccountFactsHistoryReadModel, + buildAccountFactsReadModel, + canonicalizeAccountFactsHistory, + decideAccountFactsDailyUpsert, + decideAccountFactsPut, + historyRetentionCutoff, + normalizeAccountFactsBindings, + normalizeAccountFactsHistoryPayload, + normalizeAccountFactsHistoricalPayload, + resolveTrustedAccountFactsBinding, + AccountFactsError, +} from "../../account_facts.js"; +import { + UX1_DRAFT_SCHEMA, + UX1_EVIDENCE_HASH_KEYS, + UX1_STUDY, + Ux1InputError, + assertUx1DraftPost, + assertUx1IntentPost, + assertUx1PreviewPost, + buildUx1PreviewRequest, + canonicalJson, + classifyUx1Draft, + defaultUx1Draft, + localUx1Preview, + normalizeUx1Draft, + projectUx1CalculatorResult, + ux1CustomDraft, + ux1Fingerprint, + ux1IntentAllowed, + ux1IntentRecord, + ux1ReceiptId, +} from "../../ux1_research_contract.js"; +import { + OVERVIEW_FX_PROXY_PATH, + OVERVIEW_FX_UPSTREAM_URL, + overviewFxResponse as overviewFxResponseImpl, + resetOverviewFxCache, +} from "../../infrastructure/fx/overview_fx.js"; +import { + MARKET_BENCHMARK_API_PATH, + MARKET_BENCHMARK_SYNC_PATH, + MARKET_BENCHMARK_SCHEMA, + MARKET_BENCHMARK_SERIES_IDS, + MarketBenchmarkError, + normalizeMarketBenchmarkSyncPayload, + publicMarketBenchmarkReadModel, +} from "../../infrastructure/market_benchmark/market_benchmark.js"; +import { + MARKET_BENCHMARK_REVISION_PATH, + planRevisionApply, + revisionApplyLogSummary, + MarketBenchmarkRevisionError, +} from "../../infrastructure/market_benchmark/market_benchmark_revision.js"; +import { + accountFactsResponse as accountFactsResponseImpl, + accountFactsHistoryResponse as accountFactsHistoryResponseImpl, + accountFactsIdentityDates, +} from "../account_facts/read.js"; +import { + syncAccountFactsResponse as syncAccountFactsResponseImpl, +} from "../account_facts/sync.js"; +import { + ownerDecisionQueueResponse as ownerDecisionQueueResponseImpl, + recordOwnerDecisionResponse as recordOwnerDecisionResponseImpl, +} from "../approval/owner_decisions.js"; +import { + researchPromotionApplicationResponse as researchPromotionApplicationResponseImpl, + researchPromotionApplicationInternalResponse as researchPromotionApplicationInternalResponseImpl, +} from "../research_promotion/applications.js"; + +import { HttpError } from "../http_error.js"; +import { buildRiskEnvelopeView, attachRiskEnvelopeView } from "../risk_envelope.js"; +import { + ACCOUNT_DIAGNOSIS_REQUEST_PATH, + ACCOUNT_DIAGNOSIS_INTERNAL_PREFIX, + ACCOUNT_DIAGNOSIS_TARGET_ID, + ACCOUNT_DIAGNOSIS_TARGETS, + ACCOUNT_DIAGNOSIS_TRIGGERS, + ACCOUNT_DIAGNOSIS_STATUSES, + ACCOUNT_DIAGNOSIS_REASON_CODES, + ACCOUNT_DIAGNOSIS_MAX_BODY_BYTES, + ACCOUNT_DIAGNOSIS_SUMMARY_MAX_CHARS, + ACCOUNT_DIAGNOSIS_DEDUPE_WINDOW_MS, + ACCOUNT_DIAGNOSIS_AUTO_ENABLED_ENV, + ACCOUNT_DIAGNOSIS_DO_ACTIONS, + ACCOUNT_DIAGNOSIS_CONFLICT_REASON_TEXT, +} from "../account_diagnosis/constants.js"; +import { + accountDiagnosisConflict, + accountDiagnosisErrorPayload, + accountDiagnosisTaskFromRow, +} from "../account_diagnosis/helpers.js"; +import { + UX1_CALCULATOR_DEADLINE_MS, + UX1_CALCULATOR_MAX_BYTES, + UX1_RESEARCH_REPOSITORY, + UX1_RESEARCH_WORKFLOW_FILE, + UX1_RESEARCH_WORKFLOW_PATH, + UX1_JOB_BODY_MAX_BYTES, + UX1_GITHUB_DEADLINE_MS, + UX1_GITHUB_MAX_BYTES, + UX1_JOB_UNKNOWN_AFTER_MS, + UX1_PUBLIC_JOB_REASONS, +} from "../ux1/constants.js"; +import { + requireUx1Session, + ux1DraftGet, + ux1DraftPost, + ux1PreviewPost, + ux1IntentPost, + ux1JobClaimPost, + ux1JobResultPost, + ux1MutateJob, + ux1SelectSlot, + ux1SlotPayload, + presentUx1Preview, + projectUx1Job, + ux1RunMatches, + ux1RuntimeEpoch, + readUx1CalculatorExchange, + readLimitedUtf8Body, + ux1ExactKeys, +} from "../ux1/research.js"; +import { + hasRuntimeInstanceStore, + runtimeInstanceCommand, + runtimeInstanceAccountOptions, + runtimeInstanceConflict, + runtimeInstanceFields, +} from "../runtime_instances/command.js"; +import { startLogin, finishLogin } from "../auth/login.js"; +import { readSession, makeSession } from "../auth/session.js"; + +export const CYCLE_HEALTH_HISTORY_PAGE_SIZE = 50; +export const CYCLE_HEALTH_SUMMARY_SCHEMA_VERSION = "qsl_runtime_cycle_health_state_summary.v1"; +export const DEFAULT_REPOSITORY = "QuantStrategyLab/QuantRuntimeSettings"; +export const DEFAULT_WORKFLOW = "manual-strategy-switch.yml"; +export const ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_PATH = "/api/internal/account-settings/profile-application/claim"; +export const ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_AUDIENCE = "qrs-profile-application"; +export const ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_REPOSITORY = "QuantStrategyLab/QuantRuntimeSettings"; +export const ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_REF = "refs/heads/main"; +export const ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_WORKFLOW_REF = "QuantStrategyLab/QuantRuntimeSettings/.github/workflows/apply-approved-hk-profile.yml@refs/heads/main"; +export const ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_JWKS_URL = "https://token.actions.githubusercontent.com/.well-known/jwks"; +export const ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_MAX_TOKEN_BYTES = 8192; +export const ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_MAX_JWKS_BYTES = 32 * 1024; +export const ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_TIMEOUT_MS = 5000; +export const ACCOUNT_SETTINGS_PROFILE_APPLICATION_REQUEST_TIMEOUT_MS = 1000; +export const ACCOUNT_SETTINGS_PROFILE_APPLICATION_MAX_BODY_BYTES = 1024; +export const SESSION_COOKIE = "qsl_switch_session"; +export const OAUTH_STATE_COOKIE = "qsl_switch_oauth_state"; +export const SESSION_TTL_SECONDS = 8 * 60 * 60; +export const AUTH_CONFIG_KEY = "auth_config"; +export const ACCOUNT_OPTIONS_KEY = "account_options"; +export const STRATEGY_PROFILES_KEY = "strategy_profiles"; +// Risk preferences are a separate, low-frequency owner-intent record. They +// must never be merged into a strategy switch input, runtime target, or any +// execution credential/policy path. +export const RISK_PROFILE_BINDINGS_KEY = "risk_profile_bindings"; +export const RISK_PROFILE_BINDING_REGISTRY_SCHEMA_VERSION = "qsl.risk_profile_binding_registry.v1"; +export const RISK_PROFILE_BINDING_SCHEMA_VERSION = "qsl.risk_profile_binding.v1"; +export const RISK_PROFILE_SELECTION_SCHEMA_VERSION = "qsl.risk_profile_selection.v1"; +const RISK_PROFILE_IDS = { + CAPITAL_PRESERVATION: "capital_preservation_v1", + BALANCED_COMPOUNDING: "balanced_compounding_v1", + GROWTH_COMPOUNDING: "growth_compounding_v1", +}; +export const RISK_PROFILE_PREFERENCES = Object.keys(RISK_PROFILE_IDS); +export const DECISION_ACTION_SCHEMA_VERSION = "qsl.decision_action_result.v1"; +export const OPERATION_SCHEMA_VERSION = "qsl.operation.v1"; +export const AUDIT_EVENTS_SCHEMA_VERSION = "qsl.audit_events.v1"; +export const DECISION_ACTION_MAX_BODY_BYTES = 8 * 1024; +export const AUDIT_EVENTS_MAX_ROWS = 101; +export const AUDIT_LOG_KEY = "audit_log"; +export const AUDIT_LOG_LIMIT = 50; +export const CURRENT_STRATEGIES_TIMEOUT_MS = 25000; +export const CURRENT_STRATEGIES_CACHE_KEY = "current_strategies_cache"; +// The keep-alive bump stays below the KV fresh window so one missed cron tick +// (the schedule is */5) still leaves the entry inside it; a cold isolate then +// never has to fan out to GitHub just because the stamp aged out. +export const CURRENT_STRATEGIES_MEMO_TTL_MS = 60_000; // 60 sec — a warm isolate re-checks GitHub after this +export const CURRENT_STRATEGIES_KV_FRESH_MS = 1_800_000; // 30 min — a cold isolate serves the KV entry without any GitHub fan-out +export const CURRENT_STRATEGIES_STALE_TTL_MS = 3_600_000; // 60 min — return stale plus background refresh before blocking on GitHub +export const CURRENT_STRATEGIES_KV_BUMP_MS = 900_000; // 15 min — ceiling for ts-only rewrites when the content did not change +export const GITHUB_API_TIMEOUT_MS = 8000; +export const GITHUB_LOGIN_REQUEST_TIMEOUT_MS = 20000; +export const STRATEGY_HEALTH_SNAPSHOT_KEY = "strategy_health_snapshot"; +export const STRATEGY_HEALTH_MAX_BODY_BYTES = 256 * 1024; +export const STRATEGY_HEALTH_DEFAULT_STALE_TTL_SECONDS = 2 * 60 * 60; +export const STRATEGY_HEALTH_STATUSES = ["healthy", "watch", "review", "critical"]; +// Derived from the generated platform config so a domain is declared once. +const SUPPORTED_STRATEGY_DOMAINS = Object.freeze([ + ...new Set(Object.values(PLATFORM_CONFIG).flatMap((platform) => platform.supported_domains || [])), +]); +export const STRATEGY_HEALTH_DOMAINS = SUPPORTED_STRATEGY_DOMAINS; +export const STRATEGY_HEALTH_DATA_STATUSES = ["ready", "unavailable", "stale"]; +export const CONTROL_PLANE_SNAPSHOT_KEY = "control_plane_snapshot"; +export const CONTROL_PLANE_SOURCE_PREFIX = "control_plane_source:"; +export const CONTROL_PLANE_SOURCE_SCHEMA_VERSION = "qsl_control_plane_source_snapshot.v1"; +export const CONTROL_PLANE_SOURCE_RETIREMENTS_KEY = "control_plane_source_retirements"; +export const CONTROL_PLANE_ATTENTION_STATUSES = ["research_only", "attention_required", "unavailable"]; +export const CONTROL_PLANE_MAX_SOURCES = 100; +export const CONTROL_PLANE_MAX_BODY_BYTES = 256 * 1024; +// The current source is a once-per-trading-day P1/P3 lane, rather than a live +// execution heartbeat. Freshness therefore preserves the normal TTL, but also +// waits for the next scheduled research window across the weekend before it +// calls an otherwise usable Friday/Saturday snapshot stale. +export const CONTROL_PLANE_DEFAULT_STALE_TTL_SECONDS = 36 * 60 * 60; +export const CONTROL_PLANE_RESEARCH_SCHEDULE_UTC_DAYS = new Set([2, 3, 4, 5, 6]); +export const CONTROL_PLANE_RESEARCH_SCHEDULE_UTC_HOUR = 3; +export const CONTROL_PLANE_RESEARCH_DELIVERY_GRACE_MS = 4 * 60 * 60 * 1000; +export const CONTROL_PLANE_CANDIDATE_KINDS = ["individual", "portfolio", "plugin"]; +export const CONTROL_PLANE_STAGES = ["P1", "P2", "P3", "P4", "P5", "P6"]; +const CONTROL_PLANE_LIFECYCLE_STATUSES = [ + "research", "evidence_pending", "verified", "deferred", "parked", "paper", "shadow", "owner_decision_required", +]; +const CONTROL_PLANE_RECOMMENDATIONS = [ + "none", "keep_research", "defer", "park", "auto_paper_evaluation", "auto_shadow_evaluation", "owner_live_decision", +]; +export const CONTROL_PLANE_AUTOMATION_STATES = ["not_configured", "configured", "active"]; +const FORWARD_OBSERVATION_STATES = [ + "PARKED", "FORWARD_ACTIVE", "PAUSED", "FORWARD_COMPLETE_HUMAN_REVIEW", "MANUAL_HOLD", "IDENTITY_MISMATCH", "RISK_BLOCKED", "REVOKED", "SUPERSEDED", +]; +export const FORWARD_OBSERVATION_RESEARCH_STAGES = new Set(["P1", "P2", "P3", "P4"]); +// M1 adaptive selections are a separate read-only projection. They are not +// lifecycle evidence, an account instruction, or an execution permission. +export const ADAPTIVE_SELECTION_SOURCE_PREFIX = "adaptive_selection_source:"; +export const ADAPTIVE_SELECTION_SOURCE_SCHEMA_VERSION = "qsl.adaptive_selection_source_snapshot.v1"; +export const ADAPTIVE_SELECTION_DASHBOARD_SCHEMA_VERSION = "qsl.adaptive_selection_dashboard.v1"; +export const ADAPTIVE_SELECTION_DECISION_SCHEMA_VERSION = "qsl.selection_decision.v1"; +export const ADAPTIVE_SELECTION_MAX_SOURCES = 100; +export const ADAPTIVE_SELECTION_MAX_BODY_BYTES = 256 * 1024; +export const ADAPTIVE_SELECTION_DEFAULT_STALE_TTL_SECONDS = 36 * 60 * 60; +export const ADAPTIVE_SELECTION_AUTHORITY = "shadow_only"; +// M0 research arrives through a separate, signed-at-transport-boundary +// snapshot. It is deliberately neither an M1 selection input nor a research +// task: this Worker only retains a closed, no-order ledger for authenticated +// readers. No strategy/platform/runtime/broker helper may consume it here. +export const M0_RESEARCH_TRANSPORT_SCHEMA_VERSION = "qsl_m0_research_publisher_envelope.v1"; +export const M0_RESEARCH_LEDGER_SCHEMA_VERSION = "qsl_m0_research_ledger.v1"; +export const M0_RESEARCH_DASHBOARD_SCHEMA_VERSION = "qsl_m0_research_dashboard.v1"; +export const M0_RESEARCH_STORAGE_SCHEMA_VERSION = "qsl_m0_research_ledger_storage.v1"; +export const M0_RESEARCH_CURRENT_KEY = "m0_research_ledger_current"; +export const M0_RESEARCH_ARCHIVE_PREFIX = "m0_research_ledger_archive:"; +export const M0_RESEARCH_MAX_BODY_BYTES = 256 * 1024; +export const M0_RESEARCH_RETENTION_SECONDS = 14 * 24 * 60 * 60; +export const M0_RESEARCH_ALLOWED_SOURCE_REPOSITORY = "QuantStrategyLab/QuantAdvisorResearch"; +export const M0_RESEARCH_ALLOWED_PRODUCER_REPOSITORY = "QuantStrategyLab/QuantRuntimeSettings"; +export const M0_RESEARCH_SUBJECT_KINDS = ["asset_idea", "theme_context", "strategy_hypothesis", "risk_context"]; +export const M0_RESEARCH_HORIZONS = ["short", "medium", "long", "not_applicable"]; +export const M0_RESEARCH_STATES = ["candidate", "source_verification_required", "deferred", "context_only"]; +export const M0_RESEARCH_CONFIDENCE = ["high", "medium", "low", "mixed", "no_event", "unknown"]; +export const M0_RESEARCH_STYLES = ["event_driven", "long_horizon_growth", "value_quality", "macro_context", "mixed_research"]; +const M0_RESEARCH_SUMMARY_FIELDS = [ + "subject_count", + "observation_count", + "fresh_observation_count", + "stale_observation_count", + "unknown_observation_count", + "horizon_conflict_count", + "historical_stale_horizon_drift_count", +]; +// A console decision is intentionally an auditable owner intent, not an +// execution permit. It remains separate from workflow dispatch credentials, +// broker credentials, and any future deterministic execution gateway. +export const OWNER_DECISION_INTENT_PREFIX = "owner_decision_intent:"; +export const OWNER_DECISION_CURRENT_PREFIX = "owner_decision_current:"; +export const OWNER_DECISION_INTENT_SCHEMA_VERSION = "qsl_owner_decision_intent.v1"; +export const OWNER_DECISION_QUEUE_SCHEMA_VERSION = "qsl_owner_decision_queue.v1"; +export const OWNER_DECISION_CHOICES = ["approve_limited_live_canary", "keep_parked", "retire_candidate"]; +export 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. +export const RECONCILIATION_RECOVERY_SOURCE_PREFIX = "reconciliation_recovery_source:"; +export const RECONCILIATION_RECOVERY_CONFIRMATION_PREFIX = "reconciliation_recovery_confirmation:"; +export const RECONCILIATION_RECOVERY_CURRENT_PREFIX = "reconciliation_recovery_current:"; +export const RECONCILIATION_RECOVERY_SOURCE_SCHEMA_VERSION = "qsl_reconciliation_recovery_source_snapshot.v1"; +export const RECONCILIATION_RECOVERY_DASHBOARD_SCHEMA_VERSION = "qsl_reconciliation_recovery_dashboard.v1"; +export const RECONCILIATION_RECOVERY_CONFIRMATION_SCHEMA_VERSION = "qsl_reconciliation_recovery_confirmation.v1"; +export const RECONCILIATION_RECOVERY_CONTROLLER_READ_SCHEMA_VERSION = "qsl_reconciliation_recovery_controller_read.v1"; +export const RECONCILIATION_RECOVERY_MAX_SOURCES = 100; +export const RECONCILIATION_RECOVERY_MAX_BODY_BYTES = 128 * 1024; +export const RECONCILIATION_RECOVERY_DEFAULT_STALE_TTL_SECONDS = 30 * 60; +export const RECONCILIATION_RECOVERY_MAX_SAMPLE_WINDOW_MS = 15 * 60 * 1000; +export const RECONCILIATION_RECOVERY_PLATFORMS = OBSERVABILITY_PLATFORM_IDS; +export const RECONCILIATION_RECOVERY_ENVIRONMENTS = ["live"]; +export const RECONCILIATION_RECOVERY_STATES = ["RECONCILE_ONLY"]; +export const RECONCILIATION_RECOVERY_READINESS = ["blocked", "awaiting_human_confirmation"]; +export const RECONCILIATION_RECOVERY_DUAL_REVIEW_OUTCOMES = ["approved", "rejected", "unavailable"]; +export const RECONCILIATION_RECOVERY_MAX_REQUESTS = 100; +// Private Binance holdings use one isolated KV record. They are never folded +// into public health, recovery, control-plane, or admin aggregates. +export const BINANCE_PRIVATE_SCOPE_KEY = "private_binance_scope_report"; +export const BINANCE_PRIVATE_SCOPE_MAX_BODY_BYTES = 256 * 1024; +export const BINANCE_PRIVATE_SCOPE_MAX_ASSETS = 5000; +export const BINANCE_PRIVATE_SCOPE_WRITE_TTL_SECONDS = 24 * 60 * 60; +export const BINANCE_PRIVATE_SCOPE_INGEST_MAX_AGE_MS = 10 * 60 * 1000; +export const BINANCE_PRIVATE_SCOPE_FUTURE_SKEW_MS = 60 * 1000; +export const BINANCE_PRIVATE_SCOPE_READ_MAX_AGE_MS = 24 * 60 * 60 * 1000; +// 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. +// Keeping this contract separate prevents a P1/P3 source from accidentally +// turning into an execution or P6 authority source. +export const EXECUTION_EVIDENCE_SOURCE_PREFIX = "execution_evidence_source:"; +export const RUNTIME_DAILY_KEY_PREFIX = "runtime_daily:"; +export const RUNTIME_DAILY_MAX_BODY_BYTES = 64 * 1024; +export const RUNTIME_DAILY_MAX_RUNS = 20; +export const RUNTIME_DAILY_MAX_LIST = 20; +// History coverage lists the already-stored business dates for one target. It +// never reads record bodies and stays bounded so a large backlog cannot stall +// the read model; hitting the page cap only marks the listing as truncated. +export const RUNTIME_DAILY_HISTORY_PAGE = 100; +export const RUNTIME_DAILY_HISTORY_MAX_PAGES = 10; +export const RUNTIME_DAILY_STALE_MS = 36 * 60 * 60 * 1000; +export const RUNTIME_DAILY_FUTURE_SKEW_MS = 5 * 60 * 1000; +const RUNTIME_DAILY_STATUSES = [ + "no_submission", "no_signal", "no_rebalance", "submitted", "broker_acknowledged", "partially_filled", "filled", + "reconciliation_required", "unknown", "failed", "blocked", "dry_run", "shadow", "validation", + "not_due", "market_closed", "outside_window", "within_grace", "missing_report", "read_incomplete", "insufficient", "conflict", +]; +export const RUNTIME_DAILY_SCHEDULE_ONLY = ["not_due", "market_closed", "outside_window", "within_grace"]; +const RUNTIME_DAILY_ANOMALY_STATUSES = [ + "reconciliation_required", "unknown", "failed", "blocked", "conflict", "missing_report", "read_incomplete", "insufficient", + "dry_run", "shadow", "validation", +]; +const RUNTIME_DAILY_ACTIVITIES = [ + "no_submission", "no_signal", "no_rebalance", "submitted", "broker_acknowledged", "partially_filled", "filled", + "reconciliation_required", "unknown", "failed", "blocked", "previewed", "insufficient", +]; +export const RUNTIME_DAILY_LANES = ["paper", "live", "dry_run", "shadow", "validation", "insufficient"]; +export const RUNTIME_DAILY_SCHEDULE_STATES = ["unevaluable", "not_due", "market_closed", "within_grace", "due", "outside_window"]; +export const RUNTIME_DAILY_KINDS = ["run", "schedule", "incomplete"]; +export const EXECUTION_EVIDENCE_SOURCE_SCHEMA_VERSION = "qsl_execution_evidence_source_snapshot.v1"; +export const EXECUTION_EVIDENCE_DASHBOARD_SCHEMA_VERSION = "qsl_execution_evidence_dashboard.v1"; +export const EXECUTION_EVIDENCE_MAX_SOURCES = 100; +export const EXECUTION_EVIDENCE_MAX_BODY_BYTES = 256 * 1024; +export const EXECUTION_EVIDENCE_DEFAULT_STALE_TTL_SECONDS = 36 * 60 * 60; +export const EXECUTION_EVIDENCE_PLATFORMS = OBSERVABILITY_PLATFORM_IDS; +export const EXECUTION_EVIDENCE_ENVIRONMENTS = ["shadow", "paper", "live"]; +export const EXECUTION_EVIDENCE_CAPABILITIES = ["available", "unavailable", "unknown"]; +export const EXECUTION_EVIDENCE_STATUSES = ["verified", "pending", "unavailable", "not_applicable"]; +const EXECUTION_RECEIPT_OUTCOMES = [ + "not_due", "no_action", "no_signal", "no_rebalance", "risk_blocked", "submitted", "broker_acknowledged", "partially_filled", "filled", "reconciliation_required", "failed", +]; +const EXECUTION_RECEIPT_CONFIRMATIONS = [ + "not_applicable", "not_observed", "acknowledged", "partially_filled", "filled", "reconciliation_required", +]; +const EXECUTION_EVIDENCE_RECOMMENDATIONS = [ + "continue_autonomous_shadow", "run_autonomous_paper", "owner_limited_live_canary_decision", "parked", +]; +const EXECUTION_EVIDENCE_REASON_CODES = [ + "none", "target_execution_evidence_missing", "paper_not_supported", "paper_execution_evidence_needed", + "policy_not_active", "source_stale", "manual_live_decision_required", "target_execution_receipt_observed", + "target_execution_reconciliation_required", "target_execution_receipt_failed", +]; +// Runtime target lifecycle is intentionally separate from execution evidence: +// it records whether a target is enabled and whether its no-order monitors are +// healthy. A disabled target is not a missing broker or a paper/live result. +export const RUNTIME_TARGET_LIFECYCLE_SOURCE_PREFIX = "runtime_target_lifecycle_source:"; +export const RUNTIME_TARGET_LIFECYCLE_SOURCE_SCHEMA_VERSION = "qsl_runtime_target_lifecycle_source_snapshot.v1"; +export const RUNTIME_TARGET_LIFECYCLE_DASHBOARD_SCHEMA_VERSION = "qsl_runtime_target_lifecycle_dashboard.v1"; +export const RUNTIME_TARGET_LIFECYCLE_MAX_SOURCES = 100; +export const RUNTIME_TARGET_LIFECYCLE_MAX_BODY_BYTES = 256 * 1024; +export const RUNTIME_TARGET_LIFECYCLE_CONFIGURED_STATES = ["enabled", "disabled"]; +export const RUNTIME_TARGET_LIFECYCLE_EXECUTION_MODES = ["dry_run", "paper", "live"]; +export const RUNTIME_TARGET_LIFECYCLE_CHECK_STATUSES = ["pass", "attention", "not_due", "not_applicable", "unavailable"]; +export const RUNTIME_TARGET_LIFECYCLE_DISPOSITIONS = ["continue_enabled_monitoring", "continue_disabled_validation", "parked"]; +export const RUNTIME_TARGET_LIFECYCLE_EXECUTION_OBSERVATIONS = ["not_due", "monitoring_only", "not_applicable", "attention", "unavailable"]; +const RUNTIME_TARGET_LIFECYCLE_REASON_CODES = [ + "none", "target_intentionally_disabled", "runtime_guard_attention", "execution_heartbeat_attention", "monitoring_unavailable", +]; +export const HUMAN_DECISION_DO_ACTIONS = new Set([ + "owner_sync_materials", "recovery_sync_materials", "promotion_sync_materials", + "owner_decide", "recovery_decide", "promotion_decide", + "owner_read", "recovery_read", "promotion_read", +]); +export const ACCOUNT_SETTINGS_DO_ACTIONS = new Set([ + "risk_profile_read", "risk_profile_replace", "risk_profile_set", + "account_settings_read", "account_settings_save", "account_declarations_read", + "account_settings_profile_application_lookup", "account_settings_profile_application_approve", + "account_settings_profile_application_claim", +]); +export const ACCOUNT_FACTS_DO_ACTIONS = new Set([ + "account_external_flow_put", + "account_executions_put", "account_executions_read", + "account_period_return_put", "account_period_return_read", "account_daily_native_return_research_read", + "account_facts_put", "account_facts_read", "account_facts_list", "account_facts_history_read", "account_facts_history_put", + "binance_wallet_history_put", "binance_wallet_history_read", + "binance_detail_put", "binance_detail_read", +]); +export const MARKET_BENCHMARK_DO_ACTIONS = new Set([ + "market_benchmark_put", "market_benchmark_read", "market_benchmark_revise", +]); +export const HK_STOP_DO_ACTIONS = new Set(["hk_stop_claim", "hk_stop_record", "hk_stop_read", "hk_stop_accept_result"]); +export const RUNTIME_CONTROL_DO_ACTIONS = new Set(["runtime_control_claim", "runtime_control_record", "runtime_control_read", "runtime_control_resolve", "runtime_control_capture", "runtime_control_begin_check", "runtime_control_record_check"]); +export const HK_STOP_TARGET_ID = "longbridge/hk"; +export const HK_STOP_REQUEST_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/; +export const ACCOUNT_SETTING_OVERRIDE_FIELDS = ["strategy_profile", "income_layer_enabled", "option_overlay_enabled", "reserved_cash_floor", "reserved_cash_ratio", "dca_mode", "dca_base_investment_usd"]; +export const ACCOUNT_SETTINGS_PROFILE_APPLICATION_REQUEST_KEY = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/; +// Research tasks are a separate, immutable and no-order index. They do not +// share storage or a sync credential with candidate lifecycle snapshots. +export const RESEARCH_TASK_SOURCE_PREFIX = "research_task_source:"; +export const RESEARCH_TASK_SOURCE_SCHEMA_VERSION = "qsl_research_task_source_snapshot.v1"; +export const RESEARCH_TASK_SCHEMA_VERSION = "qsl.research_task.v1"; +export const RESEARCH_TASK_DASHBOARD_SCHEMA_VERSION = "qsl_research_task_dashboard.v1"; +export const RESEARCH_TASK_MAX_SOURCES = 100; +export const RESEARCH_TASK_MAX_BODY_BYTES = 256 * 1024; +export const RESEARCH_TASK_DEFAULT_STALE_TTL_SECONDS = 36 * 60 * 60; +const RESEARCH_TASK_TYPES = [ + "strategy_diagnosis", "hypothesis_evaluation", "parameter_challenge", "strategy_candidate", "portfolio_candidate", "plugin_candidate", +]; +export const RESEARCH_TASK_OBJECTIVES = ["diagnose_degradation", "test_hypothesis", "challenge_parameters", "evaluate_candidate"]; + +// Research promotion tickets mirror QPK research_promotion_cycle.to_dict(). +// Console may record human intent only; live_authority_granted stays false. +export const RESEARCH_PROMOTION_TICKET_PREFIX = "research_promotion_ticket:"; +export const RESEARCH_PROMOTION_TICKET_SCHEMA = "qsl.research_promotion_ticket.v1"; +export const RESEARCH_PROMOTION_QUEUE_SCHEMA = "qsl.research_promotion_ticket_queue.v1"; +export const RESEARCH_PROMOTION_MAX_TICKETS = 100; +export const RESEARCH_PROMOTION_MAX_BODY_BYTES = 64 * 1024; +const RESEARCH_PROMOTION_STATES = [ + "parked", + "bounded_reopt", + "shadow_recorded", + "awaiting_human", + "human_accepted", + "human_rejected", +]; +const RESEARCH_PROMOTION_RISK_PROFILES = [ + "CAPITAL_PRESERVATION", + "BALANCED_COMPOUNDING", + "GROWTH_COMPOUNDING", +]; +export const RESEARCH_PROMOTION_EXECUTION_MODES = ["live", "paper"]; +export const DEFAULT_RESEARCH_PROMOTION_RISK_PROFILE = "CAPITAL_PRESERVATION"; +export const V7_PAPER_PREVIEW_PROFILE = "soxl_soxx_core_only_p2_v7_longterm_compounding_cash_reserve"; +export const V7_PAPER_APPLICATION_WORKFLOW_REPOSITORY = "QuantStrategyLab/LongBridgePlatform"; +export const V7_PAPER_APPLICATION_WORKFLOW = "apply-paper-candidate.yml"; +export const V7_PAPER_APPLICATION_UES_REVISION = "b83ef4b3ae67c47d132ddd660ba3ccc60d474c85"; +// Reviewed LongBridge application merge SHA for the paused validation slice. +// This remains a server constant and is never caller input or an enablement. +export const V7_PAPER_APPLICATION_SOURCE_COMMIT = "dcdcb579001e0be5579810121fc7894206e06445"; +export const V7_PAPER_APPLICATION_TICKET_RE = /^rpt_[0-9a-f]{64}$/; +export const V7_PAPER_APPLICATION_ACCOUNT_KEY = "paper"; +export const V7_PAPER_APPLICATION_ACCOUNT_SCOPE = "PAPER"; +export const V7_PAPER_APPLICATION_SERVICE = "longbridge-quant-paper-service"; +export const V7_PAPER_APPLICATION_MAX_BODY_BYTES = 16 * 1024; +export const V7_PAPER_APPLICATION_STATUSES = ["approved", "claimed", "applied_paused", "rejected", "uncertain"]; +export const V7_PAPER_APPLICATION_DISPATCH_STATES = ["pending", "sent", "unknown"]; +export const V7_PAPER_APPLICATION_REASON_CODES = ["", "readback_mismatch", "workflow_rejected", "workflow_uncertain", "dispatch_unverified", "account_changed", "qualification_failed"]; +export const V7_PAPER_APPLICATION_DO_ACTIONS = new Set([ + "application_create", "application_latest", "application_read", "application_mark_dispatch", "application_claim", "application_result", +]); +// The workflow has an explicit fail-closed guard until immutable activation +// and durable single-use consumption are connected. Keep the console from +// dispatching a workflow that cannot apply the requested target. +export const SWITCH_ACTIVATION_CONNECTED = false; + + + + + +export const SUPPORTED_PLATFORMS = Object.keys(PLATFORM_CONFIG); + +export function validateAccountOptionsSchemaOrThrow(payload, fieldName = "account_options") { + return normalizeAccountOptionsSchemaPayload(payload, fieldName); +} + + +const LIVE_CONTINUITY_STATES = [ + "NONE", + "ACTIVE_LKG", + "ACTIVE_REDUCED", + "RECONCILE_ONLY", + "RISK_REDUCTION_ONLY", + "PAUSED", + "ROLLBACK_LKG", +]; +export const LEGACY_CASH_ONLY_EXECUTION_VARIABLE = "CASH_ONLY_EXECUTION"; +export const CASH_ONLY_EXECUTION_MODES = ["current", "enabled", "disabled"]; +export const INCOME_LAYER_ENABLED_VARIABLE = "INCOME_LAYER_ENABLED"; +export const INCOME_LAYER_START_USD_VARIABLE = "INCOME_LAYER_START_USD"; +export const INCOME_LAYER_MAX_RATIO_VARIABLE = "INCOME_LAYER_MAX_RATIO"; +export const OPTION_OVERLAY_ENABLED_VARIABLE = "OPTION_OVERLAY_ENABLED"; +export const RUNTIME_TARGET_ENABLED_VARIABLE = "RUNTIME_TARGET_ENABLED"; +export const DCA_MODE_VARIABLE = "DCA_MODE"; +export const DCA_BASE_INVESTMENT_VARIABLE = "DCA_BASE_INVESTMENT_USD"; +export const IBIT_ZSCORE_EXIT_MODE_VARIABLE = "IBIT_ZSCORE_EXIT_MODE"; +export const IBIT_ZSCORE_EXIT_ENABLED_VARIABLE = "IBIT_ZSCORE_EXIT_ENABLED"; +export const IBIT_ZSCORE_EXIT_PARKING_SYMBOL_VARIABLE = "IBIT_ZSCORE_EXIT_PARKING_SYMBOL"; +const LEGACY_INCOME_LAYER_CONTROL_FIELDS = [ + "income_threshold_usd", + "qqqi_income_ratio", + "income_layer_qqqi_weight", + "income_layer_spyi_weight", +]; +const LEGACY_INCOME_LAYER_VARIABLES = [ + "INCOME_THRESHOLD_USD", + "QQQI_INCOME_RATIO", + "INCOME_LAYER_QQQI_WEIGHT", + "INCOME_LAYER_SPYI_WEIGHT", +]; +const OPTION_OVERLAY_CONTROL_FIELDS = [ + "option_overlay_enabled", + "option_growth_overlay_enabled", + "option_growth_overlay_recipe", + "option_growth_overlay_start_usd", + "option_growth_overlay_nav_budget_ratio", + "option_income_overlay_enabled", + "option_income_overlay_recipe", + "option_income_overlay_start_usd", + "option_income_overlay_nav_risk_ratio", +]; +export const OPTION_OVERLAY_VARIABLES = OPTION_OVERLAY_CONTROL_FIELDS.map((field) => field.toUpperCase()); +const OPTION_OVERLAY_PROFILE_FIELDS = [ + ...OPTION_OVERLAY_CONTROL_FIELDS, + "option_overlay_live_gate", + "option_overlay_live_status", +]; +export const OPTION_OVERLAY_MODES = ["current", "enabled", "disabled"]; +const DCA_PROFILE_CONFIG = Object.fromEntries( + Object.entries(DCA_PROFILE_DEFAULTS).map(([profile, defaults]) => [ + profile, + { + default_mode: defaults.defaultMode || "fixed", + default_base_investment_usd: defaults.defaultBaseInvestmentUsd || "1000", + }, + ]), +); +const SECURITY_HEADERS = { + "Content-Security-Policy": [ + "default-src 'self'", + "base-uri 'none'", + "object-src 'none'", + "frame-ancestors 'none'", + "frame-src https://fred.stlouisfed.org/graph/graph-landing.php", + "form-action 'self'", + "img-src 'self' data:", + "connect-src 'self'", + "script-src 'self'", + "style-src 'self'", + ].join("; "), + "Permissions-Policy": "camera=(), microphone=(), geolocation=(), payment=(), usb=()", + "Referrer-Policy": "no-referrer", + "X-Content-Type-Options": "nosniff", + "X-Frame-Options": "DENY", +}; + +// This is deliberately a same-origin, public configuration bootstrap. It +// contains only the config projections already shown by the public console; +// private account routing still arrives after an authenticated /api/config +// request. Keeping it external preserves the strict CSP without unsafe-inline. +export const BOOTSTRAP_CONFIG_JS = [ + "// Generated from non-secret console configuration. Do not edit by hand.", + `window.__PLATFORM_META__ = ${JSON.stringify(PLATFORM_META)};`, + `window.__PLATFORM_REPOSITORIES__ = ${JSON.stringify(PLATFORM_REPOSITORIES)};`, + `window.__PLATFORM_CONFIG__ = ${JSON.stringify(PLATFORM_CONFIG)};`, + `window.__DEFAULT_ACCOUNT_OPTIONS__ = ${JSON.stringify(DEFAULT_ACCOUNT_OPTIONS)};`, + `window.__DOMAIN_LABELS__ = ${JSON.stringify(DOMAIN_LABELS)};`, + `window.__DEFAULT_STRATEGY_PROFILES__ = ${JSON.stringify(DEFAULT_STRATEGY_PROFILES)};`, + `window.__DCA_PROFILE_DEFAULTS__ = ${JSON.stringify(DCA_PROFILE_DEFAULTS)};`, + `window.__INCOME_LAYER_DEFAULTS__ = ${JSON.stringify(FALLBACK_INCOME_LAYER_DEFAULTS)};`, + `window.__OPTION_OVERLAY_DEFAULTS__ = ${JSON.stringify(FALLBACK_OPTION_OVERLAY_DEFAULTS)};`, + `window.__PLATFORM_MIN_RESERVED_CASH_VARIABLES__ = ${JSON.stringify(PLATFORM_MIN_RESERVED_CASH_VARIABLES)};`, + `window.__PLATFORM_RESERVED_CASH_RATIO_VARIABLES__ = ${JSON.stringify(PLATFORM_RESERVED_CASH_RATIO_VARIABLES)};`, +].join("\n"); + +// A secondary external script keeps a failed or stale main bundle from +// trapping a visitor behind the loading screen forever. It does not fetch, +// mutate configuration, or grant access: it only reveals the static login +// surface after the main boot flow has had time to finish its own timeout. +export const BOOT_RECOVERY_JS = [ + "(() => {", + " const delayMs = 20_000;", + " window.setTimeout(() => {", + " if (!document.body?.classList.contains('app-loading')) return;", + " document.body.classList.remove('app-loading');", + " const login = document.getElementById('login-link');", + " if (login) login.hidden = false;", + " }, delayMs);", + "})();", +].join("\n"); + + + +export async function credentialMaintenanceResponse(request, env, url) { + const machine = url.pathname.startsWith("/api/internal/"); + const suffix = url.pathname.split("/").at(-1); + let action = "credential_maintenance_read"; + if (machine) { + const expected = typeof env.STRATEGY_SWITCH_SYNC_TOKEN === "string" ? env.STRATEGY_SWITCH_SYNC_TOKEN : ""; + // No dispatch fallback, and no alias for broker, control or session authority. + const aliases = Object.entries(env).filter(([name, value]) => name !== "STRATEGY_SWITCH_SYNC_TOKEN" && + (name.endsWith("_TOKEN") || name === "SESSION_SECRET") && typeof value === "string" && value && value === expected); + if (!expected || aliases.length) throw new HttpError("credential_maintenance_auth_unavailable", 503); + const supplied = (request.headers.get("Authorization") || "").match(/^Bearer\s+(.+)$/i)?.[1] || ""; + if (supplied !== expected) throw new HttpError("credential_maintenance_auth_invalid", 401); + if (suffix === "claim" || suffix === "ack") { + if (request.method !== "POST") throw new HttpError("method_not_allowed", 405); + action = `credential_maintenance_${suffix}`; + } else if (request.method !== "GET") throw new HttpError("method_not_allowed", 405); + } else { + const session = await requireUx1Session(request, env); + if (request.method === "POST") { + if (!session.admin) throw new HttpError("admin required", 403); + requireSameOrigin(request, { requireOrigin: true }); + action = "credential_maintenance_save"; + } else if (request.method !== "GET") throw new HttpError("method_not_allowed", 405); + } + let body; + if (request.method === "POST") { + const text = await request.text(); + if (text.length > 1024) throw new HttpError("invalid_credential_maintenance_request", 400); + try { body = JSON.parse(text); } catch { throw new HttpError("invalid_credential_maintenance_request", 400); } + } + const result = await runtimeInstanceCommand(env, { action, body }); + return json(!machine && request.method === "GET" ? { ...result, ...decisionFields(result, "credential_maintenance") } : result); +} + + + + +export async function runtimeInstancesResponse(request, env) { + const session = await readSession(request, env); + if (!session) return json({ ok: false, error: "login required" }, 401); + if (!session.admin) return json({ ok: false, error: "admin required" }, 403); + if (request.method === "GET") return json(await runtimeInstanceCommand(env)); + if (request.method !== "POST") return json({ ok: false, error: "method_not_allowed" }, 405); + requireSameOrigin(request, { requireOrigin: true }); + let raw; + try { + const text = await request.text(); + if (text.length > 16384) throw new Error(); + raw = JSON.parse(text); + } catch { + throw new HttpError("invalid_runtime_instance_request", 400); + } + runtimeInstanceFields(raw, ["action", "expected_revision", "platform", "key", "config", "broker_environment", "confirm"]); + const command = { action: raw.action, expected_revision: raw.expected_revision, actor: session.login }; + if (raw.action === "initialize") { + if (raw.confirm !== "IMPORT_EXISTING_CONFIG") throw new HttpError("runtime_instance_import_confirmation_required", 400); + // Explicit, one-time import only. A deployment must freeze legacy writers + // and review this input before enabling the binding and importing it. + command.account_options = (await loadLegacyAccountOptionsConfig(env)).options || {}; + } else if (raw.action === "create" || raw.action === "edit") { + try { + if (!SUPPORTED_PLATFORMS.includes(raw.platform) || PLATFORM_CONFIG[raw.platform].dry_run_only) throw new Error(); + runtimeInstanceFields(raw.config, ["key", "label", "target_name", "account_selector", "deployment_selector", "account_scope", "service_name", "github_environment", "variable_scope", "cash_currency", "supported_domains", "broker_environment", "default_strategy_profile", "default_execution_mode"]); + const option = cleanAccountOption(raw.config, raw.platform, 0); + if (!option.account_selector || !option.deployment_selector || !option.service_name) throw new Error(); + option.default_execution_mode = raw.config.default_execution_mode || "dry_run"; + option.default_strategy_profile = cleanSlug(raw.config.default_strategy_profile, "default_strategy_profile"); + assertStrategyAllowedForAccount({ platform: raw.platform, strategy_profile: option.default_strategy_profile, execution_mode: option.default_execution_mode }, option, await loadStrategyProfilesConfig(env)); + command.platform = raw.platform; + command.config = option; + if (raw.action === "edit") command.key = cleanSlug(raw.key, "key"); + } catch { + throw new HttpError("invalid_runtime_instance_config", 400); + } + } else if (raw.action === "set_broker_environment") { + try { + if (!SUPPORTED_PLATFORMS.includes(raw.platform) || PLATFORM_CONFIG[raw.platform].dry_run_only) throw new Error(); + command.platform = cleanChoice(raw.platform, SUPPORTED_PLATFORMS, "platform"); + command.key = cleanSlug(raw.key, "key"); + command.broker_environment = normalizeBrokerEnvironment(raw.broker_environment); + if (!supportedBrokerEnvironmentsForPlatform(command.platform).includes(command.broker_environment)) throw new Error(); + } catch { + throw new HttpError("invalid_broker_environment", 400); + } + } else if (raw.action === "request_retirement") { + command.platform = raw.platform; + command.key = raw.key; + } else { + throw new HttpError("unsupported_runtime_instance_action", 400); + } + return json(await runtimeInstanceCommand(env, command)); +} + +export async function sessionPayload(request, env) { + const session = await readSession(request, env); + return { + authenticated: Boolean(session), + login: session?.login || null, + allowed: Boolean(session?.allowed), + admin: Boolean(session?.admin), + server_time: new Date().toISOString(), + }; +} + +export async function adminConfigResponse(request, env) { + const session = await readSession(request, env); + if (!session) return json({ ok: false, error: "login required" }, 401); + if (!session.admin) return json({ ok: false, error: "admin required" }, 403); + return json(await buildAdminState(session, env)); +} + +import { dateOnlyExternalFlow, externalFlowEnabled, initializeExternalFlow, normalizeExternalFlow, putExternalFlow, readExternalFlow, readExternalFlowCoverage } from "../account_facts/external_flow.js"; +import { initializeExecutions, normalizeExecutions, putExecutions, readExecutions } from "../account_facts/executions.js"; + + + +export async function saveAdminConfig(request, env) { + requireSameOrigin(request, { requireOrigin: true }); + const session = await readSession(request, env); + if (!session) 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 to save admin config" }, 400); + } + + let raw; + try { + raw = await request.json(); + } catch (error) { + return json({ ok: false, error: "request body must be valid JSON" }, 400); + } + const bootstrapAdmins = parseLoginList(env.STRATEGY_SWITCH_ADMIN_LOGINS || "", "STRATEGY_SWITCH_ADMIN_LOGINS"); + const bootstrapAdminOrgs = parseOrgList(env.STRATEGY_SWITCH_ADMIN_ORGS || "", "STRATEGY_SWITCH_ADMIN_ORGS"); + const allowedLogins = normalizeLoginList(raw.allowed_logins, "allowed_logins"); + const allowedOrgs = normalizeOrgList(raw.allowed_orgs, "allowed_orgs"); + const submittedAdmins = normalizeLoginList(raw.admin_logins, "admin_logins"); + const submittedAdminOrgs = normalizeOrgList(raw.admin_orgs, "admin_orgs"); + const effectiveAdmins = uniqueStrings([...bootstrapAdmins, ...submittedAdmins]); + const effectiveAdminOrgs = uniqueStrings([...bootstrapAdminOrgs, ...submittedAdminOrgs]); + if (!effectiveAdmins.includes(session.login) && !hasOrgMatch(session.orgs, effectiveAdminOrgs)) { + throw new Error("current admin login or org must remain in admin config"); + } + const authConfig = { + allowed_logins: uniqueStrings([...allowedLogins, ...effectiveAdmins]), + allowed_orgs: allowedOrgs, + admin_logins: effectiveAdmins, + admin_orgs: effectiveAdminOrgs, + }; + const accountOptions = normalizeAccountOptionsInput(raw.account_options, "account_options"); + + if (hasRuntimeInstanceStore(env)) { + const instances = await runtimeInstanceCommand(env); + if (!instances.initialized || canonicalResearchTaskJson(accountOptions) !== canonicalResearchTaskJson(normalizeAccountOptionsPayload(instances.account_options))) { + throw new HttpError("runtime_instances_managed_separately", 409); + } + } + await writeConfigJson(env, AUTH_CONFIG_KEY, authConfig); + if (!hasRuntimeInstanceStore(env)) await writeConfigJson(env, ACCOUNT_OPTIONS_KEY, validateAccountOptionsSchemaOrThrow(accountOptions)); + await appendAuditLog(env, { + ts: new Date().toISOString(), + login: session.login, + action: "save_config", + allowed_count: authConfig.allowed_logins.length, + allowed_org_count: authConfig.allowed_orgs.length, + admin_count: authConfig.admin_logins.length, + admin_org_count: authConfig.admin_orgs.length, + account_counts: accountCounts(accountOptions), + }); + return json(await buildAdminState(session, env)); +} + +export async function riskProfileBindingsResponse(request, env) { + const session = await readSession(request, env); + if (!session) return json({ ok: false, error: "login required" }, 401); + if (!session.admin) return json({ ok: false, error: "admin required" }, 403); + const bindingState = await loadRiskProfileBindings(env); + if (bindingState.error) { + return json({ + ok: false, + error: "risk profile bindings are unavailable", + reason: bindingState.error, + no_order: true, + execution_authority_granted: false, + }, bindingState.status || 409); + } + return json({ + ok: true, + revision: bindingState.revision, + bindings: bindingState.bindings, + tier_bindings: bindingState.bindings.map((binding) => riskTierBindingView({ + scope_id: binding.scope_id, + platform: binding.platform, + target_name: binding.target_name, + binding, + revision: bindingState.revision, + })), + configured_targets: riskProfileBindingTargets(bindingState.account_options || {}), + no_order: true, + execution_authority_granted: false, + }); +} + +export async function saveRiskProfileBindings(request, env) { + requireSameOrigin(request, { requireOrigin: true }); + const session = await readSession(request, env); + if (!session) return json({ ok: false, error: "login required" }, 401); + if (!session.admin) return json({ ok: false, error: "admin required" }, 403); + let raw; + try { + raw = await request.json(); + } catch { + return json({ ok: false, error: "request body must be valid JSON" }, 400); + } + if (!hasRuntimeInstanceStore(env)) { + try { + await buildRiskProfileBindings({ bindings: raw?.bindings }, (await loadLegacyAccountOptionsConfig(env)).options || {}, session.login); + } catch (error) { + return json({ ok: false, error: error.message || "risk profile bindings are invalid" }, 400); + } + return json({ ok: false, error: "runtime_instances_not_bound", no_order: true, execution_authority_granted: false }, 503); + } + const legacy = await readRiskProfileLegacy(env); + let observed; + try { + observed = await runtimeInstanceCommand(env, { action: "risk_profile_read", actor: session.login, legacy }); + } catch (error) { + const status = error instanceof HttpError ? error.status : 503; + return json({ ok: false, error: error instanceof HttpError ? error.message : "runtime_instances_unavailable", no_order: true, execution_authority_granted: false }, status); + } + if (!Number.isSafeInteger(raw?.expected_revision)) return json({ ok: false, error: "risk_profile_revision_required" }, 400); + if (raw.expected_revision !== observed.revision) return json({ ok: false, error: "risk_profile_revision_conflict" }, 409); + let bindings; + try { + bindings = await buildRiskProfileBindings({ bindings: raw.bindings }, observed.account_options, session.login); + } catch (error) { + return json({ ok: false, error: error.message || "risk profile bindings are invalid" }, 400); + } + let saved; + try { + saved = await runtimeInstanceCommand(env, { + action: "risk_profile_replace", + actor: session.login, + expected_revision: observed.revision, + bindings, + legacy, + }); + } catch (error) { + const status = error instanceof HttpError ? error.status : 503; + return json({ ok: false, error: error instanceof HttpError ? error.message : "runtime_instances_unavailable", no_order: true, execution_authority_granted: false }, status); + } + if (hasConfigStore(env)) { + await mirrorConfigJson(env, RISK_PROFILE_BINDINGS_KEY, { + schema_version: RISK_PROFILE_BINDING_REGISTRY_SCHEMA_VERSION, + bindings: saved.bindings, + }); + } + try { + await appendAuditLog(env, { + ts: new Date().toISOString(), + login: session.login, + action: "save_risk_profile_bindings", + binding_count: saved.bindings.length, + }); + } catch { /* audit failure does not undo the durable object decision */ } + return json({ + ok: true, + revision: saved.revision, + bindings: saved.bindings, + configured_targets: riskProfileBindingTargets(saved.account_options || {}), + no_order: true, + execution_authority_granted: false, + }); +} + +export async function consoleRunControls(_request, env, account, session) { + return runControls(account, session, { + dispatchAvailable: Boolean(env.RUNTIME_SETTINGS_DISPATCH_TOKEN), + platformControl: ["ibkr", "schwab"].includes(account.platform) + ? () => readPlatformRuntimeControl(env, account.platform, account.target_name) : null, + stopReady: async () => { + if (account.platform === "longbridge" && account.target_name === "hk") { + if (account.key !== "hk") return false; + await loadHkStopAccount(env); + const { record } = await runtimeInstanceCommand(env, { action: "hk_stop_read" }); + return !record || !["reserved", "accepted", "unknown", "stopped"].includes(record.phase); + } + const config = await loadAccountOptionsConfig(env); + if (!account.target_name || (config.options?.[account.platform] || []).filter(a => a.target_name === account.target_name).length !== 1) return false; + const inputs = { ...account, platform: account.platform }; + const values = Object.fromEntries(["deployment_selector", "account_scope", "account_selector", "service_name"] + .map(field => [field, inputs[field] || defaultInputValue(field, inputs)])); + return Object.values(values).every(Boolean) && String(values.account_selector).split(",").some(value => value.trim()); + }, + resumeTarget: async () => { + const repository = platformRepositories(env).binance; + const config = await loadAccountOptionsConfig(env); + if (repository !== "QuantStrategyLab/BinancePlatform" || !account.target_name || + (config.options?.binance || []).filter(a => a.target_name === account.target_name).length !== 1 || + resolvedVariableScope(account.variable_scope, { ...account, platform: "binance" }) !== "repository") return null; + const variables = await fetchGithubVariables(env.RUNTIME_SETTINGS_DISPATCH_TOKEN, repository, "repository", ""); + return binanceResumeTarget(Object.fromEntries(variables), account); + }, + profileState: async () => { + if (!hasRuntimeInstanceStore(env)) return { reason: "runtime_instances_not_bound" }; + const observed = await runtimeInstanceCommand(env, { action: "account_settings_read", actor: session.login, platform: account.platform, key: account.key }); + const draft = observed.draft; + if (draft?.status === "identity_conflict") return { reason: "account_settings_identity_conflict" }; + const next = draft?.overrides?.strategy_profile; + if (draft?.status !== "current" || typeof next !== "string" || Object.keys(draft.overrides).length !== 1 || + !accountSettingsIdentityIsComplete(observed.identity) || + canonicalResearchTaskJson(draft.identity) !== canonicalResearchTaskJson(observed.identity)) return { reason: "profile_draft_required" }; + if (isDcaProfile(next)) return { reason: "profile_only_required" }; + const profiles = await loadAccountDraftProfiles(env); + if (!accountDraftStrategyChoices(account.platform, observed.config, profiles).some(p => p.profile === next && !p.dca_supported)) return { reason: "profile_not_selectable" }; + const readback = await accountSettingsProfileRuntimeReadback(env, observed); + if (!readback) return { reason: "runtime_readback_unavailable" }; + if (readback.runtime_target_enabled !== "false") return { reason: "runtime_target_not_disabled" }; + if (!readback.strategy_profile || readback.strategy_profile === next || isDcaProfile(readback.strategy_profile)) return { reason: "current_profile_conflict" }; + const history = await runtimeInstanceCommand(env, { action: "console_settings_history", actor: session.login, platform: account.platform, key: account.key }); + if (history.approvals.some(row => row.status === "claimed" || (row.status === "approved" && + row.identity_json === canonicalResearchTaskJson(observed.identity) && Number(row.instance_revision) === observed.instance_revision && + Number(row.draft_revision) === draft.revision && row.old_profile === readback.strategy_profile && row.new_profile === next))) + return { reason: "profile_application_pending" }; + return { ready: true }; + }, + }); +} + +export function accountSettingsOperations(admin, optionSupported) { + const allowed = Boolean(admin); + return { + save_draft: allowed, + save_draft_reason: allowed ? "available" : "admin_required", + save_risk_preference: allowed, + save_risk_preference_reason: allowed ? "available" : "admin_required", + save_declaration: allowed, + save_declaration_reason: allowed ? "available" : "admin_required", + save_option_draft: allowed && optionSupported === true, + save_option_draft_reason: !allowed ? "admin_required" : optionSupported === true ? "available" : "option_overlay_not_defined", + apply_strategy: false, + activation: false, + reason: "strategy_application_not_connected", + apply_strategy_reason: "strategy_application_not_connected", + activation_reason: "strategy_application_not_connected", + }; +} + +export function accountSettingsPayload(observed, effective, admin, extras = {}) { + const optionSupported = extras.pendingStrategy ? extras.pendingOptionSupported === true : extras.currentOptionSupported === true; + return { + ok: true, + platform: observed.platform, + key: observed.key, + instance_revision: observed.instance_revision, + identity: observed.identity, + effective, + effective_read: extras.effectiveRead, + draft: observed.draft, + declaration: observed.declaration, + strategy_options: Array.isArray(extras.strategyOptions) ? extras.strategyOptions : [], + current_option_supported: extras.currentOptionSupported === true, + risk: { + revision: observed.risk.revision, + scope_id: observed.risk.scope_id, + preference: observed.risk.preference, + }, + tier_binding: riskTierBindingView({ + scope_id: observed.risk.scope_id, + platform: observed.platform, + target_name: observed.identity?.target_name || null, + binding: observed.risk.binding || null, + revision: observed.risk.revision, + }), + operations: accountSettingsOperations(admin, optionSupported), + adopted: false, + no_order: true, + execution_authority_granted: false, + current_dca_supported: extras.currentDcaSupported === true, + }; +} + +export async function accountSettingsResponse(request, env) { + const session = await readSession(request, env); + if (!session) return json({ ok: false, error: "login required" }, 401); + const url = new URL(request.url); + const platform = url.searchParams.get("platform") || ""; + const key = url.searchParams.get("key") || ""; + if (!SUPPORTED_PLATFORMS.includes(platform) || !key) return json({ ok: false, error: "account_settings_account_required" }, 400); + if (!hasRuntimeInstanceStore(env)) return json({ ok: false, error: "runtime_instances_not_bound", no_order: true, execution_authority_granted: false }, 503); + const legacy = await readRiskProfileLegacy(env); + let observed; + try { + observed = await runtimeInstanceCommand(env, { action: "account_settings_read", actor: session.login, platform, key, legacy }); + } catch (error) { + const status = error instanceof HttpError ? error.status : 503; + return json({ ok: false, error: error instanceof HttpError ? error.message : "runtime_instances_unavailable", no_order: true, execution_authority_granted: false }, status); + } + return json(await presentAccountSettings(env, observed, session.admin)); +} + +export async function presentAccountSettings(env, observed, admin) { + const { effective, effectiveRead } = await effectiveAccountSettings(env, observed); + const profiles = await loadAccountDraftProfiles(env); + const strategyOptions = profiles ? accountDraftStrategyChoices(observed.platform, observed.config, profiles) : []; + const conflict = observed.draft?.status === "identity_conflict"; + const pending = !conflict && typeof observed.draft?.overrides?.strategy_profile === "string" ? observed.draft.overrides.strategy_profile : ""; + const trusted = effective.strategy_profile?.status === "known" && typeof effective.strategy_profile.value === "string" ? effective.strategy_profile.value : ""; + return accountSettingsPayload(observed, effective, admin, { + effectiveRead, + strategyOptions, + pendingStrategy: Boolean(pending), + pendingOptionSupported: Boolean(pending) && optionOverlayDraftAllowed(observed.platform, profiles, pending), + currentOptionSupported: Boolean(trusted) && optionOverlayDraftAllowed(observed.platform, profiles, trusted), + currentDcaSupported: dcaDraftSupported(observed.platform, profiles, trusted), + }); +} + +export async function loadAccountDraftProfiles(env) { + try { + return await loadStrategyProfilesConfig(env); + } catch { + return null; + } +} + +export function profileDefinesOptionOverlay(profiles, profileId) { + if (!Array.isArray(profiles) || typeof profileId !== "string" || !profileId) return false; + const found = profiles.find((item) => sameStrategyProfile(item?.profile, profileId)); + return found?.option_overlay_enabled === true; +} + +export function optionOverlayDraftAllowed(platform, profiles, profileId) { + return PLATFORM_CONFIG[platform]?.option_overlay === true && profileDefinesOptionOverlay(profiles, profileId); +} + +export function dcaDraftSupported(platform, profiles, profileId) { + if (!profileId || !Array.isArray(profiles)) return false; + if (!DCA_SUPPORTED_PLATFORMS.has(platform) || !isDcaProfile(profileId)) return false; + return profiles.some((item) => sameStrategyProfile(item?.profile, profileId)); +} + +export function accountDraftStrategyChoices(platform, accountOption, profiles) { + if (!Array.isArray(profiles)) return []; + let domains = []; + try { + domains = supportedDomainsForAccount(platform, accountOption || {}); + } catch { + return []; + } + const choices = []; + const allowsDryRunOnly = accountOption?.default_execution_mode === "dry_run" + && supportedExecutionModesForPlatform(platform).includes("dry_run"); + for (const profile of profiles) { + if (!profile || typeof profile.profile !== "string" || !profile.profile) continue; + if (profile.domain_explicit !== true || profile.lifecycle_stage === "frozen" || isFrozenStrategyProfile(profile)) continue; + if (!profile.domain || !domains.includes(profile.domain)) continue; + const modes = Array.isArray(profile.allowed_execution_modes) ? profile.allowed_execution_modes : []; + if (!modes.some((mode) => mode === "paper" || mode === "live" + || (allowsDryRunOnly && mode === "dry_run"))) continue; + try { + assertDcaPlatform(platform, profile.profile); + } catch { + continue; + } + choices.push({ + profile: profile.profile, + label: profile.label || profile.profile, + label_zh: profile.label_zh || profile.label || profile.profile, + label_en: profile.label_en || profile.label || profile.profile, + domain: profile.domain, + // Existing catalog declarations only, never broker or execution permission. + lifecycle_stage: typeof profile.lifecycle_stage === "string" ? profile.lifecycle_stage : null, + allowed_execution_modes: Array.isArray(profile.allowed_execution_modes) && profile.allowed_execution_modes.length ? [...profile.allowed_execution_modes] : null, + can_switch_live: typeof profile.can_switch_live === "boolean" ? profile.can_switch_live : null, + blocked_live_reason: typeof profile.blocked_live_reason === "string" ? profile.blocked_live_reason : null, + option_overlay_enabled: profile.option_overlay_enabled === true, + dca_supported: dcaDraftSupported(platform, profiles, profile.profile), + }); + } + return choices; +} + +export function assertAccountDraftPatch(profiles, observed, patch, trustedProfile, options = {}) { + if (!patch || Array.isArray(patch) || typeof patch !== "object") throw new HttpError("invalid_account_setting_overrides", 400); + const choices = profiles ? accountDraftStrategyChoices(observed.platform, observed.config, profiles) : []; + const rebuilding = observed.draft?.status === "identity_conflict" && options.acknowledgeIdentityConflict === true; + const stored = observed.draft?.overrides && typeof observed.draft.overrides === "object" && !Array.isArray(observed.draft.overrides) ? observed.draft.overrides : {}; + const current = rebuilding ? {} : stored; + const touchesStrategy = Object.prototype.hasOwnProperty.call(patch, "strategy_profile"); + const touchesOption = Object.prototype.hasOwnProperty.call(patch, "option_overlay_enabled"); + const touchesDcaMode = Object.prototype.hasOwnProperty.call(patch, "dca_mode"); + const touchesDcaBase = Object.prototype.hasOwnProperty.call(patch, "dca_base_investment_usd"); + if (touchesDcaMode !== touchesDcaBase) throw new HttpError("account_settings_dca_rejected", 400); + if (touchesDcaMode && (patch.dca_mode === null) !== (patch.dca_base_investment_usd === null)) { + throw new HttpError("account_settings_dca_rejected", 400); + } + if (touchesDcaMode && patch.dca_mode !== null && patch.dca_mode !== "fixed" && patch.dca_mode !== "smart") { + throw new HttpError("invalid_account_setting_overrides", 400); + } + if (touchesDcaBase && patch.dca_base_investment_usd !== null && !accountSettingPositiveAmount(patch.dca_base_investment_usd)) { + throw new HttpError("invalid_account_setting_overrides", 400); + } + if (!profiles && (touchesDcaMode || touchesDcaBase)) throw new HttpError("account_settings_dca_rejected", 400); + if (touchesStrategy && patch.strategy_profile !== null) { + const allowedChoice = choices.some((item) => sameStrategyProfile(item.profile, patch.strategy_profile)); + const currentEditable = sameStrategyProfile(patch.strategy_profile, trustedProfile) && dcaDraftSupported(observed.platform, profiles, patch.strategy_profile); + if (typeof patch.strategy_profile !== "string" || (!allowedChoice && !currentEditable)) { + throw new HttpError("account_settings_strategy_rejected", 400); + } + } + if (!touchesStrategy && !touchesOption && !touchesDcaMode && !touchesDcaBase) return; + const bound = touchesStrategy + ? patch.strategy_profile || trustedProfile || "" + : (typeof current.strategy_profile === "string" && current.strategy_profile ? current.strategy_profile : trustedProfile || ""); + const supports = optionOverlayDraftAllowed(observed.platform, profiles, bound); + if (touchesOption && patch.option_overlay_enabled !== null && (typeof patch.option_overlay_enabled !== "boolean" || !supports)) { + throw new HttpError("account_settings_option_rejected", 400); + } + const nextOption = touchesOption + ? patch.option_overlay_enabled + : (Object.prototype.hasOwnProperty.call(current, "option_overlay_enabled") ? current.option_overlay_enabled : undefined); + if ((nextOption === true || nextOption === false) && !supports) throw new HttpError("account_settings_option_strategy_conflict", 400); + // Other setting groups can be edited on a DCA account without rebinding its strategy. + if (!touchesStrategy && !touchesDcaMode && !touchesDcaBase) return; + const previous = typeof current.strategy_profile === "string" && current.strategy_profile ? current.strategy_profile : trustedProfile || ""; + const next = touchesStrategy + ? (patch.strategy_profile === null ? trustedProfile || "" : String(patch.strategy_profile || "")) + : previous; + const mergedMode = touchesDcaMode ? patch.dca_mode : (Object.prototype.hasOwnProperty.call(current, "dca_mode") ? current.dca_mode : null); + const mergedBase = touchesDcaBase ? patch.dca_base_investment_usd : (Object.prototype.hasOwnProperty.call(current, "dca_base_investment_usd") ? current.dca_base_investment_usd : null); + const hasMergedDca = (mergedMode !== null && mergedMode !== undefined) || (mergedBase !== null && mergedBase !== undefined); + if (!dcaDraftSupported(observed.platform, profiles, next)) { + if (hasMergedDca) throw new HttpError("account_settings_dca_rejected", 400); + if (!sameStrategyProfile(previous, next) && dcaDraftSupported(observed.platform, profiles, previous) + && (!touchesDcaMode || patch.dca_mode !== null || !touchesDcaBase || patch.dca_base_investment_usd !== null)) { + throw new HttpError("account_settings_dca_rejected", 400); + } + return; + } + if (touchesDcaMode || touchesDcaBase) { + const clearingDca = patch.dca_mode === null && patch.dca_base_investment_usd === null; + const boundToProfile = sameStrategyProfile(patch.strategy_profile, next); + const clearingCurrentBinding = patch.strategy_profile === null && clearingDca; + if (!touchesStrategy || (!boundToProfile && !clearingCurrentBinding)) { + throw new HttpError("account_settings_dca_rejected", 400); + } + } + if (!sameStrategyProfile(previous, next) || touchesDcaMode || touchesDcaBase) { + if (!touchesDcaMode || !touchesDcaBase) throw new HttpError("account_settings_dca_rejected", 400); + } +} + +export async function saveCombinedAccountDraft(request, env) { + requireSameOrigin(request, { requireOrigin: true }); + const session = await readSession(request, env); + if (!session) return json({ ok: false, error: "login required" }, 401); + if (!session.admin) return json({ ok: false, error: "admin required" }, 403); + let raw; + try { raw = await readBoundedJson(request, 16 * 1024); } catch (error) { return json({ ok: false, error: "invalid_account_setting_overrides" }, error.status || 400); } + const allowed = ["platform", "key", "identity", "expected_draft_revision", "acknowledge_identity_conflict", "overrides"]; + if (!raw || Array.isArray(raw) || Object.keys(raw).some(key => !allowed.includes(key)) || !Object.hasOwn(raw, "overrides")) return json({ ok: false, error: "invalid_account_setting_overrides" }, 400); + return saveAccountSettings(new Request(request.url, { method: "POST", headers: request.headers, body: JSON.stringify(raw) }), env); +} + +export async function saveAccountSettings(request, env) { + requireSameOrigin(request, { requireOrigin: true }); + const session = await readSession(request, env); + if (!session) return json({ ok: false, error: "login required" }, 401); + if (!session.admin) return json({ ok: false, error: "admin required" }, 403); + let raw; + try { raw = await request.json(); } catch { return json({ ok: false, error: "request body must be valid JSON" }, 400); } + const platform = raw?.platform; + const key = raw?.key; + if (!SUPPORTED_PLATFORMS.includes(platform) || typeof key !== "string" || !key) return json({ ok: false, error: "account_settings_account_required" }, 400); + if (!hasRuntimeInstanceStore(env)) return json({ ok: false, error: "runtime_instances_not_bound", no_order: true, execution_authority_granted: false }, 503); + const legacy = await readRiskProfileLegacy(env); + let observed; + try { + observed = await runtimeInstanceCommand(env, { action: "account_settings_read", actor: session.login, platform, key, legacy }); + } catch (error) { + const status = error instanceof HttpError ? error.status : 503; + return json({ ok: false, error: error instanceof HttpError ? error.message : "runtime_instances_unavailable", no_order: true, execution_authority_granted: false }, status); + } + const wantsDraft = Object.prototype.hasOwnProperty.call(raw, "overrides"); + const wantsRisk = Object.prototype.hasOwnProperty.call(raw, "risk_preference"); + const wantsDeclaration = Object.prototype.hasOwnProperty.call(raw, "declaration"); + if ((wantsDraft || wantsRisk || wantsDeclaration) && (!raw.identity || Array.isArray(raw.identity) || typeof raw.identity !== "object")) { + return json({ ok: false, error: "account_settings_identity_required" }, 400); + } + if ((wantsDraft || wantsRisk || wantsDeclaration) && canonicalResearchTaskJson(raw.identity) !== canonicalResearchTaskJson(observed.identity)) { + return json({ ok: false, error: "account_settings_identity_conflict" }, 409); + } + const command = { action: "account_settings_save", actor: session.login, platform, key, legacy }; + if (wantsDraft || wantsRisk || wantsDeclaration) command.identity = raw.identity; + if (wantsDeclaration) { + if (!Number.isSafeInteger(raw.expected_declaration_revision) || raw.expected_declaration_revision !== observed.declaration.revision) return json({ ok: false, error: "account_settings_declaration_revision_conflict" }, 409); + command.declaration = raw.declaration; + command.expected_declaration_revision = raw.expected_declaration_revision; + } + if (wantsDraft) { + if (!Number.isSafeInteger(raw.expected_draft_revision)) return json({ ok: false, error: "account_settings_draft_revision_required" }, 400); + if (raw.expected_draft_revision !== observed.draft.revision) return json({ ok: false, error: "account_settings_draft_revision_conflict" }, 409); + command.overrides = raw.overrides; + command.expected_draft_revision = observed.draft.revision; + if (raw.acknowledge_identity_conflict === true) command.acknowledge_identity_conflict = true; + const profiles = await loadAccountDraftProfiles(env); + const { effective: beforeEffective } = await effectiveAccountSettings(env, observed); + const trusted = beforeEffective.strategy_profile?.status === "known" && typeof beforeEffective.strategy_profile.value === "string" ? beforeEffective.strategy_profile.value : ""; + try { + assertAccountDraftPatch(profiles, observed, raw.overrides, trusted, { acknowledgeIdentityConflict: raw.acknowledge_identity_conflict === true }); + } catch (error) { + const status = error instanceof HttpError ? error.status : 400; + return json({ ok: false, error: error instanceof HttpError ? error.message : "invalid_account_setting_overrides", no_order: true, execution_authority_granted: false }, status); + } + } + if (wantsRisk) { + if (!Number.isSafeInteger(raw.expected_risk_revision)) return json({ ok: false, error: "risk_profile_revision_required" }, 400); + if (raw.expected_risk_revision !== observed.risk.revision) return json({ ok: false, error: "risk_profile_revision_conflict" }, 409); + command.expected_risk_revision = observed.risk.revision; + if (raw.risk_preference === null) command.risk_change = "clear"; + else { + let built; + try { + built = await buildRiskProfileBindings( + { bindings: [{ platform, target_name: observed.identity.target_name, risk_preference: raw.risk_preference }] }, + observed.account_options, + session.login, + ); + } catch (error) { + return json({ ok: false, error: error.message || "risk profile bindings are invalid" }, 400); + } + command.risk_change = "set"; + command.binding = built[0]; + } + } + if (!Object.prototype.hasOwnProperty.call(command, "overrides") && !Object.prototype.hasOwnProperty.call(command, "risk_change") && !Object.prototype.hasOwnProperty.call(command, "declaration")) { + return json({ ok: false, error: "account_settings_change_required" }, 400); + } + let saved; + try { + saved = await runtimeInstanceCommand(env, command); + } catch (error) { + const status = error instanceof HttpError ? error.status : 503; + return json({ ok: false, error: error instanceof HttpError ? error.message : "runtime_instances_unavailable", no_order: true, execution_authority_granted: false }, status); + } + if (command.risk_change && hasConfigStore(env)) { + await mirrorConfigJson(env, RISK_PROFILE_BINDINGS_KEY, { + schema_version: RISK_PROFILE_BINDING_REGISTRY_SCHEMA_VERSION, + bindings: saved.bindings, + }); + } + const changes = accountSettingsAuditChanges(observed, saved, command); + let auditLogged = false; + if (changes.length && hasConfigStore(env)) { + const entry = { + ts: new Date().toISOString(), + login: session.login, + action: "save_account_settings", + platform, + key, + changes, + }; + if (changes.some((kind) => kind === "cash_draft" || kind === "income_draft" || kind === "option_draft" || kind === "strategy_draft")) entry.draft_revision = saved.draft.revision; + if (changes.includes("risk_saved") || changes.includes("risk_cleared")) entry.risk_revision = saved.risk.revision; + if (changes.includes("account_declaration_saved")) entry.declaration_revision = saved.declaration.revision; + try { + await appendAuditLog(env, entry); + auditLogged = true; + } catch { /* the setting is already stored; a missed audit line must not be saved again */ } + } + const presented = await presentAccountSettings(env, saved, true); + presented.audit_logged = auditLogged; + return json(presented); +} + +export async function approveAccountSettingsProfileApplication(request, env) { + requireSameOrigin(request, { requireOrigin: true }); + const session = await readSession(request, env); + if (!session) return json({ ok: false, error: "login required" }, 401); + if (!session.admin) return json({ ok: false, error: "admin required" }, 403); + let raw; + try { raw = await request.json(); } catch { return json({ ok: false, error: "request body must be valid JSON" }, 400); } + const allowedFields = new Set([ + "platform", "key", "request_key", "identity", "expected_instance_revision", + "expected_draft_revision", "old_profile", "new_profile", + ]); + if (!raw || Array.isArray(raw) || typeof raw !== "object" || Object.keys(raw).some((key) => !allowedFields.has(key))) { + return json({ ok: false, error: "account_settings_profile_application_request_invalid" }, 400); + } + if (!SUPPORTED_PLATFORMS.includes(raw.platform) || typeof raw.key !== "string" || !raw.key) { + return json({ ok: false, error: "account_settings_account_required" }, 400); + } + if (!ACCOUNT_SETTINGS_PROFILE_APPLICATION_REQUEST_KEY.test(String(raw.request_key || "")) + || !Number.isSafeInteger(raw.expected_instance_revision) || raw.expected_instance_revision < 0 + || !Number.isSafeInteger(raw.expected_draft_revision) || raw.expected_draft_revision < 0 + || !accountSettingsIdentityIsComplete(raw.identity)) { + return json({ ok: false, error: "account_settings_profile_application_request_invalid" }, 400); + } + const oldProfile = accountSettingsProfileName(raw.old_profile); + const newProfile = accountSettingsProfileName(raw.new_profile); + if (!oldProfile || !newProfile || oldProfile === newProfile) { + return json({ ok: false, error: "account_settings_profile_application_profile_invalid" }, 400); + } + if (!hasRuntimeInstanceStore(env)) return json({ ok: false, error: "runtime_instances_not_bound" }, 503); + + const command = { + action: "account_settings_profile_application_approve", + actor: session.login, + platform: raw.platform, + key: raw.key, + request_key: raw.request_key, + identity: raw.identity, + expected_instance_revision: raw.expected_instance_revision, + expected_draft_revision: raw.expected_draft_revision, + old_profile: oldProfile, + new_profile: newProfile, + }; + try { + const prior = await runtimeInstanceCommand(env, { + action: "account_settings_profile_application_lookup", + actor: session.login, + request_key: raw.request_key, + }); + if (prior.application) { + const replay = await runtimeInstanceCommand(env, command); + return json({ + ok: true, + replayed: true, + application: replay.application, + runtime_applied: false, + variables_updated: false, + no_order: true, + execution_authority_granted: false, + }); + } + + const observed = await runtimeInstanceCommand(env, { + action: "account_settings_read", actor: session.login, platform: raw.platform, key: raw.key, + }); + if (observed.instance_revision !== raw.expected_instance_revision + || canonicalResearchTaskJson(observed.identity) !== canonicalResearchTaskJson(raw.identity)) { + return json({ ok: false, error: "account_settings_profile_application_identity_conflict" }, 409); + } + const draft = observed.draft; + if (draft?.status !== "current" || draft.revision !== raw.expected_draft_revision + || canonicalResearchTaskJson(draft.identity) !== canonicalResearchTaskJson(observed.identity) + || Object.keys(draft.overrides || {}).length !== 1 + || draft.overrides?.strategy_profile !== newProfile) { + return json({ ok: false, error: "account_settings_profile_application_draft_conflict" }, 409); + } + if (isDcaProfile(oldProfile) || isDcaProfile(newProfile)) { + return json({ ok: false, error: "account_settings_profile_application_profile_only_required" }, 409); + } + const profiles = await loadAccountDraftProfiles(env); + const selectable = accountDraftStrategyChoices(raw.platform, observed.config, profiles); + if (!selectable.some((profile) => profile.profile === newProfile && profile.dca_supported !== true)) { + return json({ ok: false, error: "account_settings_profile_application_profile_not_selectable" }, 409); + } + const readback = await accountSettingsProfileRuntimeReadback(env, observed); + if (!readback) return json({ ok: false, error: "account_settings_runtime_readback_unavailable" }, 503); + if (readback.runtime_target_enabled !== "false") { + return json({ ok: false, error: "account_settings_runtime_target_not_disabled" }, 409); + } + if (readback.strategy_profile !== oldProfile) { + return json({ ok: false, error: "account_settings_profile_application_current_profile_conflict" }, 409); + } + command.current_profile = readback.strategy_profile; + command.runtime_target_enabled = false; + const approved = await runtimeInstanceCommand(env, command); + let auditLogged = false; + if (!approved.replayed && hasConfigStore(env)) { + try { + await appendAuditLog(env, { + ts: approved.application.approved_at, + login: session.login, + action: "approve_account_settings_profile_application", + platform: raw.platform, + key: raw.key, + request_key: raw.request_key, + instance_revision: raw.expected_instance_revision, + draft_revision: raw.expected_draft_revision, + old_profile: oldProfile, + new_profile: newProfile, + }); + auditLogged = true; + } catch { /* the durable approval is authoritative if its audit mirror is unavailable */ } + } + return json({ + ok: true, + replayed: approved.replayed === true, + application: approved.application, + audit_logged: auditLogged, + runtime_applied: false, + variables_updated: false, + no_order: true, + execution_authority_granted: false, + }); + } catch (error) { + const status = error instanceof HttpError ? error.status : 503; + return json({ ok: false, error: error instanceof HttpError ? error.message : "runtime_instances_unavailable", no_order: true, execution_authority_granted: false }, status); + } +} + +export function decodeProfileApplicationJwtPart(value, maxBytes, parseJson = true) { + if (typeof value !== "string" || value.length === 0 || value.length > maxBytes * 2 + || !/^[A-Za-z0-9_-]+$/.test(value)) throw new HttpError("github_oidc_token_invalid", 401); + const padded = value.replaceAll("-", "+").replaceAll("_", "/") + "=".repeat((4 - value.length % 4) % 4); + let binary; + try { binary = atob(padded); } catch { throw new HttpError("github_oidc_token_invalid", 401); } + if (binary.length > maxBytes) throw new HttpError("github_oidc_token_invalid", 401); + const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0)); + if (!parseJson) return { bytes }; + try { return { bytes, value: JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes)) }; } + catch { throw new HttpError("github_oidc_token_invalid", 401); } +} + +export async function readGitHubProfileApplicationJwks() { + const controller = new AbortController(); + let rejectAbort = () => {}; + const abortPromise = new Promise((_, reject) => { rejectAbort = reject; }); + abortPromise.catch(() => {}); + const onAbort = () => rejectAbort(new HttpError("github_oidc_jwks_unavailable", 503)); + controller.signal.addEventListener("abort", onAbort); + const timeout = setTimeout(() => controller.abort(), ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_TIMEOUT_MS); + let response; + try { + response = await Promise.race([ + fetch(ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_JWKS_URL, { + method: "GET", + headers: { Accept: "application/json" }, + redirect: "manual", + signal: controller.signal, + }), + abortPromise, + ]); + if (response.status !== 200 || response.redirected) throw new HttpError("github_oidc_jwks_unavailable", 503); + const contentLength = response.headers.get("Content-Length"); + if (contentLength && (!/^\d+$/.test(contentLength) || Number(contentLength) > ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_MAX_JWKS_BYTES)) { + throw new HttpError("github_oidc_jwks_unavailable", 503); + } + const contentType = response.headers.get("Content-Type") || ""; + if (!/^application\/json(?:\s*;|$)/i.test(contentType)) throw new HttpError("github_oidc_jwks_unavailable", 503); + const text = await readLimitedUtf8Body(response, ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_MAX_JWKS_BYTES, controller, abortPromise); + const jwks = JSON.parse(text); + if (!jwks || Array.isArray(jwks) || typeof jwks !== "object" || !Array.isArray(jwks.keys) || jwks.keys.length < 1 || jwks.keys.length > 16) { + throw new HttpError("github_oidc_jwks_unavailable", 503); + } + return jwks.keys; + } catch (error) { + if (error instanceof HttpError) throw error; + throw new HttpError("github_oidc_jwks_unavailable", 503); + } finally { + clearTimeout(timeout); + controller.signal.removeEventListener("abort", onAbort); + } +} + +export async function verifyGitHubProfileApplicationToken(request, env) { + const workflowSha = env.ACCOUNT_SETTINGS_PROFILE_APPLICATION_WORKFLOW_SHA; + if (typeof workflowSha !== "string" || !/^[a-f0-9]{40}$/.test(workflowSha)) { + throw new HttpError("github_oidc_workflow_sha_not_configured", 503); + } + const authorization = request.headers.get("Authorization") || ""; + const match = /^Bearer ([A-Za-z0-9._~-]+)$/.exec(authorization); + if (!match || match[1].length > ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_MAX_TOKEN_BYTES) { + throw new HttpError("github_oidc_token_invalid", 401); + } + const parts = match[1].split("."); + if (parts.length !== 3) throw new HttpError("github_oidc_token_invalid", 401); + const header = decodeProfileApplicationJwtPart(parts[0], 2048).value; + const claims = decodeProfileApplicationJwtPart(parts[1], 4096).value; + const signature = decodeProfileApplicationJwtPart(parts[2], 1024, false).bytes; + if (!header || Array.isArray(header) || typeof header !== "object" + || header.alg !== "RS256" || header.typ !== "JWT" + || typeof header.kid !== "string" || !/^[A-Za-z0-9._-]{1,128}$/.test(header.kid) + || Object.hasOwn(header, "jku") || Object.hasOwn(header, "x5u") || Object.hasOwn(header, "crit")) { + throw new HttpError("github_oidc_token_invalid", 401); + } + if (!claims || Array.isArray(claims) || typeof claims !== "object" + || claims.iss !== "https://token.actions.githubusercontent.com" + || claims.aud !== ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_AUDIENCE + || claims.repository !== ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_REPOSITORY + || claims.ref !== ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_REF + || claims.workflow_ref !== ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_WORKFLOW_REF + || claims.workflow_sha !== workflowSha + || Object.hasOwn(claims, "job_workflow_ref") || Object.hasOwn(claims, "job_workflow_sha") + || claims.event_name !== "workflow_dispatch" + || typeof claims.run_id !== "string" || !/^[1-9]\d{0,19}$/.test(claims.run_id) + || typeof claims.run_attempt !== "string" || !/^[1-9]\d{0,5}$/.test(claims.run_attempt)) { + throw new HttpError("github_oidc_token_invalid", 401); + } + const now = Math.floor(Date.now() / 1000); + if (![claims.iat, claims.nbf, claims.exp].every(Number.isSafeInteger) + || claims.iat > now + 30 || claims.iat < now - 600 + || claims.nbf > now + 30 || claims.nbf > claims.exp + || claims.exp <= now || claims.exp <= claims.iat || claims.exp - claims.iat > 600) { + throw new HttpError("github_oidc_token_invalid", 401); + } + const keys = await readGitHubProfileApplicationJwks(); + const matches = keys.filter((key) => key?.kid === header.kid); + if (matches.length !== 1) throw new HttpError("github_oidc_token_invalid", 401); + const jwk = matches[0]; + if (jwk.kty !== "RSA" || jwk.use !== "sig" || (jwk.alg && jwk.alg !== "RS256") + || (jwk.key_ops !== undefined && (!Array.isArray(jwk.key_ops) || !jwk.key_ops.includes("verify"))) + || typeof jwk.n !== "string" || typeof jwk.e !== "string") { + throw new HttpError("github_oidc_token_invalid", 401); + } + let valid = false; + try { + const publicKey = await crypto.subtle.importKey( + "jwk", jwk, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, false, ["verify"], + ); + valid = await crypto.subtle.verify( + "RSASSA-PKCS1-v1_5", publicKey, signature, new TextEncoder().encode(`${parts[0]}.${parts[1]}`), + ); + } catch { throw new HttpError("github_oidc_token_invalid", 401); } + if (!valid) throw new HttpError("github_oidc_token_invalid", 401); + return { runId: claims.run_id, runAttempt: claims.run_attempt }; +} + +export function accountSettingsProfileApplicationIsFixedHk(application) { + const identity = application?.identity; + return application?.platform === "longbridge" && application?.key === "hk" + && accountSettingsIdentityIsComplete(identity) + && identity.platform === "longbridge" && identity.key === "hk"; +} + +export async function claimAccountSettingsProfileApplicationFromGitHub(request, env) { + if (new URL(request.url).search || !hasRuntimeInstanceStore(env)) { + return json({ ok: false, error: "account_settings_profile_application_unavailable", no_order: true, execution_authority_granted: false }, 503); + } + try { + const run = await verifyGitHubProfileApplicationToken(request, env); + const raw = await readGitHubProfileApplicationRequestBody(request); + if (!ux1ExactKeys(raw, ["request_key"]) || !ACCOUNT_SETTINGS_PROFILE_APPLICATION_REQUEST_KEY.test(String(raw.request_key || ""))) { + return json({ ok: false, error: "account_settings_profile_application_request_invalid", no_order: true, execution_authority_granted: false }, 400); + } + const actor = "github-oidc-profile-application"; + const lookup = await runtimeInstanceCommand(env, { + action: "account_settings_profile_application_lookup", actor, request_key: raw.request_key, + }); + const application = lookup.application; + if (!application) return json({ ok: false, error: "account_settings_profile_application_not_found", no_order: true, execution_authority_granted: false }, 404); + if (!accountSettingsProfileApplicationIsFixedHk(application)) { + return json({ ok: false, error: "account_settings_profile_application_target_conflict", no_order: true, execution_authority_granted: false }, 409); + } + const command = { + action: "account_settings_profile_application_claim", + actor, + request_key: application.request_key, + identity: application.identity, + expected_instance_revision: application.instance_revision, + expected_draft_revision: application.draft_revision, + old_profile: application.old_profile, + new_profile: application.new_profile, + current_profile: application.old_profile, + runtime_target_enabled: false, + workflow_run_id: run.runId, + workflow_run_attempt: run.runAttempt, + }; + if (application.status === "claimed") { + const replay = await runtimeInstanceCommand(env, command); + return json({ ...replay, ok: true }); + } + if (application.status !== "approved") { + return json({ ok: false, error: "account_settings_profile_application_conflict", no_order: true, execution_authority_granted: false }, 409); + } + const observed = await runtimeInstanceCommand(env, { + action: "account_settings_read", actor, platform: application.platform, key: application.key, + }); + if (observed.instance_revision !== application.instance_revision + || canonicalResearchTaskJson(observed.identity) !== canonicalResearchTaskJson(application.identity)) { + return json({ ok: false, error: "account_settings_profile_application_identity_conflict", no_order: true, execution_authority_granted: false }, 409); + } + const draft = observed.draft; + if (draft?.status !== "current" || draft.revision !== application.draft_revision + || canonicalResearchTaskJson(draft.identity) !== canonicalResearchTaskJson(application.identity) + || Object.keys(draft.overrides || {}).length !== 1 + || draft.overrides?.strategy_profile !== application.new_profile) { + return json({ ok: false, error: "account_settings_profile_application_draft_conflict", no_order: true, execution_authority_granted: false }, 409); + } + const variableScope = resolveVariableScope(application.platform, observed.config); + const githubEnvironment = resolveGithubEnvironment(application.platform, observed.config, variableScope); + const repository = platformRepositories(env)?.[application.platform]; + if (repository !== "QuantStrategyLab/LongBridgePlatform" + || variableScope !== "environment" + || githubEnvironment !== "longbridge-hk") { + return json({ ok: false, error: "account_settings_profile_application_target_conflict", no_order: true, execution_authority_granted: false }, 409); + } + const readback = await accountSettingsProfileRuntimeReadback(env, observed, { + repository, variableScope, githubEnvironment, + }); + if (!readback) return json({ ok: false, error: "account_settings_runtime_readback_unavailable", no_order: true, execution_authority_granted: false }, 503); + if (readback.runtime_target_enabled !== "false" || readback.strategy_profile !== application.old_profile) { + return json({ ok: false, error: "account_settings_profile_application_runtime_conflict", no_order: true, execution_authority_granted: false }, 409); + } + const claimed = await runtimeInstanceCommand(env, command); + return json({ ...claimed, ok: true }); + } catch (error) { + const status = error instanceof HttpError ? error.status : 503; + return json({ ok: false, error: error instanceof HttpError ? error.message : "account_settings_profile_application_unavailable", no_order: true, execution_authority_granted: false }, status); + } +} + +export async function readGitHubProfileApplicationRequestBody(request) { + const contentLength = request.headers.get("Content-Length"); + if (contentLength && (!/^\d+$/.test(contentLength) || Number(contentLength) > ACCOUNT_SETTINGS_PROFILE_APPLICATION_MAX_BODY_BYTES)) { + throw new HttpError("account_settings_profile_application_body_too_large", 413); + } + const controller = new AbortController(); + let rejectAbort = () => {}; + const abortPromise = new Promise((_, reject) => { rejectAbort = reject; }); + abortPromise.catch(() => {}); + const onAbort = () => rejectAbort(new HttpError("account_settings_profile_application_body_timeout", 408)); + controller.signal.addEventListener("abort", onAbort); + const timeout = setTimeout(() => controller.abort(), ACCOUNT_SETTINGS_PROFILE_APPLICATION_REQUEST_TIMEOUT_MS); + try { + const text = await readLimitedUtf8Body(request, ACCOUNT_SETTINGS_PROFILE_APPLICATION_MAX_BODY_BYTES, controller, abortPromise); + try { return JSON.parse(text); } catch { throw new HttpError("request body must be valid JSON", 400); } + } catch (error) { + if (error instanceof HttpError) throw error; + if (String(error?.message || "") === "ux1_calculator_body_limit") { + throw new HttpError("account_settings_profile_application_body_too_large", 413); + } + throw new HttpError("account_settings_profile_application_body_invalid", 400); + } finally { + clearTimeout(timeout); + controller.signal.removeEventListener("abort", onAbort); + } +} + +export function accountSettingsAuditChanges(before, after, requested) { + const changes = []; + const left = before?.draft?.overrides && typeof before.draft.overrides === "object" && !Array.isArray(before.draft.overrides) ? before.draft.overrides : {}; + const right = after?.draft?.overrides && typeof after.draft.overrides === "object" && !Array.isArray(after.draft.overrides) ? after.draft.overrides : {}; + const patch = requested?.overrides && typeof requested.overrides === "object" && !Array.isArray(requested.overrides) ? requested.overrides : {}; + const stored = (record, name) => Object.prototype.hasOwnProperty.call(record, name) ? record[name] : undefined; + const asked = (name) => Object.prototype.hasOwnProperty.call(patch, name); + if (["reserved_cash_floor", "reserved_cash_ratio"].some(asked) && ["reserved_cash_floor", "reserved_cash_ratio"].some((name) => stored(left, name) !== stored(right, name))) changes.push("cash_draft"); + if (asked("income_layer_enabled") && stored(left, "income_layer_enabled") !== stored(right, "income_layer_enabled")) changes.push("income_draft"); + if (asked("option_overlay_enabled") && stored(left, "option_overlay_enabled") !== stored(right, "option_overlay_enabled")) changes.push("option_draft"); + const strategyDraftChanged = (asked("strategy_profile") && stored(left, "strategy_profile") !== stored(right, "strategy_profile")) + || ((asked("dca_mode") || asked("dca_base_investment_usd")) && ["dca_mode", "dca_base_investment_usd"].some((name) => stored(left, name) !== stored(right, name))); + if (strategyDraftChanged) changes.push("strategy_draft"); + if (requested?.declaration && before?.declaration?.revision !== after?.declaration?.revision) changes.push("account_declaration_saved"); + const beforePreference = before?.risk?.preference || null; + const afterPreference = after?.risk?.preference || null; + if ((requested?.risk_change === "set" || requested?.risk_change === "clear") && beforePreference !== afterPreference) changes.push(afterPreference ? "risk_saved" : "risk_cleared"); + return changes; +} + +export async function buildAdminState(session, env) { + const authConfig = await loadAuthConfig(env); + const accountConfig = await loadAccountOptionsConfig(env); + const riskProfileBindingState = await loadRiskProfileBindings(env); + const runtimeInstances = hasRuntimeInstanceStore(env) ? await runtimeInstanceCommand(env) : null; + let runtimeInstanceStrategies = []; + if (runtimeInstances) { + try { + runtimeInstanceStrategies = (await loadStrategyProfilesConfig(env)).filter((item) => !isFrozenStrategyProfile(item) && (item.allowed_execution_modes || []).includes("dry_run")).map((item) => ({ profile: item.profile, label: item.label, domain: item.domain })); + } catch { + // Catalog failure affects draft authoring, never existing instance + // readback, retirement requests, or `/api/admin/config` readback. + } + } + return { + ok: true, + session: { login: session.login, admin: true }, + kvAvailable: hasConfigStore(env), + authConfig, + accountOptions: accountConfig.options || {}, + accountOptionSource: accountConfig.source, + riskProfileBindings: riskProfileBindingState.bindings, + riskProfileRevision: riskProfileBindingState.revision, + riskProfileBindingsError: riskProfileBindingState.error, + riskProfileBindingTargets: riskProfileBindingTargets(riskProfileBindingState.account_options || accountConfig.options || {}), + auditLog: await loadAuditLog(env), + runtimeInstances, + runtimeInstanceStrategies, + runtimeInstanceStrategiesAvailable: runtimeInstanceStrategies.length > 0, + }; +} + +export async function loadPlatformMeta(env = {}) { + // Use the same bundled configuration as routing and capability validation. + // Fetching GitHub main here could mix a newer catalog with an older Worker. + const merged = Object.fromEntries(Object.entries(PLATFORM_META) + .map(([platform, meta]) => [platform, { ...meta }])); + const hidden = new Set(String(env.STRATEGY_SWITCH_HIDDEN_PLATFORMS || "") + .split(",").map((value) => value.trim().toLowerCase()).filter(Boolean)); + for (const [platform, meta] of Object.entries(merged)) { + meta.console_visible = !hidden.has(platform); + } + return merged; +} + +// In-memory cache for the lifetime of this Worker isolate. +let _memCurrentStrategies = null; +let _memCurrentStrategiesTs = 0; +let _memRefreshing = false; // prevent concurrent background refreshes + +// KV free tier caps writes at 1,000/day account-wide and the */5 cron plus every +// background refresh reaches this helper, so a write is paid for only when the +// rendered content changed or the stored entry needs its ts bumped to stay inside +// the fresh window that cold isolates serve from. +// current_strategies_cache carries per-account execution_evidence.observed_at +// stamped with new Date() (see accountExecutionEvidence callers), so whole-payload +// byte equality can never hold; the comparison drops those stamps and keeps the +// stored value untouched for readers. + +// readConfigJson reports a damaged value as `STRATEGY_SWITCH_CONFIG. must be +// valid JSON`. Only that shape is a cache miss; a KV read that failed for any other +// reason is an outage and has to reach the caller instead of being mistaken for an +// empty cache — the difference is what keeps cold isolates from fanning out to +// GitHub on every request while the store is unhealthy. +export function isConfigJsonParseError(error) { + return /^STRATEGY_SWITCH_CONFIG\.[^\s]+ must be valid JSON$/.test(String(error?.message || "")); +} + +export async function readCurrentStrategiesCacheEntry(env) { + try { + return await readConfigJson(env, CURRENT_STRATEGIES_CACHE_KEY); + } catch (error) { + if (!isConfigJsonParseError(error)) throw error; + return null; + } +} + +export async function writeCurrentStrategiesCache(env, payload, ts) { + if (env.STRATEGY_SWITCH_RUNTIME_INSTANCES) { + try { + await directoryCommand(env, { action: "cache", payload, ts }); + return; + } catch (error) { + // Receiver-first compatibility only; a real storage failure remains loud. + if (error.message !== "kv_directory_legacy_receiver" && error.message !== "kv_directory_invalid_request") throw error; + } + } + const signature = currentStrategiesSignature(payload); + // A damaged stored value must not wedge the cache; this is the only path that can + // overwrite it, so the comparison reads through the same repair-aware helper. + const cached = await readCurrentStrategiesCacheEntry(env); + if (cached?.data && currentStrategiesSignature(cached.data) === signature + && cached.ts && ts - cached.ts < CURRENT_STRATEGIES_KV_BUMP_MS) { + return; + } + await writeConfigJson(env, CURRENT_STRATEGIES_CACHE_KEY, { ts, data: payload }); +} + +// loadCurrentStrategiesSafely answers with evidence-only account entries whenever +// GitHub timed out or threw, so a payload whose every account is exactly that +// shape carries no strategy information and must not be served as fresh. +export function isDegradedCurrentStrategies(payload) { + const accounts = Object.values(payload || {}).flatMap(options => Object.values(options || {})); + if (!accounts.length) return false; + return accounts.every(account => account && typeof account === "object" + && Object.keys(account).length === 1 + && account.execution_evidence?.status === "read_failed"); +} + +export async function configPayload(request, env, ctx) { + const session = await readSession(request, env); + const meta = await loadPlatformMeta(env); + if (!session?.allowed) return { accountOptions: null, platformMeta: meta }; + const accountConfig = await loadAccountOptionsConfig(env); + const strategyProfiles = await loadStrategyProfilesConfig(env); + + const currentStrategies = await readCurrentStrategies(accountConfig.options, env, ctx); + + return { + accountOptions: accountConfig.options, + accountOptionsRevision: accountConfig.revision ?? null, + runtimeDailyBindings: runtimeDailyReadModelEnabled(env) ? await runtimeDailyPublicBindings(env, accountConfig.options) : [], + platformRepositories: platformRepositories(env), + platformMeta: meta, + strategyProfiles, + currentStrategies, + }; +} + +export async function readCurrentStrategies(accountOptions, env, ctx) { + let currentStrategies = null; + let cacheFresh = false; + + // 1) In-memory cache + if (_memCurrentStrategies) { + const age = Date.now() - _memCurrentStrategiesTs; + if (age < CURRENT_STRATEGIES_MEMO_TTL_MS) { + currentStrategies = _memCurrentStrategies; + cacheFresh = true; + } else if (age < CURRENT_STRATEGIES_STALE_TTL_MS) { + currentStrategies = _memCurrentStrategies; + // stale — trigger background refresh below + } + } + + // 2) KV cache + if (!currentStrategies && hasConfigStore(env)) { + const cached = await readCurrentStrategiesCacheEntry(env); + if (cached?.ts && cached.data) { + const age = Date.now() - cached.ts; + // A degraded entry stays inside the freshness window but is never *fresh*: + // the next request must revalidate instead of serving a read_failed payload + // for the whole 30 minutes. + if (age < CURRENT_STRATEGIES_KV_FRESH_MS && !isDegradedCurrentStrategies(cached.data)) { + currentStrategies = cached.data; + cacheFresh = true; + } else if (age < CURRENT_STRATEGIES_STALE_TTL_MS) { + currentStrategies = cached.data; + // stale — trigger background refresh below + } + if (currentStrategies && !_memCurrentStrategies) { + _memCurrentStrategies = currentStrategies; + _memCurrentStrategiesTs = cached.ts; + } + } + } + + // 3) Background refresh when stale (return old data immediately) + if (currentStrategies && !cacheFresh && !_memRefreshing && hasConfigStore(env) && ctx) { + _memRefreshing = true; + ctx.waitUntil((async () => { + try { + const fresh = await loadCurrentStrategiesSafely(accountOptions, env); + _memCurrentStrategies = fresh; + _memCurrentStrategiesTs = Date.now(); + await writeCurrentStrategiesCache(env, fresh, _memCurrentStrategiesTs); + } catch { /* keep stale data */ } + finally { _memRefreshing = false; } + })()); + } + + // 4) Complete miss — must wait for GitHub + if (!currentStrategies) { + currentStrategies = await loadCurrentStrategiesSafely(accountOptions, env); + _memCurrentStrategies = currentStrategies; + _memCurrentStrategiesTs = Date.now(); + if (hasConfigStore(env) && ctx) { + ctx.waitUntil(writeCurrentStrategiesCache(env, currentStrategies, _memCurrentStrategiesTs)); + } + } + + return currentStrategies; +} + +export async function strategyProfilesPayload(env) { + return { + strategyProfiles: await loadStrategyProfilesConfig(env), + platformMeta: await loadPlatformMeta(env), + }; +} + +export async function runtimeCatalogResponse(request, env) { + const session = await readSession(request, env); + if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); + // This is a generated gate catalog, not a deployment observation. Keeping + // the distinction in the response makes it unsuitable as an accidental + // substitute for the independently refreshed control-plane/evidence views. + return json(RUNTIME_CATALOG_PROJECTION); +} + +export async function loadCurrentStrategies(accountOptions, env) { + const token = env.RUNTIME_SETTINGS_DISPATCH_TOKEN; + if (!token || !accountOptions) return executionEvidenceFallback(accountOptions, "not_connected"); + const repositories = platformRepositories(env); + + const variableCache = new Map(); + const readVariable = async (repository, scope, githubEnvironment, name) => { + const cacheKey = [repository, scope, githubEnvironment || ""].join("|"); + if (!variableCache.has(cacheKey)) { + variableCache.set(cacheKey, (async () => { + let status = "ok"; + const values = await fetchGithubVariables(token, repository, scope, githubEnvironment, { + reportReadErrors: true, onReadFailure: reason => { status = reason; }, + }); + return { values, status }; + })()); + } + const result = await variableCache.get(cacheKey); + return result.values?.get(name) || ""; + }; + + readVariable.readStatus = async (repository, scope, githubEnvironment) => + (await variableCache.get([repository, scope, githubEnvironment || ""].join("|")))?.status || "ok"; + const currentStrategies = {}; + // Process platforms sequentially: each platform's account list is also + // processed one at a time (not Promise.all). This keeps GitHub API + // concurrency low enough to avoid secondary rate limiting. + for (const platform of SUPPORTED_PLATFORMS) { + const platformStrategies = await loadStrategiesForPlatform(platform, accountOptions, repositories, readVariable); + if (Object.keys(platformStrategies).length) currentStrategies[platform] = platformStrategies; + // 100ms gap between platforms to respect GitHub secondary rate limit + await new Promise((r) => setTimeout(r, 100)); + } + return currentStrategies; +} + +export async function loadStrategiesForPlatform(platform, accountOptions, repositories, readVariable) { + const options = Array.isArray(accountOptions[platform]) ? accountOptions[platform] : []; + if (!options.length) return {}; + const repository = repositories[platform]; + if (!repository) return executionEvidenceFallback({ [platform]: options }, "not_connected")[platform]; + + // Process accounts sequentially within each platform to stay within + // GitHub's concurrent request budget per token (~30 burst limit). + const platformStrategies = {}; + for (const option of options) { + let status = "ok"; + const trackedRead = async (...args) => { + const value = await readVariable(...args); + const result = await readVariable.readStatus?.(...args.slice(0, 3)); + if (result && result !== "ok") status = result; + return value; + }; + const current = await resolveCurrentStrategyForAccount({ + platform, + option, + optionsCount: options.length, + repository, + readVariable: trackedRead, + }); + platformStrategies[option.key] = { ...current, + execution_evidence: accountExecutionEvidence(option, current || {}, { status, observedAt: new Date().toISOString() }) }; + } + return platformStrategies; +} + +export function executionEvidenceFallback(accountOptions, status) { + return Object.fromEntries(Object.entries(accountOptions || {}).map(([platform, options]) => [platform, + Object.fromEntries(options.map(option => [option.key, { + execution_evidence: accountExecutionEvidence(option, {}, { status, observedAt: new Date().toISOString() }), + }]))])); +} + +export async function loadCurrentStrategiesSafely(accountOptions, env) { + try { + return await withTimeout( + loadCurrentStrategies(accountOptions, env), + CURRENT_STRATEGIES_TIMEOUT_MS, + executionEvidenceFallback(accountOptions, "read_failed"), + ); + } catch { + return executionEvidenceFallback(accountOptions, "read_failed"); + } +} + +export function withTimeout(promise, timeoutMs, fallback) { + let timeoutId; + const timeout = new Promise((resolve) => { + timeoutId = setTimeout(() => resolve(fallback), timeoutMs); + }); + return Promise.race([promise, timeout]).finally(() => clearTimeout(timeoutId)); +} + +export async function fetchWithTimeout(resource, init = {}, timeoutMs = GITHUB_API_TIMEOUT_MS, fetchImpl = fetch) { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), timeoutMs); + try { + return await fetchImpl(resource, { ...init, signal: controller.signal }); + } catch (error) { + if (error?.name === "AbortError") throw new Error("GitHub request timed out"); + throw error; + } finally { + clearTimeout(timeoutId); + } +} + +export async function resolveCurrentStrategyForAccount({ platform, option, optionsCount, repository, readVariable }) { + const serviceTargetsValue = usesServiceTargetsAsRuntimeSource(platform) + ? await readVariable(repository, "repository", "", "CLOUD_RUN_SERVICE_TARGETS_JSON") + : ""; + const serviceTarget = runtimeTargetFromServiceTargets(serviceTargetsValue, platform, option); + const serviceTargetProfile = cleanCurrentStrategy(serviceTarget?.strategy_profile); + const serviceTargetReservedCashPayload = reservedCashPayloadFromObject(platform, serviceTarget); + const serviceTargetIncomeLayerPayload = incomeLayerPayloadFromObject(serviceTarget); + const serviceTargetOptionOverlayPayload = optionOverlayPayloadFromObject(serviceTarget); + let serviceTargetRuntimeTargetEnabledPayload = runtimeTargetEnabledPayloadFromObject(serviceTarget); + if (serviceTarget && !Object.keys(serviceTargetRuntimeTargetEnabledPayload).length) { + const variableScope = resolveVariableScope(platform, option); + serviceTargetRuntimeTargetEnabledPayload = await readRuntimeTargetEnabledVariable({ + repository, variableScope, + githubEnvironment: resolveGithubEnvironment(platform, option, variableScope), readVariable, + }); + } + const serviceTargetDcaPayload = dcaPayloadFromObject(serviceTarget); + const serviceTargetCashOnlyPayload = cashOnlyPayloadFromObject(platform, serviceTarget); + if (serviceTargetProfile) { + return { + strategy_profile: serviceTargetProfile, + ...runtimeModePayload(serviceTarget), + strategy_execution: strategyTargetExecution(serviceTarget), + ...serviceTargetReservedCashPayload, + ...serviceTargetIncomeLayerPayload, + ...serviceTargetOptionOverlayPayload, + ...serviceTargetRuntimeTargetEnabledPayload, + ...serviceTargetCashOnlyPayload, + ...dcaPayloadForProfile(serviceTargetProfile, serviceTargetDcaPayload), + source: "CLOUD_RUN_SERVICE_TARGETS_JSON", + variable_scope: "repository", + }; + } + if ( + Object.keys(serviceTargetReservedCashPayload).length || + Object.keys(serviceTargetIncomeLayerPayload).length || + Object.keys(serviceTargetOptionOverlayPayload).length || + Object.keys(serviceTargetCashOnlyPayload).length || + Object.keys(serviceTargetRuntimeTargetEnabledPayload).length + ) { + return { + ...runtimeModePayload(serviceTarget), + strategy_execution: strategyTargetExecution(serviceTarget), + ...serviceTargetReservedCashPayload, + ...serviceTargetIncomeLayerPayload, + ...serviceTargetOptionOverlayPayload, + ...serviceTargetCashOnlyPayload, + ...serviceTargetRuntimeTargetEnabledPayload, + source: "CLOUD_RUN_SERVICE_TARGETS_JSON", + variable_scope: "repository", + }; + } + + const variableScope = resolveVariableScope(platform, option); + const githubEnvironment = resolveGithubEnvironment(platform, option, variableScope); + const reservedCashPayloadPromise = readReservedCashVariables({ + platform, + repository, + variableScope, + githubEnvironment, + readVariable, + }); + const incomeLayerPayloadPromise = readIncomeLayerVariables({ + repository, + variableScope, + githubEnvironment, + readVariable, + }); + const optionOverlayPayloadPromise = readOptionOverlayVariables({ + repository, + variableScope, + githubEnvironment, + readVariable, + }); + const runtimeTargetEnabledPayloadPromise = readRuntimeTargetEnabledVariable({ + repository, + variableScope, + githubEnvironment, + readVariable, + }); + const dcaPayloadPromise = readDcaVariables({ + repository, + variableScope, + githubEnvironment, + readVariable, + }); + const cashOnlyPayloadPromise = readCashOnlyVariables({ + platform, + repository, + variableScope, + githubEnvironment, + readVariable, + }); + // All fields share one scoped variable-list read; missing fields are not retried. + const runtimeTargetValue = await readVariable(repository, variableScope, githubEnvironment, "RUNTIME_TARGET_JSON"); + const [ + reservedCashPayload, + incomeLayerPayload, + optionOverlayPayload, + runtimeTargetEnabledPayload, + dcaPayload, + cashOnlyPayload, + ] = await Promise.all([ + reservedCashPayloadPromise, + incomeLayerPayloadPromise, + optionOverlayPayloadPromise, + runtimeTargetEnabledPayloadPromise, + dcaPayloadPromise, + cashOnlyPayloadPromise, + ]); + const runtimeTarget = parseJsonObject(runtimeTargetValue); + const runtimeTargetMatches = runtimeTarget && runtimeTargetMatchesAccount(runtimeTarget, platform, option); + const runtimeTargetProfile = runtimeTargetMatches ? cleanCurrentStrategy(runtimeTarget.strategy_profile) : ""; + if (runtimeTargetProfile) { + let resumeTarget = null; + if (platform === "binance" && repository === "QuantStrategyLab/BinancePlatform" && variableScope === "repository") { + const values = { RUNTIME_TARGET_JSON: runtimeTargetValue }; + for (const name of ["RUNTIME_TARGET_ENABLED", "BINANCE_RECOVERY_CONTROL_ENABLED", "BINANCE_DRY_RUN", "STRATEGY_PROFILE", "CLOUD_RUN_SERVICE_TARGETS_JSON"]) { + values[name] = await readVariable(repository, variableScope, githubEnvironment, name); + } + resumeTarget = await binanceResumeTarget(values, option); + } + return { + strategy_profile: runtimeTargetProfile, + ...(resumeTarget ? { binance_resume_target_sha256: resumeTarget.runtime_target_sha256 } : {}), + ...runtimeModePayload(runtimeTarget), + strategy_execution: strategyTargetExecution(runtimeTarget), + ...reservedCashPayload, + ...incomeLayerPayload, + ...optionOverlayPayload, + ...runtimeTargetEnabledPayload, + ...dcaPayloadForProfile(runtimeTargetProfile, dcaPayload), + ...cashOnlyPayload, + source: "RUNTIME_TARGET_JSON", + variable_scope: variableScope, + github_environment: githubEnvironment || "", + }; + } + + if (variableScope === "environment" || optionsCount <= 1) { + const profileValue = await readVariable(repository, variableScope, githubEnvironment, "STRATEGY_PROFILE"); + const profile = cleanCurrentStrategy(profileValue); + if (profile) { + const current = { + strategy_profile: profile, + ...reservedCashPayload, + ...incomeLayerPayload, + ...optionOverlayPayload, + ...runtimeTargetEnabledPayload, + ...dcaPayloadForProfile(profile, dcaPayload), + ...cashOnlyPayload, + source: "STRATEGY_PROFILE", + variable_scope: variableScope, + github_environment: githubEnvironment || "", + }; + if (variableScope === "environment" && normalizeMatchValue(option?.target_name) === "paper") { + current.execution_mode = "paper"; + } + return current; + } + } + + if ( + Object.keys(reservedCashPayload).length || + Object.keys(incomeLayerPayload).length || + Object.keys(optionOverlayPayload).length || + Object.keys(runtimeTargetEnabledPayload).length || + Object.keys(cashOnlyPayload).length + ) { + return { + ...reservedCashPayload, + ...incomeLayerPayload, + ...optionOverlayPayload, + ...runtimeTargetEnabledPayload, + ...cashOnlyPayload, + source: Object.keys(reservedCashPayload).length + ? "RESERVED_CASH_VARIABLES" + : (Object.keys(incomeLayerPayload).length + ? "INCOME_LAYER_VARIABLES" + : (Object.keys(runtimeTargetEnabledPayload).length + ? "RUNTIME_TARGET_ENABLED_VARIABLE" + : (Object.keys(optionOverlayPayload).length + ? "OPTION_OVERLAY_VARIABLES" + : "CASH_ONLY_EXECUTION_VARIABLE"))), + variable_scope: variableScope, + github_environment: githubEnvironment || "", + }; + } + + return null; +} + +export function usesServiceTargetsAsRuntimeSource(platform) { + return ["ibkr", "schwab", "firstrade"].includes(platform); +} + +export function logout(request) { + requireSameOrigin(request, { requireOrigin: true }); + return json({ ok: true }, 200, { + "Set-Cookie": clearCookie(SESSION_COOKIE), + }); +} + +export function hkStopSourceIdentity(config, item = {}) { + const source = config || {}; + const selector = source.account_selector; + const selectors = Array.isArray(selector) + ? selector.map((value) => String(value).trim()).filter(Boolean) + : String(selector || "").split(",").map((value) => value.trim()).filter(Boolean); + const broker = source.broker_environment; + return { + key: String(source.key || item.key || ""), + target_name: String(source.target_name || ""), + platform_id: "longbridge", + deployment_selector: String(source.deployment_selector || "").trim(), + account_selector: selectors, + account_scope: String(source.account_scope || "").trim(), + service_name: String(source.service_name || "").trim(), + variable_scope: source.variable_scope === "environment" ? "environment" : "", + github_environment: String(source.github_environment || "").trim(), + broker_environment: broker === "live" || broker === "paper" ? broker : null, + runtime_status_target_id: String(source.runtime_status_target_id || "").trim(), + }; +} + +export function hkStopDigestOrEmpty(value) { + return typeof value === "string" && /^[0-9a-f]{64}$/.test(value) ? value : ""; +} + +export function hkStopExactObject(value, keys) { + return Boolean(value) && !Array.isArray(value) && typeof value === "object" + && Object.keys(value).length === keys.length + && keys.every((key) => Object.prototype.hasOwnProperty.call(value, key)); +} + +export function normalizeHkStopResult(raw, env) { + const expectedProject = env?.HK_STOP_GCP_PROJECT_ID; + if (typeof expectedProject !== "string" || !/^[a-z][a-z0-9-]{4,61}[a-z0-9]$/.test(expectedProject)) { + throw new HttpError("hk_stop_project_not_configured", 503); + } + const top = ["schema_version", "request_id", "source_revision", "source_identity_sha256", "target_id", "runtime_identity_sha256", "producer", "observed_at", "readback", "no_order", "in_flight_state", "retirement_complete"]; + const producerKeys = ["repository", "workflow_path", "run_id", "run_attempt", "head_sha"]; + const readbackKeys = ["project", "region", "service", "revision_name", "runtime_enabled", "scheduler_state", "scheduler_count", "scheduler_set_sha256", "complete"]; + if (!hkStopExactObject(raw, top) + || raw.schema_version !== "qsl_hk_stop_result.v1" + || !HK_STOP_REQUEST_ID.test(String(raw.request_id || "")) + || !Number.isSafeInteger(raw.source_revision) || raw.source_revision < 0 + || !hkStopDigestOrEmpty(raw.source_identity_sha256) || !hkStopDigestOrEmpty(raw.runtime_identity_sha256) + || raw.target_id !== HK_STOP_TARGET_ID + || !hkStopExactObject(raw.producer, producerKeys) + || raw.producer.repository !== "QuantStrategyLab/LongBridgePlatform" + || raw.producer.workflow_path !== ".github/workflows/stop-hk-runtime.yml" + || !/^(0|[1-9][0-9]*)$/.test(raw.producer.run_id) + || raw.producer.run_attempt !== 1 + || !/^[0-9a-f]{40}$/.test(raw.producer.head_sha) + || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/.test(raw.observed_at) + || !hkStopExactObject(raw.readback, readbackKeys) + || raw.readback.project !== expectedProject + || raw.readback.region !== "asia-east2" + || raw.readback.service !== "longbridge-quant-hk-service" + || typeof raw.readback.revision_name !== "string" + || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/.test(raw.readback.revision_name) + || raw.readback.runtime_enabled !== false + || raw.readback.scheduler_state !== "paused" + || !Number.isSafeInteger(raw.readback.scheduler_count) || raw.readback.scheduler_count < 1 + || !hkStopDigestOrEmpty(raw.readback.scheduler_set_sha256) + || raw.readback.complete !== true + || raw.no_order !== true + || raw.in_flight_state !== "unknown" + || raw.retirement_complete !== false) { + throw new HttpError("hk_stop_result_rejected", 400); + } + return { + schema_version: raw.schema_version, + request_id: String(raw.request_id).toLowerCase(), + source_revision: raw.source_revision, + source_identity_sha256: raw.source_identity_sha256, + target_id: raw.target_id, + runtime_identity_sha256: raw.runtime_identity_sha256, + producer: { + repository: raw.producer.repository, + workflow_path: raw.producer.workflow_path, + run_id: raw.producer.run_id, + run_attempt: raw.producer.run_attempt, + head_sha: raw.producer.head_sha, + }, + observed_at: raw.observed_at, + readback: { ...raw.readback }, + no_order: true, + in_flight_state: "unknown", + retirement_complete: false, + }; +} + +export async function acceptHkStopResult(request, env) { + requireDedicatedExecutionEvidenceSyncToken(request, env); + if (!hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instances_not_bound", 503); + const result = normalizeHkStopResult(await readBoundedJson(request, 8192), env); + const recorded = await runtimeInstanceCommand(env, { + action: "hk_stop_accept_result", + result, + now: new Date().toISOString(), + }); + return json({ + ok: true, + replayed: recorded.replayed === true, + phase: "stopped", + stop_confirmed: true, + platform_applied: false, + request_succeeded: false, + in_flight_state: "unknown", + }); +} + +export function hkStopIdentityComplete(identity) { + return identity?.key === "hk" + && identity.target_name === "hk" + && identity.platform_id === "longbridge" + && identity.deployment_selector + && identity.account_selector?.length + && identity.account_scope === "HK" + && identity.service_name === "longbridge-quant-hk-service" + && identity.variable_scope === "environment" + && identity.github_environment === "longbridge-hk"; +} + +export function presentHkStop(record, observation, repository) { + const dispatchResult = record?.dispatch_result || null; + return { + ok: true, + configured: false, + platform_applied: false, + request_succeeded: false, + stop_confirmed: record?.phase === "stopped", + in_flight_state: "unknown", + reused: record?.reused === true, + persisted: record?.persisted !== false, + request_id: record?.request_id || null, + phase: record?.phase || null, + dispatch_result: dispatchResult, + action: record ? "stop" : null, + requested_at: record?.requested_at || null, + updated_at: record?.updated_at || null, + workflow_run_id: null, + workflow_run_attempt: null, + actions_url: dispatchResult === "accepted" + ? `https://github.com/${repository}/actions/workflows/manual-runtime-stop.yml` + : null, + runtime_observation: observation || { status: "unknown", request_succeeded: false }, + }; +} + +export async function hkStopObservation() { + // Lifecycle snapshots identify a target id, not this stop request's account. + // A fresh disabled snapshot can belong to an older identity, so it stays unknown. + return { status: "unknown", request_succeeded: false }; +} + +export async function loadHkStopAccount(env) { + if (!hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instances_not_bound", 503); + const loaded = await loadAccountOptionsConfig(env); + if (loaded.source !== "durable_object" || !Number.isSafeInteger(loaded.revision)) { + throw new HttpError("hk_stop_source_unavailable", 409); + } + const matches = (loaded.options?.longbridge || []).filter((item) => item.key === "hk" && item.target_name === "hk"); + if (matches.length !== 1) throw new HttpError("stop requires one configured target", 400); + const identity = hkStopSourceIdentity(matches[0], { key: "hk" }); + if (!hkStopIdentityComplete(identity)) throw new HttpError("hk_stop_identity_incomplete", 400); + return { revision: loaded.revision, identity }; +} + +export async function runtimeControlSource(env, platform, targetName) { + if (!hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instances_not_bound", 503); + const source = await loadAccountOptionsConfig(env); + if (source.source !== "durable_object" || !Number.isSafeInteger(source.revision)) throw new HttpError("runtime_control_source_unavailable", 409); + const matches = (source.options?.[platform] || []).filter(item => item.target_name === targetName); + if (matches.length !== 1) throw new HttpError("runtime_control_identity_incomplete", 409); + const item = matches[0]; + const identity = accountSettingsIdentity({ platform, key: item.key, config: item }); + if (!runtimeControlIdentityIsComplete(identity)) throw new HttpError("runtime_control_identity_incomplete", 409); + return { item, identity, revision: source.revision }; +} + +export function presentRuntimeControl(record) { + let reason = null; + try { reason = record?.result_json ? JSON.parse(record.result_json).reason || null : null; } catch { /* invalid persisted result stays unclassified */ } + return { ok: record?.phase !== "rejected", request_id: record?.request_id || null, action: record?.action || null, + phase: record?.phase || null, updated_at: record?.updated_at || null, + unavailable_reason: reason || (record?.phase === "unknown" ? "runtime_control_result_unavailable" : null), + platform_applied: record?.phase === "applied" }; +} + +export async function readPlatformRuntimeControl(env, platform, targetName) { + const source = await runtimeControlSource(env, platform, targetName); + const args = { platform, key: source.item.key }; + let { record } = await runtimeInstanceCommand(env, { action: "runtime_control_read", ...args }); + if (record && ["reserved", "accepted", "unknown", "partial"].includes(record.phase)) { + const result = await readRuntimeControlReceipt({ repository: env.RUNTIME_SETTINGS_REPO || DEFAULT_REPOSITORY, + token: env.RUNTIME_SETTINGS_DISPATCH_TOKEN, requestId: record.request_id, operation: record.action, + runId: record.workflow_run_id, requestedAt: record.requested_at }); + if (result.run_id && !record.workflow_run_id) { + ({ record } = await runtimeInstanceCommand(env, { action: "runtime_control_record", ...args, + request_id: record.request_id, phase: "unknown", run_id: result.run_id })); + } + if (result.platform_run_id && record.workflow_run_id) { + ({ record } = await runtimeInstanceCommand(env, { action: "runtime_control_capture", ...args, + request_id: record.request_id, run_id: record.workflow_run_id, platform_run_id: result.platform_run_id, reason: result.reason })); + } + if (["applied", "partial", "protected", "rejected"].includes(result.status) && record.workflow_run_id) { + ({ record } = await runtimeInstanceCommand(env, { action: "runtime_control_resolve", ...args, + request_id: record.request_id, phase: result.status, run_id: record.workflow_run_id, reason: result.reason })); + } + } + let details = {}; + try { details = JSON.parse(record?.result_json || "{}"); } catch { /* retain unknown */ } + if (record && ["accepted", "unknown", "partial"].includes(record.phase) && details.check_id && !details.check_complete) { + const checked = await readRuntimeControlReceipt({ repository: env.RUNTIME_SETTINGS_REPO || DEFAULT_REPOSITORY, + token: env.RUNTIME_SETTINGS_DISPATCH_TOKEN, requestId: record.request_id, operation: record.action, + checkId: details.check_id, runId: details.check_run_id, requestedAt: details.check_requested_at }); + if (checked.run_id) { + ({ record } = await runtimeInstanceCommand(env, { action: "runtime_control_record_check", ...args, + request_id: record.request_id, check_id: details.check_id, run_id: checked.run_id, complete: checked.complete === true })); + } + if (["applied", "partial", "protected", "rejected"].includes(checked.status) && checked.complete + && checked.platform_run_id === details.platform_run_id) { + ({ record } = await runtimeInstanceCommand(env, { action: "runtime_control_resolve", ...args, + request_id: record.request_id, phase: checked.status, run_id: record.workflow_run_id, + check_id: details.check_id, check_run_id: checked.run_id, reason: checked.reason })); + } + } + return presentRuntimeControl(record); +} + +export function runtimeControlStopRequest(env, platform, targetName, source) { + const item = source.item; + const inputs = { ...item, platform, target_name: targetName }; + const github = { repository: platformRepositories(env)[platform], variable_scope: resolvedVariableScope(item.variable_scope, inputs) }; + if (github.variable_scope === "environment") github.environment = item.github_environment; + return { target_id: `${platform}/${targetName}`, github, runtime_target: { + platform_id: platform, deployment_selector: source.identity.deployment_selector, + account_selector: source.identity.account_selector.split(',').map(value => value.trim()).filter(Boolean), + account_scope: source.identity.account_scope, service_name: source.identity.service_name, + } }; +} + +export async function dispatchPlatformControlReadback(env, raw) { + if (raw.confirm !== "READ_ONLY" || !["ibkr", "schwab"].includes(raw.platform) + || Object.keys(raw).some(key => !["platform", "target_name", "operation", "confirm"].includes(key))) throw new HttpError("runtime_control_request_invalid", 400); + const targetName = cleanSlug(raw.target_name, "target_name"); + const source = await runtimeControlSource(env, raw.platform, targetName); + const args = { platform: raw.platform, key: source.item.key }; + // Reconcile the original QRS run first. The platform run ID can only come + // from its validated safe artifact and never from the client. + await readPlatformRuntimeControl(env, raw.platform, targetName); + let { record } = await runtimeInstanceCommand(env, { action: "runtime_control_read", ...args }); + if (!record || !["accepted", "unknown", "partial"].includes(record.phase)) return json(presentRuntimeControl(record)); + const checkId = crypto.randomUUID(); + const begun = await runtimeInstanceCommand(env, { action: "runtime_control_begin_check", ...args, request_id: record.request_id, check_id: checkId }); + if (begun.state !== "reserved") return json(presentRuntimeControl(begun.record)); + record = begun.record; + const details = JSON.parse(record.result_json || "{}"); + let runId = null, rejected = false; + try { + const response = await fetchWithTimeout(`https://api.github.com/repos/${env.RUNTIME_SETTINGS_REPO || DEFAULT_REPOSITORY}/actions/workflows/manual-runtime-stop.yml/dispatches`, { + method: "POST", headers: githubHeaders(env.RUNTIME_SETTINGS_DISPATCH_TOKEN), body: JSON.stringify({ ref: "main", return_run_details: true, + inputs: { stop_request: JSON.stringify(runtimeControlStopRequest(env, raw.platform, targetName, source)), apply: "false", confirm: "READ_ONLY", apply_hk_stop: "false", + runtime_control_mode: "readback", runtime_control_operation: record.action, runtime_control_request_id: record.request_id, + runtime_control_check_id: checkId, runtime_control_platform_run_id: details.platform_run_id } }), + }); + if (response.status === 200) { + const body = await response.json(); + if (Number.isSafeInteger(body.workflow_run_id) && body.workflow_run_id > 0) runId = String(body.workflow_run_id); + } else if (response.status >= 400 && response.status < 500 && response.status !== 429) rejected = true; + } catch { /* Readback outcome may be unknown; next explicit refresh reads the same check. */ } + if (runId || rejected) ({ record } = await runtimeInstanceCommand(env, { action: "runtime_control_record_check", ...args, + request_id: record.request_id, check_id: checkId, run_id: runId, complete: rejected, reason: rejected ? "readback_dispatch_rejected" : null })); + return json(presentRuntimeControl(record)); +} + +export async function dispatchPlatformRuntimeControl(env, raw) { + if (raw.confirm !== "CONTROL_ONLY" || !["ibkr", "schwab"].includes(raw.platform) + || !["enable", "disable"].includes(raw.operation) + || Object.keys(raw).some(key => !["platform", "target_name", "operation", "request_id", "confirm"].includes(key))) throw new HttpError("runtime_control_request_invalid", 400); + const targetName = cleanSlug(raw.target_name, "target_name"); + const requestId = raw.request_id || crypto.randomUUID(); + if (!HK_STOP_REQUEST_ID.test(requestId)) throw new HttpError("runtime_control_request_invalid", 400); + const source = await runtimeControlSource(env, raw.platform, targetName); + const args = { platform: raw.platform, key: source.item.key }; + const claimed = await runtimeInstanceCommand(env, { action: "runtime_control_claim", ...args, identity: source.identity, + source_revision: source.revision, request_id: requestId, operation: raw.operation }); + if (claimed.state !== "reserved") { + const body = presentRuntimeControl(claimed.record); + return json(claimed.state === "blocked" ? { ...body, ok: false, error: "runtime_control_pending" } : body, claimed.state === "blocked" ? 409 : 200); + } + const stopRequest = runtimeControlStopRequest(env, raw.platform, targetName, source); + let phase = "unknown", runId = null; + try { + const response = await fetchWithTimeout(`https://api.github.com/repos/${env.RUNTIME_SETTINGS_REPO || DEFAULT_REPOSITORY}/actions/workflows/manual-runtime-stop.yml/dispatches`, { + method: "POST", headers: githubHeaders(env.RUNTIME_SETTINGS_DISPATCH_TOKEN), body: JSON.stringify({ ref: "main", return_run_details: true, + inputs: { stop_request: JSON.stringify(stopRequest), apply: "true", confirm: "CONTROL_ONLY", apply_hk_stop: "false", + runtime_control_operation: raw.operation, runtime_control_request_id: requestId } }), + }); + if (response.status === 200) { + const body = await response.json(); + if (Number.isSafeInteger(body.workflow_run_id) && body.workflow_run_id > 0) { phase = "accepted"; runId = String(body.workflow_run_id); } + } else if (response.status >= 400 && response.status < 500 && response.status !== 429) phase = "rejected"; + } catch { /* Outcome may already have occurred; never resend. */ } + try { + const recorded = await runtimeInstanceCommand(env, { action: "runtime_control_record", ...args, request_id: requestId, phase, run_id: runId }); + return json(presentRuntimeControl(recorded.record), recorded.record?.phase === "rejected" ? 502 : 200); + } catch { return json({ ...presentRuntimeControl(claimed.record), phase: "unknown" }); } +} + +export async function readRuntimeStop(request, env, url) { + requireSameOrigin(request); + const session = await readSession(request, env); + if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); + const platform = url.searchParams.get("platform"); + const targetName = url.searchParams.get("target_name"); + if (["ibkr", "schwab"].includes(platform) && targetName) return json(await readPlatformRuntimeControl(env, platform, targetName)); + if (platform !== "longbridge" || targetName !== "hk") { + return json({ + ok: true, configured: false, platform_applied: false, request_succeeded: false, + phase: null, runtime_observation: { status: "unknown", request_succeeded: false }, + }); + } + if (!hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instances_not_bound", 503); + const stored = await runtimeInstanceCommand(env, { action: "hk_stop_read" }); + const observation = await hkStopObservation(env, stored.record); + const repository = env.RUNTIME_SETTINGS_REPO || DEFAULT_REPOSITORY; + return json(presentHkStop(stored.record, observation, repository)); +} + +export async function dispatchHkRuntimeStop(env, raw) { + if (!hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instances_not_bound", 503); + const requestId = raw.request_id ? String(raw.request_id).trim().toLowerCase() : crypto.randomUUID(); + if (!HK_STOP_REQUEST_ID.test(requestId)) throw new HttpError("invalid_hk_stop_request_id", 400); + const source = await loadHkStopAccount(env); + const identity = source.identity; + const identityJson = canonicalResearchTaskJson(identity); + const runtimeJson = canonicalResearchTaskJson({ + platform_id: identity.platform_id, + deployment_selector: identity.deployment_selector, + account_selector: identity.account_selector, + account_scope: identity.account_scope, + service_name: identity.service_name, + }); + const claimed = await runtimeInstanceCommand(env, { + action: "hk_stop_claim", + request_id: requestId, + action_name: "stop", + source_revision: source.revision, + identity: source.identity, + source_identity_sha256: await sha256Hex(identityJson), + runtime_identity_sha256: await sha256Hex(runtimeJson), + }); + const repository = env.RUNTIME_SETTINGS_REPO || DEFAULT_REPOSITORY; + if (claimed.state !== "reserved") { + const observation = await hkStopObservation(env, claimed.record); + const body = presentHkStop({ ...claimed.record, reused: true }, observation, repository); + if (claimed.state === "blocked") return json({ ...body, ok: false, error: "hk_stop_request_pending" }, 409); + return json(body); + } + const stored = claimed.record; + const stopRequest = { + target_id: HK_STOP_TARGET_ID, + github: { + repository: "QuantStrategyLab/LongBridgePlatform", + variable_scope: "environment", + environment: identity.github_environment, + }, + runtime_target: { + platform_id: "longbridge", + deployment_selector: identity.deployment_selector, + account_selector: identity.account_selector, + account_scope: identity.account_scope, + service_name: identity.service_name, + }, + correlation: { + request_id: stored.request_id, + source_revision: stored.source_revision, + source_identity_sha256: stored.source_identity_sha256, + }, + }; + let outcome = "unknown"; + try { + const response = await fetchWithTimeout(`https://api.github.com/repos/${repository}/actions/workflows/manual-runtime-stop.yml/dispatches`, { + method: "POST", + headers: githubHeaders(env.RUNTIME_SETTINGS_DISPATCH_TOKEN), + body: JSON.stringify({ + ref: env.RUNTIME_SETTINGS_REF || "main", + inputs: { + stop_request: JSON.stringify(stopRequest), + apply: "true", + confirm: "STOP_ONLY", + apply_hk_stop: "true", + }, + }), + }); + if (response.status === 204) outcome = "accepted"; + else if (response.status >= 400 && response.status < 500) outcome = "rejected"; + } catch { + outcome = "unknown"; + } + try { + const recorded = await runtimeInstanceCommand(env, { + action: "hk_stop_record", + request_id: claimed.record.request_id, + phase: outcome, + }); + const observation = await hkStopObservation(env, recorded.record); + return json(presentHkStop(recorded.record, observation, repository)); + } catch { + const observation = { status: "unknown", request_succeeded: false }; + return json(presentHkStop({ ...claimed.record, phase: "reserved", dispatch_result: null, persisted: false }, observation, repository)); + } +} + +export async function dispatchRuntimeStop(request, env) { + requireEnv(env, "RUNTIME_SETTINGS_DISPATCH_TOKEN"); + 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); + const raw = await request.json(); + if (raw && !Array.isArray(raw) && typeof raw === "object" && ["ibkr", "schwab"].includes(raw.platform) && raw.operation === "inspect") + return await dispatchPlatformControlReadback(env, raw); + if (raw && !Array.isArray(raw) && typeof raw === "object" && ["ibkr", "schwab"].includes(raw.platform) && raw.operation) + return await dispatchPlatformRuntimeControl(env, raw); + const hkStop = raw && !Array.isArray(raw) && raw.platform === "longbridge" && raw.target_name === "hk"; + const allowedKeys = hkStop ? ["platform", "target_name", "confirm", "request_id"] : ["platform", "target_name", "confirm"]; + if (!raw || Array.isArray(raw) || typeof raw !== "object" + || Object.keys(raw).some((key) => !allowedKeys.includes(key)) + || raw.confirm !== "STOP_ONLY") { + throw new HttpError("stop accepts only platform, target_name and STOP_ONLY confirmation", 400); + } + const platform = cleanChoice(raw.platform, SUPPORTED_PLATFORMS, "platform"); + const targetName = cleanSlug(raw.target_name, "target_name"); + if (platform === "longbridge" && targetName === "hk") return await dispatchHkRuntimeStop(env, raw); + const accountConfig = await loadAccountOptionsConfig(env); + const matches = (accountConfig.options?.[platform] || []).filter((item) => item.target_name === targetName); + if (matches.length !== 1) throw new HttpError("stop requires one configured target", 400); + // Derive routing only from protected account configuration, not form edits. + // The workflow independently checks this identity against current variables. + const inputs = { ...matches[0], platform, target_name: targetName }; + inputs.variable_scope = resolvedVariableScope(inputs.variable_scope, inputs); + for (const field of ["deployment_selector", "account_scope", "account_selector", "service_name", "github_environment"]) { + inputs[field] = inputs[field] || defaultInputValue(field, inputs); + } + const identity = { + platform_id: platform, + deployment_selector: inputs.deployment_selector, + account_selector: String(inputs.account_selector || "").split(",").map((item) => item.trim()).filter(Boolean), + account_scope: inputs.account_scope, + service_name: inputs.service_name, + }; + if (!identity.deployment_selector || !identity.account_selector.length || !identity.account_scope || !identity.service_name) { + throw new HttpError("stop target identity is incomplete", 400); + } + const github = { repository: platformRepositories(env)[platform], variable_scope: inputs.variable_scope }; + if (github.variable_scope === "environment") github.environment = inputs.github_environment; + const stopRequest = { target_id: `${platform}/${targetName}`, github, runtime_target: identity }; + const repository = env.RUNTIME_SETTINGS_REPO || DEFAULT_REPOSITORY; + const workflow = "manual-runtime-stop.yml"; + try { + const response = await fetchWithTimeout(`https://api.github.com/repos/${repository}/actions/workflows/${workflow}/dispatches`, { + method: "POST", + headers: githubHeaders(env.RUNTIME_SETTINGS_DISPATCH_TOKEN), + body: JSON.stringify({ + ref: env.RUNTIME_SETTINGS_REF || "main", + inputs: { stop_request: JSON.stringify(stopRequest), apply: "true", confirm: "STOP_ONLY" }, + }), + }); + if (!response.ok) throw new Error("dispatch failed"); + } catch { + return json({ ok: false, error: "stop dispatch unverified; check the existing run before retrying" }, 502); + } + return json({ + ok: true, configured: false, platform_applied: false, + actions_url: `https://github.com/${repository}/actions/workflows/${workflow}`, + }); +} + +export async function binanceResumeTarget(values, option) { + const target = parseJsonObject(values.RUNTIME_TARGET_JSON); + const continuity = target?.live_continuity; + if (values.RUNTIME_TARGET_ENABLED !== "false" || values.BINANCE_RECOVERY_CONTROL_ENABLED !== "true" + || !["", "false"].includes(values.BINANCE_DRY_RUN || "") + || !["", "crypto_live_pool_rotation"].includes(values.STRATEGY_PROFILE || "") + || values.CLOUD_RUN_SERVICE_TARGETS_JSON + || target?.platform_id !== "binance" || target.strategy_profile !== "crypto_live_pool_rotation" + || target.execution_mode !== "live" || target.dry_run_only !== false + || continuity?.baseline_kind !== "legacy_authorized" + || !["RECONCILE_ONLY", "ACTIVE_LKG"].includes(continuity?.state) + || typeof continuity.baseline_id !== "string" || !continuity.baseline_id.trim() + || typeof continuity.captured_at !== "string" || !continuity.captured_at.trim() + || !/^[a-f0-9]{64}$/.test(continuity.baseline_target_sha256 || "")) return null; + const inputs = { ...option, platform: "binance" }; + const identity = { platform_id: "binance" }; + for (const field of ["deployment_selector", "account_selector", "account_scope", "service_name"]) { + const value = inputs[field] || defaultInputValue(field, inputs); + identity[field] = field === "account_selector" + ? String(value || "").split(",").map((item) => item.trim()).filter(Boolean) : value; + if (!value || JSON.stringify(identity[field]) !== JSON.stringify(target[field])) return null; + } + const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(values.RUNTIME_TARGET_JSON)); + return { + runtime_target: identity, + runtime_target_sha256: [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join(""), + }; +} + +export async function dispatchBinanceResume(request, env) { + requireEnv(env, "RUNTIME_SETTINGS_DISPATCH_TOKEN"); + 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); + const raw = await request.json(); + if (!raw || Array.isArray(raw) || typeof raw !== "object" + || Object.keys(raw).some((key) => !["platform", "target_name", "runtime_target_sha256", "confirm"].includes(key)) + || raw.platform !== "binance" || raw.confirm !== "RESUME_EXISTING" + || typeof raw.runtime_target_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(raw.runtime_target_sha256)) { + throw new HttpError("resume accepts only the existing Binance target and RESUME_EXISTING confirmation", 400); + } + const config = await loadAccountOptionsConfig(env); + const matches = (config.options?.binance || []).filter((item) => item.target_name === raw.target_name); + const repository = platformRepositories(env).binance; + if (matches.length !== 1 || repository !== "QuantStrategyLab/BinancePlatform" + || resolvedVariableScope(matches[0].variable_scope, { ...matches[0], platform: "binance" }) !== "repository") { + throw new HttpError("resume requires one configured Binance repository target", 400); + } + let target; + try { + const variables = await fetchGithubVariables(env.RUNTIME_SETTINGS_DISPATCH_TOKEN, repository, "repository", ""); + target = await binanceResumeTarget(Object.fromEntries(variables), matches[0]); + } catch { + return json({ ok: false, error: "resume source unavailable; no request submitted" }, 502); + } + if (!target || target.runtime_target_sha256 !== raw.runtime_target_sha256) { + return json({ ok: false, error: "existing recovery configuration changed or unavailable; refresh before continuing" }, 409); + } + // Only the external switch is requested. The platform's private ACTIVE_LKG + // validator remains mandatory; a console confirmation cannot activate it. + const resumeRequest = { target_id: `binance/${matches[0].target_name}`, + github: { repository, variable_scope: "repository" }, ...target }; + const settingsRepository = env.RUNTIME_SETTINGS_REPO || DEFAULT_REPOSITORY; + const workflow = "manual-binance-resume.yml"; + try { + const response = await fetchWithTimeout(`https://api.github.com/repos/${settingsRepository}/actions/workflows/${workflow}/dispatches`, { + method: "POST", headers: githubHeaders(env.RUNTIME_SETTINGS_DISPATCH_TOKEN), + body: JSON.stringify({ ref: "main", inputs: { resume_request: JSON.stringify(resumeRequest), apply: "true", confirm: "RESUME_EXISTING" } }), + }); + if (!response.ok) throw new Error("dispatch failed"); + } catch { + return json({ ok: false, error: "resume dispatch unverified; check the existing run before retrying" }, 502); + } + return json({ ok: true, configured: false, platform_applied: false, + actions_url: `https://github.com/${settingsRepository}/actions/workflows/${workflow}` }); +} + +export async function dispatchSwitch(request, env) { + requireEnv(env, "RUNTIME_SETTINGS_DISPATCH_TOKEN"); + requireSameOrigin(request, { requireOrigin: true }); + const session = await readSession(request, env); + if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); + + 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)); + if (!SWITCH_ACTIVATION_CONNECTED) { + return json({ + ok: false, + error: "账户应用尚未接通,系统未提交变更", + workflow_dispatched: false, + }, 409); + } + const repository = env.RUNTIME_SETTINGS_REPO || DEFAULT_REPOSITORY; + const workflow = env.RUNTIME_SETTINGS_WORKFLOW || DEFAULT_WORKFLOW; + const apiUrl = `https://api.github.com/repos/${repository}/actions/workflows/${workflow}/dispatches`; + const response = await fetchWithTimeout(apiUrl, { + method: "POST", + headers: githubHeaders(env.RUNTIME_SETTINGS_DISPATCH_TOKEN), + body: JSON.stringify({ + ref: env.RUNTIME_SETTINGS_REF || "main", + inputs, + }), + }); + + if (!response.ok) { + const text = await response.text(); + return json({ ok: false, error: `GitHub dispatch failed: ${text.slice(0, 600)}` }, 502); + } + + return json({ + ok: true, + repository, + workflow, + actions_url: `https://github.com/${repository}/actions/workflows/${workflow}`, + account_options_sync: { + synced: false, + deferred: true, + reason: "workflow_success_required", + }, + inputs, + }); +} + +export async function syncDefaultStrategyForAccount(env, accountOptions, inputs, session) { + if (hasRuntimeInstanceStore(env)) { + try { + await runtimeInstanceCommand(env, { action: "sync_defaults", actor: session?.login || "github-actions", inputs }); + return { synced: true, audit_logged: true }; + } catch (error) { + return { synced: false, reason: error instanceof HttpError ? error.message : "runtime_instances_unavailable" }; + } + } + if (!hasConfigStore(env)) return { synced: false, reason: "kv_not_bound" }; + try { + const { options, changed } = updateAccountOptionsDefaultStrategy(accountOptions, inputs); + let auditLogged = false; + if (changed) { + await writeConfigJson(env, ACCOUNT_OPTIONS_KEY, validateAccountOptionsSchemaOrThrow(options)); + try { + await appendAuditLog(env, { + ts: new Date().toISOString(), + login: session?.login || "", + action: "sync_default_strategy", + platform: inputs.platform, + target_name: inputs.target_name, + strategy_profile: inputs.strategy_profile, + }); + auditLogged = true; + } catch { + auditLogged = false; + } + } + return { synced: true, changed, audit_logged: auditLogged }; + } catch (error) { + return { synced: false, error: error.message || "account option sync failed" }; + } +} + +export async function syncAccountDefaultResponse(request, env) { + requireInternalSyncToken(request, env); + let rawInput; + try { + rawInput = await request.json(); + } catch { + return json({ ok: false, error: "request body must be valid JSON" }, 400); + } + let inputs; + try { + inputs = normalizeSwitchInputs(rawInput); + } catch (error) { + if (isFrozenStrategyProfile({ profile: rawInput?.strategy_profile })) { + throw new HttpError("frozen strategy requires valid continuity input", 409); + } + throw error; + } + const accountConfig = await loadAccountOptionsConfig(env); + const strategyProfiles = await loadStrategyProfilesConfig(env); + const strategy = strategyProfiles.find((item) => sameStrategyProfile(item.profile, inputs.strategy_profile)); + if (!strategy) throw new Error(`strategy ${inputs.strategy_profile} is not configured`); + + let accountOptions = accountConfig.options; + let accountOption = configuredAccountForInputs(inputs, accountOptions); + let registeredLegacyContinuityAccount = false; + if (!accountOption) { + if (isFrozenStrategyProfile(strategy)) throw new HttpError(`strategy ${inputs.strategy_profile} is frozen for new selection`, 409); + if (hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instance_registration_requires_management", 409); + const registration = registerLegacyContinuityAccount(env, accountOptions, inputs, strategy); + accountOptions = registration.options; + accountOption = registration.account; + registeredLegacyContinuityAccount = registration.registered; + if (registeredLegacyContinuityAccount) { + await writeConfigJson(env, ACCOUNT_OPTIONS_KEY, validateAccountOptionsSchemaOrThrow(accountOptions)); + try { + await appendAuditLog(env, { + ts: new Date().toISOString(), + login: "github-actions", + action: "register_legacy_continuity_account", + platform: inputs.platform, + target_name: inputs.target_name, + strategy_profile: inputs.strategy_profile, + live_continuity_state: inputs.live_continuity_state, + }); + } catch { + // The routing registration is still valid if its non-critical audit + // append fails; callers receive the registration result below. + } + } + } + if (!accountOption) throw new Error("switch inputs do not match configured account options"); + if (isFrozenStrategyProfile(strategy) && !sameStrategyProfile(accountOption.default_strategy_profile, inputs.strategy_profile)) { + throw new HttpError(`strategy ${inputs.strategy_profile} requires an existing matching account default`, 409); + } + assertStrategyAllowedForAccount(inputs, accountOption, strategyProfiles); + const result = await syncDefaultStrategyForAccount(env, accountOptions, inputs, { + login: "github-actions", + }); + const kvSyncSkipped = result.reason === "kv_not_bound"; + const accountOptionsSync = kvSyncSkipped ? { ...result, skipped: true } : result; + return json( + { + ok: result.synced || kvSyncSkipped, + account_options_sync: accountOptionsSync, + legacy_continuity_account_registered: registeredLegacyContinuityAccount, + }, + result.synced || kvSyncSkipped ? 200 : 500, + ); +} + +export function requireInternalSyncToken(request, env) { + const expected = env.STRATEGY_SWITCH_SYNC_TOKEN || env.RUNTIME_SETTINGS_DISPATCH_TOKEN; + if (!expected) throw new HttpError("internal 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("internal sync token is invalid", 401); +} + +export async function syncStrategyProfilesResponse(request, env) { + requireInternalSyncToken(request, env); + const result = await syncStrategyProfilesConfig(env, { login: "github-actions" }); + const kvSyncSkipped = result.reason === "kv_not_bound"; + const strategyProfilesSync = kvSyncSkipped ? { ...result, skipped: true } : result; + return json( + { + ok: result.synced || kvSyncSkipped, + strategy_profiles_sync: strategyProfilesSync, + strategy_profiles_count: result.count, + }, + result.synced || kvSyncSkipped ? 200 : 500, + ); +} + +export async function syncStrategyHealthResponse(request, env) { + requireDedicatedStrategyHealthSyncToken(request, env); + if (!hasConfigStore(env)) { + return json({ ok: false, error: "strategy health KV is not configured" }, 503); + } + + let raw; + try { + raw = await readBoundedJson(request, STRATEGY_HEALTH_MAX_BODY_BYTES); + } catch (error) { + return json({ ok: false, error: error.message || "invalid strategy health payload" }, error.status || 400); + } + + let snapshot; + try { + snapshot = normalizeStrategyHealthSnapshot(raw, "strategy health snapshot"); + } catch (error) { + return json({ ok: false, error: error.message || "invalid strategy health payload" }, 400); + } + + await writeConfigJson(env, STRATEGY_HEALTH_SNAPSHOT_KEY, snapshot); + try { + await appendAuditLog(env, { + ts: new Date().toISOString(), + login: "strategy-health-sync", + action: "sync_strategy_health", + schema_version: snapshot.schema_version, + strategy_count: snapshot.summary.strategy_count, + data_status: snapshot.data_status, + }); + } catch { + // Snapshot delivery remains successful; audit data must never expose the payload. + } + return json({ + ok: true, + schema_version: snapshot.schema_version, + strategy_count: snapshot.summary.strategy_count, + generated_at: snapshot.generated_at, + }); +} + +export async function strategyHealthResponse(request, env) { + const session = await readSession(request, env); + if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); + if (!hasConfigStore(env)) return json(emptyStrategyHealthPayload("snapshot_unavailable")); + + let snapshot; + try { + const stored = await readConfigJson(env, STRATEGY_HEALTH_SNAPSHOT_KEY); + if (!stored) return json(emptyStrategyHealthPayload("snapshot_unavailable")); + snapshot = normalizeStrategyHealthSnapshot(stored, STRATEGY_HEALTH_SNAPSHOT_KEY); + } catch { + return json(emptyStrategyHealthPayload("snapshot_invalid")); + } + + const ttlSeconds = strategyHealthStaleTtlSeconds(env); + const freshnessTimestamps = [snapshot.generated_at, snapshot.computed_at] + .filter(Boolean) + .map((value) => Date.parse(value)); + const freshnessAt = freshnessTimestamps.length ? Math.min(...freshnessTimestamps) : Number.NaN; + const now = Date.now(); + const futureBeyondClockSkew = Number.isFinite(freshnessAt) && freshnessAt > now + 5 * 60 * 1000; + const ageSeconds = futureBeyondClockSkew + ? Number.POSITIVE_INFINITY + : Number.isFinite(freshnessAt) + ? Math.max(0, (now - freshnessAt) / 1000) + : Number.POSITIVE_INFINITY; + if (["ready", "partial"].includes(snapshot.data_status) && ageSeconds > ttlSeconds) { + snapshot.data_status = "stale"; + } + return json(snapshot); +} + +export async function humanDecisionRequestKey({ requestId, kind, actor, subjectId, materialKey, action, target }) { + if (requestId !== undefined && requestId !== null && requestId !== "") { + const text = String(requestId).trim(); + if (!/^[A-Za-z0-9._:-]{1,128}$/.test(text)) throw new HttpError("invalid request_id", 400); + return `id:${kind}:${text}`; + } + const digest = await calculateOwnerDecisionSha256({ + actor, kind, subject_id: subjectId, material_key: materialKey, action, target: target || null, + }); + return `fp:${digest}`; +} + +export async function researchPromotionMaterialKey(ticket) { + return calculateOwnerDecisionSha256({ + ticket_id: ticket.ticket_id, + strategy_profile: ticket.strategy_profile, + domain: ticket.domain, + proposed_params: ticket.proposed_params, + shadow_passed: ticket.shadow_passed, + shadow_evidence_kind: ticket.shadow_evidence_kind, + }); +} + +// The legacy six-field material key remains the durable terminal index. This +// independent versioned snapshot binds all saved producer review content without +// rekeying historical records or hashing post-decision/observation-only fields. +export function researchPromotionReviewMaterial(ticket) { + return { + schema_version: "qsl_promotion_review_material.v2", + ticket_id: ticket.ticket_id, strategy_profile: ticket.strategy_profile, domain: ticket.domain, + proposed_params: ticket.proposed_params, shadow_passed: ticket.shadow_passed, + shadow_evidence_kind: ticket.shadow_evidence_kind, + drift_status: ticket.drift_status, drift_score: ticket.drift_score, + budget: ticket.budget, search_iterations: ticket.search_iterations, + suggested_risk_profile: ticket.suggested_risk_profile, + notification_subject: ticket.notification_subject, notification_body: ticket.notification_body, + notes: ticket.notes, research_summary: ticket.research_summary || null, + }; +} +export const RESEARCH_PROMOTION_REVIEW_INVALID = "research_promotion_review_material_invalid"; +export class ResearchPromotionReviewMaterialError extends HttpError { + constructor() { super(RESEARCH_PROMOTION_REVIEW_INVALID, 400); } +} +export async function researchPromotionReviewBinding(ticket) { + let canonical; + try { + canonical = canonicalResearchTaskJson(researchPromotionReviewMaterial(ticket)); + } catch (error) { + // Only canonical data validation is recoverable per ticket. Hash/storage + // failures must not be relabelled as invalid material or a healthy queue. + if (error?.message === "research task must use finite JSON values" + || error?.message === "research task must use JSON values") throw new ResearchPromotionReviewMaterialError(); + throw error; + } + return { schema_version: "qsl_promotion_review_binding.v2", review_sha256: await sha256Hex(canonical) }; +} +export function promotionReviewBindingsMatch(left, right) { + return left?.schema_version === "qsl_promotion_review_binding.v2" && right?.schema_version === left.schema_version + && /^[0-9a-f]{64}$/.test(left.review_sha256) && left.review_sha256 === right.review_sha256; +} +export function promotionMaterialReviewBinding(row) { + try { + const binding = JSON.parse(row?.summary_json || "{}").review_binding; + return promotionReviewBindingsMatch(binding, binding) ? { schema_version: "qsl_promotion_review_binding.v2", review_sha256: binding.review_sha256 } : null; + } catch { return null; } +} + +export async function recoveryDecisionMaterialKey(recovery) { + if (recovery.mode === "initial_enrollment") return calculateOwnerDecisionSha256({ + mode: recovery.mode, recovery_id: recovery.recovery_id, + candidate_sha256: recovery.candidate?.candidate_sha256 || recovery.candidate_sha256, + facts_sha256: recovery.candidate?.facts_sha256 || recovery.facts_sha256, + }); + return calculateOwnerDecisionSha256({ + recovery_id: recovery.recovery_id, + candidate_sha256: recovery.candidate_sha256, + dual_review_binding_sha256: recovery.dual_review.evidence_binding_sha256, + }); +} + +export async function mirrorConfigJson(env, key, value) { + try { + await writeConfigJson(env, key, value); + return true; + } catch { + return false; + } +} + +export async function readLegacyConfig(env, key) { + try { + return { value: await readConfigJson(env, key), blocked: false, unreadable: false }; + } catch (error) { + if (String(error?.message || "").includes("must be valid JSON")) return { value: null, blocked: true, unreadable: false }; + return { value: null, blocked: false, unreadable: true }; + } +} + +export function humanDecisionTimestamp(value) { + return String(value || new Date().toISOString()).replace(/\.\d{3}Z$/, "Z"); +} + +// Actionable material expires when the evidence that produced it would itself be +// stale. A missing or future-dated anchor yields null, which callers treat as +// "not actionable" rather than inventing authority. +export function controlPlaneDecisionExpiryAt(snapshot, ttlSeconds, now = Date.now()) { + const timestamps = [snapshot?.generated_at, snapshot?.computed_at] + .filter(Boolean) + .map((value) => Date.parse(value)) + .filter((value) => Number.isFinite(value)); + if (!timestamps.length) return null; + const observedAt = Math.min(...timestamps); + if (observedAt > now + 5 * 60 * 1000) return null; + return observedAt + ttlSeconds * 1000; +} + +export function reconciliationRecoveryDecisionExpiryAt(recovery, source, ttlSeconds, now = Date.now()) { + const sourceExpiry = controlPlaneDecisionExpiryAt(source, ttlSeconds, now); + if (sourceExpiry == null) return null; + const observedAt = Date.parse(recovery.mode === "initial_enrollment" + ? recovery.candidate?.last_observed_at + : recovery.last_observed_at); + if (!Number.isFinite(observedAt) || observedAt > now + 5 * 60 * 1000) return null; + return Math.min(sourceExpiry, observedAt + ttlSeconds * 1000); +} + +export async function publishHumanOwnerMaterials(env, snapshot, sourceId) { + const ownerCandidates = (snapshot.candidates || []).filter(isOwnerDecisionCandidate); + if (!ownerCandidates.length && !hasRuntimeInstanceStore(env)) return { ok: true, skipped: true }; + if (!hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instances_not_bound", 503); + const expiresAtMs = controlPlaneDecisionExpiryAt(snapshot, controlPlaneStaleTtlSeconds(env)); + const grouped = new Map(); + for (const candidate of ownerCandidates) { + const list = grouped.get(candidate.candidate_id) || []; + list.push(candidate); + grouped.set(candidate.candidate_id, list); + } + const materials = []; + const duplicateSubjectIds = []; + for (const [subjectId, list] of grouped) { + if (list.length > 1) duplicateSubjectIds.push(subjectId); + const candidate = list[0]; + const materialKey = await ownerDecisionCandidateEvidenceSha256(candidate); + const legacyRead = await readLegacyConfig(env, ownerDecisionCurrentKey(subjectId)); + if (legacyRead.unreadable) throw new HttpError("human_decision_legacy_unreadable", 503); + let legacy = null; + if (legacyRead.blocked) legacy = { blocked: true }; + else if (legacyRead.value) { + try { + const intent = await normalizeOwnerDecisionIntent(legacyRead.value, ownerDecisionCurrentKey(subjectId)); + if (intent.candidate_id !== subjectId || intent.candidate_evidence_sha256 !== materialKey) legacy = { blocked: true }; + else { + legacy = { + record: { + action: intent.decision, + actor: intent.decided_by, + decided_at: intent.decided_at, + material_key: materialKey, + target_json: canonicalResearchTaskJson({ decision: intent.decision }), + payload: intent, + }, + }; + } + } catch { + legacy = { blocked: true }; + } + } + materials.push({ + subject_id: subjectId, + material_key: materialKey, + summary_json: JSON.stringify({ candidate_id: subjectId }), + eligible: list.length === 1 && candidate.freshness?.status === "fresh", + expires_at_ms: expiresAtMs, + legacy, + }); + } + return runtimeInstanceCommand(env, { + action: "owner_sync_materials", + actor: "control-plane-sync", + source_id: sourceId, + updated_at: humanDecisionTimestamp(), + now_ms: Date.now(), + replace_source: true, + materials, + duplicate_subject_ids: duplicateSubjectIds, + }); +} + +export function recoveryMaterialEligible(recovery, sourceFreshness, evidenceFreshness) { + if (recovery.mode === "initial_enrollment") return sourceFreshness?.data_status === "ready" && evidenceFreshness?.data_status === "ready" + && Object.values(recovery.candidate.coverage).every(status => status === "complete"); + return sourceFreshness?.data_status === "ready" + && evidenceFreshness?.data_status === "ready" + && recovery.readiness === "awaiting_human_confirmation" + && recovery.reconciliation_state === "RECONCILE_ONLY" + && !recovery.blocker_codes.length + && recovery.dual_review.evidence_binding_sha256 === recovery.candidate_sha256 + && recovery.evidence_sample_count >= 1; +} + +export async function publishHumanRecoveryMaterials(env, source) { + if (!hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instances_not_bound", 503); + const ttlSeconds = reconciliationRecoveryStaleTtlSeconds(env); + const now = Date.now(); + const sourceFreshness = controlPlaneSnapshotFreshness(source, ttlSeconds, now); + const grouped = new Map(); + for (const recovery of source.recoveries || []) { + const list = grouped.get(recovery.recovery_id) || []; + list.push(recovery); + grouped.set(recovery.recovery_id, list); + } + const materials = []; + const duplicateSubjectIds = []; + for (const [subjectId, list] of grouped) { + if (list.length > 1) duplicateSubjectIds.push(subjectId); + const recovery = list[0]; + const evidenceFreshness = reconciliationRecoveryEvidenceFreshness(recovery, sourceFreshness, ttlSeconds, now); + const materialKey = await recoveryDecisionMaterialKey(recovery); + const legacyRead = recovery.mode === "initial_enrollment" ? { unreadable: false, blocked: false, value: null } + : await readLegacyConfig(env, reconciliationRecoveryConfirmationCurrentKey(subjectId)); + if (legacyRead.unreadable) throw new HttpError("human_decision_legacy_unreadable", 503); + let legacy = null; + if (legacyRead.blocked) legacy = { blocked: true }; + else if (legacyRead.value) { + try { + const confirmation = await normalizeReconciliationRecoveryConfirmation(legacyRead.value, reconciliationRecoveryConfirmationCurrentKey(subjectId)); + if (confirmation.recovery_id !== subjectId || confirmation.candidate_sha256 !== recovery.candidate_sha256 || confirmation.dual_review_binding_sha256 !== recovery.dual_review.evidence_binding_sha256) { + legacy = { blocked: true }; + } else { + legacy = { + record: { + action: "approve", + actor: confirmation.confirmed_by, + decided_at: confirmation.confirmed_at, + material_key: materialKey, + target_json: canonicalResearchTaskJson({ decision: "approve" }), + payload: confirmation, + }, + }; + } + } catch { + legacy = { blocked: true }; + } + } + materials.push({ + subject_id: subjectId, + material_key: materialKey, + summary_json: JSON.stringify({ recovery_id: subjectId }), + eligible: list.length === 1 && recoveryMaterialEligible(recovery, sourceFreshness, evidenceFreshness), + expires_at_ms: reconciliationRecoveryDecisionExpiryAt(recovery, source, ttlSeconds, now), + legacy, + }); + } + return runtimeInstanceCommand(env, { + action: "recovery_sync_materials", + actor: "reconciliation-recovery-source-sync", + source_id: source.source_id, + updated_at: humanDecisionTimestamp(), + now_ms: Date.now(), + replace_source: true, + materials, + duplicate_subject_ids: duplicateSubjectIds, + }); +} + +export async function publishHumanPromotionMaterial(env, ticket, existing) { + if (!hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instances_not_bound", 503); + const materialKey = await researchPromotionMaterialKey(ticket); + let legacy = null; + if (existing && (existing.state === "human_accepted" || existing.state === "human_rejected")) { + const existingKey = await researchPromotionMaterialKey(existing); + const actor = String(existing.human_decision_actor || "").trim(); + if (!actor || !existing.human_decided_at || existingKey !== materialKey) legacy = { blocked: true }; + else { + legacy = { + record: { + action: existing.state === "human_accepted" ? "accept" : "reject", + actor, + decided_at: existing.human_decided_at, + material_key: materialKey, + target_json: canonicalResearchTaskJson({ + selected_account: existing.selected_account || null, + confirmation: { + target_platform: existing.confirmation_target_platform, + execution_mode: existing.confirmation_execution_mode, + risk_profile: existing.confirmation_risk_profile, + }, + }), + payload: { ticket: existing, selected_account: existing.selected_account || null, proposed_params: existing.proposed_params }, + }, + }; + } + } + return runtimeInstanceCommand(env, { + action: "promotion_sync_materials", + actor: "research-promotion-ticket-sync", + source_id: "research-promotion", + updated_at: humanDecisionTimestamp(), + now_ms: Date.now(), + replace_source: false, + materials: [{ + subject_id: ticket.ticket_id, + material_key: materialKey, + summary_json: JSON.stringify({ + ticket_id: ticket.ticket_id, + strategy_profile: ticket.strategy_profile, + domain: ticket.domain, + review_binding: await researchPromotionReviewBinding(ticket), + }), + eligible: true, + expires_at_ms: Date.now() + controlPlaneStaleTtlSeconds(env) * 1000, + legacy, + }], + }); +} + +// A fixed projection of the durable terminal record, never a request echo or KV fallback. +export async function humanDecisionReceipt(record, expected) { + const invalid = () => { throw new HttpError("human_decision_receipt_unavailable", 503); }; + const text = (value, max) => typeof value === "string" && value.length > 0 && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value); + const actions = { owner: ["approve_limited_live_canary", "keep_parked"], recovery: ["approve", "reject"], promotion: ["accept", "reject"] }; + if (!record || !actions[record.kind]?.includes(record.action) || !text(record.subject_id, 256) + || !/^[0-9a-f]{64}$/.test(record.material_key) || !text(record.actor, 100) + || !text(record.decided_at, 40) || !Number.isFinite(Date.parse(record.decided_at)) + || record.kind !== expected.kind || record.subject_id !== expected.subject_id || record.material_key !== expected.material_sha256) invalid(); + let target; + try { target = JSON.parse(record.target_json); } catch { invalid(); } + let projection; + if (record.kind === "promotion") { + const account = target?.selected_account; + const confirmation = target?.confirmation; + if (account != null && (!SUPPORTED_PLATFORMS.includes(account.platform) || !text(account.key, 128))) invalid(); + if (confirmation != null && (!SUPPORTED_PLATFORMS.includes(confirmation.target_platform) + || !["live", "paper", "dry_run"].includes(confirmation.execution_mode) + || !RESEARCH_PROMOTION_RISK_PROFILES.includes(confirmation.risk_profile))) invalid(); + if (record.action === "accept" && (!account || !confirmation || account.platform !== confirmation.target_platform)) invalid(); + if (record.payload?.ticket?.live_authority_granted !== false || record.payload?.ticket?.human_decision !== record.action + || record.payload?.ticket?.ticket_id !== record.subject_id || record.payload.ticket.human_decided_at !== record.decided_at + || await researchPromotionMaterialKey(record.payload.ticket) !== record.material_key + || canonicalResearchTaskJson(record.payload.selected_account ?? null) !== canonicalResearchTaskJson(account ?? null) + || canonicalResearchTaskJson(record.payload.confirmation ?? null) !== canonicalResearchTaskJson(confirmation ?? null)) invalid(); + projection = { + selected_account: account ? { platform: account.platform, key: account.key } : null, + confirmation: confirmation ? { target_platform: confirmation.target_platform, execution_mode: confirmation.execution_mode, risk_profile: confirmation.risk_profile } : null, + }; + } else { + if (target?.decision !== record.action || record.payload?.no_order !== true || record.payload?.execution_authority_granted !== false) invalid(); + const payload = record.payload; + if (record.kind === "owner") { + if (payload.schema_version !== "qsl_owner_decision_intent.v1" || payload.candidate_id !== record.subject_id + || payload.candidate_evidence_sha256 !== record.material_key || payload.decision !== record.action + || payload.decided_by !== record.actor || payload.decided_at !== record.decided_at + || payload.decision_sha256 !== await calculateOwnerDecisionSha256(payload)) invalid(); + } else if (payload.mode === "initial_enrollment") { + const approval = record.action === "approve"; + if (payload.schema_version !== (approval ? "qsl_initial_enrollment_confirmation.v1" : "qsl_initial_enrollment_rejection.v1") + || payload.recovery_id !== record.subject_id || (approval ? payload.confirmed_by : payload.decided_by) !== record.actor + || (approval ? payload.confirmed_at : payload.decided_at) !== record.decided_at || (!approval && payload.decision !== "reject") + || (approval && payload.confirmation_sha256 !== await calculateReconciliationRecoveryConfirmationSha256(payload)) + || await recoveryDecisionMaterialKey(payload) !== record.material_key) invalid(); + } else { + const approval = record.action === "approve"; + if (payload.schema_version !== (approval ? "qsl_reconciliation_recovery_confirmation.v1" : "qsl_reconciliation_recovery_rejection.v1") + || payload.recovery_id !== record.subject_id || (approval ? payload.confirmed_by : payload.decided_by) !== record.actor + || (approval ? payload.confirmed_at : payload.decided_at) !== record.decided_at || (!approval && payload.decision !== "reject") + || await recoveryDecisionMaterialKey({ recovery_id: payload.recovery_id, candidate_sha256: payload.candidate_sha256, dual_review: { evidence_binding_sha256: payload.dual_review_binding_sha256 } }) !== record.material_key) invalid(); + } + projection = { decision: target.decision }; + } + // Extra stored target fields must not be silently dropped into an apparently matching receipt. + if (canonicalResearchTaskJson(target) !== canonicalResearchTaskJson(projection)) invalid(); + let reviewBinding = null; + if (record.kind === "promotion" && record.payload?.review_binding !== undefined) { + const material = record.payload.review_material; + if (material?.schema_version !== "qsl_promotion_review_material.v2" || material.ticket_id !== record.subject_id + || await researchPromotionMaterialKey(material) !== record.material_key + || !promotionReviewBindingsMatch(record.payload.review_binding, { schema_version: "qsl_promotion_review_binding.v2", review_sha256: await calculateOwnerDecisionSha256(material) })) invalid(); + reviewBinding = { schema_version: "qsl_promotion_review_binding.v2", review_sha256: record.payload.review_binding.review_sha256 }; + } + const receipt = { + schema_version: reviewBinding ? "qsl_human_decision_receipt.v2" : "qsl_human_decision_receipt.v1", + ...(reviewBinding ? { review_binding: reviewBinding } : {}), + kind: record.kind, subject_id: record.subject_id, material_sha256: record.material_key, + action: record.action, target: projection, decided_at: record.decided_at, decided_by: record.actor, + no_order: true, execution_authority_granted: false, + }; + return { ...receipt, receipt_sha256: await calculateOwnerDecisionSha256(receipt) }; +} + +export async function exactHumanDecisionResponse(request, env, session, kind) { + const params = new URL(request.url).searchParams; + if (!params.has("decision_subject_id") && !params.has("decision_material_sha256")) return null; + requireSameOrigin(request); + if (!session.admin) return json({ ok: false, error: "admin required" }, 403); + const subjectId = params.get("decision_subject_id"); + const material = params.get("decision_material_sha256"); + if (params.getAll("decision_subject_id").length !== 1 || params.getAll("decision_material_sha256").length !== 1 + || !subjectId || subjectId.length > 256 || /[\u0000-\u001f\u007f]/.test(subjectId) || !/^[0-9a-f]{64}$/.test(material || "")) { + return json({ ok: false, error: "human_decision_lookup_invalid" }, 400); + } + try { + if (!hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instances_not_bound", 503); + const result = await runtimeInstanceCommand(env, { + action: `${kind}_read`, actor: session.login, subject_id: subjectId, exact_material_key: material, now_ms: Date.now(), + }); + if (result.ok !== true || result.exact_material_key !== material || !Object.prototype.hasOwnProperty.call(result, "decision")) { + throw new HttpError("human_decision_receipt_unavailable", 503); + } + const receipt = result.decision === null ? null : await humanDecisionReceipt(result.decision, { kind, subject_id: subjectId, material_sha256: material }); + return json({ ok: true, decision_readback: { + schema_version: "qsl_human_decision_readback.v1", source: "durable_human_decision_record", + kind, subject_id: subjectId, requested_material_sha256: material, observed_at: new Date().toISOString(), + lookup_status: receipt ? "found" : "not_found", receipt, current_material: result.current_material, + } }); + } catch { + // Unreadable authority is never reported as a completed empty lookup. + return json({ ok: false, error: "human_decision_receipt_unavailable" }, 503); + } +} + +export async function readHumanDecisionAuthority(env, kind, subjectId) { + if (!hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instances_not_bound", 503); + return runtimeInstanceCommand(env, { action: `${kind}_read`, actor: `${kind}-decision-read`, subject_id: subjectId }); +} + +// A safe projection of durable material authority. It carries only digests and +// server-side expiry; raw provider payloads and credentials never enter a read model. +export function humanDecisionAuthorityView(authority) { + if (!authority || typeof authority !== "object") return null; + const materialKey = /^[0-9a-f]{64}$/.test(authority.material_key || "") ? authority.material_key : null; + const expiresAt = typeof authority.expires_at === "string" && Number.isFinite(Date.parse(authority.expires_at)) + ? authority.expires_at + : null; + return { + material_sha256: materialKey, + expires_at: expiresAt, + eligible: typeof authority.eligible === "boolean" ? authority.eligible : null, + blocked: authority.blocked === true, + unavailable: authority.migrated === false, + source: "durable_human_decision_material", + }; +} + +export function overlayPromotionDecision(ticket, authority) { + if (!authority || authority.migrated === false) return ticket; + const current = authority.eligible === true && authority.blocked !== true ? authority.decision : null; + if (current?.payload?.ticket && (current.action === "accept" || current.action === "reject")) { + return normalizeResearchPromotionTicket(current.payload.ticket, "human decision ticket"); + } + if (ticket.state !== "human_accepted" && ticket.state !== "human_rejected") return ticket; + return normalizeResearchPromotionTicket({ + ...ticket, + state: "awaiting_human", + human_decision: "", + human_decided_at: "", + confirmation_target_platform: "", + confirmation_execution_mode: "", + confirmation_risk_profile: "", + }, "human decision ticket"); +} + +export async function promotionTicketWithAuthority(env, ticket) { + const authority = await readHumanDecisionAuthority(env, "promotion", ticket.ticket_id); + return overlayPromotionDecision(ticket, authority); +} + +export async function syncControlPlaneResponse(request, env) { + requireDedicatedControlPlaneSyncToken(request, env); + if (!hasConfigStore(env)) { + return json({ ok: false, error: "control plane KV is not configured" }, 503); + } + + let raw; + try { + raw = await readBoundedJson(request, CONTROL_PLANE_MAX_BODY_BYTES); + } catch (error) { + return json({ ok: false, error: error.message || "invalid control plane payload" }, error.status || 400); + } + + let snapshot; + try { + snapshot = normalizeControlPlaneSnapshot(raw, "control plane snapshot"); + } catch (error) { + return json({ ok: false, error: error.message || "invalid control plane payload" }, 400); + } + try { + await publishHumanOwnerMaterials(env, snapshot, "control-plane-snapshot"); + } catch (error) { + return json({ ok: false, error: error.message || "owner decision material unavailable" }, error.status || 503); + } + + await writeConfigJson(env, CONTROL_PLANE_SNAPSHOT_KEY, snapshot); + try { + await appendAuditLog(env, { + ts: new Date().toISOString(), + login: "control-plane-sync", + action: "sync_control_plane", + schema_version: snapshot.schema_version, + candidate_count: snapshot.summary.candidate_count, + data_status: snapshot.data_status, + }); + } catch { + // A successful snapshot must not expose its payload through audit failures. + } + return json({ + ok: true, + schema_version: snapshot.schema_version, + candidate_count: snapshot.summary.candidate_count, + generated_at: snapshot.generated_at, + }); +} + +export async function syncControlPlaneSourceResponse(request, env) { + const validationOnly = requireControlPlaneSourceSyncToken(request, env); + if (!hasConfigStore(env)) { + return json({ ok: false, error: "control plane KV is not configured" }, 503); + } + + let raw; + try { + raw = await readBoundedJson(request, CONTROL_PLANE_MAX_BODY_BYTES); + } catch (error) { + return json({ ok: false, error: error.message || "invalid control plane source payload" }, error.status || 400); + } + + let source; + try { + source = normalizeControlPlaneSourceSnapshot(raw, "control plane source snapshot"); + } catch (error) { + return json({ ok: false, error: error.message || "invalid control plane source payload" }, 400); + } + + if (validationOnly) { + const candidate = source.candidates[0]; + const runId = candidate?.candidate_id?.match(/^soxl_three_asset_mid_weight_validation_([1-9][0-9]*)$/)?.[1]; + if ( + source.source_id !== "aiaudit.soxl_manual_validation" || + source.candidates.length !== 1 || !runId || + candidate.candidate_kind !== "individual" || candidate.domain !== "us_equity" || + candidate.lifecycle.stage !== "P3" || candidate.lifecycle.status !== "parked" || + candidate.recommendation.code !== "park" || candidate.evidence.p3_evidence_id !== runId + ) { + return json({ ok: false, error: "validation publisher scope mismatch" }, 403); + } + } + try { + await publishHumanOwnerMaterials(env, source, source.source_id); + } catch (error) { + return json({ ok: false, error: error.message || "owner decision material unavailable" }, error.status || 503); + } + + await writeConfigJson(env, controlPlaneSourceKey(source.source_id), source); + try { + await appendAuditLog(env, { + ts: new Date().toISOString(), + login: "control-plane-source-sync", + action: "sync_control_plane_source", + source_id: source.source_id, + schema_version: source.schema_version, + candidate_count: source.candidates.length, + data_status: source.data_status, + }); + } catch { + // The source snapshot is accepted independently of an optional audit write. + } + return json({ + ok: true, + source_id: source.source_id, + schema_version: source.schema_version, + candidate_count: source.candidates.length, + generated_at: source.generated_at, + }); +} + +export async function controlPlaneResponse(request, env) { + const session = await readSession(request, env); + if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); + return json(await currentControlPlanePayload(env)); +} + +export async function currentControlPlanePayload(env) { + if (!hasConfigStore(env)) return emptyControlPlanePayload("snapshot_unavailable"); + + const sourceSnapshot = await aggregateControlPlaneSources(env); + if (sourceSnapshot) return sourceSnapshot; + + let snapshot; + try { + const stored = await readConfigJson(env, CONTROL_PLANE_SNAPSHOT_KEY); + if (!stored) return emptyControlPlanePayload("snapshot_unavailable"); + snapshot = normalizeControlPlaneSnapshot(stored, CONTROL_PLANE_SNAPSHOT_KEY); + } catch { + return emptyControlPlanePayload("snapshot_invalid"); + } + + const freshness = controlPlaneResearchSnapshotFreshness( + snapshot, + controlPlaneStaleTtlSeconds(env), + Date.now(), + ); + snapshot.data_status = freshness.data_status; + return snapshot; +} + +export function isOwnerDecisionCandidate(candidate) { + return candidate?.lifecycle?.stage === "P6" + && candidate.lifecycle.status === "owner_decision_required" + && candidate.recommendation?.code === "owner_live_decision"; +} + +export function currentOwnerDecisionCandidate(controlPlane, candidateId) { + if (controlPlane?.data_status !== "ready") { + throw new HttpError("current control-plane evidence is not ready", 409); + } + const candidate = controlPlane.candidates.find((item) => item.candidate_id === candidateId); + if (!candidate || !isOwnerDecisionCandidate(candidate)) { + throw new HttpError("candidate is not awaiting an owner decision", 409); + } + if (candidate.freshness?.status !== "fresh") { + throw new HttpError("candidate evidence is not fresh", 409); + } + return candidate; +} + +export function normalizeOwnerDecisionRequest(payload) { + const value = assertRequiredAndOptionalFields(payload, [ + "candidate_id", "decision", "candidate_evidence_sha256", + ], ["request_id"], "owner decision request"); + return { + candidate_id: normalizeControlPlaneIdentifier(value.candidate_id, "owner decision request.candidate_id", false), + decision: cleanChoice(value.decision, OWNER_DECISION_CHOICES, "owner decision request.decision"), + candidate_evidence_sha256: normalizeResearchTaskDigest( + value.candidate_evidence_sha256, + "owner decision request.candidate_evidence_sha256", + ), + request_id: value.request_id, + }; +} + +export async function ownerDecisionCandidateEvidenceSha256(candidate) { + return await calculateOwnerDecisionSha256({ + candidate_id: candidate.candidate_id, + candidate_kind: candidate.candidate_kind, + domain: candidate.domain, + lifecycle: candidate.lifecycle, + evidence: candidate.evidence, + recommendation: { code: candidate.recommendation?.code || "none" }, + }); +} + +export async function buildOwnerDecisionIntent({ candidate, decision, decidedBy, candidateEvidenceSha256, decidedAt }) { + const intent = { + schema_version: OWNER_DECISION_INTENT_SCHEMA_VERSION, + candidate_id: candidate.candidate_id, + candidate_kind: candidate.candidate_kind, + domain: candidate.domain, + decision, + decided_at: decidedAt, + decided_by: decidedBy, + candidate_evidence_sha256: candidateEvidenceSha256, + no_order: true, + execution_authority_granted: false, + decision_sha256: "", + }; + intent.decision_sha256 = await calculateOwnerDecisionSha256(intent); + return await normalizeOwnerDecisionIntent(intent, "owner decision intent"); +} + +export async function normalizeOwnerDecisionIntent(payload, fieldName) { + const value = assertExactFields(payload, [ + "schema_version", "candidate_id", "candidate_kind", "domain", "decision", "decided_at", "decided_by", + "candidate_evidence_sha256", "no_order", "execution_authority_granted", "decision_sha256", + ], fieldName); + if (value.schema_version !== OWNER_DECISION_INTENT_SCHEMA_VERSION) { + throw new Error(`${fieldName}.schema_version is unsupported`); + } + const intent = { + schema_version: OWNER_DECISION_INTENT_SCHEMA_VERSION, + candidate_id: normalizeControlPlaneIdentifier(value.candidate_id, `${fieldName}.candidate_id`, false), + candidate_kind: cleanChoice(value.candidate_kind, CONTROL_PLANE_CANDIDATE_KINDS, `${fieldName}.candidate_kind`), + domain: cleanChoice(value.domain, STRATEGY_HEALTH_DOMAINS, `${fieldName}.domain`), + decision: cleanChoice(value.decision, OWNER_DECISION_CHOICES, `${fieldName}.decision`), + decided_at: normalizeResearchTaskTimestamp(value.decided_at, `${fieldName}.decided_at`), + decided_by: cleanGithubLogin(value.decided_by, `${fieldName}.decided_by`), + candidate_evidence_sha256: normalizeResearchTaskDigest( + value.candidate_evidence_sha256, + `${fieldName}.candidate_evidence_sha256`, + ), + no_order: value.no_order, + execution_authority_granted: value.execution_authority_granted, + decision_sha256: normalizeResearchTaskDigest(value.decision_sha256, `${fieldName}.decision_sha256`), + }; + if (intent.no_order !== true || intent.execution_authority_granted !== false) { + throw new Error(`${fieldName} must remain no-order and non-executable`); + } + if (intent.decision_sha256 !== await calculateOwnerDecisionSha256(intent)) { + throw new Error(`${fieldName}.decision_sha256 mismatch`); + } + return intent; +} + +export async function calculateOwnerDecisionSha256(payload) { + const material = { ...payload }; + delete material.decision_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(""); +} + +export async function readOwnerDecisionIntent(env, candidateId, materialKey) { + let authority; + try { + authority = await readHumanDecisionAuthority(env, "owner", candidateId); + } catch (error) { + return { intent: null, authority: null, error: error.message || "runtime_instances_unavailable" }; + } + const authorityView = humanDecisionAuthorityView(authority); + if (authority.blocked) return { intent: null, authority: authorityView, error: "owner_decision_legacy_blocked" }; + if (authority.decision && authority.material_key === materialKey && authority.decision.payload?.no_order === true && authority.decision.payload?.execution_authority_granted === false) { + return { intent: authority.decision.payload, authority: authorityView, error: null }; + } + if (authority.migrated === false) { + try { + const stored = await readConfigJson(env, ownerDecisionCurrentKey(candidateId)); + if (!stored) return { intent: null, authority: authorityView, error: null }; + const intent = await normalizeOwnerDecisionIntent(stored, ownerDecisionCurrentKey(candidateId)); + if (materialKey && intent.candidate_evidence_sha256 !== materialKey) return { intent: null, authority: authorityView, error: null }; + return { intent, authority: authorityView, error: null, read_only: true }; + } catch { + return { intent: null, authority: authorityView, error: "owner_decision_intent_invalid" }; + } + } + return { intent: null, authority: authorityView, error: null }; +} + +export function ownerDecisionArchiveKey(candidateId, decisionSha256) { + return `${OWNER_DECISION_INTENT_PREFIX}${candidateId}:${decisionSha256}`; +} + +export function ownerDecisionCurrentKey(candidateId) { + return `${OWNER_DECISION_CURRENT_PREFIX}${candidateId}`; +} + +export function emptyOwnerDecisionQueue(errorCode, controlPlane = null) { + return { + schema_version: OWNER_DECISION_QUEUE_SCHEMA_VERSION, + data_status: controlPlane?.data_status === "stale" ? "stale" : "unavailable", + computed_at: controlPlane?.computed_at || null, + candidates: [], + policy: { + admin_required: true, + current_evidence_required: true, + execution_authority_granted: false, + no_order: true, + notice: "网页只记录所有者决定意图;不会下单、变更资金或启用实盘。", + }, + errors: uniqueStrings([...(controlPlane?.errors || []), errorCode]), + }; +} + +export async function ownerDecisionQueueResponse(request, env) { + return ownerDecisionQueueResponseImpl(request, env, { + json, + readSession, + exactHumanDecisionResponse, + hasConfigStore, + currentControlPlanePayload, + isOwnerDecisionCandidate, + ownerDecisionCandidateEvidenceSha256, + readOwnerDecisionIntent, + emptyOwnerDecisionQueue, + uniqueStrings, + OWNER_DECISION_MAX_CANDIDATES, + OWNER_DECISION_QUEUE_SCHEMA_VERSION, + }); +} + +export async function recordOwnerDecisionResponse(request, env) { + return recordOwnerDecisionResponseImpl(request, env, { + json, + requireSameOrigin, + readSession, + hasConfigStore, + readBoundedJson, + normalizeOwnerDecisionRequest, + currentControlPlanePayload, + currentOwnerDecisionCandidate, + ownerDecisionCandidateEvidenceSha256, + humanDecisionTimestamp, + buildOwnerDecisionIntent, + canonicalResearchTaskJson, + humanDecisionRequestKey, + runtimeInstanceCommand, + humanDecisionReceipt, + mirrorConfigJson, + ownerDecisionArchiveKey, + ownerDecisionCurrentKey, + appendAuditLog, + }); +} + +// Recovery snapshots contain digest-bound review metadata. Initial enrollment +// additionally carries bounded broker facts for the authenticated review; +// raw receipts remain in the platform's protected source, and confirmation +// records intent without granting execution authority. +export async function initialEnrollmentAccountOption(env, item) { + if (!await initialEnrollmentBindingsMatch(item.candidate, item.facts) + || item.candidate.platform_id.toLowerCase() !== "ibkr" + || item.facts.observed_at !== item.candidate.first_observed_at + || item.facts.observed_at !== item.candidate.last_observed_at) throw new HttpError("initial_enrollment_invalid", 409); + const config = await loadAccountOptionsConfig(env); + const configured = await loadAccountFactsBindings(env, { propagateReadErrors: true }); + const bindings = (configured?.bindings || []).filter(binding => binding.platform === "ibkr" && binding.target_id === item.target_id); + const options = (config.options?.ibkr || []).filter(option => bindings.length === 1 && option.key === bindings[0].account_key); + if (options.length !== 1 || bindings.length !== 1 || !accountFactsOptionMatchesBinding(options[0], bindings[0]) + || await initialEnrollmentDigest({ account_ids: [bindings[0].account_selector] }, true) !== item.candidate.account_scope_sha256) { + throw new HttpError("initial_enrollment_identity_mismatch", 409); + } + return options[0]; +} + +export 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"); + for (const recovery of source.recoveries) if (recovery.mode === "initial_enrollment") await initialEnrollmentAccountOption(env, recovery); + } catch (error) { + return json({ ok: false, error: error.message || "invalid reconciliation recovery payload" }, 400); + } + try { + await publishHumanRecoveryMaterials(env, source); + } catch (error) { + return json({ ok: false, error: error.message || "reconciliation recovery material unavailable" }, error.status || 503); + } + 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, + }); +} + +export async function reconciliationRecoveryResponse(request, env) { + const session = await readSession(request, env); + if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); + const exact = await exactHumanDecisionResponse(request, env, session, "recovery"); + if (exact) return exact; + if (!hasConfigStore(env)) return json(emptyReconciliationRecoveryPayload("snapshot_unavailable")); + return json(decorateQueue(await aggregateReconciliationRecoverySources(env), "recoveries", "reconciliation_recovery")); +} + +export async function syncBinancePrivateScopeResponse(request, env) { + requireDedicatedReconciliationRecoverySyncToken(request, env); + const store = configStore(env); + if (!store) { + return json({ ok: false, error: "binance_private_scope_storage_unavailable" }, 503); + } + let raw; + try { + raw = await readBoundedJson(request, BINANCE_PRIVATE_SCOPE_MAX_BODY_BYTES); + } catch (error) { + return json({ ok: false, error: "invalid_binance_private_scope_payload" }, error.status === 413 ? 413 : 400); + } + let report; + try { + report = normalizeBinancePrivateScopeReport(raw, { requireIngestFreshness: true }); + } catch { + return json({ ok: false, error: "invalid_binance_private_scope_payload" }, 400); + } + try { + await store.put(BINANCE_PRIVATE_SCOPE_KEY, JSON.stringify(report), { + expirationTtl: BINANCE_PRIVATE_SCOPE_WRITE_TTL_SECONDS, + }); + } catch { + return json({ ok: false, error: "binance_private_scope_storage_unavailable" }, 503); + } + return json({ + ok: true, + observed_at: report.observed_at, + source_run_id: report.source_run_id, + asset_count: report.assets.length, + }); +} + +export async function trustedBinanceFactsBinding(env, readiness = null) { + const text = String(env.BINANCE_ACCOUNT_FACTS_BINDING_JSON || ""); + if (!text) return null; + let binding; + try { binding = normalizeBinanceFactsBinding(JSON.parse(text)); } + catch { throw new BinanceFactsError("binance_account_facts_binding_invalid", 503); } + await assertBinanceFactsSourceBinding(binding); + if (readiness) readiness.binding_valid = true; + let config; + try { config = await loadAccountOptionsConfig(env); } + catch { + throw new BinanceFactsError("binance_account_facts_account_options_unavailable", 503); + } + if (readiness) readiness.account_options_readable = true; + const options = config.options?.binance || []; + const matchCount = options.filter(option => binanceFactsOptionMatches(option, binding)).length; + if (matchCount !== 1) { + throw new BinanceFactsError("binance_account_facts_binding_unmatched", 409); + } + if (readiness) readiness.unique_match = true; + return binding; +} + +export function binanceFactsErrorResponse(error) { + return json({ ok: false, error: error instanceof BinanceFactsError + ? error.code : "binance_account_facts_unavailable" }, error instanceof BinanceFactsError ? error.status : 503); +} + +export function binanceFactsSyncAuthorizationResponse(request, env) { + const token = String(env.BINANCE_ACCOUNT_FACTS_SYNC_TOKEN || ""); + const otherTokens = ["ACCOUNT_FACTS_SYNC_TOKEN", "IBKR_ACCOUNT_FACTS_SYNC_TOKEN", "SCHWAB_ACCOUNT_FACTS_SYNC_TOKEN", "FIRSTRADE_ACCOUNT_FACTS_SYNC_TOKEN", + "RECONCILIATION_RECOVERY_SYNC_TOKEN", "RECONCILIATION_RECOVERY_CONTROLLER_TOKEN", "STRATEGY_SWITCH_SYNC_TOKEN"]; + if (!token || otherTokens.some(name => env[name] && env[name] === token)) { + return json({ ok: false, error: "binance_account_facts_token_unavailable" }, 503); + } + if ((request.headers.get("Authorization") || "").match(/^Bearer\s+(.+)$/i)?.[1] !== token) { + return json({ ok: false, error: "binance_account_facts_token_invalid" }, 401); + } + return null; +} + +export async function binanceDigestSnapshotResponse(request, env, url) { + const token = env.BINANCE_DIGEST_READ_TOKEN; + const otherTokens = ["BINANCE_ACCOUNT_FACTS_SYNC_TOKEN", "ACCOUNT_FACTS_SYNC_TOKEN", "IBKR_ACCOUNT_FACTS_SYNC_TOKEN", + "SCHWAB_ACCOUNT_FACTS_SYNC_TOKEN", "FIRSTRADE_ACCOUNT_FACTS_SYNC_TOKEN", "RECONCILIATION_RECOVERY_SYNC_TOKEN", + "RECONCILIATION_RECOVERY_CONTROLLER_TOKEN", "STRATEGY_SWITCH_SYNC_TOKEN", "SESSION_SECRET"]; + if (typeof token !== "string" || !/^[A-Za-z0-9_-]{32,256}$/.test(token) + || otherTokens.some(name => env[name] === token)) { + return json({ ok: false, error: "digest_snapshot_read_unavailable" }, 503); + } + if (request.headers.get("Authorization") !== `Bearer ${token}`) { + return json({ ok: false, error: "digest_snapshot_read_unauthorized" }, 401); + } + const day = url.searchParams.get("business_day"); + const start = Date.parse(`${day}T06:00:00+08:00`); + const end = start + 86400000; + if (!/^\d{4}-\d{2}-\d{2}$/.test(day || "") || !Number.isFinite(start) + || new Date(start + 8 * 3600000).toISOString().slice(0, 10) !== day || end > Date.now()) { + return json({ ok: false, error: "digest_snapshot_window_invalid" }, 400); + } + try { + const binding = await trustedBinanceFactsBinding(env); + if (!binding || !configStore(env)) throw new Error("digest_snapshot_read_unavailable"); + const retained = await runtimeInstanceCommand(env, { + action: "binance_detail_read", binding, date: day, business_day: true, + }); + if (retained?.ok !== true) throw new Error("digest_snapshot_read_unavailable"); + // The existing latest cache can supply a snapshot from before this + // retention path was released, provided it still matches the window. + const raw = retained.report ?? await readConfigJson(env, `${BINANCE_FACTS_KEY}:${binding.source_binding.id}`); + if (!raw) return json({ ok: false, error: "digest_snapshot_missing" }, 404); + const report = normalizeBinanceAccountFacts(raw, binding); + const observedFrom = Date.parse(report.observed_started_at); + const observedUntil = Date.parse(report.observed_finished_at); + if (observedFrom < start || observedUntil >= end) { + return json({ ok: false, error: "digest_snapshot_missing" }, 404); + } + const valuation = report.wallet_valuation; + const wallet = url.searchParams.get("include_valuation") === "1" && valuation?.status === "available" + ? { wallet_valuation: { + amount: valuation.amount, currency: valuation.currency, as_of: valuation.observed_at, + } } + : {}; + return json({ ok: true, business_day: day, account_key: binding.account_key, + target_id: binding.target_id, as_of: report.observed_finished_at, + holdings: report.assets.map(({ asset, quantity }) => ({ asset, quantity })), ...wallet }); + } catch { + return json({ ok: false, error: "digest_snapshot_read_unavailable" }, 503); + } +} + +export async function binanceAccountFactsReadinessResponse(request, env) { + const authError = binanceFactsSyncAuthorizationResponse(request, env); + if (authError) return authError; + const readiness = { binding_valid: false, account_options_readable: false, unique_match: false }; + try { + const binding = await trustedBinanceFactsBinding(env, readiness); + if (!binding) throw new BinanceFactsError("binance_account_facts_binding_missing", 503); + return json({ ok: true, ready: true, ...readiness }); + } catch (error) { + return json({ ok: false, ready: false, ...readiness, + error: error instanceof BinanceFactsError ? error.code : "binance_account_facts_unavailable" }, + error instanceof BinanceFactsError ? error.status : 503); + } +} + +export async function syncBinanceAccountFactsResponse(request, env) { + const authError = binanceFactsSyncAuthorizationResponse(request, env); + if (authError) return authError; + try { + const binding = await trustedBinanceFactsBinding(env); + if (!binding) throw new BinanceFactsError("binance_account_facts_binding_missing", 503); + const raw = await readBoundedJson(request, BINANCE_FACTS_MAX_BYTES); + const report = normalizeBinanceAccountFacts(raw, binding, { ingest: true }); + const store = configStore(env); + if (!store) throw new BinanceFactsError("binance_account_facts_storage_unavailable", 503); + if (!hasRuntimeInstanceStore(env)) throw new BinanceFactsError("binance_account_facts_history_storage_unavailable", 503); + const storageKey = `${BINANCE_FACTS_KEY}:${binding.source_binding.id}`; + const previous = await readConfigJson(env, storageKey); + let unchanged = false; + if (previous) { + const validPrevious = normalizeBinanceAccountFacts(previous, binding); + const incomingTime = Date.parse(report.observed_finished_at); + const previousTime = Date.parse(validPrevious.observed_finished_at); + if (incomingTime < previousTime || (incomingTime === previousTime + && canonicalJson(report) !== canonicalJson(validPrevious))) { + throw new BinanceFactsError("binance_account_facts_observation_conflict", 409); + } + unchanged = incomingTime === previousTime; + } + try { + const detail = await runtimeInstanceCommand(env, { + action: "binance_detail_put", binding, report, date: report.observed_finished_at.slice(0, 10), + }); + if (detail.readback_verified !== true) throw new Error("binance_detail_readback_failed"); + const history = binanceWalletHistoryEntry(report, binding); + await runtimeInstanceCommand(env, { + action: "binance_wallet_history_put", + account_key: binding.account_key, + storage_account_key: binanceWalletHistoryStorageKey(binding), + target_id: binding.target_id, + account_scope: binding.account_scope, + source_binding_id: binding.source_binding.id, + history, + }); + } catch { + throw new BinanceFactsError("binance_account_facts_history_storage_unavailable", 503); + } + if (unchanged) return json({ ok: true, status: "unchanged" }); + await store.put(storageKey, JSON.stringify(report), { expirationTtl: 2 * 24 * 60 * 60 }); + return json({ ok: true, status: "published", observed_finished_at: report.observed_finished_at, + asset_count: report.assets.length }); + } catch (error) { + if (error?.status === 413) return json({ ok: false, error: "binance_account_facts_payload_too_large" }, 413); + return binanceFactsErrorResponse(error); + } +} + +export async function binanceDetailArchiveResponse(request, env, url) { + const authError = binanceFactsSyncAuthorizationResponse(request, env); + if (authError) return authError; + try { + const binding = await trustedBinanceFactsBinding(env); + if (!binding) throw new Error("binding_missing"); + const result = await runtimeInstanceCommand(env, { + action: "binance_detail_read", binding, date: url.searchParams.get("date"), + }); + return json(result, result.report ? 200 : 404); + } catch { + return json({ ok: false, error: "binance_detail_archive_unavailable" }, 503); + } +} + +export async function binanceWalletHistoryResponse(request, env, url) { + if (!accountFactsReadModelEnabled(env)) return accountFactsDisabledResponse(); + const session = await readSession(request, env); + if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); + if (!hasRuntimeInstanceStore(env)) return json({ ok: false, error: "account_facts_store_unavailable" }, 503); + try { + const binding = await trustedBinanceFactsBinding(env); + if (!binding) throw new BinanceFactsError("binance_account_facts_binding_missing", 503); + if (url.searchParams.get("account_key") !== binding.account_key) { + throw new BinanceFactsError("binance_account_facts_binding_unmatched", 409); + } + let historicalBindings; + try { + const encoded = String(env.BINANCE_ACCOUNT_FACTS_HISTORY_BINDINGS_JSON || "[]"); + historicalBindings = await assertBinanceFactsHistoryBindings(JSON.parse(encoded), binding); + } catch { + throw new BinanceFactsError("binance_account_facts_history_bindings_invalid", 503); + } + const buckets = []; + for (const sourceBinding of [binding, ...historicalBindings]) { + const stored = await runtimeInstanceCommand(env, { + action: "binance_wallet_history_read", + account_key: binding.account_key, + storage_account_key: binanceWalletHistoryStorageKey(sourceBinding), + target_id: binding.target_id, + account_scope: binding.account_scope, + source_binding_id: sourceBinding.source_binding.id, + }); + buckets.push({ binding: sourceBinding, days: Array.isArray(stored.days) ? stored.days : [] }); + } + return json(projectBinanceWalletHistoryFromSources(buckets, binding, historicalBindings)); + } catch (error) { + const allowedErrors = new Set([ + "binance_account_facts_binding_invalid", "binance_account_facts_account_options_unavailable", + "binance_account_facts_binding_unmatched", "binance_account_facts_binding_missing", + "binance_wallet_history_identity_mismatch", "binance_wallet_history_stored_invalid", + "binance_wallet_history_conflict", "binance_account_facts_history_bindings_invalid", + ]); + const code = allowedErrors.has(error?.code || error?.message) + ? (error.code || error.message) : "binance_wallet_history_unavailable"; + return json({ ok: false, error: code }, error?.status || 503); + } +} + +export async function binanceAccountFactsResponse(request, env) { + const session = await readSession(request, env); + if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); + try { + const binding = await trustedBinanceFactsBinding(env); + if (!binding) return json({ ok: true, report_status: "unavailable", report: null }); + const raw = await readConfigJson(env, `${BINANCE_FACTS_KEY}:${binding.source_binding.id}`); + if (!raw) return json({ ok: true, report_status: "missing", report: null }); + const report = projectBinanceAccountFacts(raw, binding); + if (!report) return json({ ok: true, report_status: "expired", report: null }); + return json({ ok: true, report_status: "available", report }); + } catch { return json({ ok: true, report_status: "unavailable", report: null }); } +} + +export async function binancePrivateScopeResponse(request, env) { + const session = await readSession(request, env); + if (!session) return json({ ok: false, error: "login required" }, 401); + if (!session.allowed || !session.admin) return json({ ok: false, error: "admin required" }, 403); + if (!hasConfigStore(env)) return json({ ok: true, report: null }); + try { + const raw = await readConfigJson(env, BINANCE_PRIVATE_SCOPE_KEY); + if (!raw) return json({ ok: true, report: null }); + const report = normalizeBinancePrivateScopeReport(raw); + const ageMs = Date.now() - Date.parse(report.observed_at); + if (ageMs > BINANCE_PRIVATE_SCOPE_READ_MAX_AGE_MS || ageMs < -BINANCE_PRIVATE_SCOPE_FUTURE_SKEW_MS) { + return json({ ok: true, report: null }); + } + return json({ ok: true, report }); + } catch { + return json({ ok: true, report: null }); + } +} + +// This is a least-privilege service-to-service read for a platform-owned +// controller. It deliberately returns only the current, redacted digest +// binding and immutable human receipt; broker state remains in the platform. +export async function reconciliationRecoveryControllerReadResponse(request, env, url) { + requireDedicatedReconciliationRecoveryControllerToken(request, env); + if (!hasConfigStore(env)) { + return json({ ok: false, error: "reconciliation recovery KV is not configured" }, 503); + } + let recoveryId; + try { + recoveryId = normalizeControlPlaneIdentifier( + url.searchParams.get("recovery_id") || "", + "reconciliation recovery controller recovery_id", + false, + ); + } catch (error) { + return json({ ok: false, error: error.message || "invalid recovery_id" }, 400); + } + const dashboard = await aggregateReconciliationRecoverySources(env); + let recovery; + try { + recovery = currentReconciliationRecoveryRequest(dashboard, recoveryId); + } catch (error) { + return json({ ok: false, error: error.message || "reconciliation recovery is unavailable" }, error.status || 409); + } + const entry = dashboard.recoveries.find((item) => item.recovery?.recovery_id === recoveryId); + if (!entry?.confirmation) { + return json({ ok: false, error: "reconciliation recovery has no current confirmation" }, 404); + } + if (recovery.mode === "initial_enrollment") return json({ ok: true, schema_version: "qsl_initial_enrollment_controller_read.v1", + recovery: { mode: recovery.mode, recovery_id: recovery.recovery_id, candidate_sha256: recovery.candidate_sha256, facts_sha256: recovery.facts_sha256 }, + confirmation: entry.confirmation, policy: { no_order: true, execution_authority_granted: false, controller_must_reverify: true } }); + return json({ + ok: true, + schema_version: RECONCILIATION_RECOVERY_CONTROLLER_READ_SCHEMA_VERSION, + recovery: { + recovery_id: recovery.recovery_id, + platform: recovery.platform, + strategy_profile: recovery.strategy_profile, + environment: recovery.environment, + reconciliation_state: recovery.reconciliation_state, + candidate_sha256: recovery.candidate_sha256, + dual_review_binding_sha256: recovery.dual_review.evidence_binding_sha256, + evidence_sample_count: recovery.evidence_sample_count, + first_observed_at: recovery.first_observed_at, + last_observed_at: recovery.last_observed_at, + }, + confirmation: entry.confirmation, + policy: { + no_order: true, + execution_authority_granted: false, + controller_must_reverify: true, + }, + }); +} + +export function currentReconciliationRecoveryRequest(dashboard, recoveryId) { + const entry = dashboard?.recoveries?.find((item) => item.recovery?.recovery_id === recoveryId); + if (entry?.recovery?.mode !== "initial_enrollment" && dashboard?.data_status !== "ready") { + throw new HttpError("current reconciliation recovery evidence is not ready", 409); + } + if (!entry || entry.freshness?.data_status !== "ready") { + throw new HttpError("reconciliation recovery request is not fresh", 409); + } + const recovery = entry.recovery; + if (recovery.mode === "initial_enrollment") { + if (recovery.readiness !== "awaiting_human_confirmation" || recovery.blocker_codes.length) throw new HttpError("initial_enrollment_incomplete", 409); + return recovery; + } + if ( + recovery.readiness !== "awaiting_human_confirmation" || + recovery.reconciliation_state !== "RECONCILE_ONLY" || + recovery.blocker_codes.length || + recovery.dual_review.evidence_binding_sha256 !== recovery.candidate_sha256 || + recovery.evidence_sample_count < 1 + ) { + throw new HttpError("reconciliation recovery request is not eligible for confirmation", 409); + } + return recovery; +} + +export function normalizeReconciliationRecoveryConfirmationRequest(payload) { + if (payload?.mode === "initial_enrollment") { + const value = assertRequiredAndOptionalFields(payload, ["mode", "recovery_id", "candidate_sha256", "facts_sha256"], ["decision", "request_id"], "initial enrollment confirmation request"); + const decision = value.decision ?? "approve"; + if (!["approve", "reject"].includes(decision)) throw new Error("initial enrollment decision unsupported"); + return { mode: "initial_enrollment", recovery_id: normalizeControlPlaneIdentifier(value.recovery_id, "recovery_id", false), + candidate_sha256: normalizeResearchTaskDigest(value.candidate_sha256, "candidate_sha256"), + facts_sha256: normalizeResearchTaskDigest(value.facts_sha256, "facts_sha256"), decision, request_id: value.request_id }; + } + const value = assertRequiredAndOptionalFields(payload, [ + "recovery_id", "candidate_sha256", "dual_review_binding_sha256", + ], ["decision", "request_id"], "reconciliation recovery confirmation request"); + const decision = Object.prototype.hasOwnProperty.call(value, "decision") ? value.decision : "approve"; + if (decision !== "approve" && decision !== "reject") { + throw new Error("reconciliation recovery confirmation request.decision is unsupported"); + } + 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", + ), + decision, + request_id: value.request_id, + }; +} + +export 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(""); +} + +export 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"); +} + +export 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; +} + +export function reconciliationRecoveryConfirmationArchiveKey(recoveryId, confirmationSha256) { + return `${RECONCILIATION_RECOVERY_CONFIRMATION_PREFIX}${recoveryId}:${confirmationSha256}`; +} + +export function reconciliationRecoveryConfirmationCurrentKey(recoveryId) { + return `${RECONCILIATION_RECOVERY_CURRENT_PREFIX}${recoveryId}`; +} + +export async function readReconciliationRecoveryConfirmation(env, recoveryId) { + let authority; + try { + authority = await readHumanDecisionAuthority(env, "recovery", recoveryId); + } catch (error) { + return { confirmation: null, rejection: null, authority: null, error: error.message || "runtime_instances_unavailable" }; + } + const authorityView = humanDecisionAuthorityView(authority); + if (authority.migrated === false) { + try { + const stored = await readConfigJson(env, reconciliationRecoveryConfirmationCurrentKey(recoveryId)); + if (!stored) return { confirmation: null, rejection: null, authority: authorityView, error: null }; + return { + confirmation: await normalizeReconciliationRecoveryConfirmation( + stored, + reconciliationRecoveryConfirmationCurrentKey(recoveryId), + ), + rejection: null, + authority: authorityView, + error: null, + read_only: true, + }; + } catch { + return { confirmation: null, rejection: null, authority: authorityView, error: "reconciliation_recovery_confirmation_invalid" }; + } + } + if (authority.blocked) return { confirmation: null, rejection: null, authority: authorityView, error: "reconciliation_recovery_legacy_blocked" }; + if (authority.eligible !== true) return { confirmation: null, rejection: null, authority: authorityView, error: null }; + if (authority.decision?.action === "reject") { + const payload = authority.decision.payload; + if (payload?.decision === "reject" && payload?.no_order === true && payload?.execution_authority_granted === false) { + return { confirmation: null, rejection: payload, authority: authorityView, error: null }; + } + return { confirmation: null, rejection: null, authority: authorityView, error: null }; + } + if (authority.decision?.action === "approve" && authority.decision.payload?.no_order === true && authority.decision.payload?.execution_authority_granted === false) { + return { confirmation: authority.decision.payload, rejection: null, authority: authorityView, error: null }; + } + return { confirmation: null, rejection: null, authority: authorityView, error: null }; +} + +export async function recordInitialEnrollmentDecision(env, session, requested, recovery) { + if (recovery.mode !== "initial_enrollment" || requested.candidate_sha256 !== recovery.candidate_sha256 + || requested.facts_sha256 !== recovery.facts_sha256) return json({ ok: false, error: "initial_enrollment_changed" }, 409); + const decidedAt = humanDecisionTimestamp(); + const materialKey = await recoveryDecisionMaterialKey(recovery); + const base = { mode: "initial_enrollment", recovery_id: recovery.recovery_id, + candidate_sha256: recovery.candidate_sha256, facts_sha256: recovery.facts_sha256, + no_order: true, execution_authority_granted: false }; + const payload = requested.decision === "approve" + ? { schema_version: "qsl_initial_enrollment_confirmation.v1", ...base, confirmed_at: decidedAt, confirmed_by: session.login } + : { schema_version: "qsl_initial_enrollment_rejection.v1", ...base, decision: "reject", decided_at: decidedAt, decided_by: session.login }; + if (requested.decision === "approve") payload.confirmation_sha256 = await calculateReconciliationRecoveryConfirmationSha256(payload); + const target = { decision: requested.decision }; + try { + const requestKey = await humanDecisionRequestKey({ requestId: requested.request_id, kind: "recovery", actor: session.login, + subjectId: recovery.recovery_id, materialKey, action: requested.decision, target }); + const recorded = await runtimeInstanceCommand(env, { action: "recovery_decide", actor: session.login, + subject_id: recovery.recovery_id, material_key: materialKey, action_name: requested.decision, + request_key: requestKey, target_json: canonicalResearchTaskJson(target), decided_at: decidedAt, now_ms: Date.now(), payload }); + const decisionReceipt = await humanDecisionReceipt(recorded.record, { kind: "recovery", subject_id: recovery.recovery_id, material_sha256: materialKey }); + return json({ ok: true, confirmation: recorded.record?.action === "approve" ? recorded.record.payload : null, + rejection: recorded.record?.action === "reject" ? recorded.record.payload : null, decision_receipt: decisionReceipt, replayed: recorded.replayed === true }); + } catch (error) { return json({ ok: false, error: error.message || "initial_enrollment_unavailable" }, error.status || 503); } +} + +export 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.mode === "initial_enrollment") return recordInitialEnrollmentDecision(env, session, requested, recovery); + if (recovery.mode === "initial_enrollment" || + 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 decidedAt = humanDecisionTimestamp(); + const materialKey = await recoveryDecisionMaterialKey(recovery); + const confirmation = requested.decision === "approve" + ? await buildReconciliationRecoveryConfirmation({ recovery, confirmedBy: session.login, confirmedAt: decidedAt }) + : null; + const rejection = requested.decision === "reject" ? { + schema_version: "qsl_reconciliation_recovery_rejection.v1", + recovery_id: recovery.recovery_id, + candidate_sha256: recovery.candidate_sha256, + dual_review_binding_sha256: recovery.dual_review.evidence_binding_sha256, + decision: "reject", + decided_at: decidedAt, + decided_by: session.login, + no_order: true, + execution_authority_granted: false, + } : null; + const target = { decision: requested.decision }; + let requestKey; + try { + requestKey = await humanDecisionRequestKey({ + requestId: requested.request_id, + kind: "recovery", + actor: session.login, + subjectId: recovery.recovery_id, + materialKey, + action: requested.decision, + target, + }); + } catch (error) { + return json({ ok: false, error: error.message || "invalid reconciliation recovery confirmation" }, error.status || 400); + } + let recorded; + try { + recorded = await runtimeInstanceCommand(env, { + action: "recovery_decide", + actor: session.login, + subject_id: recovery.recovery_id, + material_key: materialKey, + action_name: requested.decision, + request_key: requestKey, + target_json: canonicalResearchTaskJson(target), + decided_at: decidedAt, + now_ms: Date.now(), + payload: confirmation || rejection, + }); + } catch (error) { + return json({ ok: false, error: error.message || "reconciliation recovery confirmation is unavailable" }, error.status || 503); + } + const decisionReceipt = await humanDecisionReceipt(recorded.record, { kind: "recovery", subject_id: recovery.recovery_id, material_sha256: materialKey }); + const recordedPayload = recorded.record.payload; + if (recorded.record?.action === "approve") { + await mirrorConfigJson(env, reconciliationRecoveryConfirmationArchiveKey(recovery.recovery_id, recordedPayload.confirmation_sha256), recordedPayload); + await mirrorConfigJson(env, reconciliationRecoveryConfirmationCurrentKey(recovery.recovery_id), recordedPayload); + } + let auditLogged = false; + try { + await appendAuditLog(env, { + ts: recorded.record?.decided_at || decidedAt, + login: session.login, + action: "record_reconciliation_recovery_confirmation", + recovery_id: recovery.recovery_id, + candidate_sha256: recovery.candidate_sha256, + dual_review_binding_sha256: recovery.dual_review.evidence_binding_sha256, + confirmation_sha256: recordedPayload.confirmation_sha256 || "", + decision: recorded.record?.action || requested.decision, + 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: recorded.record?.action === "approve" ? recordedPayload : null, + rejection: recorded.record?.action === "reject" ? recordedPayload : null, + decision_receipt: decisionReceipt, + audit_logged: auditLogged, + replayed: recorded.replayed === true, + }); +} + +export 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); + if (recovery.mode === "initial_enrollment") { + const option = await initialEnrollmentAccountOption(env, recovery); + const incomplete = Object.entries(recovery.candidate.coverage).filter(([, status]) => status !== "complete").map(([name]) => `initial_enrollment_${name}_incomplete`); + const view = { ...recovery, platform: "ibkr", strategy_profile: recovery.candidate.strategy_profile, + target_name: option.target_name, account_key: option.key, candidate_sha256: recovery.candidate.candidate_sha256, + facts_sha256: recovery.candidate.facts_sha256, first_observed_at: recovery.candidate.first_observed_at, + last_observed_at: recovery.candidate.last_observed_at, + readiness: incomplete.length ? "blocked" : "awaiting_human_confirmation", blocker_codes: incomplete }; + const matches = decision => decision?.mode === "initial_enrollment" + && decision.candidate_sha256 === view.candidate_sha256 && decision.facts_sha256 === view.facts_sha256; + const confirmation = matches(result.confirmation) ? result.confirmation : null; + const rejection = matches(result.rejection) ? result.rejection : null; + const presentation = recoveryMaterialPresentation({ source, recovery: view, confirmation, rejection, decisionError: result.error }); + presentation.label = { zh: "首次登记确认", en: "Initial enrollment" }; + presentation.material_kind = "initial_enrollment"; + recoveries.push({ source_id: source.source_id, freshness, recovery: view, confirmation, rejection, presentation, + decision_authority: result.authority || null, + decision_binding: { kind: "recovery", subject_id: view.recovery_id, material_sha256: await recoveryDecisionMaterialKey(recovery) } }); + continue; + } + 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; + const rejection = result.rejection + && result.rejection.candidate_sha256 === recovery.candidate_sha256 + && result.rejection.dual_review_binding_sha256 === recovery.dual_review.evidence_binding_sha256 + ? result.rejection + : null; + recoveries.push({ source_id: source.source_id, freshness, recovery, confirmation, rejection, + presentation: recoveryMaterialPresentation({ source, recovery, confirmation, rejection, decisionError: result.error }), + decision_authority: result.authority || null, + decision_binding: { kind: "recovery", subject_id: recovery.recovery_id, material_sha256: await recoveryDecisionMaterialKey(recovery) }, + }); + } + 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 && + !entry.rejection, + ).length, + blocked: uniqueRecoveries.filter((entry) => entry.recovery.readiness === "blocked").length, + confirmed: uniqueRecoveries.filter((entry) => Boolean(entry.confirmation)).length, + }, + recoveries: uniqueRecoveries, + current_reconciliation_observation: currentReconciliationObservationPresentation(), + policy: { + human_confirmation_required: true, + current_evidence_required: true, + no_order: true, + execution_authority_granted: false, + notice: "恢复确认只记录与双审摘要绑定的人工意图;私有控制器仍须重新核验后才可恢复既有执行。", + }, + errors: uniqueStrings(errors), + }; +} + +export async function readReconciliationRecoverySources(env) { + const store = configStore(env); + if (!store) return []; + let listing; + try { + listing = await directoryList(env, RECONCILIATION_RECOVERY_SOURCE_PREFIX, RECONCILIATION_RECOVERY_MAX_SOURCES); + } catch (error) { + return [emptyReconciliationRecoverySourceSnapshot(sourceListFailure(error, "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) throw new Error("indexed_record_unavailable"); + const source = normalizeReconciliationRecoverySourceSnapshot(stored, key); + for (const recovery of source.recoveries) if (recovery.mode === "initial_enrollment") await initialEnrollmentAccountOption(env, recovery); + sources.push(source); + } catch { + sources.push(emptyReconciliationRecoverySourceSnapshot("reconciliation_recovery_source_invalid")); + } + } + return sources; +} + +export function reconciliationRecoverySourceKey(sourceId) { + return `${RECONCILIATION_RECOVERY_SOURCE_PREFIX}${sourceId}`; +} + +export function runtimeDailyReadModelEnabled(env) { + return String(env.RUNTIME_DAILY_READ_MODEL_ENABLED || "") === "true"; +} + +export function runtimeDailyDisabledResponse() { + return json({ ok: false, error: "runtime_daily_disabled" }, 404); +} + +export function runtimeDailyKey(businessDate, target = RUNTIME_DAILY_TARGET) { + return `${RUNTIME_DAILY_KEY_PREFIX}${target.target_key}:${businessDate}`; +} + +export function runtimeDailyToday(now = Date.now()) { + return runtimeDailyBusinessDate(now); +} + +export function runtimeDailyCalendarDate(value) { + if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return null; + const [year, month, day] = value.split("-").map(Number); + const utc = new Date(Date.UTC(year, month - 1, day)); + if (utc.getUTCFullYear() !== year || utc.getUTCMonth() !== month - 1 || utc.getUTCDate() !== day) return null; + return value; +} + +export function runtimeDailyInstant(value) { + if (typeof value !== "string") return null; + const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,9})?(Z|[+-](\d{2}):(\d{2}))$/.exec(value); + if (!match) return null; + const year = Number(match[1]); + const month = Number(match[2]); + const day = Number(match[3]); + const hour = Number(match[4]); + const minute = Number(match[5]); + const second = Number(match[6]); + if (hour > 23 || minute > 59 || second > 59) return null; + if (match[7] !== "Z" && (Number(match[8]) > 23 || Number(match[9]) > 59)) return null; + const utc = new Date(Date.UTC(year, month - 1, day)); + if (utc.getUTCFullYear() !== year || utc.getUTCMonth() !== month - 1 || utc.getUTCDate() !== day) return null; + const parsed = Date.parse(value); + return Number.isFinite(parsed) ? parsed : null; +} + +export function runtimeDailyExactKeys(value, keys) { + if (!value || typeof value !== "object" || Array.isArray(value)) return false; + const present = Object.keys(value); + return present.length === keys.length && keys.every((key) => Object.prototype.hasOwnProperty.call(value, key)); +} + +export function runtimeDailyBoundedText(value, max) { + if (typeof value !== "string" || !value || value.length > max) return false; + if (/[\u0000-\u001f\u007f]/.test(value)) return false; + const lowered = value.toLowerCase(); + return !["secret", "token", "password", "bearer", "traceback", "/users/", "/home/"].some((marker) => lowered.includes(marker)); +} + +export function runtimeDailyChoice(value, allowed) { + return typeof value === "string" && allowed.includes(value); +} + +export function runtimeDailyReject(code) { + throw new HttpError(code, 400); +} + +export function runtimeDailyOptionalInstant(value, observedMs) { + if (value === null) return null; + const parsed = runtimeDailyInstant(value); + if (parsed === null) runtimeDailyReject("invalid_runtime_daily_time"); + if (parsed > observedMs + RUNTIME_DAILY_FUTURE_SKEW_MS) runtimeDailyReject("runtime_daily_future_observation"); + return parsed; +} + +export function runtimeDailyScheduleInstants(schedule, observedMs) { + const latest = schedule.latest_due_at === null ? null : runtimeDailyInstant(schedule.latest_due_at); + const next = schedule.next_due_at === null ? null : runtimeDailyInstant(schedule.next_due_at); + const grace = schedule.grace_ends_at === null ? null : runtimeDailyInstant(schedule.grace_ends_at); + if (schedule.latest_due_at !== null && latest === null) runtimeDailyReject("invalid_runtime_daily_time"); + if (schedule.next_due_at !== null && next === null) runtimeDailyReject("invalid_runtime_daily_time"); + if (schedule.grace_ends_at !== null && grace === null) runtimeDailyReject("invalid_runtime_daily_time"); + // latest_due is the last cron time already reached. next_due and grace_ends are plans. + if (latest !== null && latest > observedMs + RUNTIME_DAILY_FUTURE_SKEW_MS) runtimeDailyReject("invalid_runtime_daily_time"); + if (next !== null && next <= observedMs) runtimeDailyReject("invalid_runtime_daily_time"); + if (latest !== null && next !== null && next <= latest) runtimeDailyReject("invalid_runtime_daily_time"); + if (latest !== null && grace !== null && grace < latest) runtimeDailyReject("invalid_runtime_daily_time"); +} + +// These additional constraints describe only the merged Schwab projector. The +// original LongBridge key sets, time tolerance and receipt protocol stay intact. +export function validateSchwabRuntimeDailyRun(run, observedMs, started, finished) { + const evidence = run.evidence; + const confirmations = { + not_due: ["not_applicable"], no_action: ["not_applicable"], no_signal: ["not_applicable"], no_rebalance: ["not_applicable"], + risk_blocked: ["not_applicable"], submitted: ["not_observed"], broker_acknowledged: ["acknowledged"], + partially_filled: ["partially_filled"], filled: ["filled"], reconciliation_required: ["reconciliation_required"], + failed: ["not_applicable", "not_observed", "reconciliation_required"], + }; + if ((run.run_id !== null && !/^run\.[0-9a-f]{32}$/.test(run.run_id)) + || run.source_object !== null || run.source_objects.length !== 0 || run.object_updated_at !== null + || run.execution_lane === "paper" || run.run_time_known !== (started !== null || finished !== null)) runtimeDailyReject("invalid_runtime_daily_run"); + if ((started !== null && started > observedMs) || (finished !== null && finished > observedMs)) runtimeDailyReject("invalid_runtime_daily_time"); + if (run.report_status !== null && !["ok", "skipped", "success", "completed", "no_action", "error", "failed", "failure", "cancelled", "canceled", "timed_out"].includes(run.report_status)) runtimeDailyReject("invalid_runtime_daily_run"); + if (evidence.execution_status !== null && !["no_op", "no_action", "no_signal", "no_rebalance", "skipped", "outside_market_hours", "submitted", "pending_reconciliation", "reconciliation_required", "unknown", "unknown_order", "blocked", "risk_blocked", "error", "failed", "failure", "dry_run", "previewed", "partial", "partially_filled", "filled", "completed"].includes(evidence.execution_status)) runtimeDailyReject("invalid_runtime_daily_run"); + if (!runtimeDailyChoice(evidence.receipt_state, ["valid", "missing", "invalid"])) runtimeDailyReject("invalid_runtime_daily_run"); + if (evidence.receipt_state === "valid") { + if (typeof evidence.receipt_id !== "string" || !/^execution-receipt\.[0-9a-f]{32}$/.test(evidence.receipt_id) + || started === null || finished === null + || !Object.prototype.hasOwnProperty.call(confirmations, evidence.receipt_outcome) + || !confirmations[evidence.receipt_outcome].includes(evidence.receipt_broker_confirmation)) runtimeDailyReject("invalid_runtime_daily_run"); + } else if (evidence.receipt_id !== null || evidence.receipt_outcome !== null || evidence.receipt_broker_confirmation !== null) runtimeDailyReject("invalid_runtime_daily_run"); + const completedReceipt = { no_submission: "no_action", no_signal: "no_signal", no_rebalance: "no_rebalance", filled: "filled" }; + if (Object.prototype.hasOwnProperty.call(completedReceipt, run.activity) + && (evidence.receipt_state !== "valid" || evidence.receipt_outcome !== completedReceipt[run.activity] + || !["ok", "skipped", "success", "completed", "no_action"].includes(run.report_status) + || run.execution_lane !== "live" || runtimeDailyRunIssue(run) !== null)) runtimeDailyReject("runtime_daily_summary_conflict"); + if (["no_submission", "no_signal", "no_rebalance"].includes(run.activity) + && (evidence.broker_submission_done !== false || evidence.action_done === true || evidence.orders_pending_count !== 0 + || (evidence.execution_status !== null && !["no_op", "no_action", "no_signal", "no_rebalance", "skipped", "outside_market_hours", "completed"].includes(evidence.execution_status)))) runtimeDailyReject("runtime_daily_summary_conflict"); +} + +export function validateSchwabRuntimeDailySummary(raw, record, runs, observedMs) { + if (raw.completeness !== (record.completeness === "complete" ? "complete" : "incomplete")) runtimeDailyReject("runtime_daily_summary_conflict"); + const runSignatures = new Map(); + for (const run of runs) { + if (run.run_id === null) continue; + const signature = canonicalResearchTaskJson(run); + const previous = runSignatures.get(run.run_id); + if (previous !== undefined && previous !== signature && !record.conflicts.includes(run.run_id)) runtimeDailyReject("runtime_daily_summary_conflict"); + runSignatures.set(run.run_id, signature); + } + const incomplete = raw.read_errors.length > 0 || record.conflicts.length > 0 || record.schedule.state === "unevaluable" + || record.excluded_reports.some(item => item.reason !== "other_business_date") + || runs.some(run => run.run_id === null || run.evidence.receipt_state !== "valid" || !run.run_time_known); + if (record.completeness === "complete" && incomplete) runtimeDailyReject("runtime_daily_summary_conflict"); + // A run that only finishes after the due time cannot prove that due cycle. + // This is a receiver invariant of the existing producer, not a new scheduler. + const latest = runtimeDailyInstant(record.schedule.latest_due_at); + const covering = latest === null ? [] : runs.filter(run => { + const started = runtimeDailyInstant(run.started_at), finished = runtimeDailyInstant(run.finished_at); + return started !== null && finished !== null && started >= latest && finished <= observedMs + && runtimeDailyToday(started) === record.business_date; + }); + const quiet = ["no_submission", "no_signal", "no_rebalance"]; + if ([...quiet, "filled", ...RUNTIME_DAILY_SCHEDULE_ONLY].includes(record.status) + && record.completeness !== "complete") runtimeDailyReject("runtime_daily_summary_conflict"); + if ([...quiet, "filled"].includes(record.status)) { + if (record.kind !== "run" || covering.length === 0 || covering.some(run => run.execution_lane !== "live")) runtimeDailyReject("runtime_daily_summary_conflict"); + if (quiet.includes(record.status) && (!['due', 'within_grace'].includes(record.schedule.state) + || covering.some(run => run.activity !== record.status))) runtimeDailyReject("runtime_daily_summary_conflict"); + if (record.status === "filled" && !covering.some(run => run.activity === "filled")) runtimeDailyReject("runtime_daily_summary_conflict"); + } + if (RUNTIME_DAILY_SCHEDULE_ONLY.includes(record.status) + && (record.kind !== "schedule" || record.schedule.state !== record.status || covering.length > 0)) runtimeDailyReject("runtime_daily_summary_conflict"); +} + +export function validateSchwabRuntimeDailySchedule(schedule, observedMs) { + const { state, reason, publication_grace_ended: ended, expected_window: window } = schedule; + const latest = schedule.latest_due_at === null ? null : runtimeDailyInstant(schedule.latest_due_at); + const next = schedule.next_due_at === null ? null : runtimeDailyInstant(schedule.next_due_at); + const grace = schedule.grace_ends_at === null ? null : runtimeDailyInstant(schedule.grace_ends_at); + const reasons = { due: ["publication_grace_ended"], within_grace: ["publication_grace_open"], + not_due: ["before_schedule", "no_cron_on_business_date"], market_closed: ["market_closed"], + outside_window: ["outside_expected_window"], unevaluable: ["schedule_missing", "schedule_invalid"] }; + if (!reasons[state]?.includes(reason) + || (latest !== null && (latest > observedMs || runtimeDailyToday(latest) !== schedule.business_date)) + || (next !== null && (next <= observedMs || runtimeDailyToday(next) !== schedule.business_date))) runtimeDailyReject("invalid_runtime_daily_projection"); + let valid; + if (state === "unevaluable") valid = latest === null && next === null && grace === null && ended === null && window === "unspecified"; + else if (state === "due" || state === "within_grace") valid = latest !== null && grace !== null && next === null && window !== "outside" + && (state === "due" ? ended === true && grace <= observedMs : ended === false && grace > observedMs); + else if (state === "not_due") valid = latest === null && grace === null && ended === null && window !== "outside" + && ((reason === "before_schedule") === (next !== null)); + else if (state === "market_closed") valid = latest !== null && grace === null && ended === null; + else valid = window === "outside" && ended === null && ((latest === null) === (grace === null)) && !(grace !== null && next !== null); + if (!valid) runtimeDailyReject("invalid_runtime_daily_projection"); +} + +export function normalizeRuntimeDailyRun(run, observedMs, target) { + if (!runtimeDailyExactKeys(run, [ + "run_id", "source_object", "source_objects", "started_at", "finished_at", "object_updated_at", + "report_status", "execution_lane", "activity", "run_time_known", "evidence", + ])) runtimeDailyReject("invalid_runtime_daily_run"); + if (run.run_id !== null && !runtimeDailyBoundedText(run.run_id, 120)) runtimeDailyReject("invalid_runtime_daily_run"); + if (run.source_object !== null && !runtimeDailyBoundedText(run.source_object, 512)) runtimeDailyReject("invalid_runtime_daily_run"); + if (!Array.isArray(run.source_objects) || run.source_objects.length > 8 || run.source_objects.some((item) => !runtimeDailyBoundedText(item, 512))) { + runtimeDailyReject("invalid_runtime_daily_run"); + } + const started = runtimeDailyOptionalInstant(run.started_at, observedMs); + const finished = runtimeDailyOptionalInstant(run.finished_at, observedMs); + runtimeDailyOptionalInstant(run.object_updated_at, observedMs); + if (started !== null && finished !== null && finished < started) runtimeDailyReject("invalid_runtime_daily_time"); + if (run.report_status !== null && !runtimeDailyBoundedText(run.report_status, 64)) runtimeDailyReject("invalid_runtime_daily_run"); + if (!runtimeDailyChoice(run.execution_lane, RUNTIME_DAILY_LANES) || !runtimeDailyChoice(run.activity, RUNTIME_DAILY_ACTIVITIES)) { + runtimeDailyReject("invalid_runtime_daily_run"); + } + if (typeof run.run_time_known !== "boolean") runtimeDailyReject("invalid_runtime_daily_run"); + const evidenceKeys = [ + "execution_status", "broker_submission_done", "action_done", "orders_pending_count", "errors_present", + "receipt_outcome", "receipt_broker_confirmation", + ]; + if (target.platform === "schwab") evidenceKeys.push("receipt_state", "receipt_id"); + if (!runtimeDailyExactKeys(run.evidence, evidenceKeys)) runtimeDailyReject("invalid_runtime_daily_run"); + const evidence = run.evidence; + if (evidence.execution_status !== null && !runtimeDailyBoundedText(evidence.execution_status, 64)) runtimeDailyReject("invalid_runtime_daily_run"); + for (const field of ["broker_submission_done", "action_done"]) { + if (evidence[field] !== null && typeof evidence[field] !== "boolean") runtimeDailyReject("invalid_runtime_daily_run"); + } + if (evidence.orders_pending_count !== null && (!Number.isInteger(evidence.orders_pending_count) || evidence.orders_pending_count < 0 || evidence.orders_pending_count > 100000)) { + runtimeDailyReject("invalid_runtime_daily_run"); + } + if (typeof evidence.errors_present !== "boolean") runtimeDailyReject("invalid_runtime_daily_run"); + for (const field of ["receipt_outcome", "receipt_broker_confirmation"]) { + if (evidence[field] !== null && !runtimeDailyBoundedText(evidence[field], 64)) runtimeDailyReject("invalid_runtime_daily_run"); + } + if (target.platform === "schwab") validateSchwabRuntimeDailyRun(run, observedMs, started, finished); + return { + run_id: run.run_id, + source_object: run.source_object, + source_objects: [...run.source_objects], + started_at: run.started_at, + finished_at: run.finished_at, + object_updated_at: run.object_updated_at, + report_status: run.report_status, + execution_lane: run.execution_lane, + activity: run.activity, + run_time_known: run.run_time_known, + evidence: { ...evidence }, + }; +} + +export function normalizeRuntimeDailyProjection(raw, now = Date.now(), registeredTarget = null) { + if (!runtimeDailyExactKeys(raw, ["platform", "observed_at", "completeness", "read_errors", "records", "unmatched_reports"])) { + runtimeDailyReject("invalid_runtime_daily_projection"); + } + const target = registeredTarget || runtimeDailyTarget(raw.platform); + if (target && raw.platform !== target.platform) runtimeDailyReject("invalid_runtime_daily_target"); + if (!target || !runtimeDailyChoice(raw.completeness, ["complete", "incomplete"])) { + runtimeDailyReject("invalid_runtime_daily_projection"); + } + const observedMs = runtimeDailyInstant(raw.observed_at); + if (observedMs === null) runtimeDailyReject("invalid_runtime_daily_time"); + if (observedMs > now + RUNTIME_DAILY_FUTURE_SKEW_MS) runtimeDailyReject("runtime_daily_future_observation"); + if (!Array.isArray(raw.read_errors) || raw.read_errors.length > RUNTIME_DAILY_MAX_LIST || raw.read_errors.some((item) => !runtimeDailyBoundedText(item, 120))) { + runtimeDailyReject("invalid_runtime_daily_projection"); + } + if (!Array.isArray(raw.records) || raw.records.length !== 1) runtimeDailyReject("invalid_runtime_daily_target"); + if (!Array.isArray(raw.unmatched_reports) || raw.unmatched_reports.length > RUNTIME_DAILY_MAX_LIST) { + runtimeDailyReject("invalid_runtime_daily_projection"); + } + const record = raw.records[0]; + if (!runtimeDailyExactKeys(record, [ + "platform", "target_key", "target", "business_date", "timezone", "observed_at", "status", "kind", + "completeness", "execution_lane", "schedule", "runs", "excluded_reports", "conflicts", "fills", + ])) runtimeDailyReject("invalid_runtime_daily_projection"); + if (record.platform !== target.platform || record.target_key !== target.target_key || record.timezone !== RUNTIME_DAILY_TIMEZONE) { + runtimeDailyReject(record.timezone === RUNTIME_DAILY_TIMEZONE ? "invalid_runtime_daily_target" : "invalid_runtime_daily_timezone"); + } + if (!runtimeDailyExactKeys(record.target, ["service", "strategy_profile", "account_scope"])) runtimeDailyReject("invalid_runtime_daily_target"); + if (record.target.service !== target.service || record.target.strategy_profile !== target.strategy_profile || record.target.account_scope !== target.account_scope) { + runtimeDailyReject("invalid_runtime_daily_target"); + } + const businessDate = runtimeDailyCalendarDate(record.business_date); + if (!businessDate || businessDate > runtimeDailyToday(now)) runtimeDailyReject("invalid_runtime_daily_date"); + if (target.platform === "schwab" && businessDate > runtimeDailyToday(observedMs)) runtimeDailyReject("invalid_runtime_daily_date"); + if (record.observed_at !== raw.observed_at) runtimeDailyReject("invalid_runtime_daily_time"); + if (!runtimeDailyChoice(record.status, RUNTIME_DAILY_STATUSES) || !runtimeDailyChoice(record.kind, RUNTIME_DAILY_KINDS)) { + runtimeDailyReject("invalid_runtime_daily_projection"); + } + if (!runtimeDailyChoice(record.completeness, ["complete", "incomplete", "insufficient"]) || !runtimeDailyChoice(record.execution_lane, RUNTIME_DAILY_LANES)) { + runtimeDailyReject("invalid_runtime_daily_projection"); + } + if (!runtimeDailyExactKeys(record.schedule, [ + "state", "business_date", "timezone", "latest_due_at", "next_due_at", "grace_ends_at", + "publication_grace_ended", "expected_window", "reason", + ])) runtimeDailyReject("invalid_runtime_daily_projection"); + const schedule = record.schedule; + if (!runtimeDailyChoice(schedule.state, RUNTIME_DAILY_SCHEDULE_STATES) || schedule.business_date !== businessDate || schedule.timezone !== RUNTIME_DAILY_TIMEZONE) { + runtimeDailyReject("invalid_runtime_daily_projection"); + } + runtimeDailyScheduleInstants(schedule, observedMs); + if (schedule.publication_grace_ended !== null && typeof schedule.publication_grace_ended !== "boolean") { + runtimeDailyReject("invalid_runtime_daily_projection"); + } + if (!runtimeDailyChoice(schedule.expected_window, ["unspecified", "inside", "outside"])) { + runtimeDailyReject("invalid_runtime_daily_projection"); + } + if (schedule.reason !== null && !runtimeDailyBoundedText(schedule.reason, 120)) runtimeDailyReject("invalid_runtime_daily_projection"); + if (target.platform === "schwab") validateSchwabRuntimeDailySchedule(schedule, observedMs); + if (!Array.isArray(record.runs) || record.runs.length > RUNTIME_DAILY_MAX_RUNS) runtimeDailyReject("runtime_daily_run_limit"); + const runs = record.runs.map((run) => normalizeRuntimeDailyRun(run, observedMs, target)); + if (!Array.isArray(record.excluded_reports) || record.excluded_reports.length > RUNTIME_DAILY_MAX_LIST) runtimeDailyReject("invalid_runtime_daily_projection"); + const excluded = record.excluded_reports.map((item) => { + const keys = Object.keys(item || {}); + const allowed = keys.length === 3 ? ["run_id", "reason", "business_date"] : ["run_id", "reason"]; + if (!runtimeDailyExactKeys(item, allowed) || !runtimeDailyBoundedText(item.reason, 64)) runtimeDailyReject("invalid_runtime_daily_projection"); + if (item.run_id !== null && !runtimeDailyBoundedText(item.run_id, 120)) runtimeDailyReject("invalid_runtime_daily_projection"); + if (allowed.length === 3 && runtimeDailyCalendarDate(item.business_date) === null) runtimeDailyReject("invalid_runtime_daily_date"); + if (target.platform === "schwab" && (allowed.length !== 2 + || !runtimeDailyChoice(item.reason, ["missing_run_time", "invalid_run_time", "inverted_run_time", "future_run_time", "other_business_date"]) + || (item.run_id !== null && !/^run\.[0-9a-f]{32}$/.test(item.run_id)))) runtimeDailyReject("invalid_runtime_daily_projection"); + return allowed.length === 3 + ? { run_id: item.run_id, reason: item.reason, business_date: item.business_date } + : { run_id: item.run_id, reason: item.reason }; + }); + if (!Array.isArray(record.conflicts) || record.conflicts.length > RUNTIME_DAILY_MAX_LIST || record.conflicts.some((item) => !runtimeDailyBoundedText(item, 160))) { + runtimeDailyReject("invalid_runtime_daily_projection"); + } + if (["no_submission", "no_signal", "no_rebalance", "filled", ...RUNTIME_DAILY_SCHEDULE_ONLY].includes(record.status) + && (record.conflicts.length || runs.some(run => runtimeDailyRunIssue(run) !== null))) { + runtimeDailyReject("runtime_daily_summary_conflict"); + } + if (!runtimeDailyExactKeys(record.fills, ["source", "records", "count"]) || record.fills.source !== "not_connected" || record.fills.count !== null || !Array.isArray(record.fills.records) || record.fills.records.length !== 0) { + runtimeDailyReject("invalid_runtime_daily_fills"); + } + const unmatched = raw.unmatched_reports.map((item) => { + if (target.platform === "schwab") { + if (!runtimeDailyExactKeys(item, ["reason"]) || !runtimeDailyChoice(item.reason, ["invalid_report", "wrong_platform", "wrong_target", "invalid_source_object"])) runtimeDailyReject("invalid_runtime_daily_projection"); + return { reason: item.reason }; + } + if (!runtimeDailyExactKeys(item, ["run_id", "platform", "service", "strategy_profile", "account_scope", "reason"])) { + runtimeDailyReject("invalid_runtime_daily_projection"); + } + for (const field of ["platform", "service", "strategy_profile", "account_scope", "reason"]) { + if (item[field] !== null && !runtimeDailyBoundedText(item[field], 120)) runtimeDailyReject("invalid_runtime_daily_projection"); + } + if (item.run_id !== null && !runtimeDailyBoundedText(item.run_id, 120)) runtimeDailyReject("invalid_runtime_daily_projection"); + return { ...item }; + }); + if (target.platform === "schwab") { + if (raw.read_errors.some(item => !["report_read_error", "coverage_unconfirmed", "report_rejected"].includes(item)) + || record.conflicts.some(item => !/^run\.[0-9a-f]{32}$/.test(item)) + || record.execution_lane === "paper") runtimeDailyReject("invalid_runtime_daily_projection"); + const lanes = new Set(runs.map(run => run.execution_lane)); + if (record.execution_lane !== (lanes.size === 1 ? runs[0].execution_lane : "insufficient")) runtimeDailyReject("invalid_runtime_daily_projection"); + validateSchwabRuntimeDailySummary(raw, record, runs, observedMs); + } + return { + platform: target.platform, + observed_at: raw.observed_at, + completeness: raw.completeness, + read_errors: [...raw.read_errors], + records: [{ + platform: target.platform, + target_key: target.target_key, + target: { ...record.target }, + business_date: businessDate, + timezone: RUNTIME_DAILY_TIMEZONE, + observed_at: raw.observed_at, + status: record.status, + kind: record.kind, + completeness: record.completeness, + execution_lane: record.execution_lane, + schedule: { ...schedule }, + runs, + excluded_reports: excluded, + conflicts: [...record.conflicts], + fills: { source: "not_connected", records: [], count: null }, + }], + unmatched_reports: unmatched, + }; +} + +export async function runtimeDailyTrustedBinding(env, target, accountOptions = null) { + const config = accountOptions ? { options: accountOptions } : await loadAccountOptionsConfig(env); + const accountKey = runtimeDailyBoundAccountKey(config?.options, target.platform); + if (!accountKey) return null; + if (target.platform === "longbridge") return { accountKey, fingerprint: null }; + // The payload never supplies this identity. Reuse the protected account-facts + // contract rather than inferring a physical account from service/live labels. + const bindings = await loadAccountFactsBindings(env, { propagateReadErrors: true }); + const matches = (bindings?.bindings || []).filter(item => item.platform === target.platform + && item.service_name === target.service && item.account_scope === target.account_scope); + const option = config.options.schwab.find(item => item.key === accountKey); + if (matches.length !== 1 || !accountFactsOptionMatchesBinding(option, matches[0])) return null; + return { accountKey, sourceBindingId: matches[0].source_binding.id, + fingerprint: await sha256Hex(canonicalResearchTaskJson({ target, binding: matches[0] })) }; +} + +// A protected registration opts an account into this read model; account-facts +// bindings alone do not register a daily source or authorize a publisher. +export async function runtimeDailyRegisteredBindings(env, accountOptions) { + if (!hasConfigStore(env)) throw Error("runtime_daily_bindings_store_unavailable"); + const registry = await readConfigJson(env, "runtime_daily_bindings"); + if (registry === null) return []; + if (!runtimeDailyExactKeys(registry, ["schema_version", "bindings"]) + || registry.schema_version !== "qsl_runtime_daily_bindings.v1" || !Array.isArray(registry.bindings) || registry.bindings.length > 64) + throw Error("runtime_daily_bindings_invalid"); + if (!registry.bindings.length) return []; + const facts = await loadAccountFactsBindings(env, { propagateReadErrors: true }); + const entries = registry.bindings.map(item => { + const platform = item?.platform || "ibkr"; + const keys = ["account_key", "target_id", "source_binding_id", "strategy_profile", "target_key", ...(Object.hasOwn(item || {}, "platform") ? ["platform"] : [])]; + if (!runtimeDailyExactKeys(item, keys) || !["ibkr", "longbridge", "firstrade", "binance"].includes(platform) + || !runtimeDailyBoundedText(item.account_key, 120) || !runtimeDailyBoundedText(item.target_id, 120) + || typeof item.source_binding_id !== "string" || !/^[a-f0-9]{64}$/.test(item.source_binding_id) + || !runtimeDailyBoundedText(item.strategy_profile, 120)) throw Error("runtime_daily_bindings_invalid"); + const target = parseRuntimeDailyTargetKey(item.target_key, platform); + if (!target || (platform === "ibkr" && item.target_key.split("|").length !== 4) + || target.strategy_profile !== item.strategy_profile + || ![target.service, target.strategy_profile, target.account_scope].every(value => /^[A-Za-z0-9][A-Za-z0-9._-]{0,119}$/.test(value))) + throw Error("runtime_daily_bindings_invalid"); + return { item, target, platform }; + }); + let binance = null, binanceReadFailed = false; + if (entries.some(entry => entry.platform === "binance")) { + try { binance = await trustedBinanceFactsBinding(env); } + catch { binanceReadFailed = true; } + } + return Promise.all(entries.map(async ({ item, target, platform }) => { + if (platform === "binance" && binanceReadFailed) return { platform, target_key: item.target_key, account_key: item.account_key, status: "read_failed" }; + const collision = entries.some(other => other.item !== item && (other.item.account_key === item.account_key && other.platform === platform + || other.item.target_key === item.target_key || other.item.target_id === item.target_id + || other.item.source_binding_id === item.source_binding_id)); + const resolved = platform === "binance" ? {ok: Boolean(binance), binding: binance} + : resolveTrustedAccountFactsBinding(facts, item.target_id, item.source_binding_id); + const options = (accountOptions?.[platform] || []).filter(option => option.key === item.account_key); + const b = resolved.binding; + const optionMatches = Boolean(b) && (platform === "binance" ? options.length === 1 && binanceFactsOptionMatches(options[0], b) + : options.length === 1 && accountFactsOptionMatchesBinding(options[0], b)); + const binding = resolved.ok && b.platform === platform && b.account_key === item.account_key + && b.target_id === item.target_id && b.source_binding.id === item.source_binding_id && optionMatches + && (target.service === b.service_name || (platform === "binance" && b.service_name === "")) + && (target.account_scope === b.account_scope || (platform === "longbridge" && target.account_scope === "hk" && b.account_scope === "HK")) ? b : null; + const sharedPhysicalAccount = platform === "ibkr" && binding && entries.some(other => other.platform === "ibkr" && other.item !== item && other.item.account_key !== item.account_key + && facts?.bindings.some(otherBinding => otherBinding.platform === "ibkr" && otherBinding.account_key === other.item.account_key + && otherBinding.account_selector === binding.account_selector)); + if (collision || !binding || sharedPhysicalAccount) return { platform, target_key: item.target_key, account_key: item.account_key, status: "conflict" }; + return { platform, target_key: item.target_key, account_key: item.account_key, status: "bound", target, + sourceBindingId: item.source_binding_id, + fingerprint: await sha256Hex(canonicalResearchTaskJson({ target, registration: item, binding })) }; + })); +} + +export async function runtimeDailyPublicBindings(env, accountOptions, readRegistered = () => runtimeDailyRegisteredBindings(env, accountOptions)) { + const legacy = await Promise.all(Object.values(RUNTIME_DAILY_TARGETS).map(async target => { + try { + const binding = await runtimeDailyTrustedBinding(env, target, accountOptions); + return { platform: target.platform, target_key: target.target_key, account_key: binding?.accountKey || null, + status: binding ? "bound" : "unresolved" }; + } catch { + return { platform: target.platform, target_key: target.target_key, account_key: null, status: "read_failed" }; + } + })); + try { + const registered = await readRegistered(); + return [...legacy, ...registered.map(({ platform, target_key, account_key, status }) => ({ platform, target_key, account_key, status }))]; + } catch { + return [...legacy, { platform: "ibkr", target_key: "", account_key: null, status: "read_failed" }]; + } +} + +export function runtimeDailyStoredProjection(raw, target, binding) { + if (target.platform === "longbridge" && !binding.fingerprint) return raw; + if (!runtimeDailyExactKeys(raw, ["binding_fingerprint", "projection"])) throw new HttpError("runtime_daily_record_invalid", 503); + if (raw.binding_fingerprint !== binding.fingerprint) throw new HttpError("runtime_daily_binding_changed", 409); + return raw.projection; +} + +export function runtimeDailyUnattributedResponse() { + return json({ ok: false, error: "runtime_daily_account_unattributed" }, 409); +} + +export function runtimeDailyPublicRecord(record) { + return { + target_key: record.target_key, + service: record.target.service, + strategy_profile: record.target.strategy_profile, + account_scope: record.target.account_scope, + business_date: record.business_date, + timezone: record.timezone, + observed_at: record.observed_at, + status: record.status, + kind: record.kind, + completeness: record.completeness, + execution_lane: record.execution_lane, + schedule: { + state: record.schedule.state, + business_date: record.schedule.business_date, + timezone: record.schedule.timezone, + latest_due_at: record.schedule.latest_due_at, + next_due_at: record.schedule.next_due_at, + grace_ends_at: record.schedule.grace_ends_at, + publication_grace_ended: record.schedule.publication_grace_ended, + expected_window: record.schedule.expected_window, + // Only Schwab's validated, fixed closed-session facts may reach health UI. + // Legacy LongBridge reasons remain private bounded diagnostic text. + ...(record.platform === "schwab" && ["no_cron_on_business_date", "market_closed"].includes(record.schedule.reason) + ? { reason: record.schedule.reason } : {}), + }, + runs: record.runs.map((run) => ({ + run_id: run.run_id, + started_at: run.started_at, + finished_at: run.finished_at, + activity: run.activity, + execution_lane: run.execution_lane, + errors_present: run.evidence.errors_present, + ...(record.platform === "schwab" ? { issue: runtimeDailyRunIssue(run) } : {}), + })), + conflict_count: record.conflicts.length, + excluded_count: record.excluded_reports.length, + }; +} + +// The directory contains only successfully discovered or registered record keys. +// Unknown coverage remains null; it must never look like an empty history. +export async function runtimeDailyHistory(env, target) { + const prefix = `${RUNTIME_DAILY_KEY_PREFIX}${target.target_key}:`; + try { + const listing = await directoryList(env, prefix, RUNTIME_DAILY_HISTORY_PAGE * RUNTIME_DAILY_HISTORY_MAX_PAGES); + const dates = listing.keys.map(entry => runtimeDailyCalendarDate(entry.name.slice(prefix.length))).filter(Boolean).sort(); + return { available_from: dates[0] || null, available_through: dates.at(-1) || null, + stored_days: dates.length, truncated: false }; + } catch { + return null; + } +} + +export function runtimeDailyReadSummary(status, reason, observedRunCount = null) { + return { + source: "stored_runtime_daily_projection", status, reason, + observed_run_count: observedRunCount, + total_run_count: status === "complete" ? observedRunCount : null, + }; +} + +export function runtimeDailyReadStatus(stored, now) { + const record = stored.records[0]; + // Preserve partial observations without turning their length into a day total. + // Read completeness and execution outcome/freshness are separate facts. + const complete = stored.completeness === "complete" && record.completeness === "complete" + && !stored.read_errors.length && !stored.unmatched_reports.length && !record.conflicts.length + && record.kind !== "incomplete" && !["read_incomplete", "missing_report", "insufficient", "conflict"].includes(record.status); + const observedMs = runtimeDailyInstant(stored.observed_at); + const today = record.business_date === runtimeDailyToday(now); + let dataStatus = "historical"; + if (today) { + const fresh = observedMs !== null && observedMs <= now + RUNTIME_DAILY_FUTURE_SKEW_MS && now - observedMs <= RUNTIME_DAILY_STALE_MS; + dataStatus = fresh ? "fresh" : "stale"; + } + return { + data_status: dataStatus, + read_summary: runtimeDailyReadSummary(complete ? "complete" : "partial", + complete ? null : "runtime_daily_incomplete", record.runs.length), + read_error_count: stored.read_errors.length, + // These producer codes contain no object paths, identities or exception text. + // Unknown legacy diagnostics remain private and retain an explicit error. + read_error_codes: stored.read_errors.map(error => { + if (["report_read_error", "coverage_unconfirmed", "report_rejected", "read_failed", "read_truncated", "list_failed"].includes(error)) return error; + if (error === "listing truncated") return "listing_truncated"; + if (error === "runtime_target_enabled_schedule_unverified") return "schedule_unverified"; + if (error === "runtime_target_disabled") return "target_disabled"; + return "source_read_error"; + }), + unmatched_count: stored.unmatched_reports.length, + record: runtimeDailyPublicRecord(record), + fills: { source: "not_connected", records: [], count: null }, + }; +} + +export async function syncRuntimeDailyResponse(request, env) { + if (!runtimeDailyReadModelEnabled(env)) return runtimeDailyDisabledResponse(); + const ibkrToken = String(env.IBKR_RUNTIME_DAILY_SYNC_TOKEN || ""); + const supplied = (request.headers.get("Authorization") || "").match(/^Bearer\s+(.+)$/i)?.[1] || ""; + const isIbkr = !!ibkrToken && supplied === ibkrToken && supplied !== String(env.EXECUTION_EVIDENCE_SYNC_TOKEN || ""); + if (isIbkr && [env.ACCOUNT_FACTS_SYNC_TOKEN, + env.IBKR_ACCOUNT_FACTS_SYNC_TOKEN, env.SCHWAB_ACCOUNT_FACTS_SYNC_TOKEN, + env.FIRSTRADE_ACCOUNT_FACTS_SYNC_TOKEN, env.BINANCE_ACCOUNT_FACTS_SYNC_TOKEN, + env.STRATEGY_SWITCH_SYNC_TOKEN, env.RECONCILIATION_RECOVERY_SYNC_TOKEN, + env.RECONCILIATION_RECOVERY_CONTROLLER_TOKEN, env.CYCLE_HEALTH_PROVISIONER_TOKEN] + .some(value => value && String(value) === ibkrToken)) { + throw new HttpError("runtime_daily_token_ambiguous", 503); + } + if (!isIbkr) requireDedicatedExecutionEvidenceSyncToken(request, env); + if (!hasConfigStore(env)) return json({ ok: false, error: "runtime daily KV is not configured" }, 503); + let raw; + try { + raw = await readBoundedJson(request, RUNTIME_DAILY_MAX_BODY_BYTES); + } catch (error) { + return json({ ok: false, error: error.message || "invalid_runtime_daily_projection" }, error.status || 400); + } + let target, binding, projection; + try { + const configured = String(env.IBKR_RUNTIME_DAILY_TARGET_KEY || ""); + let authorized; + try { authorized = configured.startsWith("[") || configured.startsWith("{") ? JSON.parse(configured) : [configured]; } + catch { throw new HttpError("runtime_daily_target_unauthorized", 403); } + const isGroups = authorized && !Array.isArray(authorized) && typeof authorized === "object"; + if (isGroups && !runtimeDailyExactKeys(authorized, ["ibkr", "execution"])) throw new HttpError("runtime_daily_target_unauthorized", 403); + const allowed = isGroups ? authorized[isIbkr ? "ibkr" : "execution"] : isIbkr ? authorized : []; + if (!Array.isArray(allowed) || allowed.length > 64 || new Set(allowed).size !== allowed.length + || allowed.some(key => typeof key !== "string" || !parseRuntimeDailyTargetKey(key, isIbkr ? "ibkr" : "longbridge") + || (isIbkr && key.split("|").length !== 4))) throw new HttpError("runtime_daily_target_unauthorized", 403); + const registeredRequest = raw?.records?.length === 1 && allowed.includes(raw.records[0]?.target_key); + if (isIbkr || registeredRequest) { + if (!registeredRequest || (isIbkr ? raw?.platform !== "ibkr" : raw?.platform === "ibkr")) throw new HttpError("runtime_daily_target_unauthorized", 403); + const options = (await loadAccountOptionsConfig(env)).options; + const matches = (await runtimeDailyRegisteredBindings(env, options)).filter(item => item.target_key === raw.records[0].target_key && item.platform === raw.platform); + if (matches.length !== 1 || matches[0].status !== "bound") return runtimeDailyUnattributedResponse(); + target = matches[0].target; + binding = { accountKey: matches[0].account_key, fingerprint: matches[0].fingerprint, sourceBindingId: matches[0].sourceBindingId }; + } else { + projection = normalizeRuntimeDailyProjection(raw); + target = runtimeDailyTarget(projection.platform); + binding = await runtimeDailyTrustedBinding(env, target); + } + } + catch (error) { + if (error instanceof HttpError) throw error; + return json({ ok: false, error: "runtime_daily_read_failed" }, 503); + } + if (!binding) return runtimeDailyUnattributedResponse(); + if (!projection) projection = normalizeRuntimeDailyProjection(raw, Date.now(), target); + if (target.platform === "schwab" || binding.sourceBindingId) { + // Reuse the existing protected source_binding.id, not a new identity or a + // body-supplied account. This also guards a new day with no cached fingerprint. + const sourceBindingId = request.headers.get("X-QSL-Source-Binding-ID"); + if (typeof sourceBindingId !== "string" || !/^[0-9a-f]{64}$/.test(sourceBindingId)) { + return json({ ok: false, error: "runtime_daily_source_binding_invalid" }, 400); + } + if (sourceBindingId !== binding.sourceBindingId) { + return json({ ok: false, error: "runtime_daily_source_binding_mismatch" }, 409); + } + } + const accountKey = binding.accountKey; + const businessDate = projection.records[0].business_date; + const store = configStore(env); + const key = runtimeDailyKey(businessDate, target); + const storedText = await store.get(key); + if (storedText !== null && storedText !== undefined) { + let storedRaw; + let storedProjection; + try { + storedRaw = JSON.parse(storedText); + storedProjection = normalizeRuntimeDailyProjection(runtimeDailyStoredProjection(storedRaw, target, binding), Date.now(), target); + } catch (error) { + if (error.message === "runtime_daily_binding_changed") return json({ ok: false, error: error.message }, 409); + return json({ ok: false, error: "runtime_daily_existing_record_invalid" }, 409); + } + if (storedProjection.records[0].business_date !== businessDate || storedProjection.platform !== target.platform + || storedProjection.records[0].target_key !== target.target_key) { + return json({ ok: false, error: "runtime_daily_existing_record_invalid" }, 409); + } + const storedObservedAt = runtimeDailyInstant(storedProjection.observed_at); + const incomingObservedAt = runtimeDailyInstant(projection.observed_at); + if (storedObservedAt === null || incomingObservedAt === null) { + return json({ ok: false, error: "runtime_daily_existing_record_invalid" }, 409); + } + if (incomingObservedAt < storedObservedAt) { + return json({ ok: false, error: "runtime_daily_stale_observation" }, 409); + } + if (incomingObservedAt === storedObservedAt) { + if (canonicalResearchTaskJson(projection) === canonicalResearchTaskJson(storedProjection)) { + return json({ + ok: true, + stored: true, + business_date: businessDate, + platform: target.platform, + target_key: target.target_key, + account_key: accountKey, + ...(binding.fingerprint && target.platform !== "schwab" ? { observed_at: projection.observed_at } : {}), + }); + } + return json({ ok: false, error: "runtime_daily_observation_conflict" }, 409); + } + } + await writeConfigJson(env, key, binding.fingerprint + ? { binding_fingerprint: binding.fingerprint, projection } : projection); + return json({ + ok: true, + stored: true, + business_date: businessDate, + platform: target.platform, + target_key: target.target_key, + account_key: accountKey, + ...(binding.fingerprint && target.platform !== "schwab" ? { observed_at: projection.observed_at } : {}), + }); +} + +export async function runtimeDailyResponse(request, env, url, context = null) { + if (!runtimeDailyReadModelEnabled(env)) return runtimeDailyDisabledResponse(); + const session = await (context ? context.readSession() : readSession(request, env)); + if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); + if (!hasConfigStore(env)) return json({ ok: false, error: "runtime daily KV is not configured" }, 503); + const platforms = url.searchParams.getAll("platform"); + const accounts = url.searchParams.getAll("account_key"); + if (platforms.length > 1 || accounts.length > 1 || url.searchParams.getAll("date").length !== 1 + || (platforms.length !== accounts.length)) return json({ ok: false, error: "invalid_runtime_daily_selection" }, 400); + // Legacy date-only reads always mean PAPER, never the first configured target. + let target = platforms.length ? runtimeDailyTarget(platforms[0]) : RUNTIME_DAILY_TARGET; + if ((!target && !["ibkr", "firstrade", "binance"].includes(platforms[0])) || (accounts.length && !runtimeDailyBoundedText(accounts[0], 120))) return json({ ok: false, error: "invalid_runtime_daily_selection" }, 400); + const historyOptions = url.searchParams.getAll("include_history"); + if (historyOptions.length > 1 || historyOptions.some(value => !["true", "false"].includes(value))) + return json({ ok: false, error: "invalid_runtime_daily_selection" }, 400); + const includeHistory = historyOptions[0] !== "false"; + const businessDate = runtimeDailyCalendarDate(url.searchParams.get("date")); + if (!businessDate || businessDate > runtimeDailyToday()) return json({ ok: false, error: "invalid_runtime_daily_date" }, 400); + let binding; + try { + const config = await (context ? context.readOptions() : loadAccountOptionsConfig(env)); + const matches = accounts.length ? (await (context ? context.readRegistered() : runtimeDailyRegisteredBindings(env, config.options))) + .filter(item => item.platform === platforms[0] && item.account_key === accounts[0]) : []; + if (matches.length) { + if (matches.length !== 1 || matches[0].status !== "bound") return runtimeDailyUnattributedResponse(); + binding = { accountKey: matches[0].account_key, fingerprint: matches[0].fingerprint }; + target = matches[0].target; + } else if (platforms[0] === "ibkr" || !target) return runtimeDailyUnattributedResponse(); + else binding = await runtimeDailyTrustedBinding(env, target, config.options); + } + catch { return json({ ok: false, error: "runtime_daily_read_failed" }, 503); } + if (!binding || (accounts.length && accounts[0] !== binding.accountKey)) return runtimeDailyUnattributedResponse(); + const accountKey = binding.accountKey; + let stored; + try { + stored = await readConfigJson(env, runtimeDailyKey(businessDate, target)); + } catch { + return json({ ok: false, error: "runtime_daily_read_failed", + read_summary: runtimeDailyReadSummary("read_failed", "runtime_daily_read_failed") }, 503); + } + const base = { + ok: true, + date: businessDate, + timezone: RUNTIME_DAILY_TIMEZONE, + account_key: accountKey, + platform: target.platform, + target_key: target.target_key, + history: includeHistory ? await runtimeDailyHistory(env, target) : null, + }; + if (stored === null || stored === undefined) { + return json({ ...base, data_status: "unavailable", record: null, fills: null, read_error_count: 0, unmatched_count: 0, + read_summary: runtimeDailyReadSummary("no_data", "runtime_daily_record_missing") }); + } + let projection; + try { + projection = normalizeRuntimeDailyProjection(runtimeDailyStoredProjection(stored, target, binding), Date.now(), target); + } catch (error) { + if (error.message === "runtime_daily_binding_changed") return json({ ok: false, error: error.message }, 409); + return json({ ok: false, error: "runtime_daily_record_invalid", + read_summary: runtimeDailyReadSummary("read_failed", "runtime_daily_record_invalid") }, 503); + } + if (projection.records[0].business_date !== businessDate || projection.platform !== target.platform + || projection.records[0].target_key !== target.target_key) { + return json({ ok: false, error: "runtime_daily_record_invalid", + read_summary: runtimeDailyReadSummary("read_failed", "runtime_daily_record_invalid") }, 503); + } + return json({ ...base, ...runtimeDailyReadStatus(projection, Date.now()) }); +} + +export async function syncExecutionEvidenceSourceResponse(request, env) { + requireDedicatedExecutionEvidenceSyncToken(request, env); + if (!hasConfigStore(env)) { + return json({ ok: false, error: "execution evidence KV is not configured" }, 503); + } + + const receiptId = request.headers.get("X-QSL-Execution-Evidence-Receipt"); + if (receiptId !== null && !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(receiptId)) { + return json({ ok: false, error: "invalid execution evidence receipt identifier" }, 400); + } + + let raw; + try { + raw = await readBoundedJson(request, EXECUTION_EVIDENCE_MAX_BODY_BYTES); + } catch (error) { + return json({ ok: false, error: error.message || "invalid execution evidence payload" }, error.status || 400); + } + + let source; + try { + source = normalizeExecutionEvidenceSourceSnapshot(raw, "execution evidence source snapshot"); + } catch (error) { + return json({ ok: false, error: error.message || "invalid execution evidence payload" }, 400); + } + + await writeConfigJson(env, executionEvidenceSourceKey(source.source_id), source); + try { + await appendAuditLog(env, { + ts: new Date().toISOString(), + login: "execution-evidence-source-sync", + action: "sync_execution_evidence_source", + source_id: source.source_id, + schema_version: source.schema_version, + deployment_count: source.deployments.length, + data_status: source.data_status, + ...(receiptId === null ? {} : { receipt_id: receiptId }), + }); + } catch { + // The source snapshot is accepted independently of optional audit retention. + } + return json({ + ok: true, + source_id: source.source_id, + schema_version: source.schema_version, + deployment_count: source.deployments.length, + generated_at: source.generated_at, + ...(receiptId === null ? {} : { receipt_id: receiptId }), + }); +} + +export async function executionEvidenceResponse(request, env) { + const session = await readSession(request, env); + if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); + if (!hasConfigStore(env)) return json(emptyExecutionEvidencePayload("snapshot_unavailable")); + return json(await aggregateExecutionEvidenceSources(env)); +} + +export async function aggregateExecutionEvidenceSources(env) { + const sources = await readExecutionEvidenceSources(env); + if (!sources.length) return emptyExecutionEvidencePayload("snapshot_unavailable"); + + const ttlSeconds = executionEvidenceStaleTtlSeconds(env); + const now = Date.now(); + const deployments = []; + const deploymentIds = new Set(); + const duplicateDeploymentIds = new Set(); + const errors = []; + const timestamps = []; + let hasReadySource = false; + let hasStaleSource = false; + + for (const source of sources) { + const freshness = controlPlaneSnapshotFreshness(source, ttlSeconds, now); + if (source.generated_at) timestamps.push(source.generated_at); + if (source.computed_at) timestamps.push(source.computed_at); + if (freshness.data_status === "ready") hasReadySource = true; + if (freshness.data_status === "stale") { + hasStaleSource = true; + errors.push("execution_evidence_source_stale"); + } + for (const deployment of source.deployments) { + if (deploymentIds.has(deployment.deployment_id)) { + duplicateDeploymentIds.add(deployment.deployment_id); + errors.push("execution_evidence_duplicate_deployment"); + continue; + } + deploymentIds.add(deployment.deployment_id); + deployments.push({ source_id: source.source_id, freshness, deployment }); + } + errors.push(...source.errors); + } + + const uniqueDeployments = deployments.filter((entry) => !duplicateDeploymentIds.has(entry.deployment.deployment_id)); + const dataStatus = hasStaleSource ? "stale" : (hasReadySource ? "ready" : "unavailable"); + return { + schema_version: EXECUTION_EVIDENCE_DASHBOARD_SCHEMA_VERSION, + generated_at: earliestControlPlaneTimestamp(timestamps), + computed_at: earliestControlPlaneTimestamp(timestamps), + data_status: dataStatus, + summary: normalizeExecutionEvidenceSummary(uniqueDeployments.map((entry) => entry.deployment)), + deployments: uniqueDeployments, + policy: { + execution_evidence_read_only: true, + p6_owner_decision_required: true, + limited_live_canary_active: false, + notice: "执行证据按策略、平台和执行通道分别记录;它不授权订单或实盘。", + }, + errors: uniqueStrings(errors), + }; +} + +export async function readExecutionEvidenceSources(env) { + const store = configStore(env); + if (!store) return []; + let listing; + try { + listing = await directoryList(env, EXECUTION_EVIDENCE_SOURCE_PREFIX, EXECUTION_EVIDENCE_MAX_SOURCES); + } catch { + return [emptyExecutionEvidenceSourceSnapshot("execution_evidence_source_list_unavailable")]; + } + const keys = Array.isArray(listing?.keys) ? listing.keys : []; + const sources = []; + for (const entry of keys.slice(0, EXECUTION_EVIDENCE_MAX_SOURCES)) { + const key = typeof entry?.name === "string" ? entry.name : ""; + if (!key.startsWith(EXECUTION_EVIDENCE_SOURCE_PREFIX)) continue; + try { + const stored = await readConfigJson(env, key); + if (!stored) throw new Error("indexed_record_unavailable"); + sources.push(normalizeExecutionEvidenceSourceSnapshot(stored, key)); + } catch { + sources.push(emptyExecutionEvidenceSourceSnapshot("execution_evidence_source_invalid")); + } + } + return sources; +} + +export function executionEvidenceSourceKey(sourceId) { + return `${EXECUTION_EVIDENCE_SOURCE_PREFIX}${sourceId}`; +} + +export function runtimeTargetLifecycleAuditState(source, ttlSeconds, now) { + if (!source) return null; + const state = structuredClone(source); + state.freshness = controlPlaneSnapshotFreshness(source, ttlSeconds, now).data_status; + delete state.generated_at; + delete state.computed_at; + for (const target of state.targets) { + if (target.deployment?.observed_at) { + target.deployment.observed_at = controlPlaneSnapshotFreshness({ + data_status: "ready", computed_at: target.deployment.observed_at, + }, ttlSeconds, now).data_status; + } + } + return JSON.stringify(state); +} + +export async function syncRuntimeTargetLifecycleSourceResponse(request, env) { + // This publisher has the same narrow scope as execution evidence: sanitized + // platform status only, never credentials, accounts, orders, or commands. + requireDedicatedExecutionEvidenceSyncToken(request, env); + if (!hasConfigStore(env)) { + return json({ ok: false, error: "runtime target lifecycle KV is not configured" }, 503); + } + let raw; + try { + raw = await readBoundedJson(request, RUNTIME_TARGET_LIFECYCLE_MAX_BODY_BYTES); + } catch (error) { + return json({ ok: false, error: error.message || "invalid runtime target lifecycle payload" }, error.status || 400); + } + let source; + try { + source = normalizeRuntimeTargetLifecycleSourceSnapshot(raw, "runtime target lifecycle source snapshot"); + } catch (error) { + return json({ ok: false, error: error.message || "invalid runtime target lifecycle payload" }, 400); + } + const sourceKey = runtimeTargetLifecycleSourceKey(source.source_id); + let previous = null; + try { + previous = normalizeRuntimeTargetLifecycleSourceSnapshot(await readConfigJson(env, sourceKey)); + } catch { + // Missing/unreadable history must not prevent a fresh, valid observation. + } + // Always retain the new evidence timestamps. Only the optional rolling audit + // skips unchanged polling records; operator actions are audited separately. + await writeConfigJson(env, sourceKey, source); + try { + const ttlSeconds = executionEvidenceStaleTtlSeconds(env); + const now = Date.now(); + if (runtimeTargetLifecycleAuditState(previous, ttlSeconds, now) !== runtimeTargetLifecycleAuditState(source, ttlSeconds, now)) { + await appendAuditLog(env, { + ts: new Date().toISOString(), + login: "runtime-target-lifecycle-source-sync", + action: "sync_runtime_target_lifecycle_source", + source_id: source.source_id, + schema_version: source.schema_version, + target_count: source.targets.length, + data_status: source.data_status, + }); + } + } catch { + // A valid no-order snapshot remains useful when convenience audit retention fails. + } + await maybeAutoDispatchAccountDiagnosis(env, source); + return json({ + ok: true, + source_id: source.source_id, + schema_version: source.schema_version, + target_count: source.targets.length, + generated_at: source.generated_at, + }); +} + +export async function runtimeReadbackResponse(request, env, url) { + const session = await readSession(request, env); + if (!session?.allowed) return json({ ok: false, error: "login required", error_state: "unauthorized" }, 401); + if ([...url.searchParams.keys()].some(key => key !== "scope") || url.searchParams.getAll("scope").length !== 1 + || !/^[a-z0-9_-]+:[a-zA-Z0-9_.-]+$/.test(url.searchParams.get("scope") || "")) + return json({ ok: false, error: "invalid_runtime_scope" }, 400); + const scope = url.searchParams.get("scope"); + try { + const config = await loadAccountOptionsConfig(env); + const accounts = Object.entries(config.options || {}).flatMap(([platform, rows]) => rows.map(row => ({ ...row, platform }))); + const matches = accounts.filter(row => `${row.platform}:${row.key}` === scope); + if (!matches.length) return json({ ok: false, error: "runtime_scope_not_found" }, 404); + if (matches.length !== 1) return json({ ok: false, error: "runtime_binding_conflict" }, 409); + if (!hasConfigStore(env)) return json({ ok: false, error: "runtime_readback_unavailable" }, 503); + const snapshot = await aggregateRuntimeTargetLifecycleSources(env); + if (snapshot.errors?.some(code => /source_list_unavailable|source_invalid/.test(code))) + return json({ ok: false, error: "runtime_readback_failed" }, 503); + return json(runtimeReadbackProjection(matches[0], accounts, snapshot)); + } catch { + return json({ ok: false, error: "runtime_readback_failed" }, 503); + } +} + +export async function runtimeTargetLifecycleResponse(request, env) { + const session = await readSession(request, env); + if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); + if (!hasConfigStore(env)) return json(emptyRuntimeTargetLifecyclePayload("snapshot_unavailable")); + return json(await aggregateRuntimeTargetLifecycleSources(env)); +} + +export function requireAccountDiagnosisToken(request, env) { + const expected = String(env.ACCOUNT_DIAGNOSIS_SYNC_TOKEN || ""); + if (!expected) throw new HttpError("account diagnosis 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("account diagnosis sync token is invalid", 401); +} + +export function accountDiagnosisRequestId(value) { + const text = String(value || "").trim().toLowerCase(); + if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(text)) { + throw new HttpError("invalid_account_diagnosis_request_id", 400); + } + return text; +} + +export function accountDiagnosisChecks(entry, dataStatus) { + const target = entry?.target?.target || {}; + const monitoring = entry?.target?.monitoring || {}; + const deployment = entry?.target?.deployment || {}; + return { + configured_state: ["enabled", "disabled"].includes(target.configured_state) ? target.configured_state : "unknown", + runtime_enabled: typeof deployment.runtime_enabled === "boolean" ? deployment.runtime_enabled : null, + scheduler_state: ["enabled", "paused"].includes(deployment.scheduler_state) ? deployment.scheduler_state : "unknown", + runtime_guard: ["pass", "attention", "unavailable"].includes(monitoring.runtime_guard) ? monitoring.runtime_guard : "unavailable", + execution_heartbeat: ["pass", "attention", "unavailable", "not_due", "not_applicable"].includes(monitoring.execution_heartbeat) + ? monitoring.execution_heartbeat : "unavailable", + freshness: ["ready", "stale", "unavailable"].includes(entry?.freshness?.data_status) + ? entry.freshness.data_status : (dataStatus === "ready" ? "unavailable" : dataStatus), + }; +} + +export function accountDiagnosisHasConfirmedAttention(checks) { + return ["ready", "stale"].includes(checks.freshness) + && (checks.freshness === "stale" || (checks.configured_state !== "disabled" && checks.runtime_enabled === false) + || (checks.configured_state === "enabled" && checks.scheduler_state === "paused") + || checks.runtime_guard === "attention" || checks.execution_heartbeat === "attention"); +} + +export function accountDiagnosisStateFingerprint(targetId, checks) { + return `${targetId}:${JSON.stringify(checks)}`; +} + +export function accountDiagnosisTargetSpec(platform, targetId) { + const spec = ACCOUNT_DIAGNOSIS_TARGETS[String(targetId || "").trim()]; + return spec && (platform == null || spec.platform === platform) ? spec : null; +} + +export async function accountDiagnosisAccount(env, platform, key) { + let config; + try { + config = await loadAccountOptionsConfig(env); + } catch { + throw accountDiagnosisConflict("account_diagnosis_config_unavailable"); + } + const options = Array.isArray(config.options?.[platform]) + ? config.options[platform].filter(item => item?.key === key) : []; + if (options.length === 0) throw accountDiagnosisConflict("account_diagnosis_account_not_found"); + if (options.length !== 1) throw accountDiagnosisConflict("account_diagnosis_account_not_unique"); + const account = options[0]; + const targetId = String(account.runtime_status_target_id || "").trim(); + if (!accountDiagnosisTargetSpec(platform, targetId)) throw accountDiagnosisConflict("account_diagnosis_target_not_admitted"); + const allIdentityMatches = Object.entries(config.options || {}).flatMap(([itemPlatform, items]) => + (Array.isArray(items) ? items : []).filter(item => String(item?.runtime_status_target_id || "").trim() === targetId) + .map(item => ({ platform: itemPlatform, key: item.key }))); + if (allIdentityMatches.length !== 1 || allIdentityMatches[0].platform !== platform || allIdentityMatches[0].key !== key) { + throw accountDiagnosisConflict("account_diagnosis_identity_conflict"); + } + return { platform, key, account, targetId, config }; +} + +export async function accountDiagnosisContext(env, platform, key) { + const identity = await accountDiagnosisAccount(env, platform, key); + if (!hasConfigStore(env)) throw accountDiagnosisConflict("account_diagnosis_lifecycle_store_unavailable"); + const sources = await readRuntimeTargetLifecycleSources(env); + const ttlSeconds = executionEvidenceStaleTtlSeconds(env); + const matches = []; + for (const source of sources) { + const freshness = controlPlaneSnapshotFreshness(source, ttlSeconds, Date.now()); + for (const target of source.targets) { + if (target.target_id === identity.targetId && target.target.platform === identity.platform) { + matches.push({ target, freshness, observedAt: source.computed_at || source.generated_at }); + } + } + } + if (matches.length === 0) { + throw accountDiagnosisConflict("account_diagnosis_lifecycle_match_missing"); + } + if (matches.length !== 1) { + throw accountDiagnosisConflict("account_diagnosis_lifecycle_match_ambiguous"); + } + const entry = matches[0]; + let checks; + try { + checks = accountDiagnosisChecks(entry, entry.freshness.data_status); + } catch { + throw accountDiagnosisConflict("account_diagnosis_schema_incompatible"); + } + if (!checks || typeof checks !== "object") { + throw accountDiagnosisConflict("account_diagnosis_schema_incompatible"); + } + if (!["ready", "stale"].includes(checks.freshness)) throw accountDiagnosisConflict("account_diagnosis_source_unavailable"); + const observedAt = entry.observedAt; + if (!observedAt) throw accountDiagnosisConflict("account_diagnosis_observation_missing"); + return { + platform, key, targetId: identity.targetId, checks, observedAt, + stateFingerprint: accountDiagnosisStateFingerprint(identity.targetId, checks), + attention: accountDiagnosisHasConfirmedAttention(checks), + }; +} + +export function accountDiagnosisPublicTask(task, recheckStatus = task?.recheck_dispatch_state) { + if (!task) return null; + return { + request_id: task.request_id, + platform: task.platform, + key: task.key, + target_id: task.target_id, + trigger: task.trigger, + status: task.status, + summary: task.summary || "", + reason_code: task.reason_code || "", + dispatch_state: task.dispatch_state, + recheck_status: recheckStatus, + created_at: task.created_at, + updated_at: task.updated_at, + }; +} + +export async function accountDiagnosisRecheckStatus(env, task) { + if (!task || task.status !== "succeeded" || task.recheck_dispatch_state !== "sent") { + return task?.recheck_dispatch_state || "not_requested"; + } + const resultAt = Date.parse(task.updated_at); + if (!Number.isFinite(resultAt)) return "sent"; + try { + const sources = await readRuntimeTargetLifecycleSources(env); + const ttlSeconds = executionEvidenceStaleTtlSeconds(env); + const matches = []; + for (const source of sources) { + const freshness = controlPlaneSnapshotFreshness(source, ttlSeconds, Date.now()); + const observedAt = source.computed_at || source.generated_at; + if (freshness.data_status !== "ready" || !observedAt || Date.parse(observedAt) <= resultAt) continue; + for (const target of source.targets) { + if (target.target_id === task.target_id && target.target.platform === task.platform) { + matches.push({ target, freshness, observedAt }); + } + } + } + if (matches.length !== 1) return "sent"; + const checks = accountDiagnosisChecks(matches[0], matches[0].freshness.data_status); + if (checks.freshness !== "ready" || checks.runtime_guard === "unavailable" || checks.execution_heartbeat === "unavailable" + || checks.runtime_enabled === null || checks.scheduler_state === "unknown") return "unavailable"; + if (checks.configured_state === "enabled") { + return checks.runtime_enabled === true && checks.scheduler_state === "enabled" + && checks.runtime_guard === "pass" && ["pass", "not_due"].includes(checks.execution_heartbeat) ? "passed" : "attention"; + } + if (checks.configured_state === "disabled") { + return checks.runtime_enabled === false && checks.scheduler_state === "paused" + && checks.runtime_guard === "pass" && checks.execution_heartbeat === "not_applicable" ? "passed" : "attention"; + } + return "unavailable"; + } catch { + return "sent"; + } +} + +export function accountDiagnosisInternalTask(task) { + return { + request_id: task.request_id, + platform: task.platform, + key: task.key, + target_id: task.target_id, + trigger: task.trigger, + observed_at: task.observed_at, + checks: task.checks, + status: task.status, + }; +} + +export function accountDiagnosisNativeContext(env) { + try { + const binding = JSON.parse(env.ACCOUNT_DIAGNOSIS_NATIVE_BINDING_JSON || "null"); + const expected = ["schema_version", "project", "resource", "audience", "provider", "profile", "model", "worker"]; + if (!binding || Object.keys(binding).sort().join() !== expected.sort().join() + || binding.schema_version !== "qrs.account-diagnosis-native.v1" + || binding.project !== "QuantStrategyLab/QuantRuntimeSettings" + || expected.filter(key => key !== "schema_version").some(key => typeof binding[key] !== "string" + || !binding[key] || binding[key].length > 200 || /\s/.test(binding[key])) + || binding.profile.length > 100 || binding.profile.includes(":")) throw Error(); + const url = new URL(binding.resource); + if (url.protocol !== "https:" || !url.hostname || url.username || url.password || url.search || url.hash + || binding.resource.endsWith("/")) throw Error(); + const transport = env.ACCOUNT_DIAGNOSIS_NATIVE_TRANSPORT; + // This seam carries existing approved submit/read authentication. Neither + // an old GitHub dispatch token nor the QRS callback token can substitute it. + if (!transport || typeof transport.fetch !== "function") throw Error(); + return { binding, transport }; + } catch { + throw new HttpError("account_diagnosis_native_not_configured", 503); + } +} + +export function accountDiagnosisNativeRequest(task, binding) { + const input = accountDiagnosisInternalTask(task); + const properties = { status: { enum: ["succeeded", "failed", "unknown"] }, + summary: { type: "string", minLength: 1, maxLength: ACCOUNT_DIAGNOSIS_SUMMARY_MAX_CHARS }, + reason_code: { enum: ACCOUNT_DIAGNOSIS_REASON_CODES }, advisory_only: { const: true }, + execution_authority_granted: { const: false } }; + return { mode: "agent", profile: binding.profile, model: binding.model, + objective: "Diagnose only the supplied account/runtime availability checks. Return an advisory report, not a health or execution approval. Do not access accounts, change configuration, repair, trade, deploy, publish, or notify.", + materials: [input], timeout_seconds: 600, + output_schema: { type: "object", additionalProperties: false, required: Object.keys(properties), properties } }; +} + +export async function accountDiagnosisNativeIO(context, method, path, body, headers = {}) { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), 10000); + try { + const response = await context.transport.fetch(new Request(context.binding.resource + path, { + method, redirect: "manual", signal: controller.signal, + headers: { "Content-Type": "application/json", ...headers }, + ...(body === undefined ? {} : { body: JSON.stringify(body) }), + })); + if (!response.ok || !response.body) throw Error(); + const reader = response.body.getReader(); + const chunks = []; + let length = 0; + while (true) { + const { done, value } = await reader.read(); + if (done) break; + length += value.length; + if (length > 64 * 1024) { await reader.cancel(); throw Error(); } + chunks.push(value); + } + const bytes = new Uint8Array(length); + let offset = 0; + for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.length; } + return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes)); + } catch { + throw new HttpError("account_diagnosis_native_outcome_unknown", 502); + } finally { + clearTimeout(timer); + } +} + +export async function accountDiagnosisNativeStop(env, context, task) { + const reserved = await runtimeInstanceCommand(env, { action: "diagnosis_native_cancel", actor: "native-diagnosis-caller", + request_id: task.request_id, updated_at: new Date().toISOString() }); + if (reserved.send_cancel) { + try { await accountDiagnosisNativeIO(context, "POST", `/v1/tasks/${encodeURIComponent(reserved.task.native.task_id)}/cancel`, {}); } catch { /* Unknown cancellation is never retried. */ } + } + return reserved; +} + +export async function accountDiagnosisNativeReadback(env, context, task, record, cancelRequested = false) { + const native = task.native; + if (!native || canonicalResearchTaskJson(native.binding) !== canonicalResearchTaskJson(context.binding)) { + throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); + } + const taskId = record?.id; + const validId = typeof taskId === "string" && /^[A-Za-z0-9_-]{1,200}$/.test(taskId); + if (!validId || (native.task_id && taskId !== native.task_id) + || record.owner !== native.binding.project || record.provider !== native.binding.provider + || record.result_kind !== "advisory" || record.revision !== 1 + || !Number.isFinite(record.deadline) || (native.task_deadline !== null && native.task_deadline !== record.deadline) + || await sha256Hex(canonicalResearchTaskJson(record.request ?? null)) !== native.request_sha256) { + throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); + } + const next = structuredClone(native); + next.task_id = taskId; + next.task_deadline = record.deadline; + if (record.worker !== null && record.worker !== undefined || record.attempt !== null && record.attempt !== undefined) { + if (record.worker !== native.binding.worker || typeof record.attempt !== "string" || !record.attempt || record.attempt.length > 200) { + throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); + } + const claim = [record.worker, record.attempt, record.revision]; + if (native.claim && canonicalResearchTaskJson(native.claim) !== canonicalResearchTaskJson(claim)) { + throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); + } + next.claim = claim; + } else if (native.claim) { + throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); + } + const expired = Date.now() / 1000 >= Math.min(native.deadline, record.deadline); + let status = "queued", summary = "", reason = "", terminal = false; + const negativeTerminal = ["failed", "invalid_output", "cancelled"].includes(record.status); + if (((cancelRequested || expired || native.cancel_requested || native.cancel_sent) && !negativeTerminal) + || record.status === "cancel_requested" || record.status === "outcome_unknown") { + status = "unknown"; + next.phase = "hold"; + } else if (["queued", "submitting", "running"].includes(record.status)) { + status = record.status === "running" ? "running" : "queued"; + if (status === "running" && !next.claim) throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); + next.phase = "pending"; + } else if (record.status === "completed") { + const output = record.output; + if (!next.claim || !output || Object.keys(output).sort().join() !== "advisory_only,execution_authority_granted,reason_code,status,summary" + || !["succeeded", "failed", "unknown"].includes(output.status) + || typeof output.summary !== "string" || !output.summary.trim() || output.summary.length > ACCOUNT_DIAGNOSIS_SUMMARY_MAX_CHARS + || !ACCOUNT_DIAGNOSIS_REASON_CODES.includes(output.reason_code) + || output.advisory_only !== true || output.execution_authority_granted !== false) { + throw new HttpError("invalid_account_diagnosis_result", 400); + } + next.output_sha256 = await sha256Hex(canonicalResearchTaskJson(output)); + next.phase = "terminal"; + status = output.status; summary = output.summary.trim(); reason = output.reason_code; + terminal = true; + } else if (["failed", "invalid_output", "cancelled"].includes(record.status)) { + next.phase = "terminal"; status = "failed"; terminal = true; + summary = "Native diagnosis did not produce an accepted advisory result."; reason = "invalid_response"; + } else { + throw new HttpError("invalid_account_diagnosis_result", 400); + } + // All terminal adoption is still bounded by the original operation deadline. + if (terminal && status === "succeeded" && Date.now() / 1000 >= Math.min(native.deadline, record.deadline)) { + return accountDiagnosisNativeStop(env, context, { ...task, native: next }); + } + if (terminal) next.receipt = await sha256Hex(canonicalResearchTaskJson({ task_id: taskId, claim: next.claim, + revision: record.revision, request_sha256: next.request_sha256, output_sha256: next.output_sha256, + native_status: record.status, status, summary, reason })); + const saved = await runtimeInstanceCommand(env, { action: "diagnosis_native_save", actor: "native-diagnosis-caller", + request_id: task.request_id, native: next, status, summary, reason_code: reason, updated_at: new Date().toISOString() }); + if ((cancelRequested || native.cancel_requested || expired) && !terminal) return accountDiagnosisNativeStop(env, context, saved.task); + if (terminal && status === "succeeded" && saved.changed) { + // Durable result persistence may itself cross expiry; no recheck then. + if (Date.now() / 1000 >= Math.min(native.deadline, record.deadline)) { + return accountDiagnosisNativeStop(env, context, saved.task); + } + try { + await dispatchAccountDiagnosisRecheck(env, saved.task); + await runtimeInstanceCommand(env, { action: "diagnosis_mark_recheck", actor: "native-diagnosis-caller", + request_id: task.request_id, recheck_dispatch_state: "sent", updated_at: new Date().toISOString() }); + } catch { + await runtimeInstanceCommand(env, { action: "diagnosis_mark_recheck", actor: "native-diagnosis-caller", + request_id: task.request_id, recheck_dispatch_state: "unknown", updated_at: new Date().toISOString() }); + } + } + return saved; +} + +export async function reconcileAccountDiagnosis(env, requestId, cancel = false) { + const context = accountDiagnosisNativeContext(env); + const current = await runtimeInstanceCommand(env, { action: "diagnosis_read", actor: "native-diagnosis-caller", request_id: requestId }); + let task = current.task; + if (!task?.native) throw new HttpError("account_diagnosis_legacy_retired", 409); + if (canonicalResearchTaskJson(task.native.binding) !== canonicalResearchTaskJson(context.binding)) { + throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); + } + if (task.native.phase === "terminal") return { ...current, changed: false }; + if (cancel) { + const stopped = await accountDiagnosisNativeStop(env, context, task); + task = stopped.task; + if (task.native.receipt || task.native.task_id) return stopped; + } + if (task.native.task_id && (Date.now() / 1000 >= Math.min(task.native.deadline, task.native.task_deadline ?? Infinity) + || (task.native.cancel_requested && !task.native.cancel_sent))) return accountDiagnosisNativeStop(env, context, task); + try { + let taskId = task.native.task_id; + if (!taskId) { + const found = await accountDiagnosisNativeIO(context, "GET", "/v1/tasks/lookup", undefined, + { "Idempotency-Key": task.native.operation_key, "X-Request-SHA256": task.native.request_sha256 }); + if (!found || Object.keys(found).join() !== "id" || typeof found.id !== "string" || !/^[A-Za-z0-9_-]{1,200}$/.test(found.id)) { + throw new HttpError("account_diagnosis_native_outcome_unknown", 502); + } + taskId = found.id; + } + const record = await accountDiagnosisNativeIO(context, "GET", `/v1/tasks/${encodeURIComponent(taskId)}`); + if (record.id !== taskId) throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); + return accountDiagnosisNativeReadback(env, context, task, record, cancel); + } catch (error) { + // A read/validation failure retains the original obligation beyond24h. + try { + await runtimeInstanceCommand(env, { action: "diagnosis_native_save", actor: "native-diagnosis-caller", + request_id: requestId, native: { ...task.native, phase: "hold" }, status: "unknown", summary: "", reason_code: "", + updated_at: new Date().toISOString() }); + } catch { /* Never overwrite a concurrently accepted immutable terminal receipt. */ } + throw error; + } +} + +export async function dispatchAccountDiagnosis(env, requestId) { + const context = accountDiagnosisNativeContext(env); + const current = await runtimeInstanceCommand(env, { action: "diagnosis_read", actor: "native-diagnosis-caller", request_id: requestId }); + const request = accountDiagnosisNativeRequest(current.task, context.binding); + const native = { version: "qrs.native-diagnosis.v1", binding: context.binding, request, + input_sha256: await sha256Hex(canonicalResearchTaskJson(request.materials)), + request_sha256: await sha256Hex(canonicalResearchTaskJson(request)), operation_key: "qrs-account-diagnosis:" + requestId, + deadline: Date.now() / 1000 + 600, task_id: null, task_deadline: null, claim: null, + output_sha256: null, receipt: null, cancel_requested: false, cancel_sent: false, phase: "intent" }; + const intent = await runtimeInstanceCommand(env, { action: "diagnosis_native_intent", actor: "native-diagnosis-caller", + request_id: requestId, native, updated_at: new Date().toISOString() }); + if (!intent.created) return intent; // Never replay POST, even after a lost reply. + if (Date.now() / 1000 >= intent.task.native.deadline) return accountDiagnosisNativeStop(env, context, intent.task); + const record = await accountDiagnosisNativeIO(context, "POST", "/v1/tasks", request, + { "Idempotency-Key": native.operation_key }); + return accountDiagnosisNativeReadback(env, context, intent.task, record); +} + +export function accountDiagnosisAutoEnabled(env) { + return String(env[ACCOUNT_DIAGNOSIS_AUTO_ENABLED_ENV] || "").toLowerCase() === "true"; +} + +export async function accountDiagnosisIdentityForTarget(env, targetId) { + const spec = accountDiagnosisTargetSpec(null, targetId); + if (!spec) return null; + let config; + try { + config = await loadAccountOptionsConfig(env); + } catch { + return null; + } + const matches = Object.entries(config.options || {}).flatMap(([platform, items]) => + (Array.isArray(items) ? items : []) + .filter(item => String(item?.runtime_status_target_id || "").trim() === targetId) + .map(item => ({ platform, key: item.key }))); + if (matches.length !== 1 || matches[0].platform !== spec.platform) return null; + try { + return await accountDiagnosisAccount(env, matches[0].platform, matches[0].key); + } catch { + return null; + } +} + +export async function maybeAutoDispatchAccountDiagnosis(env, source) { + if (!accountDiagnosisAutoEnabled(env)) return; + try { accountDiagnosisNativeContext(env); } catch { return; } + const now = Date.now(); + const freshness = controlPlaneSnapshotFreshness(source, executionEvidenceStaleTtlSeconds(env), now); + if (freshness.data_status !== "ready") return; + const observedAt = source.computed_at || source.generated_at; + if (!observedAt) return; + for (const [targetId, spec] of Object.entries(ACCOUNT_DIAGNOSIS_TARGETS)) { + try { + const matchingTargets = source.targets.filter(item => + item.target_id === targetId && item.target?.platform === spec.platform); + if (matchingTargets.length !== 1) continue; + const target = matchingTargets[0]; + const checks = accountDiagnosisChecks({ target, freshness }, freshness.data_status); + if (checks.configured_state !== "enabled" || !accountDiagnosisHasConfirmedAttention(checks)) continue; + const identity = await accountDiagnosisIdentityForTarget(env, targetId); + if (!identity) continue; + const createdAt = new Date().toISOString(); + const requestId = crypto.randomUUID(); + const created = await runtimeInstanceCommand(env, { + action: "diagnosis_create", actor: "runtime-target-lifecycle-source-sync", request_id: requestId, + platform: identity.platform, key: identity.key, target_id: identity.targetId, trigger: "incident", + observed_at: observedAt, checks, state_fingerprint: accountDiagnosisStateFingerprint(identity.targetId, checks), + created_at: createdAt, now, auto_source: "source_sync", + }); + if (!created.created) continue; + try { + await dispatchAccountDiagnosis(env, requestId); + await runtimeInstanceCommand(env, { + action: "diagnosis_mark_dispatch", actor: "runtime-target-lifecycle-source-sync", + request_id: requestId, dispatch_state: "sent", updated_at: new Date().toISOString(), + }); + } catch { + await runtimeInstanceCommand(env, { + action: "diagnosis_mark_dispatch", actor: "runtime-target-lifecycle-source-sync", + request_id: requestId, dispatch_state: "unknown", updated_at: new Date().toISOString(), + }); + } + } catch { + // One target's unavailable identity or dispatch must not affect the other targets. + } + } +} + +export async function dispatchAccountDiagnosisRecheck(env, task) { + const token = String(env.RUNTIME_SETTINGS_DISPATCH_TOKEN || ""); + if (!token) throw new HttpError("account_diagnosis_recheck_unavailable", 503); + const spec = accountDiagnosisTargetSpec(task?.platform, task?.target_id); + if (!spec) throw accountDiagnosisConflict("account_diagnosis_target_not_admitted"); + const url = `https://api.github.com/repos/${spec.recheckRepository}/actions/workflows/${spec.recheckWorkflow}/dispatches`; + const body = task.platform === "longbridge" + ? { ref: "main", inputs: { target: task.target_id.slice("longbridge.".length) } } + : { ref: "main" }; + const response = await fetchWithTimeout(url, { + method: "POST", + headers: githubHeaders(token), + body: JSON.stringify(body), + }); + if (!response.ok) throw new HttpError("account_diagnosis_recheck_unverified", 502); +} + +export async function accountDiagnosisResponse(request, env, url) { + const session = await readSession(request, env); + if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); + if (!hasRuntimeInstanceStore(env)) return json({ ok: false, error: "account_diagnosis_storage_unavailable" }, 503); + if (request.method === "GET") { + const params = [...url.searchParams.keys()].sort(); + if (params.join(",") !== "key,platform") return json({ ok: false, error: "account_diagnosis_query_required" }, 400); + try { + const identity = await accountDiagnosisAccount(env, url.searchParams.get("platform"), url.searchParams.get("key")); + const result = await runtimeInstanceCommand(env, { action: "diagnosis_latest", actor: session.login, platform: identity.platform, key: identity.key }); + const recheckStatus = await accountDiagnosisRecheckStatus(env, result.task); + return json({ ok: true, account: { platform: identity.platform, key: identity.key, target_id: identity.targetId }, task: accountDiagnosisPublicTask(result.task, recheckStatus) }); + } catch (error) { + if (error instanceof HttpError) return json(accountDiagnosisErrorPayload(error), error.status); + return json({ ok: false, error: "account_diagnosis_unavailable", reason_code: "account_diagnosis_unavailable" }, 503); + } + } + if (request.method !== "POST") return json({ ok: false, error: "method_not_allowed" }, 405); + try { requireSameOrigin(request, { requireOrigin: true }); } catch (error) { return json({ ok: false, error: error.message }, error.status || 403); } + let raw; + try { raw = await readBoundedJson(request, ACCOUNT_DIAGNOSIS_MAX_BODY_BYTES); } catch (error) { + return json({ ok: false, error: error.message || "invalid_account_diagnosis_request" }, error.status || 400); + } + if (!raw || typeof raw !== "object" || Array.isArray(raw) || Object.keys(raw).sort().join(",") !== "key,platform,trigger") { + return json({ ok: false, error: "invalid_account_diagnosis_request" }, 400); + } + if (!ACCOUNT_DIAGNOSIS_TRIGGERS.includes(raw.trigger)) return json({ ok: false, error: "invalid_account_diagnosis_trigger" }, 400); + try { accountDiagnosisNativeContext(env); } catch { return json({ ok: false, error: "account_diagnosis_native_not_configured" }, 503); } + try { + const context = await accountDiagnosisContext(env, raw.platform, raw.key); + if (raw.trigger === "incident" && !context.attention) { + return json(accountDiagnosisErrorPayload(accountDiagnosisConflict("account_diagnosis_incident_requires_attention")), 409); + } + const requestId = crypto.randomUUID(); + const createdAt = new Date().toISOString(); + const created = await runtimeInstanceCommand(env, { + action: "diagnosis_create", actor: session.login, request_id: requestId, + platform: context.platform, key: context.key, target_id: context.targetId, trigger: raw.trigger, + observed_at: context.observedAt, checks: context.checks, state_fingerprint: context.stateFingerprint, created_at: createdAt, now: Date.now(), + }); + if (!created.created) return json({ ok: true, deduplicated: true, request_id: created.task.request_id, status: created.task.status, task: accountDiagnosisPublicTask(created.task) }, 200); + try { + await dispatchAccountDiagnosis(env, requestId); + await runtimeInstanceCommand(env, { action: "diagnosis_mark_dispatch", actor: session.login, request_id: requestId, dispatch_state: "sent", updated_at: new Date().toISOString() }); + } catch (error) { + await runtimeInstanceCommand(env, { action: "diagnosis_mark_dispatch", actor: session.login, request_id: requestId, dispatch_state: "unknown", updated_at: new Date().toISOString() }); + const latest = await runtimeInstanceCommand(env, { action: "diagnosis_read", actor: session.login, request_id: requestId }); + return json({ ok: false, error: error.message || "account_diagnosis_dispatch_unverified", request_id: latest.task.request_id, status: latest.task.status, task: accountDiagnosisPublicTask(latest.task) }, error.status || 502); + } + const latest = await runtimeInstanceCommand(env, { action: "diagnosis_read", actor: session.login, request_id: requestId }); + return json({ ok: true, status: "queued", request_id: latest.task.request_id, task: accountDiagnosisPublicTask(latest.task) }, 202); + } catch (error) { + if (error instanceof HttpError) return json(accountDiagnosisErrorPayload(error), error.status); + return json({ ok: false, error: "account_diagnosis_unavailable", reason_code: "account_diagnosis_unavailable" }, 503); + } +} + +export async function accountDiagnosisInternalResponse(request, env, url) { + try { requireAccountDiagnosisToken(request, env); } catch (error) { return json({ ok: false, error: error.message }, error.status || 401); } + if (!hasRuntimeInstanceStore(env)) return json({ ok: false, error: "account_diagnosis_storage_unavailable" }, 503); + if (url.search) return json({ ok: false, error: "invalid_account_diagnosis_request" }, 400); + let requestId; + try { requestId = accountDiagnosisRequestId(url.pathname.slice(ACCOUNT_DIAGNOSIS_INTERNAL_PREFIX.length)); } catch (error) { + return json({ ok: false, error: error.message }, error.status || 400); + } + if (request.method === "GET") { + try { + const result = await runtimeInstanceCommand(env, { action: "diagnosis_read", actor: "aab-account-diagnosis", request_id: requestId }); + return json(accountDiagnosisInternalTask(result.task)); + } catch (error) { + return json({ ok: false, error: error.message || "account_diagnosis_request_not_found" }, error.status || 404); + } + } + if (request.method !== "POST") return json({ ok: false, error: "method_not_allowed" }, 405); + let raw; + try { raw = await readBoundedJson(request, ACCOUNT_DIAGNOSIS_MAX_BODY_BYTES); } catch (error) { + return json({ ok: false, error: error.message || "invalid_account_diagnosis_request" }, error.status || 400); + } + if (!raw || typeof raw !== "object" || Array.isArray(raw) || raw.request_id !== requestId) { + return json({ ok: false, error: "invalid_account_diagnosis_request" }, 400); + } + try { + if (Object.keys(raw).sort().join() !== "action,request_id" || !["readback", "cancel"].includes(raw.action)) { + throw new HttpError("invalid_account_diagnosis_result", 400); + } + const result = await reconcileAccountDiagnosis(env, requestId, raw.action === "cancel"); + const latest = await runtimeInstanceCommand(env, { action: "diagnosis_read", actor: "native-diagnosis-caller", request_id: requestId }); + return json({ ok: true, replayed: !result.changed, recheck_status: latest.task.recheck_dispatch_state, + task: accountDiagnosisPublicTask(latest.task) }); + } catch (error) { + if (error instanceof HttpError) return json(accountDiagnosisErrorPayload(error), error.status); + return json({ ok: false, error: "account_diagnosis_native_outcome_unknown" }, 502); + } +} + +export function projectRuntimeAccountState(entry) { + const base = { scope: "monitoring_only", limit: "not_trading_or_books" }; + const target = entry?.target || {}; + const monitoring = target.monitoring || {}; + const disposition = target.disposition || {}; + const deployment = target.deployment; + const sourceFresh = entry?.freshness?.data_status === "ready"; + const deploymentFresh = Boolean(deployment?.observed_at) && entry?.deployment_freshness?.data_status === "ready"; + let activation = "unknown"; + if (deploymentFresh && deployment.runtime_enabled === true && deployment.scheduler_state === "enabled") activation = "enabled"; + else if (deploymentFresh && deployment.runtime_enabled === false && deployment.scheduler_state === "paused") activation = "disabled"; + const configured = target.target?.configured_state; + const retainedFault = monitoring.runtime_guard === "attention" + || monitoring.execution_heartbeat === "attention" + || disposition.code === "parked"; + if (retainedFault) return { ...base, health: "abnormal", activation, reason: "retained_attention" }; + if ((configured === "enabled" && activation === "disabled") || (configured === "disabled" && activation === "enabled")) { + return { ...base, health: "abnormal", activation, reason: "config_inconsistent" }; + } + if (monitoring.execution_heartbeat === "not_due") { + return { ...base, health: "unknown", activation, reason: "check_not_due" }; + } + const enabledAgrees = configured === "enabled" + && activation === "enabled" + && monitoring.runtime_guard === "pass" + && monitoring.execution_heartbeat === "pass" + && disposition.code === "continue_enabled_monitoring"; + const disabledAgrees = configured === "disabled" + && activation === "disabled" + && monitoring.runtime_guard === "pass" + && monitoring.execution_heartbeat === "not_applicable" + && disposition.code === "continue_disabled_validation"; + if (sourceFresh && (enabledAgrees || disabledAgrees)) { + return { ...base, health: "normal", activation, reason: "monitoring_agrees" }; + } + let reason = "evidence_insufficient"; + if (!sourceFresh) reason = "source_not_fresh"; + else if (!deployment) reason = "deployment_missing"; + else if (!deploymentFresh) reason = "deployment_not_fresh"; + else if (activation === "unknown") reason = "activation_unconfirmed"; + return { ...base, health: "unknown", activation, reason }; +} + +export async function aggregateRuntimeTargetLifecycleSources(env) { + const sources = await readRuntimeTargetLifecycleSources(env); + if (!sources.length) return emptyRuntimeTargetLifecyclePayload("snapshot_unavailable"); + const ttlSeconds = executionEvidenceStaleTtlSeconds(env); + const now = Date.now(); + const targets = []; + const targetIds = new Set(); + const duplicateTargetIds = new Set(); + const errors = []; + const timestamps = []; + let hasReadySource = false; + let hasStaleSource = false; + for (const source of sources) { + const freshness = controlPlaneSnapshotFreshness(source, ttlSeconds, now); + if (source.generated_at) timestamps.push(source.generated_at); + if (source.computed_at) timestamps.push(source.computed_at); + if (freshness.data_status === "ready") hasReadySource = true; + if (freshness.data_status === "stale") { + hasStaleSource = true; + errors.push("runtime_target_lifecycle_source_stale"); + } + for (const target of source.targets) { + if (targetIds.has(target.target_id)) { + duplicateTargetIds.add(target.target_id); + errors.push("runtime_target_lifecycle_duplicate_target"); + continue; + } + targetIds.add(target.target_id); + const entry = { + source_id: source.source_id, + observed_at: earliestControlPlaneTimestamp([source.generated_at, source.computed_at].filter(Boolean)), + evidence_valid_for_seconds: ttlSeconds, + freshness, + target, + // A lifecycle source is deliberately no-order. Keep this derived + // observation separate from the monitoring checks so a green check + // cannot be mistaken for a broker submission or fill receipt. + execution_observation: runtimeTargetLifecycleExecutionObservation(target), + ...(target.deployment ? {deployment_freshness: target.deployment.observed_at + ? controlPlaneSnapshotFreshness({data_status:"ready", computed_at:target.deployment.observed_at}, ttlSeconds, now) + : freshness} : {}), + }; + entry.account_state = projectRuntimeAccountState(entry); + targets.push(entry); + } + errors.push(...source.errors); + } + const uniqueTargets = targets.filter((entry) => !duplicateTargetIds.has(entry.target.target_id)); + const dataStatus = hasStaleSource ? "stale" : (hasReadySource ? "ready" : "unavailable"); + return { + schema_version: RUNTIME_TARGET_LIFECYCLE_DASHBOARD_SCHEMA_VERSION, + generated_at: earliestControlPlaneTimestamp(timestamps), + computed_at: earliestControlPlaneTimestamp(timestamps), + data_status: dataStatus, + summary: { + target_count: uniqueTargets.length, + enabled: uniqueTargets.filter((entry) => entry.target.target.configured_state === "enabled").length, + disabled: uniqueTargets.filter((entry) => entry.target.target.configured_state === "disabled").length, + attention: uniqueTargets.filter((entry) => entry.target.disposition.code === "parked").length, + }, + targets: uniqueTargets, + policy: { + lifecycle_status_read_only: true, + execution_observation_read_only: true, + no_order: true, + order_or_fill_evidence: "not_collected", + notice: "已启用目标持续监控;已停用目标持续进行无执行验证。运行或心跳通过只说明监测正常,不代表已提交订单或已成交。", + }, + errors: uniqueStrings(errors), + }; +} + +export function runtimeTargetLifecycleExecutionObservation(target) { + const monitoring = target?.monitoring || {}; + const configuredState = target?.target?.configured_state; + let code = "unavailable"; + if (monitoring.runtime_guard === "attention" || monitoring.execution_heartbeat === "attention") { + code = "attention"; + } else if (monitoring.runtime_guard === "unavailable" || monitoring.execution_heartbeat === "unavailable") { + code = "unavailable"; + } else if (configuredState === "disabled") { + code = "not_applicable"; + } else if (monitoring.execution_heartbeat === "not_due") { + code = "not_due"; + } else if (monitoring.execution_heartbeat === "pass") { + code = "monitoring_only"; + } + return { + code: cleanChoice(code, RUNTIME_TARGET_LIFECYCLE_EXECUTION_OBSERVATIONS, "runtime target lifecycle execution observation"), + // The source contract always declares no_order=true. Only the separate + // execution-receipt chain may later provide an order/fill attestation. + order_or_fill_evidence: "not_collected", + }; +} + +export async function readRuntimeTargetLifecycleSources(env) { + const store = configStore(env); + if (!store) return []; + let listing; + try { + listing = await directoryList(env, RUNTIME_TARGET_LIFECYCLE_SOURCE_PREFIX, RUNTIME_TARGET_LIFECYCLE_MAX_SOURCES); + } catch { + return [emptyRuntimeTargetLifecycleSourceSnapshot("runtime_target_lifecycle_source_list_unavailable")]; + } + const keys = Array.isArray(listing?.keys) ? listing.keys : []; + const sources = []; + for (const entry of keys.slice(0, RUNTIME_TARGET_LIFECYCLE_MAX_SOURCES)) { + const key = typeof entry?.name === "string" ? entry.name : ""; + if (!key.startsWith(RUNTIME_TARGET_LIFECYCLE_SOURCE_PREFIX)) continue; + try { + const stored = await readConfigJson(env, key); + if (!stored) throw new Error("indexed_record_unavailable"); + sources.push(normalizeRuntimeTargetLifecycleSourceSnapshot(stored, key)); + } catch { + sources.push(emptyRuntimeTargetLifecycleSourceSnapshot("runtime_target_lifecycle_source_invalid")); + } + } + return sources; +} + +export function runtimeTargetLifecycleSourceKey(sourceId) { + return `${RUNTIME_TARGET_LIFECYCLE_SOURCE_PREFIX}${sourceId}`; +} + +export async function syncResearchTaskSourceResponse(request, env) { + requireDedicatedResearchTaskSyncToken(request, env); + if (!hasConfigStore(env)) { + return json({ ok: false, error: "research task KV is not configured" }, 503); + } + + let raw; + try { + raw = await readBoundedJson(request, RESEARCH_TASK_MAX_BODY_BYTES); + } catch (error) { + return json({ ok: false, error: error.message || "invalid research task source payload" }, error.status || 400); + } + + let source; + try { + source = await normalizeResearchTaskSourceSnapshot(raw, "research task source snapshot"); + } catch (error) { + return json({ ok: false, error: error.message || "invalid research task source payload" }, 400); + } + + await writeConfigJson(env, researchTaskSourceKey(source.source_id), source); + try { + await appendAuditLog(env, { + ts: new Date().toISOString(), + login: "research-task-source-sync", + action: "sync_research_task_source", + source_id: source.source_id, + schema_version: source.schema_version, + task_count: source.tasks.length, + data_status: source.data_status, + }); + } catch { + // A valid no-order task index does not depend on optional audit retention. + } + return json({ + ok: true, + source_id: source.source_id, + schema_version: source.schema_version, + task_count: source.tasks.length, + generated_at: source.generated_at, + }); +} + +export async function researchTaskResponse(request, env) { + const session = await readSession(request, env); + if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); + if (!hasConfigStore(env)) return json(emptyResearchTaskPayload("snapshot_unavailable")); + return json(await aggregateResearchTaskSources(env)); +} + +export async function aggregateResearchTaskSources(env) { + const sources = await readResearchTaskSources(env); + if (!sources.length) return emptyResearchTaskPayload("snapshot_unavailable"); + + const ttlSeconds = researchTaskStaleTtlSeconds(env); + const now = Date.now(); + const taskIds = new Set(); + const duplicateTaskIds = new Set(); + const tasks = []; + const errors = []; + const timestamps = []; + let hasReadySource = false; + let hasStaleSource = false; + + for (const source of sources) { + const freshness = controlPlaneSnapshotFreshness(source, ttlSeconds, now); + if (source.generated_at) timestamps.push(source.generated_at); + if (source.computed_at) timestamps.push(source.computed_at); + if (freshness.data_status === "ready") hasReadySource = true; + if (freshness.data_status === "stale") { + hasStaleSource = true; + errors.push("research_task_source_stale"); + } + for (const task of source.tasks) { + if (taskIds.has(task.task_id)) { + duplicateTaskIds.add(task.task_id); + errors.push("research_task_duplicate_task"); + continue; + } + taskIds.add(task.task_id); + tasks.push({ source_id: source.source_id, freshness, task }); + } + errors.push(...source.errors); + } + + const uniqueTasks = tasks.filter((entry) => !duplicateTaskIds.has(entry.task.task_id)); + const dataStatus = hasStaleSource ? "stale" : (hasReadySource ? "ready" : "unavailable"); + return { + schema_version: RESEARCH_TASK_DASHBOARD_SCHEMA_VERSION, + generated_at: earliestControlPlaneTimestamp(timestamps), + computed_at: earliestControlPlaneTimestamp(timestamps), + data_status: dataStatus, + summary: { task_count: uniqueTasks.length }, + tasks: uniqueTasks, + policy: { + research_only: true, + no_order: true, + size_zero_required: true, + p4_p5_p6_authorized: false, + notice: "研究任务只用于离线、零仓位实验;不会调参、改代码、部署或下单。", + }, + errors: uniqueStrings(errors), + }; +} + +export async function readResearchTaskSources(env) { + const store = configStore(env); + if (!store) return []; + let listing; + try { + listing = await directoryList(env, RESEARCH_TASK_SOURCE_PREFIX, RESEARCH_TASK_MAX_SOURCES); + } catch { + return [emptyResearchTaskSourceSnapshot("research_task_source_list_unavailable")]; + } + const keys = Array.isArray(listing?.keys) ? listing.keys : []; + const sources = []; + for (const entry of keys.slice(0, RESEARCH_TASK_MAX_SOURCES)) { + const key = typeof entry?.name === "string" ? entry.name : ""; + if (!key.startsWith(RESEARCH_TASK_SOURCE_PREFIX)) continue; + try { + const stored = await readConfigJson(env, key); + if (!stored) throw new Error("indexed_record_unavailable"); + sources.push(await normalizeResearchTaskSourceSnapshot(stored, key)); + } catch { + sources.push(emptyResearchTaskSourceSnapshot("research_task_source_invalid")); + } + } + return sources; +} + +export function researchTaskSourceKey(sourceId) { + return `${RESEARCH_TASK_SOURCE_PREFIX}${sourceId}`; +} + +export async function aggregateControlPlaneSources(env) { + const now = Date.now(); + let retirements; + try { + const stored = await configStore(env).get(CONTROL_PLANE_SOURCE_RETIREMENTS_KEY); + retirements = stored === null || stored === undefined + ? new Map() + : normalizeControlPlaneSourceRetirements(JSON.parse(stored), now); + } catch { + return emptyControlPlanePayload("control_plane_source_retirements_invalid"); + } + const sources = await readControlPlaneSources(env); + // A legacy aggregate has no source identity and cannot honor a retirement. + if (!sources?.length && !retirements.size) return null; + + const ttlSeconds = controlPlaneStaleTtlSeconds(env); + const candidates = []; + const historicalSources = []; + const candidateIds = new Set(); + const duplicateCandidateIds = new Set(); + const errors = []; + const timestamps = []; + let hasReadySource = false; + let hasStaleSource = false; + + for (const source of sources || []) { + const sourceFreshness = controlPlaneResearchSnapshotFreshness(source, ttlSeconds, now); + const retirement = retirements.get(source.source_id); + if (retirement) { + historicalSources.push({ retirement, snapshot: source, freshness: sourceFreshness }); + continue; + } + 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("control_plane_source_stale"); + } + for (const candidate of source.candidates) { + if (candidateIds.has(candidate.candidate_id)) { + duplicateCandidateIds.add(candidate.candidate_id); + errors.push("control_plane_duplicate_candidate"); + continue; + } + candidateIds.add(candidate.candidate_id); + candidates.push({ + ...candidate, + source_id: source.source_id, + freshness: mergeControlPlaneFreshness(candidate.freshness, sourceFreshness), + }); + } + errors.push(...source.errors); + } + + const dataStatus = hasStaleSource ? "stale" : (hasReadySource ? "ready" : "unavailable"); + const uniqueCandidates = candidates.filter((candidate) => !duplicateCandidateIds.has(candidate.candidate_id)); + const uniqueErrors = uniqueStrings(errors); + return { + schema_version: "qsl_control_plane_dashboard.v1", + generated_at: earliestControlPlaneTimestamp(timestamps), + computed_at: earliestControlPlaneTimestamp(timestamps), + data_status: dataStatus, + summary: normalizeControlPlaneSummary(uniqueCandidates), + attention: normalizeControlPlaneAttention({ + dataStatus, + candidates: uniqueCandidates, + errors: uniqueErrors, + }), + candidates: uniqueCandidates, + ...(retirements.size ? { historical_sources: historicalSources } : {}), + policy: normalizeControlPlanePolicy({ + p4_p5_automation: "not_configured", + p6_owner_decision_required: true, + notice: "P1–P3 自动研究已接入;P4/P5 尚未配置,live 仍需所有者明确决定。", + }, "aggregated control plane policy"), + errors: uniqueErrors, + }; +} + +export function normalizeControlPlaneSourceRetirements(payload, now) { + const value = assertExactFields(payload, ["schema_version", "retirements"], CONTROL_PLANE_SOURCE_RETIREMENTS_KEY); + if (value.schema_version !== "qsl_control_plane_source_retirements.v1" + || !Array.isArray(value.retirements) || value.retirements.length > CONTROL_PLANE_MAX_SOURCES) { + throw new Error("invalid control plane source retirements"); + } + const retirements = new Map(); + for (const entry of value.retirements) { + const item = assertExactFields(entry, ["source_id", "retired_at", "reason"], "source retirement"); + const sourceId = normalizeControlPlaneIdentifier(item.source_id, "source retirement.source_id", false); + const retiredAt = normalizeStrategyHealthTimestamp(item.retired_at, "source retirement.retired_at"); + const reason = typeof item.reason === "string" + ? sanitizeStrategyHealthText(item.reason, "source retirement.reason", 240, false, null) : null; + // "unavailable" is the diagnostic identity for unreadable source records. + if (sourceId === "unavailable" || retirements.has(sourceId) || Date.parse(retiredAt) > now || !reason) { + throw new Error("invalid control plane source retirement"); + } + retirements.set(sourceId, { source_id: sourceId, retired_at: retiredAt, reason }); + } + return retirements; +} + +export async function readControlPlaneSources(env) { + const store = configStore(env); + if (!store) return null; + let listing; + try { + listing = await directoryList(env, CONTROL_PLANE_SOURCE_PREFIX, CONTROL_PLANE_MAX_SOURCES); + } catch (error) { + return [emptyControlPlaneSourceSnapshot(sourceListFailure(error, "control_plane_source_list_unavailable"))]; + } + const keys = Array.isArray(listing?.keys) ? listing.keys : []; + const sources = []; + for (const entry of keys.slice(0, CONTROL_PLANE_MAX_SOURCES)) { + const key = typeof entry?.name === "string" ? entry.name : ""; + if (!key.startsWith(CONTROL_PLANE_SOURCE_PREFIX)) continue; + try { + const stored = await readConfigJson(env, key); + if (!stored) throw new Error("indexed_record_unavailable"); + const source = normalizeControlPlaneSourceSnapshot(stored, key); + if (key !== controlPlaneSourceKey(source.source_id)) throw new Error("control plane source identity mismatch"); + sources.push(source); + } catch { + sources.push(emptyControlPlaneSourceSnapshot("control_plane_source_invalid")); + } + } + return sources; +} + +export function controlPlaneSourceKey(sourceId) { + return `${CONTROL_PLANE_SOURCE_PREFIX}${sourceId}`; +} + +export async function syncAdaptiveSelectionSourceResponse(request, env) { + requireDedicatedAdaptiveSelectionSyncToken(request, env); + if (!hasConfigStore(env)) { + return json({ ok: false, error: "adaptive selection KV is not configured" }, 503); + } + + let raw; + try { + raw = await readBoundedJson(request, ADAPTIVE_SELECTION_MAX_BODY_BYTES); + } catch (error) { + return json({ ok: false, error: error.message || "invalid adaptive selection payload" }, error.status || 400); + } + + let source; + try { + source = await normalizeAdaptiveSelectionSourceSnapshot(raw, "adaptive selection source snapshot"); + } catch (error) { + return json({ ok: false, error: error.message || "invalid adaptive selection payload" }, 400); + } + + await writeConfigJson(env, adaptiveSelectionSourceKey(source.source_id), source); + try { + await appendAuditLog(env, { + ts: new Date().toISOString(), + login: "adaptive-selection-source-sync", + action: "sync_adaptive_selection_source", + source_id: source.source_id, + schema_version: source.schema_version, + candidate_count: source.decision?.candidates.length || 0, + data_status: source.data_status, + no_order: true, + }); + } catch { + // This source remains safe and usable if the rolling convenience log fails. + } + return json({ + ok: true, + source_id: source.source_id, + schema_version: source.schema_version, + candidate_count: source.decision?.candidates.length || 0, + generated_at: source.generated_at, + no_order: true, + }); +} + +export async function syncM0ResearchLedgerResponse(request, env) { + requireDedicatedM0ResearchSyncToken(request, env); + if (!hasConfigStore(env)) { + return json({ ok: false, error: "M0 research ledger KV is not configured" }, 503); + } + + let raw; + try { + raw = await readBoundedJson(request, M0_RESEARCH_MAX_BODY_BYTES); + } catch (error) { + return json({ ok: false, error: error.message || "invalid M0 research ledger payload" }, error.status || 400); + } + + let envelope; + try { + envelope = await normalizeM0ResearchLedgerTransport(raw, "M0 research ledger transport"); + } catch (error) { + // Validation deliberately completes before any KV write. A malformed, + // over-scoped, or digest-mismatched payload can therefore never replace + // the last known-good current ledger. + return json({ ok: false, error: error.message || "invalid M0 research ledger payload" }, 400); + } + + const current = await readCurrentM0ResearchLedgerRecord(env); + if (current) { + // A retry for the exact immutable source is not a new decision or a + // second write. Acknowledge it so a lost response cannot make a valid + // no-order publication look failed, while retaining the guards below. + if (envelope.source_artifact.sha256 === current.envelope.source_artifact.sha256) { + return json({ + ok: true, + schema_version: current.envelope.schema_version, + source_repository: current.envelope.source_artifact.repository, + source_revision: current.envelope.source_artifact.revision, + source_run_id: current.envelope.source_artifact.run_id, + artifact_id: current.envelope.source_artifact.artifact_id, + ledger_sha256: current.envelope.ledger_sha256, + replayed: true, + no_order: true, + expires_at: current.expires_at, + }); + } + const replayError = m0ResearchLedgerReplayError(current.envelope, envelope); + if (replayError) return json({ ok: false, error: replayError }, 409); + } + + // Capture once so the persisted interval is exactly the requested physical + // retention even when the request crosses a wall-clock second boundary. + const storedNow = new Date(); + const storedAt = utcTimestampSeconds(storedNow); + const expiresAt = utcTimestampSeconds(new Date(storedNow.getTime() + M0_RESEARCH_RETENTION_SECONDS * 1000)); + const record = { + schema_version: M0_RESEARCH_STORAGE_SCHEMA_VERSION, + stored_at: storedAt, + expires_at: expiresAt, + envelope, + }; + try { + // Archive first: an archive failure must not advance the current pointer. + // The archive identity is derived exclusively from the verified digest; + // callers never choose a KV key. + await writeM0ResearchLedgerRecord(env, m0ResearchLedgerArchiveKey(envelope.ledger_sha256), record); + await writeM0ResearchLedgerRecord(env, M0_RESEARCH_CURRENT_KEY, record); + } catch { + return json({ ok: false, error: "M0 research ledger persistence failed" }, 503); + } + + try { + await appendAuditLog(env, { + ts: storedAt, + login: "m0-research-ledger-sync", + action: "sync_m0_research_ledger", + source_repository: envelope.source_artifact.repository, + source_revision: envelope.source_artifact.revision, + source_run_id: envelope.source_artifact.run_id, + artifact_id: envelope.source_artifact.artifact_id, + ledger_sha256: envelope.ledger_sha256, + no_order: true, + }); + } catch { + // Retention of an optional convenience log must not change a valid, + // already persisted no-order research ledger. + } + return json({ + ok: true, + schema_version: envelope.schema_version, + source_repository: envelope.source_artifact.repository, + source_revision: envelope.source_artifact.revision, + source_run_id: envelope.source_artifact.run_id, + artifact_id: envelope.source_artifact.artifact_id, + ledger_sha256: envelope.ledger_sha256, + replayed: false, + no_order: true, + expires_at: expiresAt, + }); +} + +export async function m0ResearchLedgerResponse(request, env) { + const session = await readSession(request, env); + if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); + if (!hasConfigStore(env)) return json(emptyM0ResearchDashboardPayload("m0_research_ledger_unavailable")); + const record = await readCurrentM0ResearchLedgerRecord(env); + return json(record + ? projectM0ResearchDashboardForRead(record.envelope.ledger, record.envelope.ledger_sha256) + : emptyM0ResearchDashboardPayload("m0_research_ledger_unavailable")); +} + +export function m0ResearchLedgerReplayError(current, incoming) { + if (!current || !incoming) return null; + if (incoming.ledger_sha256 === current.ledger_sha256 + || incoming.source_artifact.sha256 === current.source_artifact.sha256) { + return "M0 research ledger current replay rejected"; + } + // Run IDs are closed identifiers rather than an assumed numeric sequence. + // An equal source/run identity is a replay; ledger time is the portable + // ordering guard for a different run identifier. + if (incoming.source_artifact.repository === current.source_artifact.repository + && incoming.source_artifact.run_id === current.source_artifact.run_id) { + return "M0 research ledger current replay rejected"; + } + const incomingTime = Date.parse(incoming.ledger.computed_at); + const currentTime = Date.parse(current.ledger.computed_at); + if (Number.isFinite(incomingTime) && Number.isFinite(currentTime) && incomingTime <= currentTime) { + return "M0 research ledger time rollback rejected"; + } + return null; +} + +export function m0ResearchLedgerArchiveKey(ledgerSha256) { + return `${M0_RESEARCH_ARCHIVE_PREFIX}${ledgerSha256}`; +} + +export async function writeM0ResearchLedgerRecord(env, key, record) { + const store = configStore(env); + if (!store) throw new Error("M0 research ledger KV is not configured"); + await store.put(key, JSON.stringify(record), { expirationTtl: M0_RESEARCH_RETENTION_SECONDS }); +} + +export async function readCurrentM0ResearchLedgerRecord(env) { + if (!hasConfigStore(env)) return null; + let stored; + try { + stored = await readConfigJson(env, M0_RESEARCH_CURRENT_KEY); + } catch { + return null; + } + if (!stored) return null; + try { + return await normalizeM0ResearchLedgerStorageRecord(stored, "M0 research ledger current record"); + } catch { + return null; + } +} + +export async function adaptiveSelectionResponse(request, env) { + const session = await readSession(request, env); + if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); + if (!hasConfigStore(env)) return json(emptyAdaptiveSelectionPayload("adaptive_selection_source_unavailable")); + return json(await aggregateAdaptiveSelectionSources(env)); +} + +export async function aggregateAdaptiveSelectionSources(env) { + const sources = await readAdaptiveSelectionSources(env); + if (!sources.length) return emptyAdaptiveSelectionPayload("adaptive_selection_source_unavailable"); + + const ttlSeconds = adaptiveSelectionStaleTtlSeconds(env); + const now = Date.now(); + const selections = []; + const errors = []; + const timestamps = []; + let hasReadySource = false; + let hasStaleSource = false; + + for (const source of sources) { + const freshness = controlPlaneSnapshotFreshness(source, ttlSeconds, now); + if (source.generated_at) timestamps.push(source.generated_at); + if (source.computed_at) timestamps.push(source.computed_at); + if (freshness.data_status === "ready") hasReadySource = true; + if (freshness.data_status === "stale") { + hasStaleSource = true; + errors.push("adaptive_selection_source_stale"); + } + if (source.decision) selections.push({ source_id: source.source_id, freshness, decision: source.decision }); + errors.push(...source.errors); + } + + return { + schema_version: ADAPTIVE_SELECTION_DASHBOARD_SCHEMA_VERSION, + generated_at: earliestControlPlaneTimestamp(timestamps), + computed_at: earliestControlPlaneTimestamp(timestamps), + data_status: hasStaleSource ? "stale" : (hasReadySource ? "ready" : "unavailable"), + summary: normalizeAdaptiveSelectionSummary(selections), + selections: selections.sort((left, right) => left.source_id.localeCompare(right.source_id)), + policy: { + authority: ADAPTIVE_SELECTION_AUTHORITY, + no_order: true, + execution_authority_granted: false, + notice: "M1 仅展示 Shadow 建议和拒绝原因;不会修改策略、平台、资金、运行状态或订单。", + }, + errors: uniqueStrings(errors), + }; +} + +export async function readAdaptiveSelectionSources(env) { + const store = configStore(env); + if (!store) return []; + let listing; + try { + listing = await directoryList(env, ADAPTIVE_SELECTION_SOURCE_PREFIX, ADAPTIVE_SELECTION_MAX_SOURCES); + } catch { + return [emptyAdaptiveSelectionSourceSnapshot("adaptive_selection_source_list_unavailable")]; + } + const keys = Array.isArray(listing?.keys) ? listing.keys : []; + const sources = []; + for (const entry of keys.slice(0, ADAPTIVE_SELECTION_MAX_SOURCES)) { + const key = typeof entry?.name === "string" ? entry.name : ""; + if (!key.startsWith(ADAPTIVE_SELECTION_SOURCE_PREFIX)) continue; + try { + const stored = await readConfigJson(env, key); + if (!stored) throw new Error("indexed_record_unavailable"); + sources.push(await normalizeAdaptiveSelectionSourceSnapshot(stored, key)); + } catch { + sources.push(emptyAdaptiveSelectionSourceSnapshot("adaptive_selection_source_invalid")); + } + } + return sources; +} + +export function adaptiveSelectionSourceKey(sourceId) { + return `${ADAPTIVE_SELECTION_SOURCE_PREFIX}${sourceId}`; +} + +export function controlPlaneSnapshotFreshness(snapshot, ttlSeconds, now) { + const timestamps = [snapshot.generated_at, snapshot.computed_at] + .filter(Boolean) + .map((value) => Date.parse(value)); + const freshnessAt = timestamps.length ? Math.min(...timestamps) : Number.NaN; + const futureBeyondClockSkew = Number.isFinite(freshnessAt) && freshnessAt > now + 5 * 60 * 1000; + const ageSeconds = futureBeyondClockSkew + ? Number.POSITIVE_INFINITY + : Number.isFinite(freshnessAt) + ? Math.max(0, Math.round((now - freshnessAt) / 1000)) + : Number.POSITIVE_INFINITY; + const dataStatus = snapshot.data_status === "ready" && ageSeconds > ttlSeconds + ? "stale" + : snapshot.data_status; + return { data_status: dataStatus, age_seconds: Number.isFinite(ageSeconds) ? ageSeconds : null }; +} + +export function controlPlaneResearchSnapshotFreshness(snapshot, ttlSeconds, now) { + if (snapshot.data_status !== "ready") { + return controlPlaneSnapshotFreshness(snapshot, ttlSeconds, now); + } + const timestamps = [snapshot.generated_at, snapshot.computed_at] + .filter(Boolean) + .map((value) => Date.parse(value)); + const freshnessAt = timestamps.length ? Math.min(...timestamps) : Number.NaN; + const futureBeyondClockSkew = Number.isFinite(freshnessAt) && freshnessAt > now + 5 * 60 * 1000; + const ageSeconds = futureBeyondClockSkew + ? Number.POSITIVE_INFINITY + : Number.isFinite(freshnessAt) + ? Math.max(0, Math.round((now - freshnessAt) / 1000)) + : Number.POSITIVE_INFINITY; + if (!Number.isFinite(freshnessAt) || futureBeyondClockSkew) { + return { data_status: "stale", age_seconds: null }; + } + + const ttlDeadline = freshnessAt + ttlSeconds * 1000; + const nextScheduledRefresh = nextControlPlaneResearchScheduleAfter(freshnessAt); + const scheduledDeadline = nextScheduledRefresh + CONTROL_PLANE_RESEARCH_DELIVERY_GRACE_MS; + const staleDeadline = Math.max(ttlDeadline, scheduledDeadline); + return { + data_status: now > staleDeadline ? "stale" : "ready", + age_seconds: ageSeconds, + }; +} + +export function nextControlPlaneResearchScheduleAfter(freshnessAt) { + const scheduled = new Date(freshnessAt); + scheduled.setUTCMinutes(0, 0, 0); + scheduled.setUTCHours(CONTROL_PLANE_RESEARCH_SCHEDULE_UTC_HOUR); + if (scheduled.getTime() <= freshnessAt) scheduled.setUTCDate(scheduled.getUTCDate() + 1); + while (!CONTROL_PLANE_RESEARCH_SCHEDULE_UTC_DAYS.has(scheduled.getUTCDay())) { + scheduled.setUTCDate(scheduled.getUTCDate() + 1); + } + return scheduled.getTime(); +} + +// 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. +export function reconciliationRecoveryEvidenceFreshness(recovery, sourceFreshness, ttlSeconds, now) { + const observedAt = Date.parse(recovery.mode === "initial_enrollment" ? recovery.candidate.last_observed_at : 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 }; +} + +export function mergeControlPlaneFreshness(candidateFreshness, sourceFreshness) { + const sourceAgeSeconds = sourceFreshness.age_seconds; + const candidateAge = candidateFreshness.age_seconds; + const ageSeconds = sourceAgeSeconds === null + ? candidateAge + : (candidateAge === null ? sourceAgeSeconds : Math.max(candidateAge, sourceAgeSeconds)); + const status = candidateFreshness.status === "stale" || sourceFreshness.data_status !== "ready" + ? "stale" + : candidateFreshness.status; + return { status, age_seconds: ageSeconds }; +} + +export function earliestControlPlaneTimestamp(values) { + const timestamps = values + .filter(Boolean) + .map((value) => ({ value, timestamp: Date.parse(value) })) + .filter((item) => Number.isFinite(item.timestamp)) + .sort((left, right) => left.timestamp - right.timestamp); + return timestamps[0]?.value || null; +} + +export async function readBoundedJson(request, maxBytes) { + const declaredLength = Number(request.headers.get("Content-Length") || 0); + if (declaredLength > maxBytes) throw new HttpError("request body is too large", 413); + const bytes = await request.arrayBuffer(); + if (bytes.byteLength > maxBytes) throw new HttpError("request body is too large", 413); + try { + return JSON.parse(new TextDecoder().decode(bytes)); + } catch { + throw new HttpError("request body must be valid JSON", 400); + } +} + +export function requireDedicatedStrategyHealthSyncToken(request, env) { + const expected = String(env.STRATEGY_HEALTH_SYNC_TOKEN || ""); + if (!expected) throw new HttpError("strategy health 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("strategy health sync token is invalid", 401); +} + +export function requireDedicatedControlPlaneSyncToken(request, env) { + const expected = String(env.CONTROL_PLANE_SYNC_TOKEN || ""); + if (!expected) throw new HttpError("control plane 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("control plane sync token is invalid", 401); +} + +export function requireControlPlaneSourceSyncToken(request, env) { + const validationToken = String(env.AAB_VALIDATION_SYNC_TOKEN || ""); + const token = (request.headers.get("Authorization") || "").match(/^Bearer\s+(.+)$/i)?.[1] || ""; + // Check the narrower credential first, including accidental equal bindings. + if (validationToken && token === validationToken) return true; + if (validationToken && !env.CONTROL_PLANE_SYNC_TOKEN) { + throw new HttpError("control plane source sync token is invalid", 401); + } + requireDedicatedControlPlaneSyncToken(request, env); + return false; +} + +export function requireDedicatedAdaptiveSelectionSyncToken(request, env) { + const expected = String(env.ADAPTIVE_SELECTION_SYNC_TOKEN || ""); + if (!expected) throw new HttpError("adaptive selection 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("adaptive selection sync token is invalid", 401); +} + +export function requireDedicatedExecutionEvidenceSyncToken(request, env) { + const expected = String(env.EXECUTION_EVIDENCE_SYNC_TOKEN || ""); + if (!expected) throw new HttpError("execution evidence 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("execution evidence sync token is invalid", 401); +} + +export function requireDedicatedAccountFactsSyncToken(request, env) { + const configured = [ + ["longbridge", String(env.ACCOUNT_FACTS_SYNC_TOKEN || "")], + [IBKR_ACCOUNT_FACTS_PLATFORM, String(env.IBKR_ACCOUNT_FACTS_SYNC_TOKEN || "")], + [SCHWAB_ACCOUNT_FACTS_PLATFORM, String(env.SCHWAB_ACCOUNT_FACTS_SYNC_TOKEN || "")], + [FIRSTRADE_ACCOUNT_FACTS_PLATFORM, String(env.FIRSTRADE_ACCOUNT_FACTS_SYNC_TOKEN || "")], + ].filter(([, token]) => token); + const firstradeToken = String(env.FIRSTRADE_ACCOUNT_FACTS_SYNC_TOKEN || ""); + if (firstradeToken && [env.BINANCE_ACCOUNT_FACTS_SYNC_TOKEN, env.EXECUTION_EVIDENCE_SYNC_TOKEN, + env.STRATEGY_SWITCH_SYNC_TOKEN, env.RECONCILIATION_RECOVERY_SYNC_TOKEN, + env.RECONCILIATION_RECOVERY_CONTROLLER_TOKEN, env.CYCLE_HEALTH_PROVISIONER_TOKEN] + .some(other => other && String(other) === firstradeToken)) { + throw new HttpError("account_facts_sync_token_ambiguous", 503); + } + if (new Set(configured.map(([, token]) => token)).size !== configured.length) { + throw new HttpError("account_facts_sync_token_ambiguous", 503); + } + if (!configured.length) throw new HttpError("account_facts_sync_token_not_configured", 500); + const header = request.headers.get("Authorization") || ""; + const token = header.match(/^Bearer\s+(.+)$/i)?.[1] || ""; + const match = configured.find(([, expected]) => token === expected); + if (match) return match[0]; + throw new HttpError("account_facts_sync_token_invalid", 401); +} + +export function accountFactsDisabledResponse() { + return json({ ok: false, error: "account_facts_disabled" }, 404); +} + +// Sync producers keep only the HTTP status, so a refused push is undiagnosable +// from their logs. Publish the refusal code, tagged with a fixed endpoint literal +// from the caller; free-form exception text is classified to "unknown" so payload +// content, identities and credentials cannot enter the log stream. The response +// body and status are exactly what this helper already returned. +export function accountFactsErrorResponse(error, endpoint = "read") { + const code = error instanceof AccountFactsError ? error.code + : error instanceof HttpError ? error.message + : error?.message || "invalid_account_facts_history"; + const status = error?.status || 400; + console.warn("account_facts_refused", { + endpoint, + http_status: status, + error_category: typeof code === "string" && /^[a-z][a-z0-9_]{2,63}$/.test(code) ? code : "unknown", + }); + return json({ ok: false, error: code }, status); +} + +export function requireCycleHealthProvisionToken(request, env) { + const expected = String(env.STRATEGY_SWITCH_SYNC_TOKEN || env.RUNTIME_SETTINGS_DISPATCH_TOKEN || ""); + if (!expected) throw new HttpError("cycle_health_provisioner_unavailable", 503); + const sourceTokens = [ + env.ACCOUNT_FACTS_SYNC_TOKEN, env.IBKR_ACCOUNT_FACTS_SYNC_TOKEN, + env.SCHWAB_ACCOUNT_FACTS_SYNC_TOKEN, env.FIRSTRADE_ACCOUNT_FACTS_SYNC_TOKEN, env.BINANCE_ACCOUNT_FACTS_SYNC_TOKEN, + env.EXECUTION_EVIDENCE_SYNC_TOKEN, + ].filter(Boolean).map(String); + if (sourceTokens.includes(expected)) throw new HttpError("cycle_health_provisioner_token_alias", 503); + const token = (request.headers.get("Authorization") || "").match(/^Bearer\s+(.+)$/i)?.[1] || ""; + if (token !== expected) throw new HttpError("cycle_health_provisioner_token_invalid", 401); +} + +export async function resolveCycleHealthBindingFingerprint(env, targetId, bindingId) { + const config = await loadAccountOptionsConfig(env); + const options = Array.isArray(config?.options?.longbridge) ? config.options.longbridge : []; + const matches = options.filter(o => o.runtime_status_target_id === targetId); + const allMatches = Object.entries(config.options || {}).flatMap(([platform, items]) => + (Array.isArray(items) ? items : []).filter(o => o.runtime_status_target_id === targetId).map(o => ({ platform, key: o.key }))); + if (matches.length !== 1 || allMatches.length !== 1 || allMatches[0].platform !== "longbridge" + || options.filter(o => o.key === matches[0]?.key).length !== 1) throw new HttpError("cycle_health_option_not_unique", 409); + const bindings = await loadAccountFactsBindings(env); + const bound = bindings?.bindings?.filter(b => b.platform === "longbridge" && b.account_key === matches[0].key) || []; + if (bound.length !== 1 || bound[0].account_scope !== "paper" || bound[0].source_binding.id !== bindingId + || !accountFactsOptionMatchesBinding(matches[0], bound[0])) throw new HttpError("cycle_health_binding_mismatch", 409); + return cycleHealthSha256({ binding: bound[0], runtime_status_target_id: targetId }); +} + +export async function runtimeCycleHealthAdmissionResponse(request, env) { + try { + requireCycleHealthProvisionToken(request, env); + if (!hasRuntimeInstanceStore(env)) throw new HttpError("cycle_health_store_unavailable", 503); + const raw = await readBoundedJson(request, 8192); + const fields = ["source_id", "target_id", "configuration_sha256", "required_from", "expected_authority_revision"]; + if (!raw || typeof raw !== "object" || Array.isArray(raw) + || Object.keys(raw).length !== fields.length || fields.some(field => !Object.hasOwn(raw, field))) { + throw new HttpError("cycle_health_admission_request_invalid", 400); + } + const sourceId = normalizeControlPlaneIdentifier(raw.source_id, "source_id", false); + const targetId = raw.target_id; + if (targetId !== "longbridge.paper" || !/^[a-f0-9]{64}$/.test(raw.configuration_sha256 || "") + || !Number.isSafeInteger(raw.expected_authority_revision) || raw.expected_authority_revision < 0) { + throw new HttpError("cycle_health_admission_request_invalid", 400); + } + const requiredFrom = normalizeCycleInstant(raw.required_from); + const bindingId = request.headers.get("X-QSL-Source-Binding-ID") || ""; + if (!/^[a-f0-9]{64}$/.test(bindingId)) throw new HttpError("cycle_health_binding_required", 409); + const bindingFingerprint = await resolveCycleHealthBindingFingerprint(env, targetId, bindingId); + return json(await runtimeInstanceCommand(env, { + action: "cycle_health_provision", source_id: sourceId, target_id: targetId, + binding_fingerprint: bindingFingerprint, configuration_sha256: raw.configuration_sha256, + required_from: requiredFrom, expected_authority_revision: raw.expected_authority_revision, + })); + } catch (error) { + return json({ ok: false, error: error instanceof HttpError || error instanceof CycleHealthError + ? error.message : "cycle_health_admission_unavailable" }, error.status || 400); + } +} + +export async function runtimeCycleHealthSourceResponse(request, env) { + try { + const platform = requireDedicatedAccountFactsSyncToken(request, env); + if (platform !== "longbridge") throw new HttpError("cycle_health_token_platform_mismatch", 403); + const dedicated = String(env.ACCOUNT_FACTS_SYNC_TOKEN || ""); + if ([env.EXECUTION_EVIDENCE_SYNC_TOKEN, env.BINANCE_ACCOUNT_FACTS_SYNC_TOKEN].some(t => t && String(t) === dedicated)) { + throw new HttpError("cycle_health_token_alias", 503); + } + if (!hasRuntimeInstanceStore(env)) throw new HttpError("cycle_health_store_unavailable", 503); + const url = new URL(request.url); + let source, targetId, sourceId, historyCursor = null; + const bindingId = request.headers.get("X-QSL-Source-Binding-ID") || ""; + if (!/^[a-f0-9]{64}$/.test(bindingId)) throw new HttpError("cycle_health_binding_required", 409); + if (request.method === "GET") { + if ([...url.searchParams.keys()].some(k => !["source_id", "target_id", "history_cursor"].includes(k)) + || url.searchParams.getAll("source_id").length !== 1 || url.searchParams.getAll("target_id").length !== 1 + || url.searchParams.getAll("history_cursor").length > 1) throw new HttpError("cycle_health_invalid_query", 400); + sourceId = normalizeControlPlaneIdentifier(url.searchParams.get("source_id"), "source_id", false); + targetId = url.searchParams.get("target_id"); + historyCursor = url.searchParams.get("history_cursor"); + if (historyCursor !== null) { + if (historyCursor.length > 512) throw new HttpError("cycle_health_history_cursor_invalid", 400); + } + } else { + const raw = await readBoundedJson(request, ACCOUNT_FACTS_MAX_BODY_BYTES); + if (!Array.isArray(raw?.targets) || raw.targets.length !== 1) throw new HttpError("cycle_health_one_target_required", 400); + const { cycle_health: health, ...legacyTarget } = raw.targets[0]; + if (!health) throw new HttpError("cycle_health_extension_required", 400); + // Reuse the lifecycle shape with the companion member removed. The legacy + // normalizer and legacy POST still reject this extension as before. + source = normalizeRuntimeTargetLifecycleSourceSnapshot({ ...raw, targets: [legacyTarget] }); + normalizeCycleInstant(raw.generated_at); normalizeCycleInstant(raw.computed_at); + if (raw.generated_at !== raw.computed_at || source.data_status !== "ready") throw new HttpError("cycle_health_observation_time_mismatch", 400); + source.generated_at = raw.generated_at; source.computed_at = raw.computed_at; + if (legacyTarget.deployment?.observed_at !== undefined) { + source.targets[0].deployment.observed_at = normalizeCycleInstant(legacyTarget.deployment.observed_at); + if (compareCycleInstants(legacyTarget.deployment.observed_at, raw.computed_at) > 0) throw new HttpError("cycle_health_deployment_chronology", 400); + } + const age = Date.now() - Date.parse(source.computed_at); + if (age > ACCOUNT_FACTS_STALE_MS || age < -ACCOUNT_FACTS_FUTURE_SKEW_MS) throw new HttpError("cycle_health_observation_age", 409); + targetId = source.targets[0].target_id; sourceId = source.source_id; + source.targets[0].cycle_health = await normalizeCycleHealth(health, { + target_id: targetId, source_binding_id: bindingId, observed_at: source.computed_at, + }); + } + if (targetId !== "longbridge.paper" || (source && (source.targets[0].target.platform !== "longbridge" || source.targets[0].target.execution_mode !== "paper"))) throw new HttpError("cycle_health_paper_target_required", 403); + const bindingFingerprint = await resolveCycleHealthBindingFingerprint(env, targetId, bindingId); + const identity = { source_id: sourceId, target_id: targetId, binding_fingerprint: bindingFingerprint, source_binding_id: bindingId }; + const admission = await runtimeInstanceCommand(env, { ...identity, action: "cycle_health_admission" }); + if (source && source.targets[0].cycle_health.configuration_sha256 !== admission.configuration_sha256) throw new HttpError("cycle_health_configuration_not_admitted", 409); + const command = { ...identity, ...admission, action: source ? "cycle_health_put" : "cycle_health_read", + ...(historyCursor ? { history_cursor: historyCursor } : {}) }; + if (source) { + command.observation = source; + command.observation_json = canonicalCycleJson(source); + command.observation_sha256 = await cycleHealthSha256(source); + command.received_at = new Date().toISOString(); + } + return json(await runtimeInstanceCommand(env, command)); + } catch (error) { + return json({ ok: false, error: error instanceof HttpError || error instanceof CycleHealthError ? error.message : "cycle_health_invalid_source" }, error.status || 400); + } +} + +export async function loadAccountFactsBindings(env, { propagateReadErrors = false } = {}) { + if (!hasConfigStore(env)) { + if (propagateReadErrors) throw new Error("account_facts_bindings_store_unavailable"); + return null; + } + let raw; + try { + raw = await readConfigJson(env, ACCOUNT_FACTS_BINDINGS_KEY); + } catch (error) { + if (propagateReadErrors) throw error; + return null; + } + if (!raw) return null; + return normalizeAccountFactsBindings(raw); +} + +export async function loadStoredAccountFactsMap(env, accountOptions, bindings) { + if (!hasRuntimeInstanceStore(env)) throw new HttpError("account_facts_store_unavailable", 503); + const accounts = []; + const bindingByAccount = new Map( + (bindings?.bindings || []).map((item) => [`${item.platform}:${item.account_key}`, item]), + ); + for (const [platform, options] of Object.entries(accountOptions || {})) { + if (!Array.isArray(options)) continue; + for (const option of options) { + if (!option || typeof option.key !== "string" || !option.key) continue; + const binding = bindingByAccount.get(`${platform}:${option.key}`); + if (!binding || !accountFactsOptionMatchesBinding(option, binding)) continue; + accounts.push({ + platform, + account_key: option.key, + account_scope: binding.account_scope, + account_selector: binding.account_selector, + ...(platform === SCHWAB_ACCOUNT_FACTS_PLATFORM + ? { broker_account_hash: binding.broker_account_hash } + : {}), + ...(platform === FIRSTRADE_ACCOUNT_FACTS_PLATFORM ? { broker_account_id: binding.broker_account_id } : {}), + target_id: binding.target_id, + source_binding_id: binding.source_binding.id, + }); + } + } + const listed = await runtimeInstanceCommand(env, { + action: "account_facts_list", + accounts, + }); + const storedByAccount = new Map(); + for (const row of listed.observations || []) { + if (!row?.platform || !row?.account_key || !row?.payload) continue; + storedByAccount.set(`${row.platform}:${row.account_key}`, row.payload); + } + return storedByAccount; +} + +export function periodReturnCommand(binding, action) { + return { action, platform: "ibkr", account_key: binding.account_key, account_scope: binding.account_scope, + account_selector: binding.account_selector, target_id: binding.target_id, source_binding_id: binding.source_binding.id, + ...(binding.history_source_binding_ids ? { history_source_binding_ids: binding.history_source_binding_ids } : {}) }; +} + +export async function readBoundAccountExecutions(option, env) { + if (option.platform !== "ibkr" || !accountFactsReadModelEnabled(env)) return null; + const bindings = await loadAccountFactsBindings(env); + const matches = (bindings?.bindings || []).filter(binding => binding.platform === option.platform + && binding.account_key === option.key); + if (!matches.length) return null; + if (matches.length !== 1 || !accountFactsOptionMatchesBinding(option, matches[0])) { + throw new HttpError("execution_account_unattributed", 409); + } + const stored = await runtimeInstanceCommand(env, periodReturnCommand(matches[0], "account_executions_read")); + if (!stored.ok) throw new HttpError("execution_store_unavailable", 503); + return stored.executions; +} + +export async function syncAccountExternalFlowResponse(request, env) { + if (!accountFactsReadModelEnabled(env)) return accountFactsDisabledResponse(); + try { + const platform = requireDedicatedAccountFactsSyncToken(request, env); + if (!hasConfigStore(env) || !hasRuntimeInstanceStore(env)) throw new HttpError("account_facts_store_unavailable", 503); + const raw = await readBoundedJson(request, ACCOUNT_FACTS_MAX_BODY_BYTES); + const fields = ["schema_version", "target_id", "source_binding_id", "account_scope", "account_selector", "externalFlow"]; + if (!raw || typeof raw !== "object" || Array.isArray(raw) || Object.keys(raw).length !== fields.length + || fields.some(key => !Object.hasOwn(raw, key)) || raw.schema_version !== "account_external_flow.v1") { + throw new HttpError("invalid_external_flow", 400); + } + const bindings = await loadAccountFactsBindings(env); + const resolved = resolveTrustedAccountFactsBinding(bindings, raw.target_id, raw.source_binding_id); + if (!resolved.ok || resolved.binding.platform !== platform) throw new HttpError("account_facts_identity_mismatch", 409); + const binding = resolved.binding; + if (raw.account_scope !== binding.account_scope || raw.account_selector !== binding.account_selector) { + throw new HttpError("account_facts_identity_mismatch", 409); + } + const config = await loadAccountOptionsConfig(env); + const options = (config.options?.[platform] || []).filter(item => item.key === binding.account_key); + if (options.length !== 1 || !accountFactsOptionMatchesBinding(options[0], binding)) throw new HttpError("account_facts_account_unattributed", 409); + const flow = normalizeExternalFlow(raw.externalFlow); + if (!externalFlowEnabled(env) && !dateOnlyExternalFlow(flow)) throw new HttpError("external_flow_disabled", 409); + const source = { ibkr: "ibkr_flex", schwab: "schwab_transactions", longbridge: "longbridge_cashflow" }[platform]; + if (!source || flow.windows.some(w => w.source !== source)) throw new HttpError("external_flow_source_mismatch", 409); + const stored = await runtimeInstanceCommand(env, { action: "account_external_flow_put", platform, + account_key: binding.account_key, account_scope: binding.account_scope, account_selector: binding.account_selector, + target_id: binding.target_id, source_binding_id: binding.source_binding.id, + ...(platform === SCHWAB_ACCOUNT_FACTS_PLATFORM ? { broker_account_hash: binding.broker_account_hash } : {}), + ...(platform === FIRSTRADE_ACCOUNT_FACTS_PLATFORM ? { broker_account_id: binding.broker_account_id } : {}), + externalFlow: flow }); + return json({ ok: true, stored: true, unchanged: stored.unchanged === true, readback_verified: stored.readback_verified === true, + account_key: binding.account_key, window_count: flow.windows.length, + record_count: new Set(flow.windows.flatMap(w => w.records.map(r => r.source_ref))).size }); + } catch (error) { + if (error instanceof AccountFactsError || error instanceof HttpError || error?.code?.startsWith("external_flow") + || error?.code === "invalid_external_flow") return accountFactsErrorResponse(error, "external_flow_sync"); + return json({ ok: false, error: "account_external_flow_unavailable" }, 503); + } +} + +export async function syncAccountExecutionsResponse(request, env) { + if (!accountFactsReadModelEnabled(env)) return accountFactsDisabledResponse(); + try { + if (requireDedicatedAccountFactsSyncToken(request, env) !== "ibkr") throw new HttpError("account_facts_sync_token_platform_mismatch", 403); + if (!hasConfigStore(env) || !hasRuntimeInstanceStore(env)) throw new HttpError("account_facts_store_unavailable", 503); + const raw = await readBoundedJson(request, ACCOUNT_FACTS_MAX_BODY_BYTES); + const bindings = await loadAccountFactsBindings(env); + const resolved = resolveTrustedAccountFactsBinding(bindings, raw?.target_id, raw?.source_binding_id); + if (!resolved.ok || resolved.binding.platform !== "ibkr") throw new HttpError("account_facts_identity_mismatch", 409); + const binding = resolved.binding; + const config = await loadAccountOptionsConfig(env); + const options = (config.options?.ibkr || []).filter(item => item.key === binding.account_key); + if (options.length !== 1 || !accountFactsOptionMatchesBinding(options[0], binding)) throw new HttpError("account_facts_account_unattributed", 409); + const payload = normalizeExecutions(raw, { binding }); + if (payload.records.some(row => row.report_ref !== payload.source_ref)) throw new HttpError("invalid_broker_executions", 400); + const stored = await runtimeInstanceCommand(env, { ...periodReturnCommand(binding, "account_executions_put"), payload }); + return json({ ok: true, stored: true, unchanged: stored.unchanged === true, + account_key: binding.account_key, period: payload.period, record_count: payload.records.length }); + } catch (error) { + if (error instanceof AccountFactsError || error instanceof HttpError || error?.code?.startsWith("execution_") + || error?.code === "invalid_broker_executions") return accountFactsErrorResponse(error, "executions_sync"); + return json({ ok: false, error: "account_executions_unavailable" }, 503); + } +} + +export async function syncAccountPeriodReturnResponse(request, env) { + if (!accountFactsReadModelEnabled(env)) return accountFactsDisabledResponse(); + try { + if (requireDedicatedAccountFactsSyncToken(request, env) !== "ibkr") throw new HttpError("account_facts_sync_token_platform_mismatch", 403); + if (!hasConfigStore(env) || !hasRuntimeInstanceStore(env)) throw new HttpError("account_facts_store_unavailable", 503); + const raw = await readBoundedJson(request, ACCOUNT_FACTS_MAX_BODY_BYTES); + const bindings = await loadAccountFactsBindings(env); + const resolved = resolveTrustedAccountFactsBinding(bindings, raw?.target_id, raw?.source_binding_id); + if (!resolved.ok || resolved.binding.platform !== "ibkr") throw new HttpError("account_facts_identity_mismatch", 409); + const binding = resolved.binding; + const config = await loadAccountOptionsConfig(env); + const options = (config.options?.ibkr || []).filter(item => item.key === binding.account_key); + if (options.length !== 1 || !accountFactsOptionMatchesBinding(options[0], binding)) throw new HttpError("account_facts_account_unattributed", 409); + const payload = normalizeIbkrPeriodReturn(raw, { binding }); + const stored = await runtimeInstanceCommand(env, { ...periodReturnCommand(binding, "account_period_return_put"), payload, + historical: new URL(request.url).searchParams.get("historical") === "true" }); + return json({ ok: true, stored: true, unchanged: stored.unchanged === true, account_key: binding.account_key, + period: payload.period, currency: payload.currency, method: payload.method }); + } catch (error) { + if (error instanceof AccountFactsError || error instanceof HttpError) return accountFactsErrorResponse(error, "period_return_sync"); + return json({ ok: false, error: "account_period_return_unavailable" }, 503); + } +} + +export async function syncAccountFactsResponse(request, env, options) { + return syncAccountFactsResponseImpl(request, env, { + json, + hasConfigStore, + hasRuntimeInstanceStore, + loadAccountOptionsConfig, + loadAccountFactsBindings, + runtimeInstanceCommand, + readBoundedJson, + requireDedicatedAccountFactsSyncToken, + accountFactsErrorResponse: (error) => accountFactsErrorResponse( + error, options?.historical ? "history_sync" : "current_sync"), + }, options); +} + +export async function accountFactsResponse(request, env) { + return accountFactsResponseImpl(request, env, { + json, + readSession, + hasConfigStore, + hasRuntimeInstanceStore, + loadAccountOptionsConfig, + loadAccountFactsBindings: (env) => loadAccountFactsBindings(env, { propagateReadErrors: true }), + loadStoredAccountFactsMap, + runtimeInstanceCommand, + periodReturnCommand, + accountFactsErrorResponse, + }); +} + +export async function accountFactsHistoryResponse(request, env, url) { + return accountFactsHistoryResponseImpl(request, env, url, { + json, + readSession, + hasConfigStore, + hasRuntimeInstanceStore, + loadAccountOptionsConfig, + loadAccountFactsBindings: (env) => loadAccountFactsBindings(env, { propagateReadErrors: true }), + runtimeInstanceCommand, + accountFactsErrorResponse, + sha256Hex, + }); +} + +export 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); +} + +export function requireDedicatedReconciliationRecoveryControllerToken(request, env) { + const expected = String(env.RECONCILIATION_RECOVERY_CONTROLLER_TOKEN || ""); + if (!expected) throw new HttpError("reconciliation recovery controller token is not configured", 500); + if (expected === String(env.RECONCILIATION_RECOVERY_SYNC_TOKEN || "")) { + throw new HttpError("reconciliation recovery controller token must differ from the source sync token", 500); + } + const header = request.headers.get("Authorization") || ""; + const token = header.match(/^Bearer\s+(.+)$/i)?.[1] || ""; + if (token !== expected) throw new HttpError("reconciliation recovery controller token is invalid", 401); +} + + + +export function requireDedicatedResearchPromotionSyncToken(request, env) { + const expected = String(env.RESEARCH_PROMOTION_SYNC_TOKEN || ""); + if (!expected) throw new HttpError("research promotion 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("research promotion sync token is invalid", 401); +} + +export function researchPromotionTicketKey(ticketId) { + return `${RESEARCH_PROMOTION_TICKET_PREFIX}${ticketId}`; +} + +export function platformSupportsBrokerPaperMode(platform) { + // Broker paper/sim is distinct from the local dry_run adapter. LongBridge's + // paper account is currently the only configured broker simulation route. + return platform === "longbridge"; +} + +export function supportedBrokerEnvironmentsForPlatform(platform) { + return platformSupportsBrokerPaperMode(platform) ? [...BROKER_ENVIRONMENTS] : ["live"]; +} + +export function normalizeBrokerEnvironment(value, fieldName = "broker_environment") { + const environment = String(value || "").trim(); + if (!BROKER_ENVIRONMENTS.includes(environment)) { + throw new Error(`${fieldName} must be live or paper`); + } + return environment; +} + +export function normalizeResearchPromotionRiskProfile(value, fieldName) { + const profile = String(value || "").trim().toUpperCase(); + if (!RESEARCH_PROMOTION_RISK_PROFILES.includes(profile)) { + throw new Error( + `${fieldName} must be CAPITAL_PRESERVATION, BALANCED_COMPOUNDING, or GROWTH_COMPOUNDING`, + ); + } + return profile; +} + +export function normalizePromotionConfirmation(value, fieldName, { paperSupported }) { + if (!value || Array.isArray(value) || typeof value !== "object") { + throw new Error(`${fieldName} must be an object`); + } + // Keep exact QPK PromotionConfirmation.to_dict() fields. + assertExactFields(value, ["target_platform", "execution_mode", "risk_profile"], fieldName); + const targetPlatform = String(value.target_platform || "").trim(); + const executionMode = String(value.execution_mode || "").trim().toLowerCase(); + const riskProfile = normalizeResearchPromotionRiskProfile( + value.risk_profile, + `${fieldName}.risk_profile`, + ); + if (!targetPlatform) throw new Error(`${fieldName}.target_platform is required`); + if (!RESEARCH_PROMOTION_EXECUTION_MODES.includes(executionMode)) { + throw new Error(`${fieldName}.execution_mode must be live or paper`); + } + if (executionMode === "paper" && !paperSupported) { + throw new Error("paper is unavailable for this platform; synthetic matching is not supported"); + } + return { + target_platform: targetPlatform, + execution_mode: executionMode, + risk_profile: riskProfile, + }; +} + +export function validateResearchPromotionSelectedAccount({ + ticket, + selectedAccount, + confirmation, + accountOptions, + strategyProfiles, +}) { + assertExactFields(selectedAccount, ["platform", "key"], "selected_account"); + const platform = cleanChoice(selectedAccount.platform, SUPPORTED_PLATFORMS, "selected_account.platform"); + const key = cleanSlug(selectedAccount.key, "selected_account.key"); + const targetPlatform = String(confirmation?.target_platform || "").trim(); + if (targetPlatform !== platform) { + throw new HttpError("selected_account platform does not match confirmation target_platform", 409); + } + const option = (accountOptions?.[platform] || []).find((item) => item.key === key); + if (!option) throw new HttpError("selected_account does not match configured account", 409); + const brokerEnvironment = String(option.broker_environment || "").trim(); + if (!BROKER_ENVIRONMENTS.includes(brokerEnvironment)) { + throw new HttpError("selected account broker environment is unknown", 409); + } + if (!supportedBrokerEnvironmentsForPlatform(platform).includes(brokerEnvironment)) { + throw new HttpError(`${platform} does not support broker environment ${brokerEnvironment}`, 400); + } + if (String(confirmation?.execution_mode || "").trim().toLowerCase() !== brokerEnvironment) { + throw new HttpError("selected account broker environment does not match confirmation execution_mode", 409); + } + const strategy = (Array.isArray(strategyProfiles) ? strategyProfiles : []) + .find((item) => item.profile === ticket?.strategy_profile); + if (!strategy) throw new HttpError("selected candidate strategy profile is not configured", 409); + if (strategy.domain !== ticket?.domain) { + throw new HttpError("selected candidate strategy domain does not match ticket domain", 409); + } + if (!supportedDomainsForAccount(platform, option).includes(ticket.domain)) { + throw new HttpError("selected account does not support candidate domain", 409); + } + if (PLATFORM_CONFIG[platform]?.dry_run_only) { + throw new HttpError(`${platform} does not support broker execution`, 400); + } + assertDcaPlatform(platform, ticket.strategy_profile); + if (!(strategy.allowed_execution_modes || []).includes(brokerEnvironment)) { + throw new HttpError(`strategy ${strategy.profile} does not support broker environment ${brokerEnvironment}`, 400); + } + // A broker paper/live selection still needs the real adapter path. The + // account's dry_run default is not an implicit paper capability. + const adapterMode = cleanExecutionMode( + option.default_execution_mode || PLATFORM_CONFIG[platform]?.default_execution_mode || "live", + ); + if (adapterMode !== "live") { + throw new HttpError("selected account adapter is not configured for broker execution", 400); + } + return { platform, key, broker_environment: brokerEnvironment }; +} + +export function researchPromotionCandidateIdentity(ticket) { + return { + strategy_profile: String(ticket?.strategy_profile || ""), + domain: String(ticket?.domain || ""), + proposed_params: + ticket?.proposed_params && typeof ticket.proposed_params === "object" && !Array.isArray(ticket.proposed_params) + ? ticket.proposed_params + : {}, + search_iterations: Number(ticket?.search_iterations || 0), + shadow_evidence_kind: String(ticket?.shadow_evidence_kind || ""), + }; +} + +export function researchPromotionCandidatesMatch(left, right) { + return ( + canonicalResearchTaskJson(researchPromotionCandidateIdentity(left)) + === canonicalResearchTaskJson(researchPromotionCandidateIdentity(right)) + ); +} + +export function assertResearchPromotionExpectedCandidate(ticket, raw, { requireExpectedParams = false } = {}) { + if (requireExpectedParams && !Object.prototype.hasOwnProperty.call(raw || {}, "expected_proposed_params")) { + throw new HttpError("accept requires expected_proposed_params", 400); + } + if (!Object.prototype.hasOwnProperty.call(raw || {}, "expected_proposed_params")) return; + const expected = raw.expected_proposed_params; + if (!expected || typeof expected !== "object" || Array.isArray(expected)) { + throw new HttpError("expected_proposed_params must be an object", 400); + } + const stored = ticket.proposed_params || {}; + if (canonicalResearchTaskJson(expected) !== canonicalResearchTaskJson(stored)) { + throw new HttpError("expected_proposed_params does not match stored ticket candidate", 409); + } + if (raw.expected_strategy_profile != null) { + if (String(raw.expected_strategy_profile).trim() !== ticket.strategy_profile) { + throw new HttpError("expected_strategy_profile does not match stored ticket", 409); + } + } + if (raw.expected_domain != null) { + if (String(raw.expected_domain).trim() !== ticket.domain) { + throw new HttpError("expected_domain does not match stored ticket", 409); + } + } +} + +export const RESEARCH_SUMMARY_HAN = /[\u4e00-\u9fff]/; +export const RESEARCH_SUMMARY_LATIN = /[A-Za-z]/; +export const RESEARCH_SUMMARY_DIGIT = /[0-9\uff10-\uff19]/; +export const RESEARCH_SUMMARY_LOCALE_FIELDS = ["question", "basis", "limits", "suggestion"]; +const RESEARCH_SUMMARY_BINDING_FIELDS = [ + "ticket_id", "strategy_profile", "domain", "proposed_params", "comparison", + "shadow_evidence_kind", "shadow_passed", "notes", +]; +export const RESEARCH_SUMMARY_NEW_KEYS = ["status", "provider", "model", "scope", "locales", "binding"]; +export const RESEARCH_SUMMARY_OLD_KEYS = ["status", "provider", "model", "text"]; +export const RESEARCH_SUMMARY_HIDDEN = { status: "unavailable", text: "", provider: "", model: "" }; + +export function researchSummaryExactKeys(value, keys) { + if (!value || Array.isArray(value) || typeof value !== "object") return false; + const present = Object.keys(value); + return present.length === keys.length && keys.every((key) => Object.prototype.hasOwnProperty.call(value, key)); +} + +export function researchSummaryCodePointLength(text) { + return Array.from(text).length; +} + +export function researchSummarySegment(text, chinese) { + if (typeof text !== "string" || !text.trim() || researchSummaryCodePointLength(text) > 240) return null; + if (RESEARCH_SUMMARY_DIGIT.test(text)) return null; + const hasHan = RESEARCH_SUMMARY_HAN.test(text); + if (chinese) return hasHan ? text : null; + if (hasHan || !RESEARCH_SUMMARY_LATIN.test(text)) return null; + return text; +} + +export function sameResearchSummaryMaterial(left, right) { + return canonicalResearchTaskJson(left) === canonicalResearchTaskJson(right); +} + +export function readBilingualResearchPromotionExplanation(ai, material) { + if (ai.status !== "available" || (ai.provider !== "codex" && ai.provider !== "cursor")) return null; + if (typeof ai.model !== "string" || !ai.model.trim() || ai.scope !== "candidate") return null; + const locales = ai.locales; + if (!researchSummaryExactKeys(locales, ["zh-CN", "en"])) return null; + const savedLocales = {}; + for (const [name, chinese] of [["zh-CN", true], ["en", false]]) { + const section = locales[name]; + if (!researchSummaryExactKeys(section, RESEARCH_SUMMARY_LOCALE_FIELDS)) return null; + const saved = {}; + for (const field of RESEARCH_SUMMARY_LOCALE_FIELDS) { + const segment = researchSummarySegment(section[field], chinese); + if (segment == null) return null; + saved[field] = segment; + } + savedLocales[name] = saved; + } + if (!researchSummaryExactKeys(ai.binding, RESEARCH_SUMMARY_BINDING_FIELDS)) return null; + if (!sameResearchSummaryMaterial(ai.binding, material)) return null; + return { + status: "available", + provider: ai.provider, + model: ai.model, + scope: "candidate", + locales: savedLocales, + binding: JSON.parse(canonicalResearchTaskJson(material)), + }; +} + +export function readLegacyResearchPromotionExplanation(ai) { + const status = String(ai.status || "").trim(); + if (status !== "available" && status !== "unavailable") return null; + if (typeof ai.text !== "string" || typeof ai.provider !== "string" || typeof ai.model !== "string") return null; + if (ai.text.length > 2000 || ai.model.length > 120) return null; + if (status === "available" && (ai.provider !== "codex" || !ai.text.trim())) return null; + if (status === "unavailable" && ai.provider !== "") return null; + return { + status, + text: ai.text.trim(), + provider: ai.provider, + model: ai.model.trim(), + }; +} + +export function normalizeSavedResearchPromotionExplanation(ai, material) { + try { + if (ai == null) return { ...RESEARCH_SUMMARY_HIDDEN }; + if (researchSummaryExactKeys(ai, RESEARCH_SUMMARY_NEW_KEYS)) { + return readBilingualResearchPromotionExplanation(ai, material) || { ...RESEARCH_SUMMARY_HIDDEN }; + } + if (researchSummaryExactKeys(ai, RESEARCH_SUMMARY_OLD_KEYS)) { + return readLegacyResearchPromotionExplanation(ai) || { ...RESEARCH_SUMMARY_HIDDEN }; + } + return { ...RESEARCH_SUMMARY_HIDDEN }; + } catch { + return { ...RESEARCH_SUMMARY_HIDDEN }; + } +} + +export function normalizeResearchPromotionSummary(raw, ticket, fieldName = "research_summary") { + if (raw == null) return null; + if (!raw || Array.isArray(raw) || typeof raw !== "object") return null; + try { + const comparisonForBinding = raw.comparison; + const identity = raw.identity; + if (!identity || Array.isArray(identity) || typeof identity !== "object") return null; + const identityParams = identity.proposed_params; + if ( + String(identity.strategy_profile || "").trim() !== ticket.strategy_profile + || String(identity.domain || "").trim() !== ticket.domain + || !identityParams || Array.isArray(identityParams) + || canonicalResearchTaskJson(identityParams) !== canonicalResearchTaskJson(ticket.proposed_params || {}) + ) return null; + + if (raw.strategy_description != null && typeof raw.strategy_description !== "string") return null; + const strategyDescription = String(raw.strategy_description || "").trim(); + if (strategyDescription.length > 500) return null; + + let plugins = null; + if (raw.plugins === null) { + plugins = null; + } else if (Array.isArray(raw.plugins)) { + plugins = raw.plugins.map((plugin, index) => { + if (!plugin || Array.isArray(plugin) || typeof plugin !== "object") { + throw new Error(`${fieldName}.plugins[${index}] must be an object`); + } + if (typeof plugin.id !== "string" || typeof plugin.description !== "string") { + throw new Error(`${fieldName}.plugins[${index}] must include text fields`); + } + const id = plugin.id.trim(); + const description = plugin.description.trim(); + if (id.length > 120 || description.length > 300) throw new Error(`${fieldName}.plugins[${index}] is too long`); + return { id, description }; + }); + } else if (raw.plugins == null) { + plugins = null; + } else { + return null; + } + + const comparison = raw.comparison == null ? { status: "unavailable" } : raw.comparison; + if (!comparison || Array.isArray(comparison) || typeof comparison !== "object") return null; + const comparisonStatus = String(comparison.status || "").trim(); + if (comparisonStatus !== "comparable" && comparisonStatus !== "unavailable") return null; + const optionalText = (value, name) => { + if (value == null) return null; + if (typeof value !== "string") throw new Error(`${fieldName}.${name} must be text or null`); + const text = value.trim(); + if (text.length > 120) throw new Error(`${fieldName}.${name} is too long`); + return text; + }; + const exactCostModel = (value, name) => { + if (value == null) return null; + if (typeof value !== "string") throw new Error(`${fieldName}.${name} must be text or null`); + if (value.length > 120) throw new Error(`${fieldName}.${name} is too long`); + return value; + }; + const normalizeMetrics = (value, name) => { + if (!value || Array.isArray(value) || typeof value !== "object") { + throw new Error(`${fieldName}.${name} must be an object`); + } + const cagr = value.cagr; + const maxDrawdown = value.max_drawdown; + if (typeof cagr !== "number" || typeof maxDrawdown !== "number" + || !Number.isFinite(cagr) || !Number.isFinite(maxDrawdown) + || cagr < -1 || maxDrawdown < -1 || maxDrawdown > 0) { + throw new Error(`${fieldName}.${name} must use finite numbers`); + } + return { cagr, max_drawdown: maxDrawdown }; + }; + const normalizedComparison = { + status: comparisonStatus, + start_date: optionalText(comparison.start_date, "comparison.start_date"), + end_date: optionalText(comparison.end_date, "comparison.end_date"), + cost_model: exactCostModel(comparison.cost_model, "comparison.cost_model"), + baseline: null, + candidate: null, + }; + if (comparisonStatus === "comparable") { + if (!normalizedComparison.start_date || !normalizedComparison.end_date || !normalizedComparison.cost_model?.trim()) return null; + if (!/^\d{4}-\d{2}-\d{2}$/.test(normalizedComparison.start_date) + || !/^\d{4}-\d{2}-\d{2}$/.test(normalizedComparison.end_date)) return null; + const startMillis = Date.parse(`${normalizedComparison.start_date}T00:00:00Z`); + const endMillis = Date.parse(`${normalizedComparison.end_date}T00:00:00Z`); + if (!Number.isFinite(startMillis) || !Number.isFinite(endMillis) || startMillis > endMillis) return null; + normalizedComparison.baseline = normalizeMetrics(comparison.baseline, "comparison.baseline"); + normalizedComparison.candidate = normalizeMetrics(comparison.candidate, "comparison.candidate"); + } else { + if (comparison.baseline != null) normalizeMetrics(comparison.baseline, "comparison.baseline"); + if (comparison.candidate != null) normalizeMetrics(comparison.candidate, "comparison.candidate"); + } + + if (!Array.isArray(raw.limitations) || raw.limitations.length > 8 || raw.limitations.some((item) => typeof item !== "string" || item.length > 300)) return null; + const aiExplanation = normalizeSavedResearchPromotionExplanation(raw.ai_explanation, { + ticket_id: ticket.ticket_id, + strategy_profile: ticket.strategy_profile, + domain: ticket.domain, + proposed_params: ticket.proposed_params || {}, + comparison: comparisonForBinding, + shadow_evidence_kind: ticket.shadow_evidence_kind, + shadow_passed: ticket.shadow_passed, + notes: Array.isArray(ticket.notes) ? ticket.notes : [], + }); + + return { + identity: { + strategy_profile: ticket.strategy_profile, + domain: ticket.domain, + proposed_params: ticket.proposed_params || {}, + }, + strategy_description: strategyDescription, + plugins, + comparison: normalizedComparison, + limitations: raw.limitations.map((item) => item.trim()), + ai_explanation: aiExplanation, + }; + } catch { + return null; + } +} + +export function normalizeResearchPromotionTicket(raw, fieldName = "research promotion ticket") { + if (!raw || Array.isArray(raw) || typeof raw !== "object") { + throw new Error(`${fieldName} must be an object`); + } + const ticketId = String(raw.ticket_id || "").trim(); + const strategyProfile = String(raw.strategy_profile || "").trim(); + const domain = String(raw.domain || "").trim(); + const state = String(raw.state || "").trim(); + if (!ticketId) throw new Error(`${fieldName}.ticket_id is required`); + if (!strategyProfile) throw new Error(`${fieldName}.strategy_profile is required`); + if (!domain) throw new Error(`${fieldName}.domain is required`); + if (!RESEARCH_PROMOTION_STATES.includes(state)) { + throw new Error(`${fieldName}.state is unsupported`); + } + if (raw.live_authority_granted === true) { + throw new Error(`${fieldName}.live_authority_granted must remain false`); + } + const suggested = normalizeResearchPromotionRiskProfile( + raw.suggested_risk_profile || DEFAULT_RESEARCH_PROMOTION_RISK_PROFILE, + `${fieldName}.suggested_risk_profile`, + ); + const driftStatus = String(raw.drift_status || ""); + const normalizedTicket = { + schema: RESEARCH_PROMOTION_TICKET_SCHEMA, + ticket_id: ticketId, + strategy_profile: strategyProfile, + domain, + state, + drift_status: driftStatus, + drift_score: driftStatus === "new_research" && raw.drift_score === null + ? null + : Number(raw.drift_score || 0), + created_at: String(raw.created_at || ""), + updated_at: String(raw.updated_at || ""), + budget: raw.budget && typeof raw.budget === "object" && !Array.isArray(raw.budget) ? raw.budget : {}, + proposed_params: + raw.proposed_params && typeof raw.proposed_params === "object" && !Array.isArray(raw.proposed_params) + ? raw.proposed_params + : {}, + search_iterations: Number(raw.search_iterations || 0), + shadow_evidence_kind: String(raw.shadow_evidence_kind || ""), + // Qualification must never treat string values such as "false" as a + // passed observation. Keep malformed legacy values unknown and let the + // application gate reject them. + shadow_passed: raw.shadow_passed === true ? true : raw.shadow_passed === false ? false : null, + notification_subject: String(raw.notification_subject || ""), + notification_body: String(raw.notification_body || ""), + human_decision: String(raw.human_decision || ""), + human_decided_at: String(raw.human_decided_at || ""), + live_authority_granted: false, + suggested_risk_profile: suggested, + confirmation_target_platform: String(raw.confirmation_target_platform || ""), + confirmation_execution_mode: String(raw.confirmation_execution_mode || ""), + confirmation_risk_profile: String(raw.confirmation_risk_profile || ""), + notes: Array.isArray(raw.notes) ? raw.notes.map((item) => String(item)) : [], + }; + const researchSummary = normalizeResearchPromotionSummary(raw.research_summary, normalizedTicket, `${fieldName}.research_summary`); + if (researchSummary) normalizedTicket.research_summary = researchSummary; + return normalizedTicket; +} + +export function applyResearchPromotionDecision( + ticket, + { + decision, + confirmation = null, + paperSupported = false, + selectedAccount = null, + accountOptions = null, + strategyProfiles = null, + decidedAt = null, + expectedRaw = null, + }, +) { + if (ticket.state !== "awaiting_human") { + throw new HttpError(`ticket ${ticket.ticket_id} is not awaiting human (state=${ticket.state})`, 409); + } + const normalized = String(decision || "").trim().toLowerCase(); + if (normalized !== "accept" && normalized !== "reject") { + throw new HttpError("decision must be accept or reject", 400); + } + assertResearchPromotionExpectedCandidate(ticket, expectedRaw || {}, { + requireExpectedParams: normalized === "accept", + }); + const stamp = decidedAt || new Date().toISOString(); + const next = { + ...ticket, + human_decision: normalized, + human_decided_at: stamp, + live_authority_granted: false, + updated_at: stamp, + notes: [...(ticket.notes || [])], + }; + if (normalized === "accept") { + if (!confirmation) { + throw new HttpError( + "accept requires confirmation (target_platform, execution_mode, risk_profile)", + 400, + ); + } + const confirmed = normalizePromotionConfirmation(confirmation, "confirmation", { + paperSupported: Boolean(paperSupported), + }); + if (!selectedAccount) { + throw new HttpError("accept requires selected_account", 400); + } + validateResearchPromotionSelectedAccount({ + ticket, + selectedAccount, + confirmation: confirmed, + accountOptions, + strategyProfiles, + }); + next.confirmation_target_platform = confirmed.target_platform; + next.confirmation_execution_mode = confirmed.execution_mode; + next.confirmation_risk_profile = confirmed.risk_profile; + next.state = "human_accepted"; + next.notes.push( + "human_accepted_intent_only_no_live_authority", + `confirmation_platform=${confirmed.target_platform}`, + `confirmation_mode=${confirmed.execution_mode}`, + `confirmation_risk_profile=${confirmed.risk_profile}`, + ); + } else { + next.state = "human_rejected"; + next.notes.push("human_rejected"); + } + return next; +} + +export async function syncResearchPromotionTicketResponse(request, env) { + requireDedicatedResearchPromotionSyncToken(request, env); + if (!hasConfigStore(env)) { + return json({ ok: false, error: "research promotion KV is not configured" }, 503); + } + let raw; + try { + raw = await readBoundedJson(request, RESEARCH_PROMOTION_MAX_BODY_BYTES); + } catch (error) { + return json({ ok: false, error: error.message || "invalid research promotion ticket" }, error.status || 400); + } + let ticket; + try { + ticket = normalizeResearchPromotionTicket(raw); + } catch (error) { + return json({ ok: false, error: error.message || "invalid research promotion ticket" }, 400); + } + if (ticket.state !== "awaiting_human") { + return json( + { ok: false, error: "research promotion sync only accepts awaiting_human tickets" }, + 400, + ); + } + const existingRaw = await readConfigJson(env, researchPromotionTicketKey(ticket.ticket_id)); + let existing = null; + if (existingRaw) { + try { + existing = normalizeResearchPromotionTicket(existingRaw, "stored research promotion ticket"); + } catch (error) { + return json( + { ok: false, error: error.message || "invalid stored research promotion ticket" }, + 400, + ); + } + } + let material; + try { + material = await publishHumanPromotionMaterial(env, ticket, existing); + } catch (error) { + return json({ ok: false, error: error.message || "research promotion material unavailable" }, error.status || 503); + } + if (material.identity_conflict) { + return json( + { ok: false, error: "refusing to overwrite awaiting_human research promotion ticket with mismatched candidate identity" }, + 409, + ); + } + if (material.terminal || material.legacy_blocked) { + return json( + { ok: false, error: `refusing to overwrite terminal research promotion ticket state=${material.terminal_state || existing?.state || "human_accepted"}` }, + 409, + ); + } + await writeConfigJson(env, researchPromotionTicketKey(ticket.ticket_id), ticket); + try { + await appendAuditLog(env, { + ts: new Date().toISOString(), + login: "research-promotion-ticket-sync", + action: "sync_research_promotion_ticket", + ticket_id: ticket.ticket_id, + state: ticket.state, + suggested_risk_profile: ticket.suggested_risk_profile, + live_authority_granted: false, + }); + } catch { + // Intent sync must not depend on optional audit retention. + } + return json({ + ok: true, + ticket_id: ticket.ticket_id, + state: ticket.state, + suggested_risk_profile: ticket.suggested_risk_profile, + live_authority_granted: false, + }); +} + +export async function fetchResearchPromotionTicketResponse(request, env) { + requireDedicatedResearchPromotionSyncToken(request, env); + if (!hasConfigStore(env)) { + return json({ ok: false, error: "research promotion KV is not configured" }, 503); + } + const ticketId = String(new URL(request.url).searchParams.get("ticket_id") || "").trim(); + if (!ticketId) return json({ ok: false, error: "ticket_id is required" }, 400); + const stored = await readConfigJson(env, researchPromotionTicketKey(ticketId)); + if (!stored) return json({ ok: false, error: "research promotion ticket not found" }, 404); + let ticket; + try { + ticket = normalizeResearchPromotionTicket(stored); + ticket = await promotionTicketWithAuthority(env, ticket); + } catch (error) { + return json({ ok: false, error: error.message || "invalid stored research promotion ticket" }, error.status || 400); + } + if (ticket.live_authority_granted) { + return json({ ok: false, error: "refusing to expose live_authority_granted=true" }, 500); + } + return json({ + ok: true, + ticket: attachRiskEnvelopeView(ticket), + live_authority_granted: false, + }); +} + +export async function listResearchPromotionTickets(env) { + if (!hasConfigStore(env)) return []; + const store = configStore(env); + let listing; + try { + listing = await directoryList(env, RESEARCH_PROMOTION_TICKET_PREFIX, RESEARCH_PROMOTION_MAX_TICKETS); + } catch (error) { + throw new HttpError(sourceListFailure(error, "research_promotion_source_list_unavailable"), 503); + } + const keys = listing?.keys || []; + const tickets = []; + for (const entry of keys) { + const key = entry.name || entry; + if (!String(key).startsWith(RESEARCH_PROMOTION_TICKET_PREFIX)) continue; + const stored = await readConfigJson(env, key); + if (!stored) throw new HttpError("kv_directory_record_unavailable", 503); + try { + const record = normalizeResearchPromotionTicket(stored, key); + const authority = await readHumanDecisionAuthority(env, "promotion", record.ticket_id); + const ticket = attachPromotionQueueReadMetadata(record, overlayPromotionDecision(record, authority), authority); + if (ticket.live_authority_granted) continue; + if (ticket.state === "awaiting_human") { + // Project the same durable observation used by the decision metadata. + ticket.decision_authority = humanDecisionAuthorityView(authority); + } + tickets.push(ticket); + } catch (error) { + if (error instanceof HttpError && error.status === 503) throw error; + // Skip corrupt tickets rather than failing the whole queue. + } + } + tickets.sort((left, right) => String(right.updated_at).localeCompare(String(left.updated_at))); + return tickets.slice(0, RESEARCH_PROMOTION_MAX_TICKETS); +} + +export function attachPromotionQueueReadMetadata(record, ticket, authority) { + // Reuse the queue's existing material observation. Unknown is not permission; + // these fixed reasons describe stored intent material flags, not full decision + // permission, ticket-hash validation, or account application gates. + const migrated = typeof authority?.migrated === "boolean" ? authority.migrated : null; + const eligible = migrated === true && typeof authority?.eligible === "boolean" ? authority.eligible : null; + const blocked = migrated === true && typeof authority?.blocked === "boolean" ? authority.blocked : null; + const blockerCodes = migrated !== true ? null + : blocked === true ? ["human_decision_legacy_blocked"] + : eligible === false ? ["human_decision_material_conflict"] + : eligible === true && blocked === false ? [] : null; + return { + ...ticket, + decision_material: { migrated, eligible, blocked, blocker_codes: blockerCodes }, + decision_review_binding: authority?.review_binding || null, + // Timestamps of the already-read KV ticket record, not producer-observed + // time, freshness, a validity window, or evidence of actual execution. + ticket_record_timestamps: { + created_at: typeof record?.created_at === "string" && record.created_at ? record.created_at : null, + updated_at: typeof record?.updated_at === "string" && record.updated_at ? record.updated_at : null, + }, + }; +} + +export async function researchPromotionTicketsResponse(request, env) { + const session = await readSession(request, env); + if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); + const exact = await exactHumanDecisionResponse(request, env, session, "promotion"); + if (exact) return exact; + const tickets = []; + const invalidTickets = []; + for (const ticket of await listResearchPromotionTickets(env)) { + let reviewBinding; + try { + reviewBinding = await researchPromotionReviewBinding(ticket); + } catch (error) { + if (!(error instanceof ResearchPromotionReviewMaterialError)) throw error; + // Keep a bounded, explicit non-actionable diagnosis; never serialize the + // invalid numbers as fake zero/null or certify this ticket with a digest. + invalidTickets.push({ + ticket_id: ticket.ticket_id, strategy_profile: ticket.strategy_profile, state: ticket.state, + decision_material: { eligible: false, blocked: true, blocker_codes: [RESEARCH_PROMOTION_REVIEW_INVALID] }, + decision_binding: null, live_authority_granted: false, no_order: true, + }); + continue; + } + tickets.push({ ...ticket, decision_binding: { + kind: "promotion", subject_id: ticket.ticket_id, material_sha256: await researchPromotionMaterialKey(ticket), + review_binding: promotionReviewBindingsMatch(reviewBinding, ticket.decision_review_binding) ? reviewBinding : null, + } }); + } + const awaiting = tickets.filter((ticket) => ticket.state === "awaiting_human"); + const applicationTickets = tickets.filter((ticket) => + ticket.state === "awaiting_human" || ticket.state === "human_accepted"); + const accountConfig = await loadAccountOptionsConfig(env); + const strategyProfiles = await loadStrategyProfilesConfig(env); + const applications = []; + for (const ticket of applicationTickets) { + const preparation = attachResearchPromotionApplicationPreparation(ticket, accountConfig.options, strategyProfiles); + let application = null; + if (hasRuntimeInstanceStore(env)) { + try { + const stored = await runtimeInstanceCommand(env, { action: "application_latest", actor: session.login, ticket_id: ticket.ticket_id }); + application = publicResearchPromotionApplication(stored.application); + } catch { + // Application storage is optional for the research-only queue view. + } + } + applications.push({ ...preparation, application }); + } + return json({ + schema_version: RESEARCH_PROMOTION_QUEUE_SCHEMA, + data_status: hasConfigStore(env) ? (invalidTickets.length ? "partial" : "ready") : "unavailable", + computed_at: new Date().toISOString(), + tickets: decorateQueue({ tickets: awaiting.map(attachRiskEnvelopeView) }, "tickets", "strategy_promotion").tickets, + applications, + ...(invalidTickets.length ? { invalid_tickets: invalidTickets } : {}), + summary: { + ticket_count: awaiting.length, + awaiting_human: awaiting.length, + application_count: applications.length, + ...(invalidTickets.length ? { invalid_review_count: invalidTickets.length } : {}), + }, + policy: { + admin_required_to_decide: true, + live_authority_granted: false, + no_order: true, + application_dispatch_allowed: false, + activation_status: "not_connected", + notice: "网页只记录晋级人工意图;确认不授予实盘权限,也不下单。", + }, + errors: invalidTickets.length ? [RESEARCH_PROMOTION_REVIEW_INVALID] : [], + }); +} + +const DECISION_POLICY = Object.freeze({ + no_order: true, + execution_authority_granted: false, + current_evidence_required: true, + human_confirmation_required: true, + notice: "只记录人工决定意图;不会下单、变更资金或启用实盘。", +}); + +export function decisionInboxSources() { + return { + owner: ownerDecisionQueueResponse, + promotion: researchPromotionTicketsResponse, + recovery: reconciliationRecoveryResponse, + credential: (request, env) => credentialMaintenanceResponse( + request, env, new URL("/api/credential-maintenance", request.url), + ), + }; +} + +export function decodeDecisionApiId(raw) { + let id; + try { + id = decodeURIComponent(String(raw || "")); + } catch { + return null; + } + const parts = decisionCategoryPart(id); + if (parts) return { id, category: parts.category, subject_id: parts.subject_id }; + const legacyCredential = /^credential:(.+)$/.exec(id); + if (!legacyCredential || legacyCredential[1].length > 256 || /[\u0000-\u001f\u007f]/.test(legacyCredential[1])) return null; + return { id, category: "credential_maintenance", subject_id: legacyCredential[1] }; +} + +export function decodeOperationApiId(raw) { + let id; + try { + id = decodeURIComponent(String(raw || "")); + } catch { + return null; + } + const match = /^operation:(owner_decision|strategy_promotion|paper_account_application|reconciliation_recovery|credential_maintenance):(.+)$/.exec(id); + if (!match || match[2].length > 256 || /[\u0000-\u001f\u007f]/.test(match[2])) return null; + return { id, category: match[1], subject_id: match[2] }; +} + +const DECISION_SOURCE_BY_CATEGORY = Object.freeze({ + owner_decision: "owner", + strategy_promotion: "promotion", + paper_account_application: "promotion", + reconciliation_recovery: "recovery", + credential_maintenance: "credential", +}); + +/** + * Absence is only proven when the owning source was read. A failed or + * unavailable source stays a 503 so a missing id never masks a broken read. + */ +export function decisionSourceUnreadable(queue, category) { + const name = DECISION_SOURCE_BY_CATEGORY[category]; + const source = (queue?.sources || []).find((entry) => entry.source === name); + if (!source) return true; + return ["read_failed", "unavailable"].includes(source.status); +} + +// The durable record keeps the operation id in its request key. The id names the +// recorded intent; a legacy fingerprint key carries no client operation id. +export function recordedDecisionOperationId(record) { + const match = /^id:(?:owner|promotion|recovery):(.+)$/.exec( + typeof record?.request_key === "string" ? record.request_key : "", + ); + return match ? match[1] : null; +} + +// The recorded intent's canonical target: accepted selection for promotion, action elsewhere. +export function decisionIntentTargetJson(category, action, selection) { + if (category === "strategy_promotion" || category === "paper_account_application") { + const accepted = action === "accept" ? selection : null; + return canonicalResearchTaskJson({ + selected_account: accepted?.selected_account ?? null, + confirmation: accepted?.confirmation ?? null, + }); + } + return canonicalResearchTaskJson({ decision: action }); +} + +// A terminal item synthesized from the durable record. It keeps the original +// decision id resolvable after the pending projection drops or recategorizes it. +export function durableDecisionItem(record, parsed, kind, aliasItem = null) { + const material = /^[0-9a-f]{64}$/.test(record.material_key || "") ? record.material_key : null; + const reviewSha = /^[0-9a-f]{64}$/.test(record.payload?.review_binding?.review_sha256 || "") + ? record.payload.review_binding.review_sha256 : null; + const binding = { + kind, + subject_id: record.subject_id, + material_sha256: material, + ...(reviewSha ? { review_binding: { schema_version: "qsl_promotion_review_binding.v2", review_sha256: reviewSha } } : {}), + }; + return { + id: parsed.id, + short_id: shortId(parsed.id), + decision_id: parsed.id, + category: parsed.category, + title_key: `decisions.${parsed.category}`, + revision: decisionRevision(binding, parsed.id), + actionable: false, + blocking_reason_codes: ["decision_already_decided"], + allowed_actions: [], + request_id_required: false, + action_endpoint: null, + decision_state: { + status: "decided", + action: record.action, + decided_at: typeof record.decided_at === "string" ? record.decided_at : null, + }, + decision_binding: binding, + operation_reference: aliasItem?.operation_reference + || decisionOperationReference({}, parsed.category, parsed.subject_id), + source: "durable_human_decision_record", + }; +} + +export async function loadDecisionById(request, env, parsed) { + // Detail and action routes address one decision by id. Reading the unpaged + // projection keeps them independent of the presentation page a client last saw. + const queue = await inbox(request, env, decisionInboxSources(), { paginate: false }); + const item = queue.items.find((entry) => entry.id === parsed.id) || null; + if (item) return { queue, item, authorityRaw: undefined }; + // A terminal record outlives the pending projection: accepted paper tickets are + // recategorized and rejected tickets leave the queue, so resolve them durably. + const kind = decisionKind(parsed.category); + if (!kind || !hasRuntimeInstanceStore(env)) return { queue, item: null, authorityRaw: undefined }; + let authorityRaw; + try { + authorityRaw = await readHumanDecisionAuthority(env, kind, parsed.subject_id); + } catch { + throw new HttpError("decision_authority_unavailable", 503); + } + const record = authorityRaw?.migrated === false ? null : authorityRaw?.decision; + if (!record || record.kind !== kind || record.subject_id !== parsed.subject_id) + return { queue, item: null, authorityRaw }; + const alias = queue.items.find((entry) => entry.decision_binding?.subject_id === parsed.subject_id + && entry.decision_binding?.kind === kind); + return { queue, item: durableDecisionItem(record, parsed, kind, alias || null), authorityRaw }; +} + +export async function readDecisionAuthorityOrNull(env, kind, subjectId) { + if (!kind) return null; + if (!hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instances_not_bound", 503); + return readHumanDecisionAuthority(env, kind, subjectId); +} + +export async function decisionReviewResponse(request, env, rawId) { + if (request.method !== "GET") return json({ ok: false, error: "method_not_allowed" }, 405); + const session = await readSession(request, env); + if (!session?.allowed) return json({ ok: false, error: "login required", error_state: "unauthorized" }, 401); + const parsed = decodeDecisionApiId(rawId); + if (!parsed) return json({ ok: false, error: "decision_id_invalid" }, 400); + const { queue, item, authorityRaw: loadedAuthority } = await loadDecisionById(request, env, parsed); + if (!item) { + if (decisionSourceUnreadable(queue, parsed.category)) + return json({ ok: false, error: "decision_source_unavailable", error_state: queue.error_state || null }, 503); + return json({ ok: false, error: "decision_not_found" }, 404); + } + const kind = decisionKind(parsed.category); + let authorityRaw; + try { + authorityRaw = loadedAuthority !== undefined + ? loadedAuthority + : await readDecisionAuthorityOrNull(env, kind, parsed.subject_id); + } catch { + return json({ ok: false, error: "decision_authority_unavailable" }, 503); + } + const authority = authorityRaw ? humanDecisionAuthorityView(authorityRaw) : null; + const now = Date.now(); + let receipt = null; + const errors = []; + if (authorityRaw?.decision) { + try { + receipt = await humanDecisionReceipt(authorityRaw.decision, { + kind, + subject_id: parsed.subject_id, + material_sha256: authorityRaw.decision.material_key, + }); + } catch { + errors.push("decision_receipt_unavailable"); + } + } + if (authorityRaw?.migrated === false) errors.push("decision_authority_unavailable"); + if (queue.status !== "ok") errors.push("decision_source_partial"); + return json({ + schema_version: DECISION_REVIEW_SCHEMA_VERSION, + server_time: new Date(now).toISOString(), + data_status: errors.length ? "partial" : "ready", + decision_id: item.id, + decision: item, + current_eligibility: decisionCurrentEligibility(item, authority, now), + decision_authority: authority, + decision_receipt: receipt, + operation: item.operation_reference, + policy: DECISION_POLICY, + tier_policies: riskTierPolicyCatalog(), + errors: [...new Set(errors)], + }); +} + +export function decisionSelection(raw) { + const selection = raw?.selection; + if (!selection || Array.isArray(selection) || typeof selection !== "object") return null; + return { + confirmation: selection.confirmation && typeof selection.confirmation === "object" && !Array.isArray(selection.confirmation) + ? selection.confirmation : null, + selected_account: selection.selected_account && typeof selection.selected_account === "object" && !Array.isArray(selection.selected_account) + ? selection.selected_account : null, + }; +} + +export async function decisionActionResponse(request, env, rawId) { + requireSameOrigin(request, { requireOrigin: true }); + const session = await readSession(request, env); + if (!session?.allowed) return json({ ok: false, error: "login required", error_state: "unauthorized" }, 401); + if (!session.admin) return json({ ok: false, error: "admin required" }, 403); + const parsed = decodeDecisionApiId(rawId); + if (!parsed) return json({ ok: false, error: "decision_id_invalid" }, 400); + const supported = DECISION_ACTIONS[parsed.category] || []; + const kind = decisionKind(parsed.category); + if (!kind || !supported.length) return json({ ok: false, error: "decision_not_actionable" }, 409); + + let raw; + try { + raw = await readBoundedJson(request, DECISION_ACTION_MAX_BODY_BYTES); + } catch (error) { + return json({ ok: false, error: error.message || "invalid decision action" }, error.status || 400); + } + if (!raw || Array.isArray(raw) || typeof raw !== "object") + return json({ ok: false, error: "decision_action_request_invalid" }, 400); + if (Object.keys(raw).some((key) => !["action", "expected_revision", "expected_expires_at", "request_id", "operation_id", "selection"].includes(key))) + return json({ ok: false, error: "decision_action_request_invalid" }, 400); + const action = typeof raw.action === "string" ? raw.action.trim() : ""; + if (!supported.includes(action)) + return json({ ok: false, error: "decision_action_invalid" }, 400); + if (typeof raw.expected_revision !== "string" || !/^rev[0-9]-[0-9a-z-]{1,64}$/.test(raw.expected_revision)) + return json({ ok: false, error: "decision_revision_required" }, 400); + const requestId = typeof raw.request_id === "string" ? raw.request_id.trim() : ""; + // The operation id names the intent. It is the durable idempotency key, so a + // retry with the same id replays and a new intent needs a new id. + const operationId = decisionOperationId(raw); + if (!operationId) { + if (requestId && !/^[A-Za-z0-9._:-]{1,128}$/.test(requestId)) + return json({ ok: false, error: "invalid request_id" }, 400); + return json({ ok: false, error: "decision_operation_id_invalid" }, 400); + } + const operationSha256 = await decisionOperationSha256(parsed.id, raw); + const selection = decisionSelection(raw); + if ((parsed.category === "strategy_promotion" || parsed.category === "paper_account_application") + && action === "accept" && (!selection?.confirmation || !selection?.selected_account)) + return json({ ok: false, error: "decision_selection_required" }, 400); + + const { queue, item } = await loadDecisionById(request, env, parsed); + if (!item) { + if (decisionSourceUnreadable(queue, parsed.category)) + return json({ ok: false, error: "decision_source_unavailable", error_state: queue.error_state || null }, 503); + return json({ ok: false, error: "decision_not_found" }, 404); + } + const now = Date.now(); + const serverTime = new Date(now).toISOString(); + let authorityRaw; + try { + authorityRaw = await readHumanDecisionAuthority(env, kind, parsed.subject_id); + } catch { + return json({ ok: false, error: "decision_authority_unavailable", server_time: serverTime }, 503); + } + const authority = humanDecisionAuthorityView(authorityRaw); + const materialKey = item.decision_binding?.material_sha256 || null; + const existing = authorityRaw?.decision; + if (existing && materialKey && existing.material_key === materialKey) { + // The durable request key keeps the operation id that produced the record. + const recordedOperationId = recordedDecisionOperationId(existing); + // A retry replays only when the recorded operation id and the complete + // canonical intent match. Any other intent on the same material conflicts, + // whether it reuses the id or presents a new one with the same action. + let recordedTarget = null; + try { + recordedTarget = canonicalResearchTaskJson(JSON.parse(existing.target_json || "{}")); + } catch { + recordedTarget = null; + } + const intentMatches = recordedOperationId !== null && recordedOperationId === operationId + && recordedTarget !== null + && recordedTarget === decisionIntentTargetJson(parsed.category, action, selection); + if (existing.kind !== kind || existing.subject_id !== parsed.subject_id || existing.action !== action + || !intentMatches) { + return json({ + ok: false, + error: "decision_operation_conflict", + decision_id: item.id, + operation_id: operationId, + recorded_operation_id: recordedOperationId, + server_time: serverTime, + }, 409); + } + const receipt = await humanDecisionReceipt(existing, { + kind, subject_id: parsed.subject_id, material_sha256: materialKey, + }); + return json({ + schema_version: DECISION_ACTION_SCHEMA_VERSION, + ok: true, + replayed: true, + decision_id: item.id, + revision: item.revision, + action, + operation_id: operationId, + operation_sha256: operationSha256, + decision_receipt: receipt, + decided_at: existing.decided_at, + decision_state: { status: "decided", action, decided_at: existing.decided_at }, + operation: item.operation_reference, + policy: DECISION_POLICY, + server_time: serverTime, + }); + } + if (raw.expected_revision !== item.revision) + return json({ ok: false, error: "decision_revision_conflict", decision_id: item.id, server_time: serverTime }, 409); + if (raw.expected_expires_at !== undefined && raw.expected_expires_at !== item.expires_at) + return json({ ok: false, error: "decision_expiry_changed", decision_id: item.id, server_time: serverTime }, 409); + if (decisionExpiryState(item.expires_at, now) !== "active") + return json({ ok: false, error: "decision_expired", decision_id: item.id, server_time: serverTime }, 409); + if (decisionExpiryState(authority?.expires_at, now) !== "active") + return json({ ok: false, error: "decision_expired", decision_id: item.id, server_time: serverTime }, 409); + if (authority?.blocked === true) + return json({ ok: false, error: "decision_legacy_blocked", decision_id: item.id, server_time: serverTime }, 409); + if (authority?.eligible === false) + return json({ ok: false, error: "decision_material_ineligible", decision_id: item.id, server_time: serverTime }, 409); + if (item.actionable !== true) + return json({ + ok: false, error: "decision_not_actionable", decision_id: item.id, + blocking_reason_codes: item.blocking_reason_codes || [], server_time: serverTime, + }, 409); + if (!(item.allowed_actions || []).some((entry) => entry.action === action)) + return json({ ok: false, error: "decision_action_not_allowed", decision_id: item.id, server_time: serverTime }, 409); + if (!materialKey) + return json({ ok: false, error: "decision_evidence_unavailable", decision_id: item.id, server_time: serverTime }, 409); + + let targetPath; + let innerBody; + if (parsed.category === "owner_decision") { + targetPath = "/api/owner-decisions"; + innerBody = { + candidate_id: parsed.subject_id, + decision: action, + candidate_evidence_sha256: materialKey, + request_id: operationId, + }; + } else if (parsed.category === "reconciliation_recovery") { + targetPath = "/api/reconciliation-recovery-confirmations"; + if (item.initial_enrollment) { + innerBody = { + mode: "initial_enrollment", + recovery_id: parsed.subject_id, + candidate_sha256: item.initial_enrollment.candidate_sha256, + facts_sha256: item.initial_enrollment.facts_sha256, + decision: action, + request_id: operationId, + }; + } else { + innerBody = { + recovery_id: parsed.subject_id, + candidate_sha256: item.evidence_summary?.candidate_sha256, + dual_review_binding_sha256: item.evidence_summary?.dual_review_binding_sha256, + decision: action, + request_id: operationId, + }; + } + } else { + targetPath = "/api/research-promotion-decisions"; + let ticket; + try { + ticket = await loadStoredResearchPromotionTicket(env, parsed.subject_id); + } catch (error) { + return json({ + ok: false, error: error.message || "research promotion ticket unavailable", + decision_id: item.id, server_time: serverTime, + }, error.status || 503); + } + innerBody = { + ticket_id: parsed.subject_id, + decision: action, + expected_proposed_params: ticket.proposed_params, + expected_material_sha256: materialKey, + ...(item.decision_binding?.review_binding?.review_sha256 + ? { expected_review_sha256: item.decision_binding.review_binding.review_sha256 } : {}), + ...(action === "accept" && selection ? { + confirmation: selection.confirmation, + selected_account: selection.selected_account, + } : {}), + request_id: operationId, + }; + } + + const innerResponse = await ({ + "/api/owner-decisions": recordOwnerDecisionResponse, + "/api/reconciliation-recovery-confirmations": recordReconciliationRecoveryConfirmationResponse, + "/api/research-promotion-decisions": recordResearchPromotionDecisionResponse, + })[targetPath](new Request(new URL(targetPath, request.url), { + method: "POST", + headers: new Headers(request.headers), + body: JSON.stringify(innerBody), + }), env); + let payload = null; + try { + payload = await innerResponse.json(); + } catch { + payload = null; + } + if (!innerResponse.ok || payload?.ok !== true) { + return json({ + ...(payload && typeof payload === "object" ? payload : { ok: false, error: "decision_action_unavailable" }), + schema_version: DECISION_ACTION_SCHEMA_VERSION, + decision_id: item.id, + action, + server_time: serverTime, + }, innerResponse.status || 503); + } + const decidedAt = payload.intent?.decided_at + || payload.confirmation?.confirmed_at + || payload.ticket?.human_decided_at + || null; + return json({ + schema_version: DECISION_ACTION_SCHEMA_VERSION, + ok: true, + replayed: payload.replayed === true, + decision_id: item.id, + revision: item.revision, + action, + operation_id: operationId, + operation_sha256: operationSha256, + decision_receipt: payload.decision_receipt || null, + decided_at: decidedAt, + decision_state: { status: "decided", action, decided_at: decidedAt }, + operation: item.operation_reference, + policy: DECISION_POLICY, + server_time: serverTime, + }); +} + +export function operationDecisionStatus(category, decision, application) { + const promoted = category === "strategy_promotion" || category === "paper_account_application"; + if (promoted && application) { + return { status: operationStatusFromApplication(application), reason_code: application.reason_code || null }; + } + if (!decision) return { status: "unaccepted", reason_code: "decision_pending" }; + // The operation records the human decision; it never implies activation. + if (promoted) return decision.action === "accept" + ? { status: "accepted", reason_code: "application_not_created" } + : { status: "failed", reason_code: "decision_rejected" }; + return { status: "accepted", reason_code: "operation_not_connected" }; +} + +export async function operationResponse(request, env, rawId) { + const session = await readSession(request, env); + if (!session?.allowed) return json({ ok: false, error: "login required", error_state: "unauthorized" }, 401); + const parsed = decodeOperationApiId(rawId); + if (!parsed) return json({ ok: false, error: "operation_not_found" }, 404); + const now = Date.now(); + const serverTime = new Date(now).toISOString(); + const kind = decisionKind(parsed.category); + if (kind && !hasRuntimeInstanceStore(env)) + return json({ ok: false, error: "runtime_instances_not_bound", server_time: serverTime }, 503); + + let authorityRaw = null; + let ticket = null; + if (kind) { + try { + if (parsed.category === "strategy_promotion" || parsed.category === "paper_account_application") { + ticket = await loadStoredResearchPromotionTicket(env, parsed.subject_id); + } + authorityRaw = await readHumanDecisionAuthority(env, kind, parsed.subject_id); + } catch (error) { + return json({ + ok: false, error: error.message || "operation_unavailable", + operation_id: parsed.id, server_time: serverTime, + }, error.status || 503); + } + } + let application = null; + if (kind === "promotion") { + try { + const stored = await runtimeInstanceCommand(env, { + action: "application_latest", actor: session.login, ticket_id: parsed.subject_id, + }); + application = stored.application || null; + } catch { + return json({ ok: false, error: "operation_unavailable", operation_id: parsed.id, server_time: serverTime }, 503); + } + } + const decision = authorityRaw?.decision || null; + const status = operationDecisionStatus(parsed.category, decision, application); + const publicApplication = application ? publicResearchPromotionApplication(application) : null; + return json({ + schema_version: OPERATION_SCHEMA_VERSION, + server_time: serverTime, + data_status: "ready", + operation: { + id: parsed.id, + decision_id: `${parsed.category}:${parsed.subject_id}`, + kind: kind || parsed.category, + subject_id: parsed.subject_id, + status: status.status, + reason_code: status.reason_code, + activation_status: "not_connected", + updated_at: publicApplication?.updated_at || decision?.decided_at || ticket?.updated_at || null, + dispatch: publicApplication ? { + status: publicApplication.dispatch_state || "unknown", + workflow_run_id: publicApplication.workflow_run_id || null, + updated_at: publicApplication.updated_at || null, + } : null, + application: publicApplication, + }, + decision: decision ? { + material_sha256: /^[0-9a-f]{64}$/.test(decision.material_key || "") ? decision.material_key : null, + action: decision.action, + decided_at: decision.decided_at, + } : null, + policy: DECISION_POLICY, + errors: [], + }); +} + +// Event type by normalized projection source. Comparing the normalized source +// keeps a promotion application from being reported as an account-settings approval. +export function auditEventAction(source, kind) { + if (source === "durable_human_decision") return `record_${kind}_decision`; + if (source === "research_promotion_application") return "research_promotion_application"; + if (source === "account_settings_application") return "account_settings_approval"; + return null; +} + +export function auditEventSubjectId(row) { + if (typeof row.ticket_id === "string" && row.ticket_id) return { kind: "strategy_promotion", id: row.ticket_id }; + if (typeof row.recovery_id === "string" && row.recovery_id) return { kind: "reconciliation_recovery", id: row.recovery_id }; + if (typeof row.candidate_id === "string" && row.candidate_id) return { kind: "owner_decision", id: row.candidate_id }; + if (typeof row.platform === "string" && typeof row.key === "string") return { kind: "account_settings", id: `${row.platform}:${row.key}` }; + return null; +} + +export async function auditEventsResponse(request, env, url) { + const session = await readSession(request, env); + if (!session?.allowed) return json({ ok: false, error: "login required", error_state: "unauthorized" }, 401); + let page; + try { + page = auditPageQuery(url); + } catch { + return json({ ok: false, error: "invalid_pagination" }, 400); + } + const now = Date.now(); + const serverTime = new Date(now).toISOString(); + if (!hasRuntimeInstanceStore(env)) + return json({ ok: false, error: "runtime_instances_not_bound", server_time: serverTime }, 503); + let rows; + try { + const result = await runtimeInstanceCommand(env, { + action: "console_audit_events", actor: session.login, limit: page.limit + 1, snapshot: page.snapshot, before: page.before, + }); + if (!Array.isArray(result.rows)) throw Error("invalid_audit_rows"); + rows = result.rows; + } catch { + return json({ ok: false, error: "audit_projection_unavailable", server_time: serverTime }, 503); + } + const events = []; + for (const row of rows) { + const occurredAt = typeof row.occurred_at === "string" && Number.isFinite(Date.parse(row.occurred_at)) + ? row.occurred_at : null; + if (!occurredAt) return json({ ok: false, error: "audit_projection_invalid" }, 503); + if (row.source === "credential_rotation") { + events.push({ id: row.event_id, event_id: row.event_id, occurred_at: occurredAt, + actor_alias: await actorAlias(row.actor || null), action: row.action, + subject: { kind: "credential", id: "ibkr_flex" }, operation_id: row.ref ?? null, outcome: row.status }); + continue; + } + const source = row.source === "decision" ? "durable_human_decision" + : row.source === "promotion_application" ? "research_promotion_application" + : row.source === "account_settings_application" ? "account_settings_application" + : null; + if (!source) return json({ ok: false, error: "audit_projection_invalid" }, 503); + const subject = source === "durable_human_decision" + ? { kind: `${row.kind}_decision`, id: row.subject_id } + : source === "research_promotion_application" + ? { kind: "strategy_promotion", id: row.subject_id } + : { kind: "account_settings", id: row.subject_id }; + if (typeof subject.id !== "string" || !subject.id || subject.id.length > 256) return json({ ok: false, error: "audit_projection_invalid" }, 503); + const revision = /^[0-9a-f]{64}$/.test(row.revision || "") ? row.revision : null; + events.push({ + id: `${source}:${row.ref}`, + source, + occurred_at: occurredAt, + action: auditEventAction(source, row.kind), + actor_alias: await actorAlias(row.actor || null), + subject, + revision, + outcome: typeof row.status === "string" && row.status.length <= 40 ? row.status : null, + ref: source === "durable_human_decision" + ? `/api/decisions/${encodeURIComponent(`${row.kind === "promotion" ? "strategy_promotion" : row.kind === "owner" ? "owner_decision" : "reconciliation_recovery"}:${row.subject_id}`)}` + : source === "research_promotion_application" + ? `/api/operations/${encodeURIComponent(`operation:strategy_promotion:${row.subject_id}`)}` + : null, + }); + } + try { + if (!hasConfigStore(env)) throw Error("audit_log_unavailable"); + const storedLog = await readConfigJson(env, AUDIT_LOG_KEY); + if (storedLog !== null && !Array.isArray(storedLog)) throw Error("audit_log_invalid"); + const log = storedLog || []; + const ids = await Promise.all(log.map(row => cycleHealthSha256(row))); + const occurrences = new Map(); + const aliases = await Promise.all(log.map((row) => + actorAlias(typeof row?.login === "string" ? row.login : null))); + log.forEach((row, index) => { + if (!row || typeof row !== "object" || Array.isArray(row)) throw Error("audit_log_invalid"); + const occurredAt = typeof row.ts === "string" && Number.isFinite(Date.parse(row.ts)) ? row.ts : null; + const action = typeof row.action === "string" && row.action.length <= 64 ? row.action : null; + if (!occurredAt || !action) throw Error("audit_log_invalid"); + const subject = auditEventSubjectId(row); + const occurrence = occurrences.get(ids[index]) || 0; + occurrences.set(ids[index], occurrence + 1); + events.push({ + id: `bounded_audit_log:${ids[index]}:${occurrence}`, + source: "bounded_audit_log", + occurred_at: new Date(occurredAt).toISOString(), + action, + actor_alias: aliases[index] || null, + subject, + revision: null, + outcome: "recorded", + ref: null, + }); + }); + } catch { + return json({ ok: false, error: "audit_log_unavailable", server_time: serverTime }, 503); + } + const seen = new Set(); + const unique = events + .filter((event) => { + if (seen.has(event.id)) return false; + seen.add(event.id); + return true; + }) + .filter(event => auditEventInPage(event, page)) + .sort(auditEventOrder); + const pageEvents = unique.slice(0, page.limit); + return json({ + schema_version: AUDIT_EVENTS_SCHEMA_VERSION, + server_time: serverTime, + data_status: "ready", + events: pageEvents.map(event => { if (!event.event_id) return event; const { id, ...safe } = event; return safe; }), + next_cursor: unique.length > page.limit ? auditNextCursor(pageEvents.at(-1), page) : null, + coverage: "durable_human_decisions_and_application_records_and_bounded_audit_log", + policy: DECISION_POLICY, + errors: [], + }); +} + +export function attachResearchPromotionApplicationPreparation(ticket, accountOptions, strategyProfiles) { + const blockers = new Set(["activation_not_connected"]); + if (ticket?.shadow_passed !== true || !String(ticket?.shadow_evidence_kind || "").trim()) { + blockers.add("research_evidence_unverified"); + } + const params = ticket?.proposed_params; + const strategy = (Array.isArray(strategyProfiles) ? strategyProfiles : []) + .find((item) => item.profile === ticket.strategy_profile); + const candidateIdentity = researchCandidateIdentityForStrategy(strategy); + const applicationAdapter = researchCandidateApplicationAdapter(strategy); + const candidateParamsBound = candidateIdentity + ? Boolean( + params + && !Array.isArray(params) + && typeof params === "object" + && canonicalResearchTaskJson(params) === canonicalResearchTaskJson(candidateIdentity), + ) + : Boolean(params && !Array.isArray(params) && typeof params === "object" && !Object.keys(params).length); + if (!candidateParamsBound) { + blockers.add("candidate_params_unbound"); + } + if (!strategy) blockers.add("strategy_not_configured"); + if (candidateIdentity && !applicationAdapter) blockers.add("paper_application_adapter_unavailable"); + + const confirmedPlatform = String(ticket.confirmation_target_platform || "").trim(); + const platforms = confirmedPlatform ? [confirmedPlatform] : SUPPORTED_PLATFORMS; + const accounts = []; + for (const platform of strategy ? platforms : []) { + const options = Array.isArray(accountOptions?.[platform]) ? accountOptions[platform] : []; + for (const option of options) { + if (!supportedDomainsForAccount(platform, option).includes(ticket.domain)) continue; + const accountBlockers = []; + const configuredExecutionMode = cleanExecutionMode( + option.default_execution_mode || PLATFORM_CONFIG[platform]?.default_execution_mode || "live", + ); + if (strategy) { + try { + const brokerEnvironment = normalizeBrokerEnvironment( + option.broker_environment, + `account_options.${platform}.${option.key}.broker_environment`, + ); + if (!supportedBrokerEnvironmentsForPlatform(platform).includes(brokerEnvironment)) { + throw new HttpError(`${platform} does not support broker environment ${brokerEnvironment}`, 400); + } + const executionMode = String( + ticket.confirmation_execution_mode || brokerEnvironment, + ).trim().toLowerCase(); + validateResearchPromotionSelectedAccount({ + ticket, + selectedAccount: { platform, key: option.key }, + confirmation: { + target_platform: platform, + execution_mode: executionMode, + risk_profile: ticket.confirmation_risk_profile || ticket.suggested_risk_profile, + }, + accountOptions, + strategyProfiles, + }); + } catch (error) { + accountBlockers.push(researchPromotionApplicationBlocker(error)); + } + } + if (accountBlockers.length) continue; + accounts.push({ + platform, + key: option.key, + label: option.label, + configured_execution_mode: configuredExecutionMode, + broker_environment: option.broker_environment, + preflight_status: accountBlockers.length ? "blocked" : (strategy ? "ready" : "blocked"), + blocker_codes: [...new Set(accountBlockers)], + }); + } + } + if (!accounts.length) blockers.add("configured_account_missing"); + if (strategy && accounts.length && accounts.every((account) => account.preflight_status !== "ready")) { + blockers.add("account_preflight_failed"); + } + const candidateBlockers = [...blockers].filter((code) => code !== "activation_not_connected"); + const previewAccounts = ( + ticket?.state === "human_accepted" + && ticket?.shadow_passed === true + && String(ticket?.shadow_evidence_kind || "").trim() + && candidateIdentity + && applicationAdapter + && strategy?.domain === "us_equity" + && candidateParamsBound + && candidateBlockers.length === 0 + ) + ? accounts.filter((account) => { + const option = (accountOptions?.[account.platform] || []) + .find((item) => item.key === account.key); + return ( + account.platform === "longbridge" + && account.broker_environment === "paper" + && account.configured_execution_mode === "live" + && String(option?.account_selector || "").trim() + ); + }) + : []; + const previewRequest = previewAccounts.length === 1 + ? { + platform_id: previewAccounts[0].platform, + account_scope: String( + (accountOptions?.[previewAccounts[0].platform] || []) + .find((item) => item.key === previewAccounts[0].key)?.account_selector || "", + ).trim(), + strategy_profile: ticket.strategy_profile, + candidate_id: params.candidate_id, + config_sha256: params.config_sha256, + } + : null; + return { + ...attachRiskEnvelopeView(ticket), + application_preparation: { + status: "blocked", + preflight_status: candidateBlockers.length ? "blocked" : "ready", + activation_status: "not_connected", + dispatch_allowed: false, + blocker_codes: [...blockers].sort(), + account_options: accounts, + preview_request: previewRequest, + }, + }; +} + +export function researchCandidateIdentityForStrategy(strategy) { + const identity = strategy?.research_candidate_identity; + return identity && typeof identity === "object" && !Array.isArray(identity) ? identity : null; +} + +export function researchCandidateApplicationAdapter(strategy) { + if (strategy?.profile !== V7_PAPER_PREVIEW_PROFILE) return null; + return { + platform: "longbridge", + account_key: V7_PAPER_APPLICATION_ACCOUNT_KEY, + workflow_repository: V7_PAPER_APPLICATION_WORKFLOW_REPOSITORY, + workflow: V7_PAPER_APPLICATION_WORKFLOW, + }; +} + +export function researchPromotionApplicationBlocker(error) { + const message = String(error?.message || error || "").toLowerCase(); + if (message.includes("broker environment is unknown")) return "broker_environment_unknown"; + if (message.includes("does not support broker environment")) return "broker_environment_unsupported"; + if (message.includes("not configured")) return "strategy_not_configured"; + if (message.includes("domain") && message.includes("not supported")) return "strategy_domain_not_supported"; + if (message.includes("live-enabled") || message.includes("blocked for live") || message.includes("does not allow")) { + return "strategy_execution_not_allowed"; + } + if (message.includes("configured account")) return "configured_account_mismatch"; + return "account_preflight_failed"; +} + +export async function recordResearchPromotionDecisionResponse(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, 8 * 1024); + } catch (error) { + return json({ ok: false, error: error.message || "invalid research promotion decision" }, error.status || 400); + } + const ticketId = String(raw?.ticket_id || "").trim(); + const decision = String(raw?.decision || "").trim().toLowerCase(); + if (!ticketId) return json({ ok: false, error: "ticket_id is required" }, 400); + const stored = await readConfigJson(env, researchPromotionTicketKey(ticketId)); + if (!stored) return json({ ok: false, error: "research promotion ticket not found" }, 404); + let ticket; + try { + ticket = normalizeResearchPromotionTicket(stored); + assertResearchPromotionExpectedCandidate(ticket, raw, { requireExpectedParams: decision === "accept" }); + } catch (error) { + return json({ ok: false, error: error.message || "invalid research promotion decision" }, error.status || 400); + } + const materialKey = await researchPromotionMaterialKey(ticket); + // Keep the legacy material index stable; the separate review digest binds the complete producer review. + if (Object.prototype.hasOwnProperty.call(raw, "expected_material_sha256") && raw.expected_material_sha256 !== materialKey) { + return json({ ok: false, error: "human_decision_material_conflict" }, 409); + } + const reviewMaterial = researchPromotionReviewMaterial(ticket); + const reviewBinding = await researchPromotionReviewBinding(ticket); + const selectedAccount = raw?.selected_account || null; + const confirmation = raw?.confirmation || null; + const target = { selected_account: selectedAccount, confirmation }; + const targetJson = canonicalResearchTaskJson(target); + let authority; + try { + authority = await readHumanDecisionAuthority(env, "promotion", ticket.ticket_id); + } catch (error) { + return json({ ok: false, error: error.message || "research promotion decision unavailable" }, error.status || 503); + } + if (authority.migrated !== false) { + if (authority.blocked) return json({ ok: false, error: "human_decision_legacy_blocked" }, 409); + if (authority.eligible !== true || authority.material_key !== materialKey) { + return json({ ok: false, error: "human_decision_material_conflict" }, 409); + } + const existing = authority.decision; + if (existing && existing.action === decision && existing.target_json === targetJson && existing.payload?.ticket) { + if (Object.prototype.hasOwnProperty.call(raw, "expected_review_sha256") + && (existing.payload.review_binding?.schema_version !== "qsl_promotion_review_binding.v2" + || raw.expected_review_sha256 !== existing.payload.review_binding.review_sha256)) { + return json({ ok: false, error: "human_decision_review_binding_unqualified" }, 409); + } + return json({ + ok: true, + ticket: attachRiskEnvelopeView(existing.payload.ticket), + live_authority_granted: false, + replayed: true, + actor: existing.actor, + decision_receipt: await humanDecisionReceipt(existing, { kind: "promotion", subject_id: ticket.ticket_id, material_sha256: materialKey }), + }); + } + if (existing) return json({ ok: false, error: "human_decision_conflict" }, 409); + } + if (Object.prototype.hasOwnProperty.call(raw, "expected_review_sha256") && raw.expected_review_sha256 !== reviewBinding.review_sha256) { + return json({ ok: false, error: "human_decision_review_material_conflict" }, 409); + } + const targetPlatform = String( + raw?.confirmation?.target_platform || ticket.confirmation_target_platform || "", + ).trim(); + const paperSupported = platformSupportsBrokerPaperMode(targetPlatform); + const accountConfig = decision === "accept" ? await loadAccountOptionsConfig(env) : null; + const serverAccountRevision = accountConfig?.source === "durable_object" && Number.isSafeInteger(accountConfig.revision) + ? accountConfig.revision + : null; + const strategyProfiles = decision === "accept" ? await loadStrategyProfilesConfig(env) : null; + let decided; + try { + decided = applyResearchPromotionDecision(ticket, { + decision, + confirmation, + paperSupported, + selectedAccount, + accountOptions: accountConfig?.options, + strategyProfiles, + decidedAt: new Date().toISOString(), + expectedRaw: raw, + }); + } catch (error) { + return json({ ok: false, error: error.message || "invalid research promotion decision" }, error.status || 400); + } + if (decided.live_authority_granted) { + return json({ ok: false, error: "refusing to persist live_authority_granted=true" }, 500); + } + const preflightRevision = decision === "accept" ? serverAccountRevision : null; + if (decision === "accept" && Object.prototype.hasOwnProperty.call(raw, "expected_account_revision") + && Number(raw.expected_account_revision) !== serverAccountRevision) { + return json({ ok: false, error: "runtime_instance_revision_conflict" }, 409); + } + const decidedAt = decided.human_decided_at || humanDecisionTimestamp(); + let requestKey; + try { + requestKey = await humanDecisionRequestKey({ + requestId: raw?.request_id, + kind: "promotion", + actor: session.login, + subjectId: ticket.ticket_id, + materialKey, + action: decision, + target, + }); + } catch (error) { + return json({ ok: false, error: error.message || "invalid research promotion decision" }, error.status || 400); + } + let recorded; + try { + recorded = await runtimeInstanceCommand(env, { + action: "promotion_decide", + ...(Object.prototype.hasOwnProperty.call(raw, "expected_review_sha256") ? { review_binding: reviewBinding } : {}), + actor: session.login, + subject_id: ticket.ticket_id, + material_key: materialKey, + action_name: decision, + request_key: requestKey, + target_json: canonicalResearchTaskJson(target), + decided_at: decidedAt, + now_ms: Date.now(), + preflight_revision: preflightRevision, + payload: { + ticket: decided, + ...(Object.prototype.hasOwnProperty.call(raw, "expected_review_sha256") ? { review_binding: reviewBinding, review_material: reviewMaterial } : {}), + selected_account: selectedAccount, + confirmation, + proposed_params: ticket.proposed_params, + }, + }); + } catch (error) { + return json({ ok: false, error: error.message || "research promotion decision unavailable" }, error.status || 503); + } + const decisionReceipt = await humanDecisionReceipt(recorded.record, { kind: "promotion", subject_id: ticket.ticket_id, material_sha256: materialKey }); + const recordedTicket = recorded.record.payload.ticket; + await mirrorConfigJson(env, researchPromotionTicketKey(recordedTicket.ticket_id), recordedTicket); + try { + await appendAuditLog(env, { + ts: recordedTicket.updated_at, + login: session.login, + action: "research_promotion_decision", + ticket_id: recordedTicket.ticket_id, + decision: recordedTicket.human_decision, + confirmation_target_platform: recordedTicket.confirmation_target_platform, + confirmation_execution_mode: recordedTicket.confirmation_execution_mode, + confirmation_risk_profile: recordedTicket.confirmation_risk_profile, + suggested_risk_profile: recordedTicket.suggested_risk_profile, + live_authority_granted: false, + }); + } catch { + // Decision persistence must not depend on optional audit retention. + } + return json({ + ok: true, + ticket: attachRiskEnvelopeView(recordedTicket), + decision_receipt: decisionReceipt, + live_authority_granted: false, + replayed: recorded.replayed === true, + }); +} + +export function v7PaperApplicationCandidate(strategy, ticket) { + if (!researchCandidateApplicationAdapter(strategy)) { + throw new HttpError("paper application adapter is not configured for candidate", 409); + } + if (ticket?.shadow_evidence_kind !== "v7_nonlive_shadow_and_simulated_paper") { + throw new HttpError("v7_shadow_evidence_kind_not_qualified", 409); + } + const notes = Array.isArray(ticket?.notes) ? ticket.notes : []; + const requiredEvidence = [ + "frozen_v7_forward_financial_gates_passed_research_only", + "forward_record_sha256", + "financial_evidence_sha256", + "p1_manifest_sha256", + "p4_policy_sha256", + "baseline_p3_evidence_sha256", + ]; + if (notes[0] !== requiredEvidence[0] || requiredEvidence.slice(1).some((prefix) => + !notes.some((note) => new RegExp(`^${prefix}=[0-9a-f]{64}$`).test(String(note))))) { + throw new HttpError("v7_shadow_evidence_provenance_incomplete", 409); + } + const identity = researchCandidateIdentityForStrategy(strategy); + if (!identity || ticket?.domain !== "us_equity") { + throw new HttpError("research_candidate_not_eligible_for_paper_application", 409); + } + const params = ticket?.proposed_params; + if (!params || Array.isArray(params) || typeof params !== "object" + || canonicalResearchTaskJson(params) !== canonicalResearchTaskJson(identity)) { + throw new HttpError("candidate_params_unbound", 409); + } + return { + candidate_id: String(identity.candidate_id || ""), + config_sha256: String(identity.config_sha256 || ""), + }; +} + +export async function loadStoredResearchPromotionTicket(env, ticketId) { + if (!hasConfigStore(env)) throw new HttpError("research promotion KV is not configured", 503); + const stored = await readConfigJson(env, researchPromotionTicketKey(ticketId)); + if (!stored) throw new HttpError("research promotion ticket not found", 404); + let ticket; + try { + ticket = normalizeResearchPromotionTicket(stored, "stored research promotion ticket"); + } catch (error) { + throw new HttpError(error.message || "invalid stored research promotion ticket", 409); + } + return promotionTicketWithAuthority(env, ticket); +} + +export async function qualifyV7PaperApplication(env, { ticketId, selectedAccount, expectedRevision }) { + assertExactFields(selectedAccount, ["platform", "key"], "selected_account"); + const platform = cleanChoice(selectedAccount.platform, SUPPORTED_PLATFORMS, "selected_account.platform"); + const key = cleanSlug(selectedAccount.key, "selected_account.key"); + if (platform !== "longbridge" || key !== V7_PAPER_APPLICATION_ACCOUNT_KEY) { + throw new HttpError("only the configured LongBridge paper account is supported", 409); + } + if (!V7_PAPER_APPLICATION_TICKET_RE.test(String(ticketId || ""))) { + throw new HttpError("research promotion ticket is not from the V7 producer", 409); + } + if (!Number.isSafeInteger(expectedRevision) || expectedRevision < 0) { + throw new HttpError("runtime instance revision is required", 400); + } + const ticket = await loadStoredResearchPromotionTicket(env, ticketId); + const promotionMaterialKey = await researchPromotionMaterialKey(ticket); + const promotionAuthority = await readHumanDecisionAuthority(env, "promotion", ticket.ticket_id); + if (promotionAuthority.blocked || promotionAuthority.eligible !== true || promotionAuthority.decision?.action !== "accept" || promotionAuthority.material_key !== promotionMaterialKey) { + throw new HttpError("research candidate is not human_accepted", 409); + } + const boundAccount = promotionAuthority.decision.payload?.selected_account || {}; + if (boundAccount.platform !== platform || boundAccount.key !== key) { + throw new HttpError("promotion_account_binding_conflict", 409); + } + if (ticket.state !== "human_accepted") throw new HttpError("research candidate is not human_accepted", 409); + if (ticket.live_authority_granted !== false || ticket.shadow_passed !== true || !String(ticket.shadow_evidence_kind || "").trim()) { + throw new HttpError("research candidate evidence is not qualified", 409); + } + const strategyProfiles = await loadStrategyProfilesConfig(env); + const strategy = (Array.isArray(strategyProfiles) ? strategyProfiles : []) + .find((item) => item.profile === ticket.strategy_profile); + if (!strategy) throw new HttpError("selected candidate strategy profile is not configured", 409); + const candidate = v7PaperApplicationCandidate(strategy, ticket); + const state = await runtimeInstanceCommand(env); + if (!state.initialized) throw new HttpError("runtime instances are not initialized", 409); + if (state.revision !== expectedRevision) throw new HttpError("runtime instance revision conflict", 409); + const instance = (state.instances || []).find((item) => item.kind === "existing" && item.platform === platform && item.key === key && item.retirement_status === "none"); + const config = instance?.config; + if (!config) throw new HttpError("selected LongBridge paper account is unavailable", 409); + if (String(config.broker_environment || "") !== "paper" + || String(config.account_selector || "") !== V7_PAPER_APPLICATION_ACCOUNT_SCOPE + || String(config.service_name || "") !== V7_PAPER_APPLICATION_SERVICE + || String(config.runtime_status_target_id || "") !== "longbridge.paper" + || String(config.default_execution_mode || "") !== "live") { + throw new HttpError("selected LongBridge paper account binding is incomplete", 409); + } + const expectedStrategyProfile = String(config.default_strategy_profile || "").trim(); + if (!expectedStrategyProfile) throw new HttpError("selected account strategy profile is unknown", 409); + validateResearchPromotionSelectedAccount({ + ticket, + selectedAccount: { platform, key }, + confirmation: { + target_platform: ticket.confirmation_target_platform, + execution_mode: ticket.confirmation_execution_mode, + risk_profile: ticket.confirmation_risk_profile, + }, + accountOptions: { longbridge: [config] }, + strategyProfiles, + }); + return { + ticket, + strategy, + candidate, + promotionMaterialKey, + revision: state.revision, + account: { + platform_id: platform, + account_key: key, + account_scope: V7_PAPER_APPLICATION_ACCOUNT_SCOPE, + account_selector: V7_PAPER_APPLICATION_ACCOUNT_SCOPE, + service_name: V7_PAPER_APPLICATION_SERVICE, + broker_environment: "paper", + expected_strategy_profile: expectedStrategyProfile, + }, + }; +} + +export function publicResearchPromotionApplication(application) { + if (!application) return null; + const { claim_token: _claimToken, ...publicApplication } = application; + return publicApplication; +} + +export async function dispatchV7PaperApplication(env, applicationId) { + const token = String(env.RUNTIME_SETTINGS_DISPATCH_TOKEN || ""); + if (!token) throw new HttpError("paper application dispatch unavailable", 503); + const url = `https://api.github.com/repos/${V7_PAPER_APPLICATION_WORKFLOW_REPOSITORY}/actions/workflows/${V7_PAPER_APPLICATION_WORKFLOW}/dispatches`; + const response = await fetchWithTimeout(url, { + method: "POST", + headers: githubHeaders(token), + body: JSON.stringify({ ref: "main", inputs: { application_id: applicationId } }), + }); + if (!response.ok) throw new HttpError("paper application dispatch unverified", 502); +} + +export function applicationIdFromPath(pathname) { + const value = String(pathname || "").slice("/api/internal/research-promotion-application/".length); + if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)) { + throw new HttpError("invalid application_id", 400); + } + return value; +} + +export async function researchPromotionApplicationResponse(request, env, url) { + return researchPromotionApplicationResponseImpl(request, env, url, { + json, + readSession, + requireSameOrigin, + readBoundedJson, + runtimeInstanceCommand, + assertExactFields, + randomToken, + qualifyV7PaperApplication, + dispatchV7PaperApplication, + publicResearchPromotionApplication, + isUnsupportedApplicationActionError: (error) => ( + error instanceof HttpError + && error.message === "unsupported_research_promotion_application_action" + ), + V7_PAPER_APPLICATION_MAX_BODY_BYTES, + V7_PAPER_APPLICATION_SOURCE_COMMIT, + V7_PAPER_APPLICATION_UES_REVISION, + }); +} + +export function validateV7PaperApplicationReadback(application, readback) { + if (!readback || typeof readback !== "object" || Array.isArray(readback)) return false; + return readback.application_id === application.application_id + && readback.platform_id === application.platform_id + && readback.account_scope === application.account_scope + && readback.service_name === application.service_name + && readback.strategy_profile === application.strategy_profile + && readback.candidate_id === application.candidate_id + && readback.config_sha256 === application.config_sha256 + && readback.source_commit === application.source_commit + && readback.ues_revision === application.approved_ues_revision + && readback.runtime_target_enabled === false + && typeof readback.revision_name === "string" && readback.revision_name.trim() !== ""; +} + +export async function researchPromotionApplicationInternalResponse(request, env, url) { + return researchPromotionApplicationInternalResponseImpl(request, env, url, { + json, + requireDedicatedExecutionEvidenceSyncToken, + hasRuntimeInstanceStore, + applicationIdFromPath, + readBoundedJson, + runtimeInstanceCommand, + assertExactFields, + qualifyV7PaperApplication, + validateV7PaperApplicationReadback, + V7_PAPER_APPLICATION_MAX_BODY_BYTES, + V7_PAPER_APPLICATION_WORKFLOW_REPOSITORY, + V7_PAPER_APPLICATION_WORKFLOW, + }); +} + +export 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); + const header = request.headers.get("Authorization") || ""; + const token = header.match(/^Bearer\s+(.+)$/i)?.[1] || ""; + if (token !== expected) throw new HttpError("research task sync token is invalid", 401); +} + +export function assertExactFields(value, fields, fieldName) { + if (!value || Array.isArray(value) || typeof value !== "object") { + throw new Error(`${fieldName} must be an object`); + } + const actual = Object.keys(value).sort(); + const expected = [...fields].sort(); + if (actual.length !== expected.length || actual.some((item, index) => item !== expected[index])) { + throw new Error(`${fieldName} has invalid fields`); + } + return value; +} + +export function assertRequiredAndOptionalFields(value, requiredFields, optionalFields, fieldName) { + if (!value || Array.isArray(value) || typeof value !== "object") { + throw new Error(`${fieldName} must be an object`); + } + const allowed = new Set([...requiredFields, ...optionalFields]); + const actual = Object.keys(value); + if ( + requiredFields.some((field) => !Object.prototype.hasOwnProperty.call(value, field)) + || actual.some((field) => !allowed.has(field)) + ) { + throw new Error(`${fieldName} has invalid fields`); + } + return value; +} + +export function normalizeResearchTaskIdentity(value, fieldName) { + const text = String(value || "").trim(); + if (!/^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/.test(text)) { + throw new Error(`${fieldName} must be a stable identity`); + } + return text; +} + +export function normalizeResearchTaskDigest(value, fieldName, nullable = false) { + if ((value === null || value === undefined || value === "") && nullable) return null; + const text = String(value || "").trim(); + if (!/^[0-9a-f]{64}$/.test(text)) throw new Error(`${fieldName} must be a lowercase SHA-256`); + return text; +} + +export function normalizeResearchTaskRevision(value, fieldName) { + const text = String(value || "").trim(); + if (!/^[0-9a-f]{40}$/.test(text)) throw new Error(`${fieldName} must be a 40-character revision`); + return text; +} + +export function normalizeResearchTaskTimestamp(value, fieldName) { + const text = String(value || "").trim(); + if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/.test(text) || Number.isNaN(Date.parse(text))) { + throw new Error(`${fieldName} must be a UTC timestamp`); + } + return text; +} + +export function normalizeResearchTaskText(value, fieldName, maxLength) { + const text = String(value || "").trim(); + if (!text || text.length > maxLength || /[<>\\]/.test(text)) throw new Error(`${fieldName} is invalid`); + if (/\b(?:token|secret|password|credential|api[_ -]?key)\s*[:=]/i.test(text)) { + throw new Error(`${fieldName} contains sensitive material`); + } + return text; +} + +export function assertResearchTaskHasNoUnsafeMaterial(value, fieldName = "research task") { + if (Array.isArray(value)) { + value.forEach((item, index) => assertResearchTaskHasNoUnsafeMaterial(item, `${fieldName}[${index}]`)); + return; + } + if (!value || typeof value !== "object") return; + for (const [key, nested] of Object.entries(value)) { + if (key !== "no_order" && /(?:secret|token|password|credential|api[_-]?key|order|fill|capital|account|broker)/i.test(key)) { + throw new Error(`${fieldName} contains forbidden execution or secret material`); + } + assertResearchTaskHasNoUnsafeMaterial(nested, `${fieldName}.${key}`); + } +} + +export function canonicalResearchTaskJson(value) { + if (value === null) return "null"; + if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value); + if (typeof value === "number") { + if (!Number.isFinite(value)) throw new Error("research task must use finite JSON values"); + return JSON.stringify(value); + } + if (Array.isArray(value)) return `[${value.map(canonicalResearchTaskJson).join(",")}]`; + if (!value || typeof value !== "object") throw new Error("research task must use JSON values"); + return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalResearchTaskJson(value[key])}`).join(",")}}`; +} + +export async function sha256Hex(text) { + const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text)); + return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join(""); +} + +export async function calculateResearchTaskSha256(payload) { + const material = { ...payload }; + delete material.task_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(""); +} + +export async function normalizeResearchTask(payload, fieldName) { + const task = assertExactFields(payload, [ + "schema", "task_id", "created_at", "digest_algorithm", "task_type", "target", "evidence", "experiment", "authority", "task_sha256", + ], fieldName); + assertResearchTaskHasNoUnsafeMaterial(task, fieldName); + if (task.schema !== RESEARCH_TASK_SCHEMA_VERSION) throw new Error(`${fieldName}.schema is unsupported`); + if (task.digest_algorithm !== "sha256") throw new Error(`${fieldName}.digest_algorithm must be sha256`); + const target = assertExactFields(task.target, ["candidate_id", "candidate_kind", "domain", "repository", "strategy_revision"], `${fieldName}.target`); + const evidence = assertExactFields(task.evidence, ["p1_input_digest", "p2_config_digest", "p3_evidence_id", "producer_revision"], `${fieldName}.evidence`); + const experiment = assertExactFields(task.experiment, ["objective", "hypothesis", "parameter_bounds_sha256", "max_runs", "max_wall_seconds"], `${fieldName}.experiment`); + const authority = assertExactFields(task.authority, ["research_only", "no_order", "size_zero_required", "p4_p5_p6_authorized"], `${fieldName}.authority`); + const normalized = { + schema: RESEARCH_TASK_SCHEMA_VERSION, + task_id: normalizeResearchTaskIdentity(task.task_id, `${fieldName}.task_id`), + created_at: normalizeResearchTaskTimestamp(task.created_at, `${fieldName}.created_at`), + digest_algorithm: "sha256", + task_type: cleanChoice(task.task_type, RESEARCH_TASK_TYPES, `${fieldName}.task_type`), + target: { + candidate_id: normalizeResearchTaskIdentity(target.candidate_id, `${fieldName}.target.candidate_id`), + candidate_kind: cleanChoice(target.candidate_kind, CONTROL_PLANE_CANDIDATE_KINDS, `${fieldName}.target.candidate_kind`), + domain: cleanChoice(target.domain, STRATEGY_HEALTH_DOMAINS, `${fieldName}.target.domain`), + repository: normalizeResearchTaskRepository(target.repository, `${fieldName}.target.repository`), + strategy_revision: normalizeResearchTaskRevision(target.strategy_revision, `${fieldName}.target.strategy_revision`), + }, + evidence: { + p1_input_digest: normalizeResearchTaskDigest(evidence.p1_input_digest, `${fieldName}.evidence.p1_input_digest`, true), + p2_config_digest: normalizeResearchTaskDigest(evidence.p2_config_digest, `${fieldName}.evidence.p2_config_digest`, true), + p3_evidence_id: normalizeResearchTaskDigest(evidence.p3_evidence_id, `${fieldName}.evidence.p3_evidence_id`, true), + producer_revision: normalizeResearchTaskRevision(evidence.producer_revision, `${fieldName}.evidence.producer_revision`), + }, + experiment: { + objective: cleanChoice(experiment.objective, RESEARCH_TASK_OBJECTIVES, `${fieldName}.experiment.objective`), + hypothesis: normalizeResearchTaskText(experiment.hypothesis, `${fieldName}.experiment.hypothesis`, 800), + parameter_bounds_sha256: normalizeResearchTaskDigest(experiment.parameter_bounds_sha256, `${fieldName}.experiment.parameter_bounds_sha256`, true), + max_runs: normalizeResearchTaskBound(experiment.max_runs, `${fieldName}.experiment.max_runs`, 100), + max_wall_seconds: normalizeResearchTaskBound(experiment.max_wall_seconds, `${fieldName}.experiment.max_wall_seconds`, 86400), + }, + authority: { + research_only: authority.research_only, + no_order: authority.no_order, + size_zero_required: authority.size_zero_required, + p4_p5_p6_authorized: authority.p4_p5_p6_authorized, + }, + task_sha256: normalizeResearchTaskDigest(task.task_sha256, `${fieldName}.task_sha256`), + }; + if (JSON.stringify(normalized.authority) !== JSON.stringify({ research_only: true, no_order: true, size_zero_required: true, p4_p5_p6_authorized: false })) { + throw new Error(`${fieldName}.authority must be fixed to offline research only`); + } + if (normalized.task_sha256 !== await calculateResearchTaskSha256(normalized)) { + throw new Error(`${fieldName}.task_sha256 mismatch`); + } + return normalized; +} + +export function normalizeResearchTaskRepository(value, fieldName) { + const text = String(value || "").trim(); + if (!/^[A-Za-z0-9][A-Za-z0-9_.-]*\/[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(text)) { + throw new Error(`${fieldName} is invalid`); + } + return text; +} + +export function normalizeResearchTaskBound(value, fieldName, maximum) { + if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > maximum) { + throw new Error(`${fieldName} is outside its safe bound`); + } + return value; +} + +export async function normalizeResearchTaskSourceSnapshot(payload, fieldName = "research task source snapshot") { + const source = assertExactFields(payload, ["schema_version", "source_id", "generated_at", "computed_at", "data_status", "tasks", "errors"], fieldName); + if (source.schema_version !== RESEARCH_TASK_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.tasks) || source.tasks.length > 100) { + throw new Error(`${fieldName}.tasks must be an array with at most 100 items`); + } + const tasks = []; + const seen = new Set(); + for (const [index, item] of source.tasks.entries()) { + const task = await normalizeResearchTask(item, `${fieldName}.tasks[${index}]`); + if (seen.has(task.task_id)) throw new Error(`${fieldName}.tasks contains duplicate task_id`); + seen.add(task.task_id); + tasks.push(task); + } + if (dataStatus === "unavailable" && tasks.length) throw new Error(`${fieldName}.tasks must be empty when unavailable`); + return { + schema_version: RESEARCH_TASK_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, + tasks, + errors: normalizeStrategyHealthErrors(source.errors), + }; +} + +export function emptyResearchTaskSourceSnapshot(errorCode) { + return { + schema_version: RESEARCH_TASK_SOURCE_SCHEMA_VERSION, + source_id: "unavailable", + generated_at: null, + computed_at: null, + data_status: "unavailable", + tasks: [], + errors: [errorCode], + }; +} + +export function emptyResearchTaskPayload(errorCode) { + return { + schema_version: RESEARCH_TASK_DASHBOARD_SCHEMA_VERSION, + generated_at: null, + computed_at: null, + data_status: "unavailable", + summary: { task_count: 0 }, + tasks: [], + policy: { + research_only: true, + no_order: true, + size_zero_required: true, + p4_p5_p6_authorized: false, + notice: "研究任务只用于离线、零仓位实验;不会调参、改代码、部署或下单。", + }, + errors: [errorCode], + }; +} + +export function researchTaskStaleTtlSeconds(env) { + const configured = Number(env.RESEARCH_TASK_STALE_TTL_SECONDS); + if (!Number.isFinite(configured) || configured < 300 || configured > 604800) { + return RESEARCH_TASK_DEFAULT_STALE_TTL_SECONDS; + } + return Math.floor(configured); +} + +export function normalizeStrategyHealthSnapshot(payload, fieldName = "strategy health snapshot") { + if (!payload || Array.isArray(payload) || typeof payload !== "object") { + throw new Error(`${fieldName} must be an object`); + } + if (payload.schema_version !== "strategy_health_dashboard.v1") { + throw new Error(`${fieldName}.schema_version is unsupported`); + } + const status = cleanChoice(payload.data_status || "unavailable", [...STRATEGY_HEALTH_DATA_STATUSES, "partial"], `${fieldName}.data_status`); + const strategies = status === "unavailable" + ? [] + : normalizeStrategyHealthStrategies(payload.strategies, fieldName); + const summary = normalizeStrategyHealthSummary(payload.summary, strategies); + const errors = normalizeStrategyHealthErrors(payload.errors); + return { + schema_version: "strategy_health_dashboard.v1", + generated_at: normalizeStrategyHealthTimestamp(payload.generated_at, `${fieldName}.generated_at`, true), + computed_at: normalizeStrategyHealthTimestamp(payload.computed_at, `${fieldName}.computed_at`, true), + data_status: status, + summary, + strategies, + policy: normalizeStrategyHealthPolicy(payload.policy), + errors, + }; +} + +export function normalizeStrategyHealthStrategies(value, fieldName) { + if (!Array.isArray(value) || value.length > 100) { + throw new Error(`${fieldName}.strategies must be an array with at most 100 items`); + } + const seen = new Set(); + return value.map((item, index) => { + if (!item || Array.isArray(item) || typeof item !== "object") { + throw new Error(`${fieldName}.strategies[${index}] must be an object`); + } + const profile = cleanSlug(item.profile, `${fieldName}.strategies[${index}].profile`).toLowerCase(); + if (seen.has(profile)) throw new Error(`${fieldName}.strategies contains duplicate profile`); + seen.add(profile); + return { + profile, + domain: cleanChoice(item.domain, STRATEGY_HEALTH_DOMAINS, `${fieldName}.strategies[${index}].domain`), + as_of: sanitizeStrategyHealthText(item.as_of, `${fieldName}.strategies[${index}].as_of`, 64, true), + status: cleanChoice(item.status, STRATEGY_HEALTH_STATUSES, `${fieldName}.strategies[${index}].status`), + score: normalizeStrategyHealthScore(item.score, `${fieldName}.strategies[${index}].score`), + components: normalizeStrategyHealthComponents(item.components, `${fieldName}.strategies[${index}].components`), + decision: normalizeStrategyHealthDecision(item.decision, `${fieldName}.strategies[${index}].decision`), + review: normalizeStrategyHealthReview(item.review, `${fieldName}.strategies[${index}].review`), + freshness: normalizeStrategyHealthFreshness(item.freshness, `${fieldName}.strategies[${index}].freshness`), + source_revision: sanitizeStrategyHealthText(item.source_revision, `${fieldName}.strategies[${index}].source_revision`, 120, true), + }; + }); +} + +export function normalizeStrategyHealthSummary(value, strategies) { + const counts = Object.fromEntries(STRATEGY_HEALTH_STATUSES.map((status) => [status, 0])); + for (const item of strategies) counts[item.status] += 1; + return { strategy_count: strategies.length, ...counts }; +} + +export function normalizeStrategyHealthComponents(value, fieldName) { + const source = value && typeof value === "object" && !Array.isArray(value) ? value : {}; + return { + performance: normalizeStrategyHealthScore(source.performance, `${fieldName}.performance`), + risk: normalizeStrategyHealthScore(source.risk, `${fieldName}.risk`), + decay: normalizeStrategyHealthScore(source.decay, `${fieldName}.decay`), + stability: normalizeStrategyHealthScore(source.stability, `${fieldName}.stability`), + operations: normalizeStrategyHealthScore(source.operations, `${fieldName}.operations`), + }; +} + +export function normalizeStrategyHealthDecision(value, fieldName) { + const source = value && typeof value === "object" && !Array.isArray(value) ? value : {}; + return { + code: cleanSlug(source.code || "evidence_missing", `${fieldName}.code`).toLowerCase(), + label: sanitizeStrategyHealthText(source.label, `${fieldName}.label`, 120, false, "证据不足,保持研究态"), + reason: sanitizeStrategyHealthText(source.reason, `${fieldName}.reason`, 240, false, "没有可用的机器检查结果。"), + }; +} + +export function normalizeStrategyHealthReview(value, fieldName) { + const source = value && typeof value === "object" && !Array.isArray(value) ? value : {}; + return { + requested_stage: sanitizeStrategyHealthText(source.requested_stage, `${fieldName}.requested_stage`, 80, true), + evidence_package_id: sanitizeStrategyHealthText(source.evidence_package_id, `${fieldName}.evidence_package_id`, 120, true), + validation: normalizeStrategyHealthSummaryObject(source.validation), + risk: normalizeStrategyHealthSummaryObject(source.risk), + kelly_readiness: normalizeStrategyHealthSummaryObject(source.kelly_readiness), + }; +} + +export function normalizeStrategyHealthSummaryObject(value) { + if (!value || typeof value !== "object" || Array.isArray(value)) return {}; + const result = {}; + for (const [key, raw] of Object.entries(value).slice(0, 12)) { + if (!/^[A-Za-z0-9_.-]{1,48}$/.test(key)) continue; + if (/(token|secret|password|cookie|private|path|key)/i.test(key)) continue; + if (typeof raw === "boolean") result[key] = raw; + else if (typeof raw === "number" && Number.isFinite(raw)) result[key] = raw; + else if (typeof raw === "string") { + const safe = sanitizeStrategyHealthText(raw, "summary.value", 120, true); + if (safe !== null) result[key] = safe; + } + } + return result; +} + +export function normalizeStrategyHealthFreshness(value, fieldName) { + const source = value && typeof value === "object" && !Array.isArray(value) ? value : {}; + return { + status: cleanChoice(source.status || "unknown", ["fresh", "stale", "unknown"], `${fieldName}.status`), + age_seconds: normalizeStrategyHealthAge(source.age_seconds, `${fieldName}.age_seconds`), + }; +} + +export function normalizeStrategyHealthAge(value, fieldName) { + if (value === null || value === undefined || value === "") return null; + const number = Number(value); + if (!Number.isFinite(number) || number < 0 || number > 315360000) throw new Error(`${fieldName} is invalid`); + return Math.round(number); +} + +export function normalizeStrategyHealthScore(value, fieldName) { + if (value === null || value === undefined || value === "") return null; + const number = Number(value); + if (!Number.isFinite(number) || number < 0 || number > 100) throw new Error(`${fieldName} must be between 0 and 100`); + return number; +} + +export function normalizeStrategyHealthTimestamp(value, fieldName, nullable = false) { + if ((value === null || value === undefined || value === "") && nullable) return null; + const text = normalizeStrategyHealthText(value, fieldName, 64); + if (!/^\d{4}-\d{2}-\d{2}T.+(?:Z|[+-]\d{2}:\d{2})$/.test(text) || Number.isNaN(Date.parse(text))) { + throw new Error(`${fieldName} must be an ISO timestamp`); + } + return text; +} + +export function normalizeStrategyHealthText(value, fieldName, maxLength, nullable = false) { + if ((value === null || value === undefined || value === "") && nullable) return null; + const text = String(value || "").trim(); + if ( + !text || + text.length > maxLength || + /[<>\\]/.test(text) || + text.startsWith("/") || + /^[A-Za-z]:[\\/]/.test(text) + ) throw new Error(`${fieldName} is invalid`); + return text; +} + +export function sanitizeStrategyHealthText(value, fieldName, maxLength, nullable = false, fallback = null) { + if (value === null || value === undefined || value === "") return nullable ? null : fallback; + let text; + try { + text = normalizeStrategyHealthText(value, fieldName, maxLength); + } catch { + return nullable ? null : fallback; + } + if ( + /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/i.test(text) || + /\b(?:token|secret|password|api[_ -]?key|private[_ -]?key|cookie)\s*[:=]\s*[^\s,;]{8,}/i.test(text) || + /\b(?:gh[pousr]_[A-Za-z0-9_]{20,}|sk-[A-Za-z0-9_-]{20,}|eyJ[A-Za-z0-9_-]{20,})\b/.test(text) || + /(?:^|[\s(])(?:\/Users\/|\/home\/|[A-Za-z]:[\\/])/.test(text) + ) return nullable ? null : fallback; + return text; +} + +export function normalizeStrategyHealthErrors(value) { + if (!Array.isArray(value)) return []; + return uniqueStrings(value.slice(0, 20).filter((item) => /^[a-z][a-z0-9_.-]{0,63}$/.test(String(item || "")))); +} + +export function normalizeStrategyHealthPolicy(value) { + const source = value && typeof value === "object" && !Array.isArray(value) ? value : {}; + const list = (raw) => Array.isArray(raw) + ? raw.slice(0, 20).map((item) => sanitizeStrategyHealthText(item, "policy.item", 120, true)).filter(Boolean) + : []; + return { + mode: sanitizeStrategyHealthText(source.mode, "policy.mode", 40, false, "read_only"), + automatic_stages: list(source.automatic_stages), + automatic_modes: list(source.automatic_modes), + human_gate_stages: list(source.human_gate_stages), + canary_requirements: list(source.canary_requirements), + human_actions: list(source.human_actions), + machine_checks: list(source.machine_checks), + notice: sanitizeStrategyHealthText(source.notice, "policy.notice", 240, false, "健康不等于已批准 live。"), + }; +} + +export function emptyStrategyHealthPayload(errorCode) { + return { + schema_version: "strategy_health_dashboard.v1", + generated_at: null, + computed_at: null, + data_status: "unavailable", + summary: { strategy_count: 0, healthy: 0, watch: 0, review: 0, critical: 0 }, + strategies: [], + policy: normalizeStrategyHealthPolicy({}), + errors: [errorCode], + }; +} + +export function strategyHealthStaleTtlSeconds(env) { + const configured = Number(env.STRATEGY_HEALTH_STALE_TTL_SECONDS); + if (!Number.isFinite(configured) || configured < 300 || configured > 604800) { + return STRATEGY_HEALTH_DEFAULT_STALE_TTL_SECONDS; + } + return Math.floor(configured); +} + +export function normalizeControlPlaneSnapshot(payload, fieldName = "control plane snapshot") { + if (!payload || Array.isArray(payload) || typeof payload !== "object") { + throw new Error(`${fieldName} must be an object`); + } + if (payload.schema_version !== "qsl_control_plane_dashboard.v1") { + throw new Error(`${fieldName}.schema_version is unsupported`); + } + for (const required of ["generated_at", "computed_at", "data_status", "summary", "candidates", "policy", "errors"]) { + if (!(required in payload)) throw new Error(`${fieldName}.${required} is required`); + } + if (!payload.summary || Array.isArray(payload.summary) || typeof payload.summary !== "object") { + throw new Error(`${fieldName}.summary must be an object`); + } + const dataStatus = cleanChoice(payload.data_status || "unavailable", STRATEGY_HEALTH_DATA_STATUSES, `${fieldName}.data_status`); + const candidates = dataStatus === "unavailable" ? [] : normalizeControlPlaneCandidates(payload.candidates, fieldName); + const errors = normalizeStrategyHealthErrors(payload.errors); + return { + schema_version: "qsl_control_plane_dashboard.v1", + generated_at: normalizeStrategyHealthTimestamp(payload.generated_at, `${fieldName}.generated_at`, true), + computed_at: normalizeStrategyHealthTimestamp(payload.computed_at, `${fieldName}.computed_at`, true), + data_status: dataStatus, + summary: normalizeControlPlaneSummary(candidates), + attention: normalizeControlPlaneAttention({ dataStatus, candidates, errors }), + candidates, + policy: normalizeControlPlanePolicy(payload.policy, fieldName), + errors, + }; +} + +export function normalizeControlPlaneSourceSnapshot(payload, fieldName = "control plane source snapshot") { + if (!payload || Array.isArray(payload) || typeof payload !== "object") { + throw new Error(`${fieldName} must be an object`); + } + if (payload.schema_version !== CONTROL_PLANE_SOURCE_SCHEMA_VERSION) { + throw new Error(`${fieldName}.schema_version is unsupported`); + } + for (const required of ["source_id", "generated_at", "computed_at", "data_status", "candidates", "errors"]) { + if (!(required in payload)) throw new Error(`${fieldName}.${required} is required`); + } + const dataStatus = cleanChoice(payload.data_status || "unavailable", STRATEGY_HEALTH_DATA_STATUSES, `${fieldName}.data_status`); + return { + schema_version: CONTROL_PLANE_SOURCE_SCHEMA_VERSION, + source_id: normalizeControlPlaneIdentifier(payload.source_id, `${fieldName}.source_id`, false), + generated_at: normalizeStrategyHealthTimestamp(payload.generated_at, `${fieldName}.generated_at`, true), + computed_at: normalizeStrategyHealthTimestamp(payload.computed_at, `${fieldName}.computed_at`, true), + data_status: dataStatus, + candidates: dataStatus === "unavailable" ? [] : normalizeControlPlaneCandidates(payload.candidates, fieldName), + errors: normalizeStrategyHealthErrors(payload.errors), + }; +} + +export function emptyControlPlaneSourceSnapshot(errorCode) { + return { + schema_version: CONTROL_PLANE_SOURCE_SCHEMA_VERSION, + source_id: "unavailable", + generated_at: null, + computed_at: null, + data_status: "unavailable", + candidates: [], + errors: [errorCode], + }; +} + +export function requireDedicatedM0ResearchSyncToken(request, env) { + const expected = String(env.M0_RESEARCH_SYNC_TOKEN || ""); + if (!expected) throw new HttpError("M0 research 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("M0 research sync token is invalid", 401); +} + +export async function normalizeM0ResearchLedgerTransport(payload, fieldName = "M0 research ledger transport") { + const source = assertExactFields(payload, [ + "schema_version", "producer", "source_artifact", "ledger_sha256", "ledger", + ], fieldName); + if (source.schema_version !== M0_RESEARCH_TRANSPORT_SCHEMA_VERSION) { + throw new Error(`${fieldName}.schema_version is unsupported`); + } + const normalized = { + schema_version: M0_RESEARCH_TRANSPORT_SCHEMA_VERSION, + producer: normalizeM0ResearchProducer(source.producer, `${fieldName}.producer`), + source_artifact: normalizeM0ResearchSourceArtifact(source.source_artifact, `${fieldName}.source_artifact`), + ledger_sha256: normalizeResearchTaskDigest(source.ledger_sha256, `${fieldName}.ledger_sha256`), + ledger: normalizeM0ResearchLedger(source.ledger, `${fieldName}.ledger`), + }; + // The ledger digest binds the closed embedded ledger. source_artifact.sha256 + // intentionally remains a distinct immutable declaration about the QAR + // source artifact; it is not a hash of this derived ledger. + const recomputed = await calculateM0ResearchLedgerSha256(normalized.ledger); + if (normalized.ledger_sha256 !== recomputed) { + throw new Error(`${fieldName}.ledger_sha256 mismatch`); + } + const computedAt = Date.parse(normalized.ledger.computed_at); + if (!Number.isFinite(computedAt) || computedAt > Date.now() + 5 * 60 * 1000) { + throw new Error(`${fieldName}.ledger.computed_at is in the future`); + } + if (computedAt < Date.now() - M0_RESEARCH_RETENTION_SECONDS * 1000) { + throw new Error(`${fieldName}.ledger is expired`); + } + return normalized; +} + +export async function normalizeM0ResearchLedgerStorageRecord(payload, fieldName) { + const record = assertExactFields(payload, ["schema_version", "stored_at", "expires_at", "envelope"], fieldName); + if (record.schema_version !== M0_RESEARCH_STORAGE_SCHEMA_VERSION) { + throw new Error(`${fieldName}.schema_version is unsupported`); + } + const storedAt = normalizeM0ResearchTimestamp(record.stored_at, `${fieldName}.stored_at`); + const expiresAt = normalizeM0ResearchTimestamp(record.expires_at, `${fieldName}.expires_at`); + const storedMillis = Date.parse(storedAt); + const expiresMillis = Date.parse(expiresAt); + if (expiresMillis - storedMillis !== M0_RESEARCH_RETENTION_SECONDS * 1000 || expiresMillis <= Date.now()) { + throw new Error(`${fieldName} is expired`); + } + return { + schema_version: M0_RESEARCH_STORAGE_SCHEMA_VERSION, + stored_at: storedAt, + expires_at: expiresAt, + envelope: await normalizeM0ResearchLedgerTransport(record.envelope, `${fieldName}.envelope`), + }; +} + +export function normalizeM0ResearchLedger(payload, fieldName) { + const ledger = assertExactFields(payload, [ + "schema_version", "generated_at", "computed_at", "data_status", "summary", "subjects", "policy", "errors", + ], fieldName); + if (ledger.schema_version !== M0_RESEARCH_LEDGER_SCHEMA_VERSION) { + throw new Error(`${fieldName}.schema_version is unsupported`); + } + const generatedAt = normalizeM0ResearchTimestamp(ledger.generated_at, `${fieldName}.generated_at`); + const computedAt = normalizeM0ResearchTimestamp(ledger.computed_at, `${fieldName}.computed_at`); + if (generatedAt !== computedAt || /\./.test(generatedAt)) { + throw new Error(`${fieldName} timestamps must be the same canonical second`); + } + const dataStatus = cleanChoice(ledger.data_status, ["ready", "unavailable", "stale"], `${fieldName}.data_status`); + if (!Array.isArray(ledger.subjects) || ledger.subjects.length > 50000) { + throw new Error(`${fieldName}.subjects must be a bounded array`); + } + const subjects = ledger.subjects.map((subject, index) => normalizeM0ResearchSubject( + subject, `${fieldName}.subjects[${index}]`, computedAt, + )); + assertM0ResearchSortedUnique(subjects, (subject) => `${subject.subject.kind}\u0000${subject.subject.identifier}`, `${fieldName}.subjects`); + const summary = normalizeM0ResearchLedgerSummary(ledger.summary, `${fieldName}.summary`); + const calculatedSummary = summarizeM0ResearchSubjects(subjects); + if (!m0ResearchSummariesEqual(summary, calculatedSummary)) { + throw new Error(`${fieldName}.summary does not match subjects`); + } + const expectedStatus = summary.fresh_observation_count > 0 + ? "ready" + : (summary.observation_count > 0 ? "stale" : "unavailable"); + if (dataStatus !== expectedStatus) throw new Error(`${fieldName}.data_status does not match subjects`); + if (dataStatus === "unavailable" && subjects.length) throw new Error(`${fieldName}.subjects must be empty when unavailable`); + return { + schema_version: M0_RESEARCH_LEDGER_SCHEMA_VERSION, + generated_at: generatedAt, + computed_at: computedAt, + data_status: dataStatus, + summary, + subjects, + policy: normalizeM0ResearchLedgerPolicy(ledger.policy, `${fieldName}.policy`), + errors: normalizeM0ResearchErrorCodes(ledger.errors, `${fieldName}.errors`), + }; +} + +export function normalizeM0ResearchLedgerSummary(value, fieldName) { + const summary = assertExactFields(value, M0_RESEARCH_SUMMARY_FIELDS, fieldName); + const result = {}; + for (const key of M0_RESEARCH_SUMMARY_FIELDS) { + result[key] = normalizeM0ResearchCount(summary[key], `${fieldName}.${key}`); + } + return result; +} + +export function m0ResearchSummariesEqual(left, right) { + return M0_RESEARCH_SUMMARY_FIELDS.every((field) => left[field] === right[field]); +} + +export function normalizeM0ResearchLedgerPolicy(value, fieldName) { + const policy = assertExactFields(value, ["authority", "no_order", "permitted_next_step", "notice"], fieldName); + if (policy.authority !== "research_only" || policy.no_order !== true || policy.permitted_next_step !== "research_validation_only") { + throw new Error(`${fieldName} must remain research-only and no-order`); + } + return { + authority: "research_only", + no_order: true, + permitted_next_step: "research_validation_only", + notice: normalizeM0ResearchText(policy.notice, `${fieldName}.notice`, 240), + }; +} + +export function normalizeM0ResearchSubject(value, fieldName, ledgerComputedAt) { + const item = assertExactFields(value, ["subject", "observations", "horizon_conflict", "historical_stale_horizon_drift"], fieldName); + const subject = assertExactFields(item.subject, ["kind", "identifier"], `${fieldName}.subject`); + const normalizedSubject = { + kind: cleanChoice(subject.kind, M0_RESEARCH_SUBJECT_KINDS, `${fieldName}.subject.kind`), + identifier: normalizeM0ResearchIdentifier(subject.identifier, `${fieldName}.subject.identifier`), + }; + if (!Array.isArray(item.observations) || !item.observations.length || item.observations.length > 100) { + throw new Error(`${fieldName}.observations must be a non-empty bounded array`); + } + const observations = item.observations.map((observation, index) => normalizeM0ResearchObservation( + observation, `${fieldName}.observations[${index}]`, ledgerComputedAt, + )); + assertM0ResearchSortedUnique(observations, (observation) => ( + `${observation.source_report_digest}\u0000${observation.source_entry_digest}` + ), `${fieldName}.observations`); + const expectedHorizonView = calculateM0ResearchHorizonViews(observations); + const horizonConflict = normalizeM0ResearchHorizonView( + item.horizon_conflict, `${fieldName}.horizon_conflict`, ["none", "conflict"], expectedHorizonView.horizon_conflict, + ); + const historicalStaleHorizonDrift = normalizeM0ResearchHorizonView( + item.historical_stale_horizon_drift, `${fieldName}.historical_stale_horizon_drift`, + ["none", "drift", "unavailable"], expectedHorizonView.historical_stale_horizon_drift, + ); + return { + subject: normalizedSubject, + observations, + horizon_conflict: horizonConflict, + historical_stale_horizon_drift: historicalStaleHorizonDrift, + }; +} + +export function normalizeM0ResearchObservation(value, fieldName, ledgerComputedAt) { + const item = assertExactFields(value, [ + "source_ids", "source_report_digest", "source_entry_digest", "hypothesis_id", "as_of", "generated_at", "expires_at", + "research_context", "freshness", + ], fieldName); + if (!Array.isArray(item.source_ids) || !item.source_ids.length || item.source_ids.length > 100) { + throw new Error(`${fieldName}.source_ids must be a non-empty bounded array`); + } + const sourceIds = item.source_ids.map((sourceId, index) => normalizeM0ResearchIdentifier(sourceId, `${fieldName}.source_ids[${index}]`)); + assertM0ResearchSortedUnique(sourceIds, (sourceId) => sourceId, `${fieldName}.source_ids`); + const generatedAt = normalizeM0ResearchTimestamp(item.generated_at, `${fieldName}.generated_at`); + const expiresAt = normalizeM0ResearchTimestamp(item.expires_at, `${fieldName}.expires_at`); + const generatedMillis = Date.parse(generatedAt); + const expiresMillis = Date.parse(expiresAt); + if (expiresMillis - generatedMillis !== 7 * 24 * 60 * 60 * 1000) { + throw new Error(`${fieldName} must have the fixed seven-day M0 expiry`); + } + const asOf = normalizeM0ResearchDate(item.as_of, `${fieldName}.as_of`); + if (asOf > generatedAt.slice(0, 10)) throw new Error(`${fieldName}.as_of cannot be after generated_at`); + const context = normalizeM0ResearchContext(item.research_context, `${fieldName}.research_context`); + const freshness = normalizeM0ResearchFreshness( + item.freshness, `${fieldName}.freshness`, generatedMillis, expiresMillis, Date.parse(ledgerComputedAt), + ); + return { + source_ids: sourceIds, + source_report_digest: normalizeResearchTaskDigest(item.source_report_digest, `${fieldName}.source_report_digest`), + source_entry_digest: normalizeResearchTaskDigest(item.source_entry_digest, `${fieldName}.source_entry_digest`), + hypothesis_id: normalizeM0ResearchIdentifier(item.hypothesis_id, `${fieldName}.hypothesis_id`), + as_of: asOf, + generated_at: generatedAt, + expires_at: expiresAt, + research_context: context, + freshness, + }; +} + +export function normalizeM0ResearchContext(value, fieldName) { + const context = assertExactFields(value, [ + "state", "primary_horizon", "suitable_horizons", "source_confidence", "source_style", "theme_ids", + ], fieldName); + const primaryHorizon = cleanChoice(context.primary_horizon, M0_RESEARCH_HORIZONS, `${fieldName}.primary_horizon`); + if (!Array.isArray(context.suitable_horizons) || !context.suitable_horizons.length || context.suitable_horizons.length > 4) { + throw new Error(`${fieldName}.suitable_horizons is invalid`); + } + const suitableHorizons = context.suitable_horizons.map((horizon, index) => cleanChoice( + horizon, M0_RESEARCH_HORIZONS, `${fieldName}.suitable_horizons[${index}]`, + )); + if (!suitableHorizons.includes(primaryHorizon)) throw new Error(`${fieldName}.primary_horizon must be suitable`); + assertM0ResearchUnique(suitableHorizons, `${fieldName}.suitable_horizons`); + if (!Array.isArray(context.theme_ids) || context.theme_ids.length > 24) throw new Error(`${fieldName}.theme_ids is invalid`); + const themeIds = context.theme_ids.map((themeId, index) => normalizeM0ResearchIdentifier(themeId, `${fieldName}.theme_ids[${index}]`)); + assertM0ResearchUnique(themeIds, `${fieldName}.theme_ids`); + return { + state: cleanChoice(context.state, M0_RESEARCH_STATES, `${fieldName}.state`), + primary_horizon: primaryHorizon, + suitable_horizons: suitableHorizons, + source_confidence: cleanChoice(context.source_confidence, M0_RESEARCH_CONFIDENCE, `${fieldName}.source_confidence`), + source_style: cleanChoice(context.source_style, M0_RESEARCH_STYLES, `${fieldName}.source_style`), + theme_ids: themeIds, + }; +} + +export function normalizeM0ResearchFreshness(value, fieldName, generatedMillis, expiresMillis, ledgerComputedMillis) { + const freshness = assertExactFields(value, ["status", "age_seconds"], fieldName); + const status = cleanChoice(freshness.status, ["fresh", "stale", "unknown"], `${fieldName}.status`); + const expectedAge = generatedMillis > ledgerComputedMillis + ? null + : Math.max(0, Math.floor((ledgerComputedMillis - generatedMillis) / 1000)); + const age = freshness.age_seconds === null ? null : normalizeM0ResearchCount(freshness.age_seconds, `${fieldName}.age_seconds`, 315360000); + if (status === "unknown") { + if (age !== null || generatedMillis <= ledgerComputedMillis) throw new Error(`${fieldName} unknown state is invalid`); + return { status, age_seconds: null }; + } + if (age !== expectedAge || generatedMillis > ledgerComputedMillis) throw new Error(`${fieldName}.age_seconds is invalid`); + if (status === "fresh" && ledgerComputedMillis >= expiresMillis) throw new Error(`${fieldName} cannot be fresh after expiry`); + return { status, age_seconds: age }; +} + +export function calculateM0ResearchHorizonViews(observations) { + const freshHorizons = [...new Set(observations + .filter((observation) => observation.freshness.status === "fresh") + .map((observation) => observation.research_context.primary_horizon))].sort(); + const staleHorizons = [...new Set(observations + .filter((observation) => observation.freshness.status === "stale") + .map((observation) => observation.research_context.primary_horizon))].sort(); + return { + horizon_conflict: { status: freshHorizons.length > 1 ? "conflict" : "none", primary_horizons: freshHorizons }, + historical_stale_horizon_drift: { + status: freshHorizons.length + ? (staleHorizons.length && JSON.stringify(staleHorizons) !== JSON.stringify(freshHorizons) ? "drift" : "none") + : (staleHorizons.length ? "unavailable" : "none"), + primary_horizons: staleHorizons, + }, + }; +} + +export function projectM0ResearchDashboardForRead(ledger, sourceLedgerSha256, now = new Date()) { + // The stored envelope remains the immutable, digest-bound publication + // record. This is deliberately a different dashboard schema: changing + // freshness at read time must never pretend to be a re-validatable ledger. + const nowMillis = now instanceof Date ? now.getTime() : Date.parse(now); + if (!Number.isFinite(nowMillis)) return emptyM0ResearchDashboardPayload("m0_research_ledger_unavailable"); + const subjects = ledger.subjects.map((entry) => { + const observations = entry.observations.map((observation) => ({ + ...observation, + freshness: projectM0ResearchObservationFreshness(observation, nowMillis), + })); + const horizonViews = calculateM0ResearchHorizonViews(observations); + return { + subject: { ...entry.subject }, + observations, + horizon_conflict: horizonViews.horizon_conflict, + historical_stale_horizon_drift: horizonViews.historical_stale_horizon_drift, + }; + }); + const summary = summarizeM0ResearchSubjects(subjects); + const dataStatus = summary.fresh_observation_count > 0 + ? "ready" + : (summary.observation_count > 0 ? "stale" : "unavailable"); + return { + schema_version: M0_RESEARCH_DASHBOARD_SCHEMA_VERSION, + source_ledger_sha256: sourceLedgerSha256, + source_generated_at: ledger.generated_at, + source_computed_at: ledger.computed_at, + viewed_at: utcTimestampSeconds(new Date(nowMillis)), + data_status: dataStatus, + summary, + subjects, + policy: { ...ledger.policy }, + errors: [...ledger.errors], + }; +} + +export function projectM0ResearchObservationFreshness(observation, nowMillis) { + const generatedMillis = Date.parse(observation.generated_at); + const expiresMillis = Date.parse(observation.expires_at); + if (!Number.isFinite(generatedMillis) || !Number.isFinite(expiresMillis) || nowMillis < generatedMillis) { + return { status: "unknown", age_seconds: null }; + } + const ageSeconds = Math.max(0, Math.floor((nowMillis - generatedMillis) / 1000)); + // A stale source is never promoted by this projection. A former fresh + // observation is demoted as soon as its own expiry is reached. + const status = observation.freshness.status === "fresh" && nowMillis < expiresMillis + ? "fresh" + : "stale"; + return { status, age_seconds: ageSeconds }; +} + +export function normalizeM0ResearchHorizonView(value, fieldName, allowedStatuses, expected) { + const view = assertExactFields(value, ["status", "primary_horizons"], fieldName); + const status = cleanChoice(view.status, allowedStatuses, `${fieldName}.status`); + if (!Array.isArray(view.primary_horizons) || view.primary_horizons.length > 4) { + throw new Error(`${fieldName}.primary_horizons is invalid`); + } + const horizons = view.primary_horizons.map((horizon, index) => cleanChoice( + horizon, M0_RESEARCH_HORIZONS, `${fieldName}.primary_horizons[${index}]`, + )); + assertM0ResearchSortedUnique(horizons, (horizon) => horizon, `${fieldName}.primary_horizons`); + if (status !== expected.status || JSON.stringify(horizons) !== JSON.stringify(expected.primary_horizons)) { + throw new Error(`${fieldName} does not match observations`); + } + return { status, primary_horizons: horizons }; +} + +export function summarizeM0ResearchSubjects(subjects) { + const observations = subjects.flatMap((subject) => subject.observations); + return { + subject_count: subjects.length, + observation_count: observations.length, + fresh_observation_count: observations.filter((observation) => observation.freshness.status === "fresh").length, + stale_observation_count: observations.filter((observation) => observation.freshness.status === "stale").length, + unknown_observation_count: observations.filter((observation) => observation.freshness.status === "unknown").length, + horizon_conflict_count: subjects.filter((subject) => subject.horizon_conflict.status === "conflict").length, + historical_stale_horizon_drift_count: subjects.filter((subject) => subject.historical_stale_horizon_drift.status === "drift").length, + }; +} + +export function normalizeM0ResearchErrorCodes(value, fieldName) { + if (!Array.isArray(value) || value.length > 20) throw new Error(`${fieldName} is invalid`); + const errors = value.map((error, index) => { + if (typeof error !== "string" || !/^[a-z][a-z0-9_.-]{0,63}$/.test(error)) { + throw new Error(`${fieldName}[${index}] is invalid`); + } + return error; + }); + assertM0ResearchSortedUnique(errors, (error) => error, fieldName); + return errors; +} + +export function normalizeM0ResearchRepository(value, fieldName, allowedRepository) { + const repository = normalizeResearchTaskRepository(value, fieldName); + if (repository !== allowedRepository) throw new Error(`${fieldName} is not an approved M0 repository`); + return repository; +} + +export function normalizeM0ResearchRevision(value, fieldName) { + return normalizeResearchTaskRevision(value, fieldName); +} + +export function normalizeM0ResearchProducer(value, fieldName) { + const producer = assertExactFields(value, ["repository", "revision"], fieldName); + return { + repository: normalizeM0ResearchRepository( + producer.repository, `${fieldName}.repository`, M0_RESEARCH_ALLOWED_PRODUCER_REPOSITORY, + ), + revision: normalizeM0ResearchRevision(producer.revision, `${fieldName}.revision`), + }; +} + +export function normalizeM0ResearchSourceArtifact(value, fieldName) { + const artifact = assertExactFields(value, ["repository", "revision", "run_id", "artifact_id", "sha256"], fieldName); + return { + repository: normalizeM0ResearchRepository( + artifact.repository, `${fieldName}.repository`, M0_RESEARCH_ALLOWED_SOURCE_REPOSITORY, + ), + revision: normalizeM0ResearchRevision(artifact.revision, `${fieldName}.revision`), + run_id: normalizeM0ResearchIdentifier(artifact.run_id, `${fieldName}.run_id`), + artifact_id: normalizeM0ResearchArtifactId(artifact.artifact_id, `${fieldName}.artifact_id`), + sha256: normalizeResearchTaskDigest(artifact.sha256, `${fieldName}.sha256`), + }; +} + +export function normalizeM0ResearchArtifactId(value, fieldName) { + // Keep this exactly aligned with #309's generic identifier schema. Artifact + // IDs are provenance labels, not strategy slugs, so uppercase and `:/` are + // valid when they satisfy the closed source-artifact contract. + return normalizeM0ResearchIdentifier(value, fieldName); +} + +export function normalizeM0ResearchIdentifier(value, fieldName) { + if (typeof value !== "string" || !/^[A-Za-z0-9._:/-]{1,128}$/.test(value)) { + throw new Error(`${fieldName} is invalid`); + } + return value; +} + +export function normalizeM0ResearchTimestamp(value, fieldName) { + if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?Z$/.test(value) || Number.isNaN(Date.parse(value))) { + throw new Error(`${fieldName} must be a UTC timestamp`); + } + if (new Date(value).toISOString().slice(0, 10) !== value.slice(0, 10)) { + throw new Error(`${fieldName} must be a real UTC timestamp`); + } + return value; +} + +export function normalizeM0ResearchDate(value, fieldName) { + if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value) || Number.isNaN(Date.parse(`${value}T00:00:00Z`))) { + throw new Error(`${fieldName} must be an ISO date`); + } + if (new Date(`${value}T00:00:00Z`).toISOString().slice(0, 10) !== value) { + throw new Error(`${fieldName} must be a real ISO date`); + } + return value; +} + +export function normalizeM0ResearchCount(value, fieldName, maximum = 50000) { + if (!Number.isInteger(value) || value < 0 || value > maximum) throw new Error(`${fieldName} is invalid`); + return value; +} + +export function normalizeM0ResearchText(value, fieldName, maximum) { + if (typeof value !== "string" || !value || value.length > maximum || /[<>\\\u0000-\u001f]/.test(value)) { + throw new Error(`${fieldName} is invalid`); + } + return value; +} + +export function assertM0ResearchUnique(values, fieldName) { + if (new Set(values).size !== values.length) throw new Error(`${fieldName} contains duplicates`); +} + +export function assertM0ResearchSortedUnique(values, key, fieldName) { + let previous = null; + for (const value of values) { + const current = key(value); + if (previous !== null && current <= previous) throw new Error(`${fieldName} must be sorted and unique`); + previous = current; + } +} + +export function canonicalM0ResearchLedgerJson(value) { + if (value === null) return "null"; + if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value); + if (typeof value === "number") { + if (!Number.isFinite(value)) throw new Error("M0 research ledger must use finite JSON values"); + return JSON.stringify(value); + } + if (Array.isArray(value)) return `[${value.map(canonicalM0ResearchLedgerJson).join(",")}]`; + if (!value || typeof value !== "object") throw new Error("M0 research ledger must use JSON values"); + return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalM0ResearchLedgerJson(value[key])}`).join(",")}}`; +} + +export async function calculateM0ResearchLedgerSha256(ledger) { + const raw = new TextEncoder().encode(canonicalM0ResearchLedgerJson(ledger)); + const digest = await crypto.subtle.digest("SHA-256", raw); + return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join(""); +} + +export function emptyM0ResearchDashboardPayload(errorCode, now = new Date()) { + return { + schema_version: M0_RESEARCH_DASHBOARD_SCHEMA_VERSION, + source_ledger_sha256: null, + source_generated_at: null, + source_computed_at: null, + viewed_at: utcTimestampSeconds(now), + data_status: "unavailable", + summary: { + subject_count: 0, + observation_count: 0, + fresh_observation_count: 0, + stale_observation_count: 0, + unknown_observation_count: 0, + horizon_conflict_count: 0, + historical_stale_horizon_drift_count: 0, + }, + subjects: [], + policy: { + authority: "research_only", + no_order: true, + permitted_next_step: "research_validation_only", + notice: "M0 研究台账不可用;不会推断策略、平台、运行状态或订单。", + }, + errors: [errorCode], + }; +} + +export async function normalizeAdaptiveSelectionSourceSnapshot(payload, fieldName = "adaptive selection source snapshot") { + const source = assertExactFields(payload, [ + "schema_version", "source_id", "generated_at", "computed_at", "data_status", "decision", "errors", + ], fieldName); + if (source.schema_version !== ADAPTIVE_SELECTION_SOURCE_SCHEMA_VERSION) { + throw new Error(`${fieldName}.schema_version is unsupported`); + } + const dataStatus = cleanChoice(source.data_status, STRATEGY_HEALTH_DATA_STATUSES, `${fieldName}.data_status`); + const generatedAt = normalizeStrategyHealthTimestamp(source.generated_at, `${fieldName}.generated_at`, true); + const computedAt = normalizeStrategyHealthTimestamp(source.computed_at, `${fieldName}.computed_at`, true); + const decision = source.decision === null + ? null + : await normalizeAdaptiveSelectionDecision(source.decision, `${fieldName}.decision`); + if (dataStatus === "unavailable" && decision !== null) { + throw new Error(`${fieldName}.decision must be null when unavailable`); + } + if (dataStatus !== "unavailable" && (!decision || !generatedAt || !computedAt)) { + throw new Error(`${fieldName} requires decision and timestamps when available`); + } + return { + schema_version: ADAPTIVE_SELECTION_SOURCE_SCHEMA_VERSION, + source_id: normalizeControlPlaneIdentifier(source.source_id, `${fieldName}.source_id`, false), + generated_at: generatedAt, + computed_at: computedAt, + data_status: dataStatus, + decision, + errors: normalizeStrategyHealthErrors(source.errors), + }; +} + +export async function normalizeAdaptiveSelectionDecision(payload, fieldName) { + const value = assertExactFields(payload, [ + "schema", "decision_id", "created_at", "authority", "no_order", "market_context", "policy_id", + "recommended_strategy_profile", "recommended_platform_id", "candidates", "input_digest", "decision_digest", + ], fieldName); + if (value.schema !== ADAPTIVE_SELECTION_DECISION_SCHEMA_VERSION) { + throw new Error(`${fieldName}.schema is unsupported`); + } + if (value.authority !== ADAPTIVE_SELECTION_AUTHORITY || value.no_order !== true) { + throw new Error(`${fieldName} must remain shadow-only and no-order`); + } + if (!Array.isArray(value.candidates) || value.candidates.length > 1000) { + throw new Error(`${fieldName}.candidates must be a bounded array`); + } + const candidates = value.candidates.map((item, index) => + normalizeAdaptiveSelectionCandidate(item, `${fieldName}.candidates[${index}]`), + ); + const candidateIds = new Set(); + for (const candidate of candidates) { + if (candidateIds.has(candidate.strategy_profile)) { + throw new Error(`${fieldName}.candidates contains duplicate strategy_profile`); + } + candidateIds.add(candidate.strategy_profile); + } + const recommendedStrategy = normalizeControlPlaneIdentifier( + value.recommended_strategy_profile, `${fieldName}.recommended_strategy_profile`, true, + ); + const recommendedPlatform = normalizeControlPlaneIdentifier( + value.recommended_platform_id, `${fieldName}.recommended_platform_id`, true, + ); + const recommendedCandidate = candidates.find((item) => item.strategy_profile === recommendedStrategy && item.accepted); + if ((recommendedStrategy === null) !== (recommendedPlatform === null)) { + throw new Error(`${fieldName}.recommended strategy and platform must be provided together`); + } + if (recommendedStrategy && (!recommendedCandidate || recommendedCandidate.selected_platform_id !== recommendedPlatform)) { + throw new Error(`${fieldName}.recommended candidate is not accepted`); + } + const normalized = { + schema: ADAPTIVE_SELECTION_DECISION_SCHEMA_VERSION, + decision_id: normalizeControlPlaneIdentifier(value.decision_id, `${fieldName}.decision_id`, false), + created_at: normalizeStrategyHealthTimestamp(value.created_at, `${fieldName}.created_at`), + authority: ADAPTIVE_SELECTION_AUTHORITY, + no_order: true, + market_context: normalizeAdaptiveSelectionMarketContext(value.market_context, `${fieldName}.market_context`), + policy_id: normalizeControlPlaneIdentifier(value.policy_id, `${fieldName}.policy_id`, false), + recommended_strategy_profile: recommendedStrategy, + recommended_platform_id: recommendedPlatform, + candidates, + input_digest: normalizeAdaptiveSelectionDigest(value.input_digest, `${fieldName}.input_digest`), + decision_digest: normalizeAdaptiveSelectionDigest(value.decision_digest, `${fieldName}.decision_digest`), + }; + // `input_digest` cannot be reconstructed from the display projection alone: + // QPK calculates it from the private, immutable selection input. The QPK + // decision digest binds that exact input digest to every decision field that + // is displayed here, so a changed input digest cannot be silently accepted. + if (normalized.decision_digest !== await calculateAdaptiveSelectionDecisionDigest(normalized)) { + throw new Error(`${fieldName}.decision_digest mismatch`); + } + return normalized; +} + +export function normalizeAdaptiveSelectionMarketContext(value, fieldName) { + const item = assertExactFields(value, [ + "schema", "as_of", "domain", "data_version", "data_freshness_days", "regime", "regime_confidence", "factors", + ], fieldName); + if (item.schema !== "qsl.market_context_snapshot.v1") { + throw new Error(`${fieldName}.schema is unsupported`); + } + const asOf = String(item.as_of || ""); + if (!/^\d{4}-\d{2}-\d{2}$/.test(asOf) || Number.isNaN(Date.parse(`${asOf}T00:00:00Z`))) { + throw new Error(`${fieldName}.as_of must be an ISO date`); + } + if (!Number.isInteger(item.data_freshness_days) || item.data_freshness_days < 0 || item.data_freshness_days > 366) { + throw new Error(`${fieldName}.data_freshness_days is invalid`); + } + const confidence = Number(item.regime_confidence); + if (!Number.isFinite(confidence) || confidence < 0 || confidence > 1) { + throw new Error(`${fieldName}.regime_confidence is invalid`); + } + if (!item.factors || Array.isArray(item.factors) || typeof item.factors !== "object" || Object.keys(item.factors).length > 100) { + throw new Error(`${fieldName}.factors is invalid`); + } + const factors = {}; + for (const [key, raw] of Object.entries(item.factors)) { + if (!/^[a-z][a-z0-9_.-]{0,63}$/.test(key)) throw new Error(`${fieldName}.factors key is invalid`); + const numeric = Number(raw); + if (!Number.isFinite(numeric) || Math.abs(numeric) > 1_000_000) { + throw new Error(`${fieldName}.factors.${key} is invalid`); + } + factors[key] = numeric; + } + return { + schema: "qsl.market_context_snapshot.v1", + as_of: asOf, + domain: cleanChoice(item.domain, STRATEGY_HEALTH_DOMAINS, `${fieldName}.domain`), + data_version: normalizeControlPlaneIdentifier(item.data_version, `${fieldName}.data_version`, false), + data_freshness_days: item.data_freshness_days, + regime: normalizeControlPlaneIdentifier(item.regime, `${fieldName}.regime`, false), + regime_confidence: confidence, + factors, + }; +} + +export function normalizeAdaptiveSelectionCandidate(value, fieldName) { + const item = assertExactFields(value, [ + "strategy_profile", "release_digest", "selected_platform_id", "score", "risk_multiplier", "accepted", "reasons", "proposed_weight", + ], fieldName); + const score = item.score === null ? null : Number(item.score); + const riskMultiplier = Number(item.risk_multiplier); + if (score !== null && (!Number.isFinite(score) || Math.abs(score) > 1_000_000)) { + throw new Error(`${fieldName}.score is invalid`); + } + if (!Number.isFinite(riskMultiplier) || riskMultiplier < 0 || riskMultiplier > 1) { + throw new Error(`${fieldName}.risk_multiplier is invalid`); + } + if (typeof item.accepted !== "boolean") throw new Error(`${fieldName}.accepted must be boolean`); + if (item.proposed_weight !== 0) throw new Error(`${fieldName}.proposed_weight must remain zero`); + if (!Array.isArray(item.reasons) || item.reasons.length > 32) throw new Error(`${fieldName}.reasons is invalid`); + const reasons = item.reasons.map((reason) => { + const code = String(reason || ""); + if (!/^[a-z][a-z0-9_.:-]{0,127}$/.test(code)) throw new Error(`${fieldName}.reason is invalid`); + return code; + }); + return { + strategy_profile: normalizeControlPlaneIdentifier(item.strategy_profile, `${fieldName}.strategy_profile`, false), + release_digest: normalizeAdaptiveSelectionReleaseDigest(item.release_digest, `${fieldName}.release_digest`), + selected_platform_id: normalizeControlPlaneIdentifier(item.selected_platform_id, `${fieldName}.selected_platform_id`, true), + score, + risk_multiplier: riskMultiplier, + accepted: item.accepted, + reasons, + proposed_weight: 0, + }; +} + +export function normalizeAdaptiveSelectionReleaseDigest(value, fieldName) { + const text = String(value || "").trim(); + if (!/^[A-Za-z0-9._:=+-]{1,160}$/.test(text) || /(token|secret|password|cookie|private|api[_-]?key)/i.test(text)) { + throw new Error(`${fieldName} is invalid`); + } + return text; +} + +export function normalizeAdaptiveSelectionDigest(value, fieldName) { + const text = String(value || ""); + if (!/^[a-f0-9]{64}$/.test(text)) throw new Error(`${fieldName} is invalid`); + return text; +} + +export function isAdaptiveSelectionFloatPath(path) { + if (path[0] === "market_context") { + return path[1] === "regime_confidence" || path[1] === "factors"; + } + return path[0] === "candidates" + && ["score", "risk_multiplier", "proposed_weight"].includes(path[2]); +} + +export function canonicalAdaptiveSelectionNumber(value, forceFloat) { + if (!Number.isFinite(value)) throw new Error("adaptive selection decision must use finite JSON values"); + if (Object.is(value, -0)) return forceFloat ? "-0.0" : "0"; + let text = String(value); + const exponent = text.match(/^(.*)e([+-]?)(\d+)$/i); + if (exponent) { + const [, mantissa, sign, rawExponent] = exponent; + // Python's json.dumps (used by QPK canonical_sha256) pads one-digit + // negative exponents, while V8's Number#toString does not. + text = `${mantissa}e${sign || "+"}${sign === "-" ? rawExponent.padStart(2, "0") : rawExponent}`; + } + if (forceFloat && !/[.eE]/.test(text)) return `${text}.0`; + return text; +} + +export function canonicalAdaptiveSelectionDecisionJson(value, path = []) { + if (value === null) return "null"; + if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value); + if (typeof value === "number") return canonicalAdaptiveSelectionNumber(value, isAdaptiveSelectionFloatPath(path)); + if (Array.isArray(value)) { + return `[${value.map((item) => canonicalAdaptiveSelectionDecisionJson(item, [...path, "*"])).join(",")}]`; + } + if (!value || typeof value !== "object") throw new Error("adaptive selection decision must use JSON values"); + return `{${Object.keys(value).sort().map((key) => ( + `${JSON.stringify(key)}:${canonicalAdaptiveSelectionDecisionJson(value[key], [...path, key])}` + )).join(",")}}`; +} + +export async function calculateAdaptiveSelectionDecisionDigest(payload) { + const material = { ...payload }; + delete material.decision_digest; + const raw = new TextEncoder().encode(canonicalAdaptiveSelectionDecisionJson(material)); + const digest = await crypto.subtle.digest("SHA-256", raw); + return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join(""); +} + +export function normalizeAdaptiveSelectionSummary(selections) { + const entries = Array.isArray(selections) ? selections : []; + const decisions = entries.map((item) => item.decision).filter(Boolean); + const candidates = decisions.flatMap((item) => item.candidates); + return { + source_count: entries.length, + decision_count: decisions.length, + candidate_count: candidates.length, + recommended_count: decisions.filter((item) => item.recommended_strategy_profile).length, + rejected_candidate_count: candidates.filter((item) => !item.accepted).length, + }; +} + +export function emptyAdaptiveSelectionSourceSnapshot(errorCode) { + return { + schema_version: ADAPTIVE_SELECTION_SOURCE_SCHEMA_VERSION, + source_id: "unavailable", + generated_at: null, + computed_at: null, + data_status: "unavailable", + decision: null, + errors: [errorCode], + }; +} + +export function emptyAdaptiveSelectionPayload(errorCode) { + return { + schema_version: ADAPTIVE_SELECTION_DASHBOARD_SCHEMA_VERSION, + generated_at: null, + computed_at: null, + data_status: "unavailable", + summary: { source_count: 0, decision_count: 0, candidate_count: 0, recommended_count: 0, rejected_candidate_count: 0 }, + selections: [], + policy: { + authority: ADAPTIVE_SELECTION_AUTHORITY, + no_order: true, + execution_authority_granted: false, + notice: "M1 Shadow 建议尚不可用;控制台不会推断策略、平台或订单。", + }, + errors: [errorCode], + }; +} + +export function adaptiveSelectionStaleTtlSeconds(env) { + const configured = Number(env.ADAPTIVE_SELECTION_STALE_TTL_SECONDS); + if (!Number.isFinite(configured) || configured < 300 || configured > 604800) { + return ADAPTIVE_SELECTION_DEFAULT_STALE_TTL_SECONDS; + } + return Math.floor(configured); +} + +export function normalizeControlPlaneCandidates(value, fieldName) { + if (!Array.isArray(value) || value.length > 1000) { + throw new Error(`${fieldName}.candidates must be an array with at most 1000 items`); + } + const seen = new Set(); + return value.map((item, index) => { + const prefix = `${fieldName}.candidates[${index}]`; + if (!item || Array.isArray(item) || typeof item !== "object") throw new Error(`${prefix} must be an object`); + const candidateId = normalizeControlPlaneIdentifier(item.candidate_id, `${prefix}.candidate_id`, false); + if (seen.has(candidateId)) throw new Error(`${fieldName}.candidates contains duplicate candidate_id`); + seen.add(candidateId); + const lifecycle = normalizeControlPlaneLifecycle(item.lifecycle, prefix); + const recommendation = normalizeControlPlaneRecommendation(item.recommendation, prefix); + const isOwnerDecision = lifecycle.stage === "P6"; + if (isOwnerDecision && (lifecycle.status !== "owner_decision_required" || recommendation.code !== "owner_live_decision")) { + throw new Error(`${prefix} P6 must be an owner_live_decision`); + } + if (!isOwnerDecision && (lifecycle.status === "owner_decision_required" || recommendation.code === "owner_live_decision")) { + throw new Error(`${prefix} only P6 can require an owner_live_decision`); + } + const forwardObservation = item.forward_observation === undefined + ? undefined + : normalizeForwardObservation(item.forward_observation, lifecycle, prefix); + return { + candidate_id: candidateId, + candidate_kind: cleanChoice(item.candidate_kind, CONTROL_PLANE_CANDIDATE_KINDS, `${prefix}.candidate_kind`), + domain: cleanChoice(item.domain, STRATEGY_HEALTH_DOMAINS, `${prefix}.domain`), + lifecycle, + evidence: normalizeControlPlaneEvidence(item.evidence, prefix), + recommendation, + freshness: normalizeStrategyHealthFreshness(item.freshness, `${prefix}.freshness`), + ...(forwardObservation === undefined ? {} : { forward_observation: forwardObservation }), + }; + }); +} + +export function normalizeForwardObservation(value, lifecycle, prefix) { + if (!FORWARD_OBSERVATION_RESEARCH_STAGES.has(lifecycle.stage)) { + throw new Error(`${prefix}.forward_observation is only allowed for research stages`); + } + const observation = assertExactFields(value, [ + "state", "observations_completed", "required_trading_sessions", "last_observed_session", "observed_at", "no_order", "live_authority_granted", + ], `${prefix}.forward_observation`); + const completed = observation.observations_completed; + const required = observation.required_trading_sessions; + if (!Number.isSafeInteger(completed) || !Number.isSafeInteger(required) || completed < 0 || required < 1 || completed > required || required > 10000) { + throw new Error(`${prefix}.forward_observation observation counts are invalid`); + } + if (observation.no_order !== true) throw new Error(`${prefix}.forward_observation.no_order must be true`); + if (observation.live_authority_granted !== false) throw new Error(`${prefix}.forward_observation.live_authority_granted must be false`); + return { + state: cleanChoice(observation.state, FORWARD_OBSERVATION_STATES, `${prefix}.forward_observation.state`), + observations_completed: completed, + required_trading_sessions: required, + last_observed_session: normalizeForwardObservationDate(observation.last_observed_session, `${prefix}.forward_observation.last_observed_session`), + observed_at: normalizeStrategyHealthTimestamp(observation.observed_at, `${prefix}.forward_observation.observed_at`), + no_order: true, + live_authority_granted: false, + }; +} + +export function normalizeForwardObservationDate(value, fieldName) { + const text = normalizeStrategyHealthText(value, fieldName, 10); + if (!/^\d{4}-\d{2}-\d{2}$/.test(text) || Number.isNaN(Date.parse(`${text}T00:00:00.000Z`)) || new Date(`${text}T00:00:00.000Z`).toISOString().slice(0, 10) !== text) { + throw new Error(`${fieldName} must be an ISO date`); + } + return text; +} + +export function normalizeControlPlaneIdentifier(value, fieldName, nullable = true) { + if ((value === null || value === undefined || value === "") && nullable) return null; + const text = String(value || "").trim(); + if (!/^[A-Za-z0-9._=-]{1,128}$/.test(text)) throw new Error(`${fieldName} is invalid`); + if (/(token|secret|password|cookie|private|api[_-]?key)/i.test(text)) throw new Error(`${fieldName} is sensitive`); + return text; +} + +export function normalizeControlPlaneLifecycle(value, prefix) { + const source = value && typeof value === "object" && !Array.isArray(value) ? value : {}; + return { + stage: cleanChoice(source.stage, CONTROL_PLANE_STAGES, `${prefix}.lifecycle.stage`), + status: cleanChoice(source.status, CONTROL_PLANE_LIFECYCLE_STATUSES, `${prefix}.lifecycle.status`), + }; +} + +export function normalizeControlPlaneEvidence(value, prefix) { + const source = value && typeof value === "object" && !Array.isArray(value) ? value : {}; + return { + p1_input_digest: normalizeControlPlaneIdentifier(source.p1_input_digest, `${prefix}.evidence.p1_input_digest`), + p2_config_digest: normalizeControlPlaneIdentifier(source.p2_config_digest, `${prefix}.evidence.p2_config_digest`), + p3_evidence_id: normalizeControlPlaneIdentifier(source.p3_evidence_id, `${prefix}.evidence.p3_evidence_id`), + source_revision: normalizeControlPlaneIdentifier(source.source_revision, `${prefix}.evidence.source_revision`), + }; +} + +export function normalizeControlPlaneRecommendation(value, prefix) { + const source = value && typeof value === "object" && !Array.isArray(value) ? value : {}; + return { + code: cleanChoice(source.code || "none", CONTROL_PLANE_RECOMMENDATIONS, `${prefix}.recommendation.code`), + reason: sanitizeStrategyHealthText(source.reason, `${prefix}.recommendation.reason`, 240, false, "没有可用的机器建议。"), + }; +} + +export function normalizeControlPlaneSummary(candidates) { + return { + candidate_count: candidates.length, + deferred: candidates.filter((item) => item.lifecycle.status === "deferred").length, + parked: candidates.filter((item) => item.lifecycle.status === "parked").length, + owner_decision_required: candidates.filter((item) => item.lifecycle.status === "owner_decision_required").length, + }; +} + +export function normalizeControlPlaneAttention({ dataStatus, candidates, errors }) { + const normalizedCandidates = Array.isArray(candidates) ? candidates : []; + const normalizedErrors = uniqueStrings(Array.isArray(errors) ? errors : []); + const reasonCodes = new Set(normalizedErrors); + if (dataStatus !== "ready") reasonCodes.add("control_plane_source_unavailable"); + if (!normalizedCandidates.length && dataStatus === "ready") reasonCodes.add("control_plane_candidate_missing"); + if (normalizedCandidates.some((item) => item.lifecycle?.status === "deferred")) { + reasonCodes.add("control_plane_candidate_deferred"); + } + if (normalizedCandidates.some((item) => item.lifecycle?.status === "parked")) { + reasonCodes.add("control_plane_candidate_parked"); + } + const status = dataStatus !== "ready" + ? "unavailable" + : (reasonCodes.size ? "attention_required" : "research_only"); + return { + status: cleanChoice(status, CONTROL_PLANE_ATTENTION_STATUSES, "control plane attention status"), + reason_codes: [...reasonCodes].sort(), + }; +} + +export function normalizeControlPlanePolicy(value, fieldName) { + const source = value && typeof value === "object" && !Array.isArray(value) ? value : {}; + if (source.p6_owner_decision_required !== true) { + throw new Error(`${fieldName}.policy.p6_owner_decision_required must be true`); + } + return { + p4_p5_automation: cleanChoice( + source.p4_p5_automation || "not_configured", + CONTROL_PLANE_AUTOMATION_STATES, + `${fieldName}.policy.p4_p5_automation`, + ), + p6_owner_decision_required: true, + notice: sanitizeStrategyHealthText(source.notice, `${fieldName}.policy.notice`, 240, false, "live 仍需所有者明确决定。"), + }; +} + +export function normalizeBinancePrivateScopeReport(payload, { requireIngestFreshness = false } = {}) { + const report = assertExactFields(payload, [ + "platform", + "observed_at", + "source_run_id", + "source_sha", + "account_scope_sha256", + "assets", + "historical_difference_unresolved", + "no_order", + "execution_authority_granted", + ], "Binance private scope report"); + if (report.platform !== "binance") throw new Error("Binance private scope report.platform must be binance"); + const observedAt = report.observed_at; + if (typeof observedAt !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(observedAt)) { + throw new Error("Binance private scope report.observed_at must be an ISO timestamp with timezone"); + } + const observedAtMs = Date.parse(observedAt); + if (!Number.isFinite(observedAtMs)) { + throw new Error("Binance private scope report.observed_at must be valid"); + } + if (requireIngestFreshness) { + const ageMs = Date.now() - observedAtMs; + if (ageMs > BINANCE_PRIVATE_SCOPE_INGEST_MAX_AGE_MS || ageMs < -BINANCE_PRIVATE_SCOPE_FUTURE_SKEW_MS) { + throw new Error("Binance private scope report.observed_at is outside the ingestion window"); + } + } + const sourceRunId = report.source_run_id; + if (typeof sourceRunId !== "string" || !/^[1-9]\d{0,19}$/.test(sourceRunId)) { + throw new Error("Binance private scope report.source_run_id must be a decimal string"); + } + const sourceSha = report.source_sha; + if (typeof sourceSha !== "string" || !/^[0-9a-fA-F]{40}$/.test(sourceSha)) { + throw new Error("Binance private scope report.source_sha must be a 40 character hex string"); + } + const accountScopeSha256 = report.account_scope_sha256; + if (typeof accountScopeSha256 !== "string" || !/^[0-9a-fA-F]{64}$/.test(accountScopeSha256)) { + throw new Error("Binance private scope report.account_scope_sha256 must be a 64 character hex string"); + } + if (!Array.isArray(report.assets) || report.assets.length > BINANCE_PRIVATE_SCOPE_MAX_ASSETS) { + throw new Error(`Binance private scope report.assets must contain at most ${BINANCE_PRIVATE_SCOPE_MAX_ASSETS} items`); + } + const seenAssets = new Set(); + const assets = report.assets.map((value, index) => { + const item = assertExactFields(value, ["asset", "free", "locked"], `Binance private scope report.assets[${index}]`); + const asset = item.asset; + if (typeof asset !== "string" || !/^[\p{L}\p{N}]{1,20}$/u.test(asset)) { + throw new Error(`Binance private scope report.assets[${index}].asset is invalid`); + } + if (seenAssets.has(asset)) throw new Error("Binance private scope report.assets contains duplicate assets"); + seenAssets.add(asset); + const free = normalizeBinancePrivateScopeDecimal(item.free, `Binance private scope report.assets[${index}].free`); + const locked = normalizeBinancePrivateScopeDecimal(item.locked, `Binance private scope report.assets[${index}].locked`); + if (binancePrivateScopeDecimalIsZero(free) && binancePrivateScopeDecimalIsZero(locked)) { + throw new Error(`Binance private scope report.assets[${index}] must not be an all-zero row`); + } + return { asset, free, locked }; + }); + if (report.historical_difference_unresolved !== true) { + throw new Error("Binance private scope report.historical_difference_unresolved must be true"); + } + if (report.no_order !== true) throw new Error("Binance private scope report.no_order must be true"); + if (report.execution_authority_granted !== false) { + throw new Error("Binance private scope report.execution_authority_granted must be false"); + } + return { + platform: "binance", + observed_at: observedAt, + source_run_id: sourceRunId, + source_sha: sourceSha.toLowerCase(), + account_scope_sha256: accountScopeSha256.toLowerCase(), + assets, + historical_difference_unresolved: true, + no_order: true, + execution_authority_granted: false, + }; +} + +export function normalizeBinancePrivateScopeDecimal(value, fieldName) { + if (typeof value !== "string" || !/^\d+(?:\.\d+)?$/.test(value)) { + throw new Error(`${fieldName} must be a non-negative finite decimal string`); + } + return value; +} + +export function binancePrivateScopeDecimalIsZero(value) { + return /^0+(?:\.0+)?$/.test(value); +} + +export 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), + }; +} + +export function normalizeReconciliationRecoveryRequest(value, fieldName) { + if (value?.mode === "initial_enrollment") { + const item = assertExactFields(value, ["mode", "recovery_id", "target_id", "candidate", "facts"], fieldName); + return { mode: "initial_enrollment", recovery_id: normalizeControlPlaneIdentifier(item.recovery_id, `${fieldName}.recovery_id`, false), + target_id: normalizeControlPlaneIdentifier(item.target_id, `${fieldName}.target_id`, false), candidate: item.candidate, facts: item.facts }; + } + 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 < 1 || + observationWindowMs > RECONCILIATION_RECOVERY_MAX_SAMPLE_WINDOW_MS || + recovery.dual_review.evidence_binding_sha256 !== recovery.candidate_sha256 || + recovery.blocker_codes.length + ) { + throw new Error(`${fieldName}.readiness requires at least one observation within a 15-minute window, bound candidate, and no blockers`); + } + } + return recovery; +} + +export function normalizeExecutionEvidenceSourceSnapshot(payload, fieldName = "execution evidence source snapshot") { + const source = assertExactFields(payload, [ + "schema_version", "source_id", "generated_at", "computed_at", "data_status", "deployments", "errors", + ], fieldName); + if (source.schema_version !== EXECUTION_EVIDENCE_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.deployments) || source.deployments.length > 1000) { + throw new Error(`${fieldName}.deployments must be an array with at most 1000 items`); + } + const deployments = []; + const seen = new Set(); + for (const [index, item] of source.deployments.entries()) { + const deployment = normalizeExecutionEvidenceDeployment(item, `${fieldName}.deployments[${index}]`); + if (seen.has(deployment.deployment_id)) { + throw new Error(`${fieldName}.deployments contains duplicate deployment_id`); + } + seen.add(deployment.deployment_id); + deployments.push(deployment); + } + if (dataStatus === "unavailable" && deployments.length) { + throw new Error(`${fieldName}.deployments must be empty when unavailable`); + } + return { + schema_version: EXECUTION_EVIDENCE_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, + deployments, + errors: normalizeStrategyHealthErrors(source.errors), + }; +} + +export function normalizeExecutionEvidenceDeployment(value, fieldName) { + const item = assertRequiredAndOptionalFields(value, [ + "deployment_id", "strategy", "target", "capabilities", "evidence", "recommendation", + ], ["execution_receipt"], fieldName); + const strategy = assertExactFields(item.strategy, [ + "candidate_id", "candidate_kind", "domain", "strategy_revision", + ], `${fieldName}.strategy`); + const target = assertExactFields(item.target, ["platform", "environment"], `${fieldName}.target`); + const capabilities = assertExactFields(item.capabilities, ["shadow", "paper"], `${fieldName}.capabilities`); + const evidence = assertExactFields(item.evidence, [ + "strategy", "target_data", "target_execution", + ], `${fieldName}.evidence`); + const recommendation = assertExactFields(item.recommendation, ["code", "reason_code"], `${fieldName}.recommendation`); + const executionReceipt = item.execution_receipt === undefined + ? null + : normalizeExecutionEvidenceReceipt(item.execution_receipt, `${fieldName}.execution_receipt`); + const normalized = { + deployment_id: normalizeControlPlaneIdentifier(item.deployment_id, `${fieldName}.deployment_id`, false), + strategy: { + candidate_id: normalizeControlPlaneIdentifier(strategy.candidate_id, `${fieldName}.strategy.candidate_id`, false), + candidate_kind: cleanChoice(strategy.candidate_kind, CONTROL_PLANE_CANDIDATE_KINDS, `${fieldName}.strategy.candidate_kind`), + domain: cleanChoice(strategy.domain, STRATEGY_HEALTH_DOMAINS, `${fieldName}.strategy.domain`), + strategy_revision: normalizeResearchTaskRevision(strategy.strategy_revision, `${fieldName}.strategy.strategy_revision`), + }, + target: { + platform: cleanChoice(target.platform, EXECUTION_EVIDENCE_PLATFORMS, `${fieldName}.target.platform`), + environment: cleanChoice(target.environment, EXECUTION_EVIDENCE_ENVIRONMENTS, `${fieldName}.target.environment`), + }, + capabilities: { + shadow: cleanChoice(capabilities.shadow, EXECUTION_EVIDENCE_CAPABILITIES, `${fieldName}.capabilities.shadow`), + paper: cleanChoice(capabilities.paper, EXECUTION_EVIDENCE_CAPABILITIES, `${fieldName}.capabilities.paper`), + }, + evidence: { + strategy: cleanChoice(evidence.strategy, EXECUTION_EVIDENCE_STATUSES, `${fieldName}.evidence.strategy`), + target_data: cleanChoice(evidence.target_data, EXECUTION_EVIDENCE_STATUSES, `${fieldName}.evidence.target_data`), + target_execution: cleanChoice(evidence.target_execution, EXECUTION_EVIDENCE_STATUSES, `${fieldName}.evidence.target_execution`), + }, + recommendation: { + code: cleanChoice(recommendation.code, EXECUTION_EVIDENCE_RECOMMENDATIONS, `${fieldName}.recommendation.code`), + reason_code: cleanChoice(recommendation.reason_code, EXECUTION_EVIDENCE_REASON_CODES, `${fieldName}.recommendation.reason_code`), + }, + }; + if (normalized.recommendation.code === "continue_autonomous_shadow" && normalized.capabilities.shadow !== "available") { + throw new Error(`${fieldName}.recommendation requires an available shadow capability`); + } + if (normalized.recommendation.code === "run_autonomous_paper" && normalized.capabilities.paper !== "available") { + throw new Error(`${fieldName}.recommendation requires an available paper capability`); + } + if (normalized.recommendation.code === "owner_limited_live_canary_decision") { + const statuses = Object.values(normalized.evidence); + if (!statuses.every((status) => status === "verified")) { + throw new Error(`${fieldName}.recommendation requires matching verified strategy, data, and execution evidence`); + } + } + if (executionReceipt !== null) { + const expectedExecutionStatus = ["failed", "reconciliation_required"].includes(executionReceipt.outcome) + ? "unavailable" + : (["no_signal", "no_rebalance"].includes(executionReceipt.outcome) ? "not_applicable" : "verified"); + if (normalized.evidence.target_execution !== expectedExecutionStatus) { + throw new Error(`${fieldName}.execution_receipt does not match target_execution evidence`); + } + normalized.execution_receipt = executionReceipt; + } + return normalized; +} + +export function normalizeExecutionEvidenceReceipt(value, fieldName) { + const receipt = assertExactFields(value, ["outcome", "broker_confirmation", "observed_at"], fieldName); + const outcome = cleanChoice(receipt.outcome, EXECUTION_RECEIPT_OUTCOMES, `${fieldName}.outcome`); + const confirmation = cleanChoice( + receipt.broker_confirmation, + EXECUTION_RECEIPT_CONFIRMATIONS, + `${fieldName}.broker_confirmation`, + ); + const allowedConfirmations = { + not_due: ["not_applicable"], + no_action: ["not_applicable"], + no_signal: ["not_applicable"], + no_rebalance: ["not_applicable"], + risk_blocked: ["not_applicable"], + submitted: ["not_observed"], + broker_acknowledged: ["acknowledged"], + partially_filled: ["partially_filled"], + filled: ["filled"], + reconciliation_required: ["reconciliation_required"], + failed: ["not_applicable", "not_observed", "reconciliation_required"], + }; + if (!allowedConfirmations[outcome].includes(confirmation)) { + throw new Error(`${fieldName}.broker_confirmation does not match outcome`); + } + return { + outcome, + broker_confirmation: confirmation, + observed_at: normalizeResearchTaskTimestamp(receipt.observed_at, `${fieldName}.observed_at`), + }; +} + +export function normalizeRuntimeTargetLifecycleSourceSnapshot(payload, fieldName = "runtime target lifecycle source snapshot") { + const source = assertExactFields(payload, [ + "schema_version", "source_id", "generated_at", "computed_at", "data_status", "targets", "errors", + ], fieldName); + if (source.schema_version !== RUNTIME_TARGET_LIFECYCLE_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.targets) || source.targets.length > 1000) { + throw new Error(`${fieldName}.targets must be an array with at most 1000 items`); + } + const targets = []; + const seen = new Set(); + for (const [index, item] of source.targets.entries()) { + const target = normalizeRuntimeTargetLifecycleTarget(item, `${fieldName}.targets[${index}]`); + if (seen.has(target.target_id)) throw new Error(`${fieldName}.targets contains duplicate target_id`); + seen.add(target.target_id); + targets.push(target); + } + if (dataStatus === "unavailable" && targets.length) { + throw new Error(`${fieldName}.targets must be empty when unavailable`); + } + return { + schema_version: RUNTIME_TARGET_LIFECYCLE_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, + targets, + errors: normalizeStrategyHealthErrors(source.errors), + }; +} + +export function normalizeRuntimeTargetLifecycleTarget(value, fieldName) { + const item = assertRequiredAndOptionalFields(value, ["target_id", "target", "monitoring", "disposition", "no_order"], ["deployment"], fieldName); + const target = assertExactFields(item.target, ["platform", "configured_state", "execution_mode"], `${fieldName}.target`); + const monitoring = assertExactFields(item.monitoring, ["runtime_guard", "execution_heartbeat"], `${fieldName}.monitoring`); + const disposition = assertExactFields(item.disposition, ["code", "reason_code"], `${fieldName}.disposition`); + if (item.no_order !== true) throw new Error(`${fieldName}.no_order must be true`); + const normalized = { + target_id: normalizeControlPlaneIdentifier(item.target_id, `${fieldName}.target_id`, false), + target: { + platform: cleanChoice(target.platform, EXECUTION_EVIDENCE_PLATFORMS, `${fieldName}.target.platform`), + configured_state: cleanChoice(target.configured_state, RUNTIME_TARGET_LIFECYCLE_CONFIGURED_STATES, `${fieldName}.target.configured_state`), + execution_mode: cleanChoice(target.execution_mode, RUNTIME_TARGET_LIFECYCLE_EXECUTION_MODES, `${fieldName}.target.execution_mode`), + }, + monitoring: { + runtime_guard: cleanChoice(monitoring.runtime_guard, RUNTIME_TARGET_LIFECYCLE_CHECK_STATUSES, `${fieldName}.monitoring.runtime_guard`), + execution_heartbeat: cleanChoice(monitoring.execution_heartbeat, RUNTIME_TARGET_LIFECYCLE_CHECK_STATUSES, `${fieldName}.monitoring.execution_heartbeat`), + }, + disposition: { + code: cleanChoice(disposition.code, RUNTIME_TARGET_LIFECYCLE_DISPOSITIONS, `${fieldName}.disposition.code`), + reason_code: cleanChoice(disposition.reason_code, RUNTIME_TARGET_LIFECYCLE_REASON_CODES, `${fieldName}.disposition.reason_code`), + }, + no_order: true, + }; + if (item.deployment !== undefined) { + const observed = assertRequiredAndOptionalFields(item.deployment, ["runtime_enabled", "scheduler_state", "strategy_profile", "execution_mode"], ["observed_at"], `${fieldName}.deployment`); + if (observed.runtime_enabled !== null && typeof observed.runtime_enabled !== "boolean") throw new Error("deployment switch must be boolean or null"); + normalized.deployment = { + ...(observed.observed_at === undefined ? {} : {observed_at: normalizeM0ResearchTimestamp(observed.observed_at, "deployment.observed_at")}), + runtime_enabled: observed.runtime_enabled, + scheduler_state: cleanChoice(observed.scheduler_state, ["enabled", "paused", "mixed", "missing", "unknown", "not_applicable"], "deployment.scheduler_state"), + strategy_profile: observed.strategy_profile === null ? null : cleanSlug(observed.strategy_profile, "deployment.strategy_profile"), + execution_mode: observed.execution_mode === null ? null : cleanChoice(observed.execution_mode, RUNTIME_TARGET_LIFECYCLE_EXECUTION_MODES, "deployment.execution_mode"), + }; + } + const guardUnavailable = normalized.monitoring.runtime_guard === "unavailable"; + const heartbeatUnavailable = normalized.monitoring.execution_heartbeat === "unavailable"; + const hasAttention = normalized.monitoring.runtime_guard === "attention" || normalized.monitoring.execution_heartbeat === "attention"; + if (normalized.target.configured_state === "disabled") { + if (normalized.monitoring.execution_heartbeat !== "not_applicable") { + throw new Error(`${fieldName}.disabled target must not claim an execution heartbeat`); + } + if (!hasAttention && !guardUnavailable && normalized.disposition.code !== "continue_disabled_validation") { + throw new Error(`${fieldName}.disabled target requires continue_disabled_validation`); + } + } else if (!hasAttention && !guardUnavailable && !heartbeatUnavailable && normalized.disposition.code !== "continue_enabled_monitoring") { + throw new Error(`${fieldName}.enabled healthy target requires continue_enabled_monitoring`); + } + if (hasAttention || guardUnavailable || heartbeatUnavailable) { + if (normalized.disposition.code !== "parked") throw new Error(`${fieldName}.unhealthy monitoring requires parked`); + } + return normalized; +} + +export function normalizeExecutionEvidenceSummary(deployments) { + return { + deployment_count: deployments.length, + autonomous_shadow: deployments.filter((item) => item.recommendation.code === "continue_autonomous_shadow").length, + autonomous_paper: deployments.filter((item) => item.recommendation.code === "run_autonomous_paper").length, + owner_canary_decision: deployments.filter((item) => item.recommendation.code === "owner_limited_live_canary_decision").length, + parked: deployments.filter((item) => item.recommendation.code === "parked").length, + }; +} + +export function emptyExecutionEvidenceSourceSnapshot(errorCode) { + return { + schema_version: EXECUTION_EVIDENCE_SOURCE_SCHEMA_VERSION, + source_id: "unavailable", + generated_at: null, + computed_at: null, + data_status: "unavailable", + deployments: [], + errors: [errorCode], + }; +} + +export function emptyRuntimeTargetLifecycleSourceSnapshot(errorCode) { + return { + schema_version: RUNTIME_TARGET_LIFECYCLE_SOURCE_SCHEMA_VERSION, + source_id: "unavailable", + generated_at: null, + computed_at: null, + data_status: "unavailable", + targets: [], + errors: [errorCode], + }; +} + +export function emptyRuntimeTargetLifecyclePayload(errorCode) { + return { + schema_version: RUNTIME_TARGET_LIFECYCLE_DASHBOARD_SCHEMA_VERSION, + generated_at: null, + computed_at: null, + data_status: "unavailable", + summary: { target_count: 0, enabled: 0, disabled: 0, attention: 0 }, + targets: [], + policy: { + lifecycle_status_read_only: true, + execution_observation_read_only: true, + no_order: true, + order_or_fill_evidence: "not_collected", + notice: "运行目标生命周期快照尚不可用;页面不会推断目标已启用或存在订单/成交。", + }, + errors: [errorCode], + }; +} + +export function emptyExecutionEvidencePayload(errorCode) { + return { + schema_version: EXECUTION_EVIDENCE_DASHBOARD_SCHEMA_VERSION, + generated_at: null, + computed_at: null, + data_status: "unavailable", + summary: { deployment_count: 0, autonomous_shadow: 0, autonomous_paper: 0, owner_canary_decision: 0, parked: 0 }, + deployments: [], + policy: { + execution_evidence_read_only: true, + p6_owner_decision_required: true, + limited_live_canary_active: false, + notice: "执行证据尚不可用;控制台不会推断 paper 或 live 状态。", + }, + errors: [errorCode], + }; +} + +export 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], + }; +} + +export 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: [], + current_reconciliation_observation: currentReconciliationObservationPresentation(), + policy: { + human_confirmation_required: true, + current_evidence_required: true, + no_order: true, + execution_authority_granted: false, + notice: "恢复核验尚不可用;控制台不会推断已恢复或已启用。", + }, + errors: [errorCode], + }; +} + +export 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); +} + +export function executionEvidenceStaleTtlSeconds(env) { + const configured = Number(env.EXECUTION_EVIDENCE_STALE_TTL_SECONDS); + if (!Number.isFinite(configured) || configured < 300 || configured > 604800) { + return EXECUTION_EVIDENCE_DEFAULT_STALE_TTL_SECONDS; + } + return Math.floor(configured); +} + +export function emptyControlPlanePayload(errorCode) { + return { + schema_version: "qsl_control_plane_dashboard.v1", + generated_at: null, + computed_at: null, + data_status: "unavailable", + summary: { candidate_count: 0, deferred: 0, parked: 0, owner_decision_required: 0 }, + attention: { status: "unavailable", reason_codes: ["control_plane_source_unavailable", errorCode] }, + candidates: [], + policy: { p4_p5_automation: "not_configured", p6_owner_decision_required: true, notice: "live 仍需所有者明确决定。" }, + errors: [errorCode], + }; +} + +export function controlPlaneStaleTtlSeconds(env) { + const configured = Number(env.CONTROL_PLANE_STALE_TTL_SECONDS); + if (!Number.isFinite(configured) || configured < 300 || configured > 604800) { + return CONTROL_PLANE_DEFAULT_STALE_TTL_SECONDS; + } + return Math.floor(configured); +} + +export async function syncStrategyProfilesConfig(env, session) { + const profiles = normalizeStrategyProfilesPayload(DEFAULT_STRATEGY_PROFILES, "DEFAULT_STRATEGY_PROFILES"); + if (!hasConfigStore(env)) return { synced: false, reason: "kv_not_bound", count: profiles.length }; + let changed = true; + try { + const current = await readConfigJson(env, STRATEGY_PROFILES_KEY); + if (current) { + const normalizedCurrent = normalizeStrategyProfilesPayload(current, STRATEGY_PROFILES_KEY); + changed = JSON.stringify(normalizedCurrent) !== JSON.stringify(profiles); + } + } catch { + changed = true; + } + let auditLogged = false; + if (changed) { + await writeConfigJson(env, STRATEGY_PROFILES_KEY, profiles); + try { + await appendAuditLog(env, { + ts: new Date().toISOString(), + login: session?.login || "", + action: "sync_strategy_profiles", + count: profiles.length, + }); + auditLogged = true; + } catch { + auditLogged = false; + } + } + return { synced: true, changed, count: profiles.length, audit_logged: auditLogged }; +} + +export function updateAccountOptionsDefaultStrategy(accountOptions, inputs) { + const options = normalizeAccountOptionsPayload(accountOptions || {}, ACCOUNT_OPTIONS_KEY); + const platformOptions = options[inputs.platform] || []; + const rawPlatformOptions = Array.isArray(accountOptions?.[inputs.platform]) ? accountOptions[inputs.platform] : []; + let matched = false; + let changed = false; + const updatedPlatformOptions = platformOptions.map((option, index) => { + if (!accountOptionMatchesInputs(option, inputs)) return option; + matched = true; + const rawOption = rawPlatformOptions[index] && typeof rawPlatformOptions[index] === "object" + ? rawPlatformOptions[index] + : {}; + const nextOption = { ...option }; + let optionChanged = false; + if ("ibit_zscore_exit_mode" in rawOption) optionChanged = true; + if (inputs.plugin_mode === "none") { + const currentPluginMode = nextOption.plugin_mode || "none"; + if (currentPluginMode !== inputs.plugin_mode) { + nextOption.plugin_mode = inputs.plugin_mode; + optionChanged = true; + } + } + if (inputs.option_overlay_mode === "enabled" || inputs.option_overlay_mode === "disabled") { + if (nextOption.option_overlay_mode !== inputs.option_overlay_mode) { + nextOption.option_overlay_mode = inputs.option_overlay_mode; + optionChanged = true; + } + } + const cashOnlyExecutionMode = cashOnlyExecutionModeFromInputs(inputs); + if (cashOnlyExecutionMode === "enabled" || cashOnlyExecutionMode === "disabled") { + if (nextOption.cash_only_execution_mode !== cashOnlyExecutionMode) { + nextOption.cash_only_execution_mode = cashOnlyExecutionMode; + optionChanged = true; + } + } + const dcaControls = dcaControlsFromInputs(inputs); + if (isDcaProfile(inputs.strategy_profile)) { + if (dcaControls.dca_mode && nextOption.dca_mode !== dcaControls.dca_mode) { + nextOption.dca_mode = dcaControls.dca_mode; + optionChanged = true; + } + if ( + dcaControls.dca_base_investment_usd && + nextOption.dca_base_investment_usd !== dcaControls.dca_base_investment_usd + ) { + nextOption.dca_base_investment_usd = dcaControls.dca_base_investment_usd; + optionChanged = true; + } + } else { + for (const field of ["dca_mode", "dca_base_investment_usd"]) { + if (field in nextOption) { + delete nextOption[field]; + optionChanged = true; + } + } + } + if ("ibit_zscore_exit_mode" in nextOption) { + delete nextOption.ibit_zscore_exit_mode; + optionChanged = true; + } + changed = changed || optionChanged; + if (!optionChanged) return option; + return nextOption; + }); + if (!matched) throw new Error("switch inputs do not match configured account options"); + return { + options: { ...options, [inputs.platform]: updatedPlatformOptions }, + changed, + }; +} + +export function normalizeSwitchInputs(raw) { + const platform = cleanChoice(raw.platform, SUPPORTED_PLATFORMS, "platform"); + const targetName = cleanSlug(raw.target_name, "target_name"); + const strategyProfile = cleanSlug(raw.strategy_profile, "strategy_profile").toLowerCase(); + assertDcaPlatform(platform, strategyProfile); + const executionMode = cleanExecutionMode(raw.execution_mode || "live"); + if (!supportedExecutionModesForPlatform(platform).includes(executionMode)) { + throw new Error(`${platform} does not support ${executionMode} control execution`); + } + const liveContinuity = normalizeLiveContinuityInputs(raw, executionMode); + // "current" is used only by internal deployment reconciliation to retain + // a service's existing plugin mount. It is deliberately not exposed as a + // console editing mode, where operators can still select only "none". + const requestedPluginMode = cleanChoice(raw.plugin_mode || "none", ["auto", "none", "current"], "plugin_mode"); + const pluginMode = requestedPluginMode === "auto" ? "none" : requestedPluginMode; + if (String(raw.custom_plugin_mounts_json || "").trim()) { + throw new Error("legacy custom plugin mounts are retired pending a P1/P2/P3-bound signal.v2 adapter"); + } + const optionOverlayMode = cleanChoice(raw.option_overlay_mode || "current", OPTION_OVERLAY_MODES, "option_overlay_mode"); + if (optionOverlayMode === "enabled") { + throw new Error("option overlay is retired; migrate existing option settings and obligations explicitly"); + } + const cashOnlyExecutionMode = cleanChoice( + raw.cash_only_execution_mode || "enabled", + CASH_ONLY_EXECUTION_MODES, + "cash_only_execution_mode", + ); + const variableScope = cleanChoice( + raw.variable_scope || "default", + ["default", "repository", "environment"], + "variable_scope", + ); + const apply = cleanBoolean(raw.apply); + const triggerPlatformSync = cleanBoolean(raw.trigger_platform_sync) && apply; + const extraVariablesJson = cleanOptionalJsonObject(raw.extra_variables_json || "", "extra_variables_json"); + const extraVariables = extraVariablesJson ? JSON.parse(extraVariablesJson) : {}; + const directDcaVariables = [DCA_MODE_VARIABLE, DCA_BASE_INVESTMENT_VARIABLE].filter((name) => + extraVariables[name] !== undefined && String(extraVariables[name] || "").trim() !== "", + ); + if (directDcaVariables.length) { + throw new Error("use dca_mode and dca_base_investment_usd control fields instead of DCA_MODE variables"); + } + const directIbitZscoreVariables = [ + IBIT_ZSCORE_EXIT_ENABLED_VARIABLE, + IBIT_ZSCORE_EXIT_MODE_VARIABLE, + IBIT_ZSCORE_EXIT_PARKING_SYMBOL_VARIABLE, + "IBIT_ZSCORE_EXIT_RISK_REDUCED_EXPOSURE", + "IBIT_ZSCORE_EXIT_RISK_OFF_EXPOSURE", + "IBIT_ZSCORE_EXIT_ALLOW_OUTSIDE_EXECUTION_WINDOW", + ].filter((name) => extraVariables[name] !== undefined && String(extraVariables[name] || "").trim() !== ""); + if (directIbitZscoreVariables.length) { + throw new Error( + "IBIT_ZSCORE_EXIT variables are derived from ibit_smart_dca smart DCA mode; do not set them directly", + ); + } + rejectResearchOnlyExtraVariables(extraVariables); + const directCashOnlyVariables = [ + LEGACY_CASH_ONLY_EXECUTION_VARIABLE, + ...Object.values(PLATFORM_CASH_ONLY_EXECUTION_VARIABLES), + ].filter((name) => extraVariables[name] !== undefined && String(extraVariables[name] || "").trim() !== ""); + if (directCashOnlyVariables.length) { + throw new Error("use cash_only_execution_mode instead of CASH_ONLY_EXECUTION variables"); + } + const dcaExtraControls = dcaPayloadFromObject(extraVariables); + stripLegacyIbitZscoreExitControls(extraVariables); + + const inputs = { + platform, + target_name: targetName, + strategy_profile: strategyProfile, + execution_mode: executionMode, + live_continuity_state: liveContinuity.state, + variable_scope: variableScope, + plugin_mode: pluginMode, + option_overlay_mode: optionOverlayMode, + service_targets_mode: "auto", + apply: apply ? "true" : "false", + trigger_platform_sync: triggerPlatformSync ? "true" : "false", + confirm_apply: apply ? (triggerPlatformSync ? "APPLY_AND_SYNC" : "APPLY") : "", + platform_sync_workflow: "sync-cloud-run-env.yml", + }; + + if (liveContinuity.state !== "NONE") { + inputs.live_continuity_baseline_id = liveContinuity.baseline_id; + inputs.live_continuity_captured_at = liveContinuity.captured_at; + } + + addOptional(inputs, "github_environment", raw.github_environment, cleanSlug); + addOptional(inputs, "deployment_selector", raw.deployment_selector, cleanSlug); + addOptional(inputs, "account_selector", raw.account_selector, cleanCsv); + addOptional(inputs, "account_scope", raw.account_scope, cleanSlug); + addOptional(inputs, "service_name", raw.service_name, cleanSlug); + addOptional(inputs, "reserved_cash_ratio", raw.reserved_cash_ratio, cleanRatio); + addOptional(inputs, "min_reserved_cash_usd", raw.min_reserved_cash_usd, cleanNonNegativeNumber); + addOptional(inputs, "income_layer_start_usd", raw.income_layer_start_usd, cleanNonNegativeNumber); + addOptional(inputs, "income_layer_max_ratio", raw.income_layer_max_ratio, cleanRatio); + const rawHasDcaMode = raw.dca_mode !== undefined && raw.dca_mode !== null && String(raw.dca_mode).trim() !== ""; + const rawHasDcaBase = raw.dca_base_investment_usd !== undefined && + raw.dca_base_investment_usd !== null && + String(raw.dca_base_investment_usd).trim() !== ""; + const dcaModeValue = rawHasDcaMode ? raw.dca_mode : dcaExtraControls.dca_mode; + const dcaBaseInvestmentValue = rawHasDcaBase + ? raw.dca_base_investment_usd + : dcaExtraControls.dca_base_investment_usd; + const hasDcaMode = Boolean(String(dcaModeValue || "").trim()); + const hasDcaBase = Boolean(String(dcaBaseInvestmentValue || "").trim()); + if (!isDcaProfile(strategyProfile) && (hasDcaMode || hasDcaBase)) { + throw new Error("DCA settings are only supported for DCA strategy profiles"); + } + if (isDcaProfile(strategyProfile)) { + if (hasDcaMode) extraVariables.dca_mode = cleanDcaMode(dcaModeValue); + if (hasDcaBase) extraVariables.dca_base_investment_usd = cleanPositiveNumber( + dcaBaseInvestmentValue, + "dca_base_investment_usd", + ); + } + const cashOnlyMode = cleanChoice( + raw.cash_only_execution_mode || extraVariables.cash_only_execution_mode || "enabled", + CASH_ONLY_EXECUTION_MODES, + "cash_only_execution_mode", + ); + if (cashOnlyMode !== "current") { + extraVariables.cash_only_execution_mode = cashOnlyMode; + } + if (Object.keys(extraVariables).length) inputs.extra_variables_json = JSON.stringify(extraVariables); + return inputs; +} + +export function normalizeLiveContinuityInputs(raw, executionMode) { + const state = String(raw.live_continuity_state || "NONE").trim().toUpperCase(); + if (!LIVE_CONTINUITY_STATES.includes(state)) { + throw new Error(`live_continuity_state must be one of ${LIVE_CONTINUITY_STATES.join(", ")}`); + } + const rawBaselineId = String(raw.live_continuity_baseline_id || "").trim(); + const rawCapturedAt = String(raw.live_continuity_captured_at || "").trim(); + if (state === "NONE") { + if (rawBaselineId || rawCapturedAt) { + throw new Error("live continuity baseline fields require a non-NONE live_continuity_state"); + } + return { state }; + } + if (executionMode !== "live") { + throw new Error("live continuity is only valid for live execution_mode"); + } + return { + state, + baseline_id: cleanSlug(rawBaselineId, "live_continuity_baseline_id"), + captured_at: normalizeM0ResearchDate(rawCapturedAt, "live_continuity_captured_at"), + }; +} + +export function assertSwitchIntent(inputs) { + if ( + inputs.apply !== "true" || + inputs.trigger_platform_sync !== "true" || + inputs.confirm_apply !== "APPLY_AND_SYNC" + ) { + throw new Error("switch endpoint requires apply=true and APPLY_AND_SYNC"); + } +} + +export function assertConfiguredAccount(inputs, accountOptions) { + if (!accountOptions) throw new Error("account options are not configured"); + const matched = configuredAccountForInputs(inputs, accountOptions); + if (!matched) throw new Error("switch inputs do not match configured account options"); + return matched; +} + +export function configuredAccountForInputs(inputs, accountOptions) { + if (!accountOptions) return null; + const options = accountOptions[inputs.platform] || []; + return options.find((option) => accountOptionMatchesInputs(option, inputs)) || null; +} + +export function registerLegacyContinuityAccount(env, accountOptions, inputs, strategy) { + if (!hasConfigStore(env)) { + throw new Error("switch inputs do not match configured account options"); + } + if (!isEligibleLegacyContinuityInput(inputs, strategy)) { + throw new Error("switch inputs do not match configured account options"); + } + const platformOptions = accountOptions[inputs.platform] || []; + if (platformOptions.some((option) => option.target_name === inputs.target_name)) { + throw new Error("legacy continuity account target conflicts with configured account options"); + } + if (platformOptions.some((option) => option.service_name + && option.service_name === inputs.service_name + && accountOptionMatchesInputs(option, { ...inputs, target_name: option.target_name }))) { + throw new HttpError("deployment already has a configured account; use its existing target", 400); + } + if (platformOptions.length >= 20) { + throw new Error(`account options for ${inputs.platform} have reached the maximum`); + } + + const account = cleanAccountOption( + { + key: inputs.target_name, + label: `Legacy continuity ${inputs.target_name}`, + target_name: inputs.target_name, + account_selector: inputs.account_selector, + deployment_selector: inputs.deployment_selector, + account_scope: inputs.account_scope, + service_name: inputs.service_name, + github_environment: inputs.github_environment, + variable_scope: resolvedVariableScope(inputs.variable_scope, inputs), + supported_domains: [strategy.domain], + }, + inputs.platform, + platformOptions.length, + ); + const options = normalizeAccountOptionsPayload( + { ...accountOptions, [inputs.platform]: [...platformOptions, account] }, + ACCOUNT_OPTIONS_KEY, + ); + return { options, account: options[inputs.platform].at(-1), registered: true }; +} + +export function assertStrategyAllowedForAccount(inputs, accountOption, strategyProfiles) { + const strategy = strategyProfiles.find((item) => sameStrategyProfile(item.profile, inputs.strategy_profile)); + if (!strategy) { + throw new Error(`strategy ${inputs.strategy_profile} is not configured`); + } + const supportedDomains = supportedDomainsForAccount(inputs.platform, accountOption); + if (!supportedDomains.includes(strategy.domain)) { + throw new Error( + `strategy domain ${strategy.domain} is not supported by ${inputs.platform}/${accountOption.key}`, + ); + } + const executionMode = cleanExecutionMode(inputs.execution_mode); + if (!supportedExecutionModesForPlatform(inputs.platform).includes(executionMode)) { + throw new Error(`${inputs.platform} does not support ${executionMode} control execution`); + } + const allowedModes = strategy.allowed_execution_modes || []; + if (isFrozenStrategyProfile(strategy) && !( + ["RECONCILE_ONLY", "RISK_REDUCTION_ONLY"].includes(inputs.live_continuity_state) + && isEligibleLegacyContinuityInput(inputs, strategy) + )) { + throw new HttpError(`strategy ${inputs.strategy_profile} is frozen for new selection`, 409); + } + if (executionMode === "live") { + if (isEligibleLegacyContinuityInput(inputs, strategy)) { + assertDcaPlatform(inputs.platform, inputs.strategy_profile); + return; + } + assertLiveSwitchAllowed(strategy, inputs.strategy_profile); + const lifecycleStage = cleanLifecycleStage(strategy.lifecycle_stage || "research_active"); + if (!["live_enabled", "runtime_enabled"].includes(lifecycleStage)) { + throw new Error(`strategy ${inputs.strategy_profile} is not live-enabled`); + } + if (!allowedModes.includes(executionMode)) { + throw new Error(`strategy ${inputs.strategy_profile} is not live-enabled`); + } + if (strategy.blocked_live_reason) { + throw new Error(`strategy ${inputs.strategy_profile} is blocked for live: ${strategy.blocked_live_reason}`); + } + } else if (allowedModes.length && !allowedModes.includes(executionMode)) { + throw new Error(`strategy ${inputs.strategy_profile} does not allow ${executionMode} execution`); + } + if (inputs.option_overlay_mode === "enabled" && strategy.option_overlay_enabled !== true) { + throw new Error(`strategy ${inputs.strategy_profile} does not define an option overlay`); + } + assertDcaPlatform(inputs.platform, inputs.strategy_profile); +} + +export function isEligibleLegacyContinuityInput(inputs, strategy) { + if ( + inputs.execution_mode !== "live" || + !inputs.live_continuity_state || + inputs.live_continuity_state === "NONE" || + !inputs.live_continuity_baseline_id || + !inputs.live_continuity_captured_at || + inputs.plugin_mode !== "current" || + inputs.option_overlay_mode !== "current" || + inputs.reserved_cash_ratio || + inputs.min_reserved_cash_usd || + inputs.income_layer_start_usd || + inputs.income_layer_max_ratio + ) { + return false; + } + const extraVariables = inputs.extra_variables_json ? JSON.parse(inputs.extra_variables_json) : {}; + const extraVariableNames = Object.keys(extraVariables); + if ( + extraVariableNames.length > 1 || + (extraVariableNames.length === 1 && ( + extraVariableNames[0] !== "RUNTIME_TARGET_ENABLED" || + extraVariables.RUNTIME_TARGET_ENABLED !== "true" + )) + ) { + return false; + } + const policy = strategy?.live_continuity; + return policy?.eligible === true && Array.isArray(policy.allowed_platforms) && policy.allowed_platforms.includes(inputs.platform); +} + +export function resolvedVariableScope(value, inputs) { + const text = String(value || "").trim(); + if (!text || text === "default") return defaultInputValue("variable_scope", inputs); + return text; +} + +export function accountOptionMatchesInputs(option, inputs) { + if (option.target_name !== inputs.target_name) return false; + const fields = [ + "account_selector", + "deployment_selector", + "account_scope", + "service_name", + "github_environment", + "variable_scope", + ]; + for (const field of fields) { + if (field === "variable_scope") { + if (resolvedVariableScope(option[field], inputs) !== resolvedVariableScope(inputs[field], inputs)) { + return false; + } + continue; + } + const expected = option[field] || ""; + const actual = inputs[field] || ""; + if (expected && actual !== expected) return false; + if (!expected && actual && !["default", "auto", defaultInputValue(field, inputs)].includes(actual)) return false; + } + return true; +} + +export function defaultInputValue(field, inputs) { + const platform = inputs.platform; + const targetName = inputs.target_name; + if (field === "variable_scope") return DEFAULT_VARIABLE_SCOPE[platform] || "repository"; + if (field === "plugin_mode") return "none"; + if (field === "deployment_selector") { + if (platform === "firstrade") return "firstrade"; + if (platform === "qmt") return "qmt"; + return ["sg", "hk", "paper"].includes(targetName.toLowerCase()) ? targetName.toUpperCase() : targetName; + } + if (field === "account_scope") { + if (platform === "firstrade") return "US"; + if (platform === "qmt") return "CN"; + return inputs.deployment_selector || defaultInputValue("deployment_selector", inputs); + } + if (field === "account_selector") { + if (platform === "firstrade") return "firstrade"; + if (platform === "qmt") return "qmt"; + return inputs.account_scope || defaultInputValue("account_scope", inputs); + } + if (field === "github_environment") { + const variableScope = inputs.variable_scope === "default" + ? defaultInputValue("variable_scope", inputs) + : inputs.variable_scope; + if (variableScope !== "environment") return ""; + return platform === "longbridge" ? `longbridge-${targetName.toLowerCase()}` : targetName; + } + if (field === "service_name") { + if (platform === "schwab") return "charles-schwab-quant-service"; + if (platform === "firstrade") return "firstrade-quant-service"; + if (platform === "qmt") return "qmt-quant-service"; + if (platform === "longbridge") return `longbridge-quant-${targetName.toLowerCase()}-service`; + if (platform === "ibkr") return `interactive-brokers-${targetName.toLowerCase()}-service`; + } + return ""; +} + +export function parseAccountOptions(raw, fieldName = "account options") { + const text = String(raw || "").trim(); + if (!text) return null; + let payload; + try { + payload = JSON.parse(text); + } catch (error) { + throw new Error(`${fieldName} must be valid JSON`); + } + return normalizeAccountOptionsPayload(payload, fieldName); +} + +export function normalizeAccountOptionsInput(value, fieldName) { + if (typeof value === "string") return parseAccountOptions(value, fieldName) || {}; + return normalizeAccountOptionsPayload(value, fieldName); +} + +export function parseStrategyProfiles(raw, fieldName = "strategy profiles") { + const text = String(raw || "").trim(); + if (!text) return null; + let payload; + try { + payload = JSON.parse(text); + } catch (error) { + throw new Error(`${fieldName} must be valid JSON`); + } + return normalizeStrategyProfilesPayload(payload, fieldName); +} + +export function isFrozenStrategyProfile(strategy) { + const profile = canonicalStrategyProfile(strategy?.profile); + // Normalize only the policy lookup; retain the original profile in payloads. + return strategy?.frozen === true || DEFAULT_STRATEGY_PROFILES.some((item) => item.profile === profile && item.frozen === true); +} + +export function normalizeStrategyProfilesPayload(payload, fieldName = "strategy profiles") { + if (!Array.isArray(payload) || payload.length > 100) { + throw new Error(`${fieldName} must be an array with at most 100 items`); + } + + const result = []; + const seen = new Set(); + for (const [index, item] of payload.entries()) { + if (!item || Array.isArray(item) || typeof item !== "object") { + throw new Error(`${fieldName}[${index}] must be an object`); + } + const profile = cleanCurrentStrategy(item.profile || item.strategy_profile); + if (!profile) throw new Error(`${fieldName}[${index}].profile is invalid`); + if (seen.has(profile)) continue; + seen.add(profile); + const entry = { + profile, + label: cleanLabel(item.label || item.display_name || profile, `${fieldName}[${index}].label`), + runtime_enabled: cleanProfileBoolean(item.runtime_enabled ?? false), + }; + addConfigOptional( + entry, + "label_en", + item.label_en || item.display_name_en || item.label, + cleanLabel, + ); + addConfigOptional( + entry, + "label_zh", + item.label_zh || item.display_name_zh, + cleanLabel, + ); + const rawDomain = typeof item.domain === "string" ? item.domain.trim() : ""; + entry.domain = cleanStrategyDomain(rawDomain || "us_equity", `${fieldName}[${index}].domain`); + Object.defineProperty(entry, "domain_explicit", { value: rawDomain !== "", enumerable: false }); + if (isFrozenStrategyProfile(item)) entry.frozen = true; + const sourceLifecycleStage = item.lifecycle_stage || item.lifecycleStage; + const canSwitchLive = cleanOptionalBoolean(item.can_switch_live); + if (canSwitchLive !== null) entry.can_switch_live = canSwitchLive; + const allowedExecutionModes = cleanAllowedExecutionModes(item.allowed_execution_modes); + if (allowedExecutionModes.length) entry.allowed_execution_modes = allowedExecutionModes; + if (sourceLifecycleStage) { + entry.lifecycle_stage = canonicalLifecycleStage(sourceLifecycleStage, { + runtimeEnabled: entry.runtime_enabled === true, + canSwitchLive: canSwitchLive === true, + allowedExecutionModes, + }); + } + addConfigOptional(entry, "blocked_live_reason", item.blocked_live_reason, cleanLabel); + if (item.live_continuity !== undefined && item.live_continuity !== null) { + entry.live_continuity = normalizeLiveContinuityPolicy( + item.live_continuity, + `${fieldName}[${index}].live_continuity`, + ); + } + if (item.research_candidate_identity !== undefined && item.research_candidate_identity !== null) { + entry.research_candidate_identity = normalizeResearchCandidateIdentity( + item.research_candidate_identity, + `${fieldName}[${index}].research_candidate_identity`, + profile, + ); + } + addConfigOptional(entry, "latest_evidence_status", item.latest_evidence_status, cleanLifecycleStage); + addConfigOptional(entry, "plugin_gate_status", item.plugin_gate_status, cleanLifecycleStage); + // DCA detection: accept from item payload OR hardcoded DCA_PROFILE_CONFIG + const dcaEnabled = item.dca_enabled === true || Boolean(DCA_PROFILE_CONFIG[profile]); + if (dcaEnabled) { + const dcaDefaults = DCA_PROFILE_CONFIG[profile] || null; + entry.dca_enabled = true; + entry.dca_default_mode = cleanDcaMode(item.dca_default_mode || item.default_dca_mode || dcaDefaults?.default_mode || "fixed"); + entry.dca_default_base_investment_usd = cleanPositiveNumber( + item.dca_default_base_investment_usd || + item.default_dca_base_investment_usd || + dcaDefaults?.default_base_investment_usd || + "1000", + `${fieldName}[${index}].dca_default_base_investment_usd`, + ); + } + // Pass through combo_enabled and combo_mode from item payload + if (item.combo_enabled === true) { + entry.combo_enabled = true; + entry.combo_mode = String(item.combo_mode || "dynamic").trim() || "dynamic"; + } + const incomeLayerConfig = incomeLayerConfigFromProfileItem(item, `${fieldName}[${index}]`); + if (incomeLayerConfig) Object.assign(entry, incomeLayerConfig); + const optionOverlayConfig = optionOverlayConfigFromProfileItem(item, `${fieldName}[${index}]`); + if (optionOverlayConfig) Object.assign(entry, optionOverlayConfig); + result.push(entry); + } + return result; +} + +export function normalizeResearchCandidateIdentity(value, fieldName, profile) { + if (!value || Array.isArray(value) || typeof value !== "object") { + throw new Error(`${fieldName} must be an object`); + } + assertExactFields(value, ["candidate_id", "config_sha256"], fieldName); + const candidateId = cleanCurrentStrategy(value.candidate_id); + if (!candidateId || candidateId !== profile) { + throw new Error(`${fieldName}.candidate_id must match profile`); + } + const configSha256 = String(value.config_sha256 || "").trim().toLowerCase(); + if (!/^[0-9a-f]{64}$/.test(configSha256)) { + throw new Error(`${fieldName}.config_sha256 must be a 64-character sha256`); + } + return { candidate_id: candidateId, config_sha256: configSha256 }; +} + +export function normalizeLiveContinuityPolicy(value, fieldName) { + if (!value || Array.isArray(value) || typeof value !== "object") { + throw new Error(`${fieldName} must be an object`); + } + const unsupported = Object.keys(value).filter((key) => !["eligible", "allowed_platforms"].includes(key)); + if (unsupported.length) { + throw new Error(`${fieldName} contains unsupported fields: ${unsupported.sort().join(", ")}`); + } + if (typeof value.eligible !== "boolean") { + throw new Error(`${fieldName}.eligible must be boolean`); + } + if (!Array.isArray(value.allowed_platforms)) { + throw new Error(`${fieldName}.allowed_platforms must be an array`); + } + const allowedPlatforms = value.allowed_platforms.map((platform) => + cleanChoice(platform, SUPPORTED_PLATFORMS, `${fieldName}.allowed_platforms`), + ); + if (new Set(allowedPlatforms).size !== allowedPlatforms.length) { + throw new Error(`${fieldName}.allowed_platforms must not contain duplicates`); + } + if (value.eligible && !allowedPlatforms.length) { + throw new Error(`${fieldName}.eligible requires allowed_platforms`); + } + return { eligible: value.eligible, allowed_platforms: allowedPlatforms }; +} + +export function rejectResearchOnlyExtraVariables(extraVariables) { + const blocked = [ + ...LEGACY_INCOME_LAYER_CONTROL_FIELDS, + ...LEGACY_INCOME_LAYER_VARIABLES, + ...OPTION_OVERLAY_CONTROL_FIELDS, + ...OPTION_OVERLAY_VARIABLES, + ].filter((name) => extraVariables[name] !== undefined); + if (blocked.length) { + throw new Error( + `direct option overlay settings and legacy income controls are research-only: ${blocked.join(", ")}`, + ); + } +} + +export function incomeLayerConfigFromProfileItem(item, fieldName) { + const hasIncomeLayerConfig = [ + "income_layer_enabled", + "income_layer_start_usd", + "income_layer_max_ratio", + "income_layer_allocations", + ].some((field) => item[field] !== undefined && item[field] !== null && String(item[field]).trim() !== ""); + if (!hasIncomeLayerConfig) return null; + const enabled = item.income_layer_enabled === undefined || item.income_layer_enabled === null + ? true + : cleanProfileBoolean(item.income_layer_enabled); + if (!enabled) return { income_layer_enabled: false }; + return { + income_layer_enabled: true, + income_layer_start_usd: cleanNonNegativeNumber( + item.income_layer_start_usd, + `${fieldName}.income_layer_start_usd`, + ), + income_layer_max_ratio: cleanRatio(item.income_layer_max_ratio, `${fieldName}.income_layer_max_ratio`), + income_layer_allocations: cleanIncomeLayerAllocations( + item.income_layer_allocations, + `${fieldName}.income_layer_allocations`, + ), + }; +} + +export function optionOverlayConfigFromProfileItem(item, fieldName) { + const hasOptionOverlayConfig = OPTION_OVERLAY_PROFILE_FIELDS.some((field) => + item[field] !== undefined && item[field] !== null && String(item[field]).trim() !== "", + ); + if (!hasOptionOverlayConfig) return null; + const enabled = item.option_overlay_enabled === undefined || item.option_overlay_enabled === null + ? true + : cleanProfileBoolean(item.option_overlay_enabled); + const result = { option_overlay_enabled: enabled }; + addConfigOptional(result, "option_overlay_live_gate", item.option_overlay_live_gate || (enabled ? "promotion_required" : "disabled"), (value, field) => + cleanChoice(value, ["promotion_required", "live_allowed", "disabled"], field), + ); + addConfigOptional(result, "option_overlay_live_status", item.option_overlay_live_status || (enabled ? "research_only" : "disabled"), (value, field) => + cleanChoice(value, ["research_only", "live_allowed", "disabled"], field), + ); + if (!enabled) return result; + + addOptionalOptionFamilyConfig(result, item, "growth", fieldName); + addOptionalOptionFamilyConfig(result, item, "income", fieldName); + return result; +} + +export function addOptionalOptionFamilyConfig(target, item, family, fieldName) { + const prefix = `option_${family}_overlay`; + const enabledField = `${prefix}_enabled`; + if (item[enabledField] === undefined || item[enabledField] === null || String(item[enabledField]).trim() === "") { + return; + } + const enabled = cleanProfileBoolean(item[enabledField]); + target[enabledField] = enabled; + if (!enabled) return; + + target[`${prefix}_recipe`] = cleanSlug(item[`${prefix}_recipe`], `${fieldName}.${prefix}_recipe`); + target[`${prefix}_start_usd`] = cleanNonNegativeNumber( + item[`${prefix}_start_usd`], + `${fieldName}.${prefix}_start_usd`, + ); + if (family === "growth") { + target.option_growth_overlay_nav_budget_ratio = cleanRatio( + item.option_growth_overlay_nav_budget_ratio, + `${fieldName}.option_growth_overlay_nav_budget_ratio`, + ); + } else { + target.option_income_overlay_nav_risk_ratio = cleanRatio( + item.option_income_overlay_nav_risk_ratio, + `${fieldName}.option_income_overlay_nav_risk_ratio`, + ); + } +} + +export function cleanIncomeLayerAllocations(value, fieldName) { + if (!value || Array.isArray(value) || typeof value !== "object") { + throw new Error(`${fieldName} must be an object`); + } + const result = {}; + let total = 0; + for (const [rawSymbol, rawWeight] of Object.entries(value)) { + const symbol = String(rawSymbol || "").trim().toUpperCase(); + if (!/^[A-Z0-9.-]{1,12}$/.test(symbol)) throw new Error(`${fieldName} contains an invalid symbol`); + const weight = Number(cleanPositiveNumber(rawWeight, `${fieldName}.${symbol}`)); + total += weight; + result[symbol] = weight; + } + if (!Object.keys(result).length || total <= 0) throw new Error(`${fieldName} must contain positive allocations`); + return result; +} + +export function cleanProfileBoolean(value) { + if (value === true || value === "true" || value === "1" || value === 1) return true; + if (value === false || value === "false" || value === "0" || value === 0) return false; + throw new Error("runtime_enabled must be boolean"); +} + +export function normalizeAccountOptionsPayload(payload, fieldName = "account options") { + if (!payload || Array.isArray(payload) || typeof payload !== "object") { + throw new Error(`${fieldName} must be an object`); + } + + // Structural gate from account_options_schema.js; cleanAccountOption keeps + // console-specific enrichment (domain inference, mode defaults, length caps). + validateAccountOptionsSchemaOrThrow(payload, fieldName); + + const result = {}; + for (const platform of SUPPORTED_PLATFORMS) { + const items = payload[platform]; + if (items === undefined) continue; + if (!Array.isArray(items) || items.length > 20) { + throw new Error(`${fieldName}.${platform} must be an array with at most 20 items`); + } + result[platform] = items.map((item, index) => cleanAccountOption(item, platform, index)); + } + return result; +} + +export function cleanAccountOption(item, platform, index) { + if (!item || Array.isArray(item) || typeof item !== "object") { + throw new Error(`account option ${platform}[${index}] must be an object`); + } + const key = cleanSlug(item.key || item.target_name || `${platform}-${index}`, "account key"); + const label = cleanLabel(item.label || item.target_name || key, "account label"); + const option = { + key, + label, + target_name: cleanSlug(item.target_name || key, "target_name"), + }; + addConfigOptional(option, "account_selector", item.account_selector, cleanCsv); + addConfigOptional(option, "deployment_selector", item.deployment_selector, cleanSlug); + addConfigOptional(option, "account_scope", item.account_scope, cleanSlug); + addConfigOptional(option, "service_name", item.service_name, cleanSlug); + addConfigOptional(option, "runtime_status_target_id", item.runtime_status_target_id, cleanSlug); + addConfigOptional(option, "broker_environment", item.broker_environment, normalizeBrokerEnvironment); + addConfigOptional(option, "default_execution_mode", item.default_execution_mode, cleanExecutionMode); + addConfigOptional(option, "default_strategy_profile", item.default_strategy_profile, cleanSlug); + addConfigOptional( + option, + "cash_currency", + item.cash_currency || item.market_currency || item.trading_currency, + cleanCashCurrency, + ); + addConfigOptional(option, "github_environment", item.github_environment, cleanSlug); + addConfigOptional(option, "variable_scope", item.variable_scope, (value, field) => + cleanChoice(value || "default", ["default", "repository", "environment"], field), + ); + addConfigOptional(option, "plugin_mode", item.plugin_mode, (value, field) => + cleanChoice(value || "auto", ["auto", "none"], field), + ); + addConfigOptional(option, "option_overlay_mode", item.option_overlay_mode, (value, field) => + cleanChoice(value || "enabled", OPTION_OVERLAY_MODES, field), + ); + addConfigOptional(option, "cash_only_execution_mode", item.cash_only_execution_mode, (value, field) => + cleanChoice(value || "enabled", CASH_ONLY_EXECUTION_MODES, field), + ); + addConfigOptional(option, "dca_mode", item.dca_mode, cleanDcaMode); + addConfigOptional(option, "dca_base_investment_usd", item.dca_base_investment_usd, cleanPositiveNumber); + option.supported_domains = shouldInferSupportedDomains(item.supported_domains) + ? inferAccountSupportedDomains(platform, option) + : normalizeSupportedDomains(item.supported_domains, `account option ${platform}[${index}].supported_domains`); + return option; +} + +export function shouldInferSupportedDomains(value) { + if (value === undefined || value === null) return true; + if (Array.isArray(value)) return value.length === 0; + return String(value).trim() === ""; +} + +export function supportedDomainsForAccount(platform, option) { + const platformDomains = inferAccountSupportedDomains(platform, option || {}); + if (Array.isArray(option?.supported_domains) && option.supported_domains.length) { + return normalizeSupportedDomains(option.supported_domains, "supported_domains") + .filter((domain) => platformDomains.includes(domain)); + } + return platformDomains; +} + +export function inferAccountSupportedDomains(platform, option) { + void option; + const domains = PLATFORM_CONFIG[platform]?.supported_domains; + return Array.isArray(domains) ? [...domains] : []; +} + +export function platformRepositories(env) { + return resolvePlatformRepositories(env); +} + +export function normalizeSupportedDomains(value, fieldName) { + const items = Array.isArray(value) + ? value + : String(value || "").split(/[\s,;]+/); + if (!items.length || items.length > SUPPORTED_STRATEGY_DOMAINS.length) { + throw new Error(`${fieldName} must list one or more strategy domains`); + } + const result = []; + for (const item of items) { + const domain = cleanStrategyDomain(item, fieldName); + if (!result.includes(domain)) result.push(domain); + } + if (!result.length) throw new Error(`${fieldName} must list one or more strategy domains`); + return result; +} + +export function cleanStrategyDomain(value, fieldName) { + return cleanChoice(value, SUPPORTED_STRATEGY_DOMAINS, fieldName); +} + +export function cleanCashCurrency(value, fieldName) { + return cleanChoice(String(value || "").trim().toUpperCase(), ["USD", "HKD", "CNY"], fieldName); +} + +export function addConfigOptional(target, key, value, cleaner) { + if (value === undefined || value === null || String(value).trim() === "") return; + target[key] = cleaner(value, key); +} + +export function addOptional(target, key, value, cleaner) { + if (value === undefined || value === null || String(value).trim() === "") return; + target[key] = cleaner(value, key); +} + +export function cleanChoice(value, allowed, field) { + const text = String(value || "").trim(); + if (!allowed.includes(text)) throw new Error(`${field} is invalid`); + return text; +} + +export function isDcaProfile(profile) { + return Boolean(DCA_PROFILE_CONFIG[canonicalStrategyProfile(profile)]); +} + +export function assertDcaPlatform(platform, strategyProfile) { + if (isDcaProfile(strategyProfile) && !DCA_SUPPORTED_PLATFORMS.has(platform)) { + throw new Error( + `DCA strategy profiles are not supported on ${platform}; got strategy_profile=${strategyProfile}`, + ); + } +} + +export function cleanDcaMode(value, field = "dca_mode") { + const mode = String(value || "").trim().toLowerCase(); + const aliases = { + ordinary: "fixed", + ordinary_dca: "fixed", + fixed_dca: "fixed", + smart_dca: "smart", + }; + const normalized = aliases[mode] || mode; + return cleanChoice(normalized, ["fixed", "smart"], field); +} + +export function cleanBoolean(value) { + if (value === true || value === "true") return true; + if (value === false || value === "false" || value === "" || value === undefined || value === null) return false; + throw new Error("boolean input is invalid"); +} + +export function cleanRatio(value, field) { + const text = cleanNumberText(value, field); + const numeric = Number(text); + if (numeric < 0 || numeric > 1) throw new Error(`${field} must be between 0 and 1`); + return text; +} + +export function cleanNonNegativeNumber(value, field) { + const text = cleanNumberText(value, field); + if (Number(text) < 0) throw new Error(`${field} must be non-negative`); + return text; +} + +export function cleanPositiveNumber(value, field) { + const text = cleanNumberText(value, field); + if (Number(text) <= 0) throw new Error(`${field} must be greater than 0`); + return text; +} + +export function cleanNumberText(value, field) { + const text = String(value || "").trim(); + if (!text || text.length > 32 || !/^(?:\d+|\d*\.\d+)$/.test(text)) { + throw new Error(`${field} must be a finite decimal number`); + } + const numeric = Number(text); + if (!Number.isFinite(numeric)) throw new Error(`${field} must be finite`); + return text; +} + +export function cleanSlug(value, field) { + const text = String(value || "").trim(); + if (!text || text.length > 120 || !/^[A-Za-z0-9._=-]+$/.test(text)) { + throw new Error(`${field} must use letters, numbers, dot, underscore, dash, or equals`); + } + return text; +} + +export function cleanRepositoryName(value, field) { + const text = String(value || "").trim(); + if (!text || text.length > 160 || !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(text)) { + throw new Error(`${field} must be owner/repo`); + } + return text; +} + +export function cleanLabel(value, field) { + const text = String(value || "").trim(); + if (!text || text.length > 80 || /[<>{}]/.test(text)) { + throw new Error(`${field} is invalid`); + } + return text; +} + +export function cleanLifecycleStage(value, field = "lifecycle_stage") { + const text = String(value || "").trim().toLowerCase(); + if (!text || text.length > 80 || !/^[a-z0-9._-]+$/.test(text)) { + throw new Error(`${field} is invalid`); + } + return text; +} + +export function canonicalLifecycleStage(value, deployment = {}) { + const stage = cleanLifecycleStage(value); + // Keep runtime_enabled as a first-class catalog stage so bundled SSOT and + // /api/strategy-profiles readback stay bit-identical for deploy verification. + if (["research_active", "shadow_active", "paper_active", "live_candidate", "live_enabled", "runtime_enabled"].includes(stage)) { + return stage; + } + if (["research", "research_backtest_only", "ai_monitored_candidate"].includes(stage)) return "research_active"; + if (stage === "shadow_candidate") return "shadow_active"; + void deployment; + throw new Error(`lifecycle_stage ${stage} is unsupported`); +} + +export function cleanAllowedExecutionModes(value) { + const items = Array.isArray(value) + ? value + : String(value || "").split(/[,\s/|]+/); + const modes = []; + for (const item of items) { + const mode = String(item || "").trim().toLowerCase(); + if (!mode) continue; + if (!["live", "paper", "dry_run"].includes(mode)) { + throw new Error(`allowed_execution_modes contains unsupported mode ${mode}`); + } + if (!modes.includes(mode)) modes.push(mode); + } + return modes; +} + +export function cleanExecutionMode(value) { + const mode = cleanChoice(value || "live", ["live", "paper", "dry_run"], "execution_mode"); + return mode === "paper" ? "dry_run" : mode; +} + +export function supportedExecutionModesForPlatform(platform) { + const modes = PLATFORM_CONFIG[platform]?.supported_execution_modes; + if (!Array.isArray(modes)) return []; + return modes.filter((mode) => mode === "live" || mode === "dry_run"); +} + +export function requireSameOrigin(request, options = {}) { + const origin = request.headers.get("Origin"); + if (!origin) { + if (options.requireOrigin) throw new HttpError("Origin header is required", 403); + return; + } + if (origin !== new URL(request.url).origin) throw new HttpError("cross-origin request rejected", 403); +} + +export const deskAuth = createDeskAuth({ readSession, requireSameOrigin }); + +export async function fetchGithubVariables(token, repository, scope, githubEnvironment, { reportReadErrors = false, onReadFailure = () => {} } = {}) { + const apiUrl = githubVariablesUrl(repository, scope, githubEnvironment); + const values = new Map(); + // Evidence-aware readers opt in. Other callers retain their existing fallback. + const failed = (reason = "read_failed") => { onReadFailure(reason); return reportReadErrors ? null : new Map(); }; + if (!apiUrl) return failed(); + try { + // Follow the variable-list pages, never arbitrary URLs returned by an upstream. + for (let page = 1; ; page += 1) { + const response = await fetchWithTimeout(`${apiUrl}?per_page=100&page=${page}`, { + headers: githubHeaders(token), + }); + if (!response.ok) return failed([401, 403].includes(response.status) ? "permission_denied" : "read_failed"); + const payload = await response.json(); + if (!Array.isArray(payload?.variables)) return failed(); + for (const item of payload.variables) { + if (typeof item.name === "string" && typeof item.value === "string") values.set(item.name, item.value); + } + if (!response.headers.get("Link")?.includes('rel="next"')) return values; + } + } catch { + return failed(); + } +} + + +export function githubVariablesUrl(repository, scope, githubEnvironment) { + const [owner, repo] = String(repository || "").split("/"); + if (!owner || !repo) return ""; + const base = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`; + if (scope === "environment") { + if (!githubEnvironment) return ""; + return `${base}/environments/${encodeURIComponent(githubEnvironment)}/variables`; + } + return `${base}/actions/variables`; +} + +export function resolveVariableScope(platform, option) { + const configured = String(option?.variable_scope || "").trim(); + if (configured && configured !== "default") return configured; + return DEFAULT_VARIABLE_SCOPE[platform] || "repository"; +} + +export function resolveGithubEnvironment(platform, option, variableScope) { + if (variableScope !== "environment") return ""; + const configured = String(option?.github_environment || "").trim(); + if (configured) return configured; + const targetName = String(option?.target_name || option?.key || "").trim(); + if (!targetName) return ""; + if (platform === "longbridge") return `longbridge-${targetName.toLowerCase()}`; + return targetName; +} + +export function runtimeTargetFromServiceTargets(rawValue, platform, option) { + const payload = parseJsonValue(rawValue); + const targets = Array.isArray(payload) + ? payload + : (Array.isArray(payload?.targets) ? payload.targets : []); + const matches = []; + for (const entry of targets) { + if (!entry || Array.isArray(entry) || typeof entry !== "object") continue; + const runtimeTarget = entry.runtime_target && typeof entry.runtime_target === "object" + ? entry.runtime_target + : {}; + if (!runtimeTargetMatchesAccount(runtimeTarget, platform, option, entry)) continue; + // Match deployment identity before reading overrides. The deploy planner + // supports nested env values and gives explicit top-level values precedence. + const nestedEnv = entry.env && typeof entry.env === "object" && !Array.isArray(entry.env) + ? entry.env : {}; + const settings = { ...nestedEnv, ...entry }; + matches.push({ + ...runtimeTarget, + strategy_profile: runtimeTarget.strategy_profile || entry.strategy_profile, + ...reservedCashPayloadFromObject(platform, settings), + ...incomeLayerPayloadFromObject(settings), + ...optionOverlayPayloadFromObject(settings), + ...runtimeTargetEnabledPayloadFromObject(settings), + ...dcaPayloadFromObject(settings), + ...cashOnlyPayloadFromObject(platform, settings), + }); + } + return matches.length === 1 ? matches[0] : null; +} + +export async function readCashOnlyVariables({ platform, repository, variableScope, githubEnvironment, readVariable }) { + const platformVariable = PLATFORM_CASH_ONLY_EXECUTION_VARIABLES[platform]; + const [platformValue, legacyValue] = await Promise.all([ + readVariable(repository, variableScope, githubEnvironment, platformVariable), + readVariable(repository, variableScope, githubEnvironment, LEGACY_CASH_ONLY_EXECUTION_VARIABLE), + ]); + return cashOnlyPayloadFromValues(platformValue ?? legacyValue); +} + +export function cashOnlyPayloadFromObject(platform, payload) { + if (!payload || Array.isArray(payload) || typeof payload !== "object") return {}; + return cashOnlyPayloadFromValues( + payload[PLATFORM_CASH_ONLY_EXECUTION_VARIABLES[platform]] ?? + payload[LEGACY_CASH_ONLY_EXECUTION_VARIABLE] ?? + payload.cash_only_execution, + ); +} + +export function cashOnlyPayloadFromValues(value) { + const enabled = cleanOptionalBoolean(value); + if (enabled === null) return {}; + return { cash_only_execution: enabled }; +} + +export async function readReservedCashVariables({ platform, repository, variableScope, githubEnvironment, readVariable }) { + const [floorValue, ratioValue] = await Promise.all([ + readVariable(repository, variableScope, githubEnvironment, PLATFORM_MIN_RESERVED_CASH_VARIABLES[platform]), + readVariable(repository, variableScope, githubEnvironment, PLATFORM_RESERVED_CASH_RATIO_VARIABLES[platform]), + ]); + return reservedCashPayloadFromValues(floorValue, ratioValue); +} + +export async function readIncomeLayerVariables({ repository, variableScope, githubEnvironment, readVariable }) { + const [enabledValue, startUsdValue, maxRatioValue] = await Promise.all([ + readVariable(repository, variableScope, githubEnvironment, INCOME_LAYER_ENABLED_VARIABLE), + readVariable(repository, variableScope, githubEnvironment, INCOME_LAYER_START_USD_VARIABLE), + readVariable(repository, variableScope, githubEnvironment, INCOME_LAYER_MAX_RATIO_VARIABLE), + ]); + return incomeLayerPayloadFromValues(enabledValue, startUsdValue, maxRatioValue); +} + +export async function readOptionOverlayVariables({ repository, variableScope, githubEnvironment, readVariable }) { + const enabledValue = await readVariable(repository, variableScope, githubEnvironment, OPTION_OVERLAY_ENABLED_VARIABLE); + return optionOverlayPayloadFromValue(enabledValue); +} + +export async function readRuntimeTargetEnabledVariable({ repository, variableScope, githubEnvironment, readVariable }) { + const value = await readVariable(repository, variableScope, githubEnvironment, RUNTIME_TARGET_ENABLED_VARIABLE); + return runtimeTargetEnabledPayloadFromValue(value); +} + +export async function readDcaVariables({ repository, variableScope, githubEnvironment, readVariable }) { + const [modeValue, baseInvestmentValue] = await Promise.all([ + readVariable(repository, variableScope, githubEnvironment, DCA_MODE_VARIABLE), + readVariable(repository, variableScope, githubEnvironment, DCA_BASE_INVESTMENT_VARIABLE), + ]); + return dcaPayloadFromValues(modeValue, baseInvestmentValue); +} + +export function reservedCashPayloadFromObject(platform, payload) { + if (!payload || Array.isArray(payload) || typeof payload !== "object") return {}; + return reservedCashPayloadFromValues( + payload[PLATFORM_MIN_RESERVED_CASH_VARIABLES[platform]] ?? + payload.min_reserved_cash_usd ?? + payload.reserved_cash_floor_usd, + payload[PLATFORM_RESERVED_CASH_RATIO_VARIABLES[platform]] ?? + payload.reserved_cash_ratio, + ); +} + +export function reservedCashPayloadFromValues(floorValue, ratioValue) { + const result = {}; + const floor = cleanCurrentNonNegativeNumber(floorValue); + const ratio = cleanCurrentRatio(ratioValue); + if (floor) result.min_reserved_cash_usd = floor; + if (ratio) result.reserved_cash_ratio = ratio; + return result; +} + +export function incomeLayerPayloadFromObject(payload) { + if (!payload || Array.isArray(payload) || typeof payload !== "object") return {}; + return incomeLayerPayloadFromValues( + payload[INCOME_LAYER_ENABLED_VARIABLE] ?? payload.income_layer_enabled, + payload[INCOME_LAYER_START_USD_VARIABLE] ?? payload.income_layer_start_usd, + payload[INCOME_LAYER_MAX_RATIO_VARIABLE] ?? payload.income_layer_max_ratio, + ); +} + +export function incomeLayerPayloadFromValues(enabledValue, startUsdValue, maxRatioValue) { + const result = {}; + const enabled = cleanOptionalBoolean(enabledValue); + const startUsd = cleanCurrentNonNegativeNumber(startUsdValue); + const maxRatio = cleanCurrentRatio(maxRatioValue); + if (enabled !== null) result.income_layer_enabled = enabled; + if (startUsd) result.income_layer_start_usd = startUsd; + if (maxRatio) result.income_layer_max_ratio = maxRatio; + return result; +} + +export function optionOverlayPayloadFromObject(payload) { + if (!payload || Array.isArray(payload) || typeof payload !== "object") return {}; + return optionOverlayPayloadFromValue( + payload[OPTION_OVERLAY_ENABLED_VARIABLE] ?? payload.option_overlay_enabled, + ); +} + +export function optionOverlayPayloadFromValue(value) { + const enabled = cleanOptionalBoolean(value); + return enabled === null ? {} : { option_overlay_enabled: enabled }; +} + +export function runtimeTargetEnabledPayloadFromObject(payload) { + if (!payload || Array.isArray(payload) || typeof payload !== "object") return {}; + return runtimeTargetEnabledPayloadFromValue( + payload[RUNTIME_TARGET_ENABLED_VARIABLE] ?? payload.runtime_target_enabled, + ); +} + +export function runtimeTargetEnabledPayloadFromValue(value) { + const enabled = cleanOptionalBoolean(value); + return enabled === null ? {} : { runtime_target_enabled: enabled }; +} + +export function dcaPayloadFromObject(payload) { + if (!payload || Array.isArray(payload) || typeof payload !== "object") return {}; + return dcaPayloadFromValues( + payload[DCA_MODE_VARIABLE] ?? payload.dca_mode, + payload[DCA_BASE_INVESTMENT_VARIABLE] ?? + payload.dca_base_investment_usd ?? + payload.base_investment_usd, + ); +} + +export function stripLegacyIbitZscoreExitControls(payload) { + if (!payload || Array.isArray(payload) || typeof payload !== "object") return; + for (const field of [ + "ibit_zscore_exit_mode", + "ibit_zscore_exit_enabled", + "ibit_zscore_exit_parking_symbol", + "ibit_zscore_exit_risk_reduced_exposure", + "ibit_zscore_exit_risk_off_exposure", + "ibit_zscore_exit_allow_outside_execution_window", + ]) { + delete payload[field]; + } +} + +export function dcaPayloadFromValues(modeValue, baseInvestmentValue) { + const result = {}; + const mode = cleanCurrentDcaMode(modeValue); + const baseInvestmentUsd = cleanCurrentPositiveNumber(baseInvestmentValue); + if (mode) result.dca_mode = mode; + if (baseInvestmentUsd) result.dca_base_investment_usd = baseInvestmentUsd; + return result; +} + +export function dcaControlsFromInputs(inputs) { + const payload = inputs?.extra_variables_json ? JSON.parse(inputs.extra_variables_json) : {}; + return { + ...dcaPayloadFromObject(payload), + ...dcaPayloadFromObject(inputs), + }; +} + +export function cashOnlyExecutionModeFromInputs(inputs) { + const direct = String(inputs?.cash_only_execution_mode || "").trim().toLowerCase(); + if (direct === "enabled" || direct === "disabled") return direct; + try { + const payload = inputs?.extra_variables_json ? JSON.parse(inputs.extra_variables_json) : {}; + const mode = String(payload.cash_only_execution_mode || "").trim().toLowerCase(); + return mode === "enabled" || mode === "disabled" ? mode : ""; + } catch { + return ""; + } +} + +export function dcaPayloadForProfile(profile, payload) { + return isDcaProfile(profile) ? payload : {}; +} + +export function runtimeModePayload(runtimeTarget) { + const executionMode = normalizeRuntimeExecutionMode(runtimeTarget?.execution_mode, runtimeTarget?.dry_run_only); + const payload = {}; + if (executionMode) payload.execution_mode = executionMode; + const dryRunOnly = cleanOptionalBoolean(runtimeTarget?.dry_run_only); + if (dryRunOnly !== null) payload.dry_run_only = dryRunOnly; + return payload; +} + +export function normalizeRuntimeExecutionMode(value, dryRunOnly) { + const mode = String(value || "").trim().toLowerCase(); + if (mode === "live" || mode === "dry_run") return mode; + if (mode === "paper" && cleanOptionalBoolean(dryRunOnly) !== true) return mode; + const dryRun = cleanOptionalBoolean(dryRunOnly); + if (dryRun === true) return "dry_run"; + if (dryRun === false) return "live"; + return ""; +} + +export function cleanOptionalBoolean(value) { + if (value === true || value === 1) return true; + if (value === false || value === 0) return false; + if (typeof value === "string") { + const text = value.trim().toLowerCase(); + if (text === "true" || text === "1") return true; + if (text === "false" || text === "0") return false; + } + return null; +} + +export function cleanCurrentNonNegativeNumber(value) { + const text = String(value ?? "").trim(); + if (!text || text.length > 32 || !/^(?:\d+|\d*\.\d+)$/.test(text)) return ""; + const numeric = Number(text); + if (!Number.isFinite(numeric) || numeric < 0) return ""; + return text; +} + +export function cleanCurrentRatio(value) { + const text = cleanCurrentNonNegativeNumber(value); + if (!text) return ""; + const numeric = Number(text); + if (numeric < 0 || numeric > 1) return ""; + return text; +} + +export function cleanCurrentPositiveNumber(value) { + const text = cleanCurrentNonNegativeNumber(value); + if (!text || Number(text) <= 0) return ""; + return text; +} + +export function cleanCurrentDcaMode(value) { + try { + return cleanDcaMode(value || ""); + } catch { + return ""; + } +} + +export function runtimeTargetMatchesAccount(runtimeTarget, platform, option, entry = {}) { + const runtimePlatform = String(runtimeTarget?.platform_id || "").trim().toLowerCase(); + if (runtimePlatform && runtimePlatform !== platform) return false; + + const serviceName = String(option?.service_name || defaultCurrentServiceName(platform, option?.target_name || option?.key) || ""); + const serviceCandidates = [ + runtimeTarget?.service_name, + entry?.service, + entry?.service_name, + ].filter((value) => normalizeMatchValue(value)); + if (serviceName && serviceCandidates.length) { + return hasCandidate(serviceName, serviceCandidates); + } + + if (hasCandidate(option?.account_scope, [ + runtimeTarget?.account_scope, + entry?.ACCOUNT_GROUP, + entry?.account_scope, + ])) return true; + + if (hasCandidate(option?.deployment_selector, [ + runtimeTarget?.deployment_selector, + entry?.deployment_selector, + ])) return true; + + const optionSelectors = splitSelectorValues(option?.account_selector); + const runtimeSelectors = splitSelectorValues(runtimeTarget?.account_selector || entry?.account_selector); + if (optionSelectors.some((value) => runtimeSelectors.includes(value))) return true; + + const targetName = String(option?.target_name || option?.key || "").trim(); + return Boolean(targetName && hasCandidate(targetName, [ + runtimeTarget?.target_name, + runtimeTarget?.deployment_selector, + runtimeTarget?.account_scope, + entry?.target_name, + ])); +} + +export function defaultCurrentServiceName(platform, targetName) { + const normalized = String(targetName || "").trim().toLowerCase(); + if (!normalized) return ""; + if (platform === "longbridge") return `longbridge-quant-${normalized}-service`; + if (platform === "ibkr") return `interactive-brokers-${normalized}-service`; + if (platform === "schwab") return "charles-schwab-quant-service"; + if (platform === "firstrade") return "firstrade-quant-service"; + if (platform === "qmt") return "qmt-quant-service"; + return ""; +} + +export function hasCandidate(expected, candidates) { + const normalizedExpected = normalizeMatchValue(expected); + if (!normalizedExpected) return false; + return candidates.some((candidate) => normalizeMatchValue(candidate) === normalizedExpected); +} + +export function splitSelectorValues(value) { + if (Array.isArray(value)) return value.map(normalizeMatchValue).filter(Boolean); + return String(value || "") + .split(/[,\s]+/) + .map(normalizeMatchValue) + .filter(Boolean); +} + +export function normalizeMatchValue(value) { + return String(value || "").trim().toLowerCase(); +} + +export function parseJsonObject(value) { + const text = String(value || "").trim(); + if (!text) return null; + for (const candidate of [text, text.replaceAll("\\n", "\n")]) { + try { + const payload = JSON.parse(candidate); + return payload && !Array.isArray(payload) && typeof payload === "object" ? payload : null; + } catch { + // Try the next representation. + } + } + return null; +} + +export function parseJsonValue(value) { + const text = String(value || "").trim(); + if (!text) return null; + for (const candidate of [text, text.replaceAll("\\n", "\n")]) { + try { + return JSON.parse(candidate); + } catch { + // Try the next representation. + } + } + return null; +} + +export function cleanCurrentStrategy(value) { + const text = String(value || "").trim().toLowerCase(); + if (!text || text.length > 120 || !/^[a-z0-9._=-]+$/.test(text)) return ""; + return text; +} + +export function cleanCsv(value, field) { + const text = String(value || "").trim(); + if (text.length > 300 || !/^[A-Za-z0-9._=,\-\s]+$/.test(text)) { + throw new Error(`${field} is invalid`); + } + return text; +} + +export function cleanJson(value, field) { + const text = String(value || "").trim(); + if (!text) return ""; + if (text.length > 8000) throw new Error(`${field} is too long`); + JSON.parse(text); + return text; +} + +export function cleanOptionalJsonObject(value, field) { + const text = cleanJson(value, field); + if (!text) return ""; + const payload = JSON.parse(text); + if (!payload || Array.isArray(payload) || typeof payload !== "object") { + throw new Error(`${field} must be a JSON object`); + } + for (const name of Object.keys(payload)) { + if (looksLikeSecretName(name)) { + throw new Error(`${field}.${name} looks like a secret and must not be stored here`); + } + } + return text; +} + +export function looksLikeSecretName(name) { + const upperName = String(name || "").toUpperCase(); + if (/_SECRET_(ID|NAME|REF|RESOURCE|RESOURCE_NAME|VERSION)$/.test(upperName)) return false; + return /PASSWORD|PRIVATE_KEY|TOKEN|API_KEY|ACCESS_KEY|CLIENT_SECRET|SECRET/.test(upperName); +} + +export function githubHeaders(token) { + return { + Authorization: `Bearer ${token}`, + Accept: "application/vnd.github+json", + "Content-Type": "application/json", + "User-Agent": "QuantRuntimeSettings-StrategySwitchConsole", + "X-GitHub-Api-Version": "2022-11-28", + }; +} + +export async function fetchGithubOrgLogins(token, timeoutMs = GITHUB_API_TIMEOUT_MS) { + const orgs = []; + for (let page = 1; page <= 5; page += 1) { + const response = await fetchWithTimeout(`https://api.github.com/user/orgs?per_page=100&page=${page}`, { + headers: githubHeaders(token), + }, timeoutMs); + if (!response.ok) return orgs; + const payload = await response.json(); + if (!Array.isArray(payload) || !payload.length) break; + for (const org of payload) { + const login = cleanGithubOrg(org?.login || "", "github org"); + if (login) orgs.push(login); + } + if (payload.length < 100) break; + } + return uniqueStrings(orgs); +} + +export function requireEnv(env, name) { + if (!env[name]) throw new Error(`${name} is not configured`); +} + +export async function loadAuthConfig(env) { + const bootstrapAdmins = parseLoginList(env.STRATEGY_SWITCH_ADMIN_LOGINS || "", "STRATEGY_SWITCH_ADMIN_LOGINS"); + const bootstrapAdminOrgs = parseOrgList(env.STRATEGY_SWITCH_ADMIN_ORGS || "", "STRATEGY_SWITCH_ADMIN_ORGS"); + const envAllowed = parseLoginList( + env.ALLOWED_GITHUB_LOGINS || env.ALLOWED_GITHUB_LOGIN || "", + "ALLOWED_GITHUB_LOGINS", + ); + const envAllowedOrgs = parseOrgList( + env.ALLOWED_GITHUB_ORGS || env.ALLOWED_GITHUB_ORG || "", + "ALLOWED_GITHUB_ORGS", + ); + let storedAllowed = []; + let storedAllowedOrgs = []; + let storedAdmins = []; + let storedAdminOrgs = []; + let source = "secret"; + if (hasConfigStore(env)) { + const stored = await readConfigJson(env, AUTH_CONFIG_KEY); + if (stored) { + const normalized = normalizeAuthConfigPayload(stored, AUTH_CONFIG_KEY); + storedAllowed = normalized.allowed_logins; + storedAllowedOrgs = normalized.allowed_orgs; + storedAdmins = normalized.admin_logins; + storedAdminOrgs = normalized.admin_orgs; + source = "kv"; + } + } + const adminLogins = uniqueStrings([...bootstrapAdmins, ...storedAdmins]); + const adminOrgs = uniqueStrings([...bootstrapAdminOrgs, ...storedAdminOrgs]); + const allowedLogins = uniqueStrings([...envAllowed, ...storedAllowed, ...adminLogins]); + const allowedOrgs = uniqueStrings([...envAllowedOrgs, ...storedAllowedOrgs]); + return { + allowed_logins: allowedLogins, + allowed_orgs: allowedOrgs, + admin_logins: adminLogins, + admin_orgs: adminOrgs, + bootstrap_admin_logins: bootstrapAdmins, + bootstrap_admin_orgs: bootstrapAdminOrgs, + env_allowed_logins: envAllowed, + env_allowed_orgs: envAllowedOrgs, + source, + kv_available: hasConfigStore(env), + }; +} + +export function normalizeAuthConfigPayload(payload, fieldName) { + if (!payload || Array.isArray(payload) || typeof payload !== "object") { + throw new Error(`${fieldName} must be an object`); + } + return { + allowed_logins: normalizeLoginList(payload.allowed_logins || [], `${fieldName}.allowed_logins`), + allowed_orgs: normalizeOrgList(payload.allowed_orgs || [], `${fieldName}.allowed_orgs`), + admin_logins: normalizeLoginList(payload.admin_logins || [], `${fieldName}.admin_logins`), + admin_orgs: normalizeOrgList(payload.admin_orgs || [], `${fieldName}.admin_orgs`), + }; +} + +export function parseLoginList(value, fieldName) { + return normalizeLoginList(value, fieldName); +} + +export function normalizeLoginList(value, fieldName) { + const items = Array.isArray(value) ? value : String(value || "").split(/[\s,]+/); + if (items.length > 80) throw new Error(`${fieldName} supports at most 80 logins`); + return uniqueStrings(items.map((item) => cleanGithubLogin(item, fieldName)).filter(Boolean)); +} + +export function parseOrgList(value, fieldName) { + return normalizeOrgList(value, fieldName); +} + +export function normalizeOrgList(value, fieldName) { + const items = Array.isArray(value) ? value : String(value || "").split(/[\s,]+/); + if (items.length > 80) throw new Error(`${fieldName} supports at most 80 orgs`); + return uniqueStrings(items.map((item) => cleanGithubOrg(item, fieldName)).filter(Boolean)); +} + +export function cleanGithubLogin(value, fieldName) { + const login = String(value || "").trim().toLowerCase(); + if (!login) return ""; + if ( + login.length > 39 || + !/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(login) || + login.includes("--") + ) { + throw new Error(`${fieldName} contains an invalid GitHub login`); + } + return login; +} + +export function cleanGithubOrg(value, fieldName) { + return cleanGithubLogin(value, fieldName); +} + +export function isAdminLogin(login, orgLogins, authConfig) { + return authConfig.admin_logins.includes(String(login || "").toLowerCase()); +} + +export function isAdminPrincipal(login, orgLogins, authConfig) { + return isAdminLogin(login, orgLogins, authConfig) || hasOrgMatch(orgLogins, authConfig.admin_orgs); +} + +export function isAllowedPrincipal(login, orgLogins, authConfig) { + const normalizedLogin = String(login || "").toLowerCase(); + return ( + authConfig.allowed_logins.includes(normalizedLogin) || + hasOrgMatch(orgLogins, authConfig.allowed_orgs) || + isAdminPrincipal(normalizedLogin, orgLogins, authConfig) + ); +} + +export function authorizedOrgLogins(orgLogins, authConfig) { + const authorized = new Set([...authConfig.allowed_orgs, ...authConfig.admin_orgs]); + return uniqueStrings(orgLogins).filter((org) => authorized.has(org)); +} + +export function hasOrgMatch(orgLogins, configuredOrgs) { + const orgs = new Set(uniqueStrings(orgLogins)); + return configuredOrgs.some((org) => orgs.has(String(org || "").toLowerCase())); +} + +export async function loadAccountOptionsConfig(env) { + if (hasRuntimeInstanceStore(env)) { + const state = await runtimeInstanceCommand(env); + if (state.initialized) return { options: state.account_options, source: "durable_object", revision: state.revision }; + // Existing execution can read the frozen legacy configuration during import; + // every instance writer rejects this uninitialized state. + } + return loadLegacyAccountOptionsConfig(env); +} + +export async function loadLegacyAccountOptionsConfig(env) { + if (hasConfigStore(env)) { + const stored = await readConfigJson(env, ACCOUNT_OPTIONS_KEY); + if (stored) { + return { + options: normalizeAccountOptionsPayload(stored, ACCOUNT_OPTIONS_KEY), + source: "kv", + }; + } + } + return { + options: parseAccountOptions(env.STRATEGY_SWITCH_ACCOUNT_OPTIONS_JSON || "", "STRATEGY_SWITCH_ACCOUNT_OPTIONS_JSON"), + source: env.STRATEGY_SWITCH_ACCOUNT_OPTIONS_JSON ? "secret" : "none", + }; +} + +export async function loadStrategyProfilesConfig(env) { + if (hasConfigStore(env)) { + const stored = await readConfigJson(env, STRATEGY_PROFILES_KEY); + if (stored) return normalizeStrategyProfilesPayload(stored, STRATEGY_PROFILES_KEY); + } + const configured = parseStrategyProfiles( + env.STRATEGY_SWITCH_STRATEGY_PROFILES_JSON || "", + "STRATEGY_SWITCH_STRATEGY_PROFILES_JSON", + ); + if (configured) return configured; + return normalizeStrategyProfilesPayload(DEFAULT_STRATEGY_PROFILES, "DEFAULT_STRATEGY_PROFILES"); +} + +export function riskProfileScopeId(platform, targetName) { + return `${platform}--${targetName}`; +} + +export function riskProfileBindingTargets(accountOptions) { + const targets = []; + for (const platform of SUPPORTED_PLATFORMS) { + const options = Array.isArray(accountOptions?.[platform]) ? accountOptions[platform] : []; + for (const option of options) { + targets.push({ + scope_id: riskProfileScopeId(platform, option.target_name), + platform, + target_name: option.target_name, + }); + } + } + return targets.sort((left, right) => left.scope_id.localeCompare(right.scope_id)); +} + +export function utcTimestampSeconds(now = new Date()) { + return now.toISOString().replace(/\.\d{3}Z$/, "Z"); +} + +export async function calculateRiskProfileSelectionSha256(payload) { + const material = { ...payload }; + delete material.selection_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(""); +} + +export async function normalizeRiskProfileSelection(payload, fieldName) { + const value = assertExactFields(payload, ["schema", "profile_id", "risk_preference", "selection_sha256"], fieldName); + const riskPreference = cleanChoice(value.risk_preference, RISK_PROFILE_PREFERENCES, `${fieldName}.risk_preference`); + const normalized = { + schema: RISK_PROFILE_SELECTION_SCHEMA_VERSION, + profile_id: RISK_PROFILE_IDS[riskPreference], + risk_preference: riskPreference, + selection_sha256: normalizeResearchTaskDigest(value.selection_sha256, `${fieldName}.selection_sha256`), + }; + if (value.schema !== RISK_PROFILE_SELECTION_SCHEMA_VERSION) { + throw new Error(`${fieldName}.schema is unsupported`); + } + if (value.profile_id !== normalized.profile_id) { + throw new Error(`${fieldName}.profile_id does not match risk_preference`); + } + if (normalized.selection_sha256 !== await calculateRiskProfileSelectionSha256(normalized)) { + throw new Error(`${fieldName}.selection_sha256 mismatch`); + } + return normalized; +} + +export async function calculateRiskProfileBindingSha256(payload) { + const material = { ...payload }; + delete material.binding_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(""); +} + +export async function normalizeRiskProfileBinding(payload, fieldName) { + const value = assertExactFields(payload, [ + "schema_version", "scope_id", "platform", "target_name", "profile_selection", "updated_at", "updated_by", + "no_order", "execution_authority_granted", "binding_sha256", + ], fieldName); + const platform = cleanChoice(value.platform, SUPPORTED_PLATFORMS, `${fieldName}.platform`); + const targetName = cleanSlug(value.target_name, `${fieldName}.target_name`); + const normalized = { + schema_version: RISK_PROFILE_BINDING_SCHEMA_VERSION, + scope_id: riskProfileScopeId(platform, targetName), + platform, + target_name: targetName, + profile_selection: await normalizeRiskProfileSelection(value.profile_selection, `${fieldName}.profile_selection`), + updated_at: normalizeResearchTaskTimestamp(value.updated_at, `${fieldName}.updated_at`), + updated_by: cleanGithubLogin(value.updated_by, `${fieldName}.updated_by`), + no_order: value.no_order, + execution_authority_granted: value.execution_authority_granted, + binding_sha256: normalizeResearchTaskDigest(value.binding_sha256, `${fieldName}.binding_sha256`), + }; + if (value.schema_version !== RISK_PROFILE_BINDING_SCHEMA_VERSION) { + throw new Error(`${fieldName}.schema_version is unsupported`); + } + if (value.scope_id !== normalized.scope_id) { + throw new Error(`${fieldName}.scope_id does not match platform and target_name`); + } + if (normalized.no_order !== true || normalized.execution_authority_granted !== false) { + throw new Error(`${fieldName} must remain no-order and non-executable`); + } + if (normalized.binding_sha256 !== await calculateRiskProfileBindingSha256(normalized)) { + throw new Error(`${fieldName}.binding_sha256 mismatch`); + } + return normalized; +} + +export async function normalizeRiskProfileBindingRegistry(payload, fieldName = RISK_PROFILE_BINDINGS_KEY) { + const value = assertExactFields(payload, ["schema_version", "bindings"], fieldName); + if (value.schema_version !== RISK_PROFILE_BINDING_REGISTRY_SCHEMA_VERSION) { + throw new Error(`${fieldName}.schema_version is unsupported`); + } + if (!Array.isArray(value.bindings) || value.bindings.length > SUPPORTED_PLATFORMS.length * 20) { + throw new Error(`${fieldName}.bindings must be a bounded array`); + } + const bindings = []; + const scopes = new Set(); + for (const [index, item] of value.bindings.entries()) { + const binding = await normalizeRiskProfileBinding(item, `${fieldName}.bindings[${index}]`); + if (scopes.has(binding.scope_id)) throw new Error(`${fieldName}.bindings contains duplicate scope_id`); + scopes.add(binding.scope_id); + bindings.push(binding); + } + return bindings.sort((left, right) => left.scope_id.localeCompare(right.scope_id)); +} + +export async function buildRiskProfileBindings(payload, accountOptions, updatedBy) { + const value = assertExactFields(payload, ["bindings"], "risk profile bindings request"); + if (!Array.isArray(value.bindings) || value.bindings.length > SUPPORTED_PLATFORMS.length * 20) { + throw new Error("risk profile bindings request.bindings must be a bounded array"); + } + const bindings = []; + const scopes = new Set(); + const updatedAt = utcTimestampSeconds(); + for (const [index, item] of value.bindings.entries()) { + const itemValue = assertExactFields(item, ["platform", "target_name", "risk_preference"], `risk profile bindings request.bindings[${index}]`); + const platform = cleanChoice(itemValue.platform, SUPPORTED_PLATFORMS, `risk profile bindings request.bindings[${index}].platform`); + const targetName = cleanSlug(itemValue.target_name, `risk profile bindings request.bindings[${index}].target_name`); + if (!riskProfileBindingTargets(accountOptions).some((target) => target.platform === platform && target.target_name === targetName)) { + throw new Error(`risk profile binding target is not configured: ${platform}/${targetName}`); + } + const scopeId = riskProfileScopeId(platform, targetName); + if (scopes.has(scopeId)) throw new Error("risk profile bindings request contains duplicate target"); + scopes.add(scopeId); + const riskPreference = cleanChoice( + itemValue.risk_preference, + RISK_PROFILE_PREFERENCES, + `risk profile bindings request.bindings[${index}].risk_preference`, + ); + const profileSelection = { + schema: RISK_PROFILE_SELECTION_SCHEMA_VERSION, + profile_id: RISK_PROFILE_IDS[riskPreference], + risk_preference: riskPreference, + selection_sha256: "", + }; + profileSelection.selection_sha256 = await calculateRiskProfileSelectionSha256(profileSelection); + const binding = { + schema_version: RISK_PROFILE_BINDING_SCHEMA_VERSION, + scope_id: scopeId, + platform, + target_name: targetName, + profile_selection: profileSelection, + updated_at: updatedAt, + updated_by: cleanGithubLogin(updatedBy, "risk profile binding.updated_by"), + no_order: true, + execution_authority_granted: false, + binding_sha256: "", + }; + binding.binding_sha256 = await calculateRiskProfileBindingSha256(binding); + bindings.push(await normalizeRiskProfileBinding(binding, `risk profile bindings request.bindings[${index}]`)); + } + return bindings.sort((left, right) => left.scope_id.localeCompare(right.scope_id)); +} + +export function accountSettingsIdentity(instance) { + const config = instance?.config || {}; + const broker = config.broker_environment; + return { + platform: instance.platform, + key: instance.key, + target_name: typeof config.target_name === "string" ? config.target_name : "", + broker_environment: broker === "live" || broker === "paper" ? broker : null, + account_selector: typeof config.account_selector === "string" ? config.account_selector : "", + deployment_selector: typeof config.deployment_selector === "string" ? config.deployment_selector : "", + account_scope: typeof config.account_scope === "string" ? config.account_scope : "", + service_name: typeof config.service_name === "string" ? config.service_name : "", + }; +} + +export function accountSettingsIdentityIsComplete(identity) { + if (!identity || Array.isArray(identity) || typeof identity !== "object" + || !SUPPORTED_PLATFORMS.includes(identity.platform) + || (identity.broker_environment !== "paper" && identity.broker_environment !== "live")) return false; + return ["key", "target_name", "account_selector", "deployment_selector", "account_scope", "service_name"] + .every((field) => typeof identity[field] === "string" && identity[field].trim() !== ""); +} + +export function runtimeControlIdentityIsComplete(identity) { + return Boolean(identity && SUPPORTED_PLATFORMS.includes(identity.platform) + && [null, "live", "paper"].includes(identity.broker_environment) + && ["key", "target_name", "account_selector", "deployment_selector", "account_scope", "service_name"] + .every((field) => typeof identity[field] === "string" && identity[field].trim() !== "")); +} + +export function accountSettingsProfileName(value) { + if (typeof value !== "string" || value.length > 128 || value !== value.trim() + || !/^[a-z0-9][a-z0-9._-]*$/.test(value)) return ""; + return value; +} + +export function accountSettingPositiveAmount(value) { + if (typeof value !== "string" || value.length > 32 || !/^(?:0|[1-9]\d*)(?:\.\d+)?$/.test(value)) return false; + const numeric = Number(value); + return Number.isFinite(numeric) && numeric > 0; +} + +export function accountSettingRatio(value) { + if (typeof value !== "string" || value.length > 32 || !/^(?:0|[1-9]\d*)(?:\.\d+)?$/.test(value)) return null; + const [whole, frac = ""] = value.split("."); + if (whole === "0") return value; + if (whole === "1" && (frac === "" || /^0+$/.test(frac))) return value; + return null; +} + +export function applyAccountSettingOverrides(current, patch) { + if (!patch || Array.isArray(patch) || typeof patch !== "object") throw new HttpError("invalid_account_setting_overrides", 400); + const next = current && typeof current === "object" && !Array.isArray(current) ? { ...current } : {}; + for (const key of Object.keys(patch)) { + if (!ACCOUNT_SETTING_OVERRIDE_FIELDS.includes(key)) throw new HttpError("invalid_account_setting_overrides", 400); + const value = patch[key]; + if (value === null) { + delete next[key]; + continue; + } + if (typeof value === "number") throw new HttpError("invalid_account_setting_overrides", 400); + if (key === "income_layer_enabled" || key === "option_overlay_enabled") { + if (typeof value !== "boolean") throw new HttpError("invalid_account_setting_overrides", 400); + next[key] = value; + } else if (key === "reserved_cash_floor") { + if (typeof value !== "string" || value.length > 32 || !/^\d+(?:\.\d+)?$/.test(value) || !Number.isFinite(Number(value))) { + throw new HttpError("invalid_account_setting_overrides", 400); + } + next[key] = value; + } else if (key === "reserved_cash_ratio") { + const ratio = accountSettingRatio(value); + if (ratio === null) throw new HttpError("invalid_account_setting_overrides", 400); + next[key] = ratio; + } else if (key === "dca_mode") { + if (value !== "fixed" && value !== "smart") throw new HttpError("invalid_account_setting_overrides", 400); + next[key] = value; + } else if (key === "dca_base_investment_usd") { + if (!accountSettingPositiveAmount(value)) throw new HttpError("invalid_account_setting_overrides", 400); + next[key] = value; + } else if (typeof value !== "string" || value.trim() === "" || value !== value.trim()) { + throw new HttpError("invalid_account_setting_overrides", 400); + } else { + next[key] = value; + } + } + return next; +} + +export async function readRiskProfileLegacy(env) { + if (!hasConfigStore(env)) return { state: "absent" }; + try { + const stored = await readConfigJson(env, RISK_PROFILE_BINDINGS_KEY); + if (!stored) return { state: "absent" }; + return { state: "valid", bindings: await normalizeRiskProfileBindingRegistry(stored) }; + } catch { + return { state: "blocked" }; + } +} + +export async function loadRiskProfileBindings(env) { + if (!hasRuntimeInstanceStore(env)) { + return { bindings: [], revision: null, account_options: null, error: "runtime_instances_not_bound", status: 503 }; + } + const legacy = await readRiskProfileLegacy(env); + try { + const state = await runtimeInstanceCommand(env, { action: "risk_profile_read", actor: "risk-profile-read", legacy }); + return { bindings: state.bindings, revision: state.revision, account_options: state.account_options, error: null, status: 200 }; + } catch (error) { + const status = error instanceof HttpError ? error.status : 503; + return { + bindings: [], + revision: null, + account_options: null, + error: error instanceof HttpError ? error.message : "runtime_instances_unavailable", + status, + }; + } +} + +// Desk reads deliberately bypass the current-strategies memory/KV cache. Raw +// variable maps stay local; only explicit resolver fields enter the projection. +export async function deskTargetReadback(env, target, actor, suppliedObserved = undefined) { + let observed = suppliedObserved; + if (observed === undefined) { + try { + observed = await runtimeInstanceCommand(env, { action: "account_settings_read", actor, + platform: target.platform, key: target.key, legacy: await readRiskProfileLegacy(env) }); + } catch { observed = null; } + } + if (observed && observed.config?.target_name !== target.config.target_name) throw new DeskError("not_found", 404); + const config = observed?.config || target.config; + const token = env.RUNTIME_SETTINGS_DISPATCH_TOKEN; + const repository = platformRepositories(env)?.[target.platform]; + let platform = {}, platformReason = "source_unavailable"; + if (token && repository && config) { + const cache = new Map(); + let failed = false; + const readVariable = async (repo, scope, environment, name) => { + const key = JSON.stringify([repo, scope, environment]); + if (!cache.has(key)) cache.set(key, fetchGithubVariables(token, repo, scope, environment, { reportReadErrors: true })); + const variables = await cache.get(key); + if (variables === null) failed = true; + return variables?.get(name) || ""; + }; + try { + const options = observed?.account_options?.[target.platform] || []; + const current = await resolveCurrentStrategyForAccount({ platform: target.platform, option: config, + optionsCount: options.length || target.optionsCount || 1, repository, readVariable }); + if (failed) throw new Error("read_failed"); + const scope = resolveVariableScope(target.platform, config); + const environment = resolveGithubEnvironment(target.platform, config, scope); + // Identity must come from a uniquely matched runtime record, never config. + const service = current?.source === "CLOUD_RUN_SERVICE_TARGETS_JSON" + ? runtimeTargetFromServiceTargets(await readVariable(repository, "repository", "", "CLOUD_RUN_SERVICE_TARGETS_JSON"), target.platform, config) : null; + const runtime = current?.source === "RUNTIME_TARGET_JSON" + ? parseJsonObject(await readVariable(repository, scope, environment, "RUNTIME_TARGET_JSON")) : null; + if (failed) throw new Error("read_failed"); + const identity = current?.source === "CLOUD_RUN_SERVICE_TARGETS_JSON" ? service + : runtime && runtimeTargetMatchesAccount(runtime, target.platform, config) ? runtime : null; + platform = { strategy_profile: current?.strategy_profile, enabled: current?.runtime_target_enabled, + income_layer_enabled: current?.income_layer_enabled, option_overlay_enabled: current?.option_overlay_enabled, + reserved_cash_floor: current?.min_reserved_cash_usd, reserved_cash_ratio: current?.reserved_cash_ratio, + dca_mode: current?.dca_mode, dca_base_investment_usd: current?.dca_base_investment_usd, + broker_environment: identity?.broker_environment, account_selector: identity?.account_selector, + cash_currency: identity?.cash_currency, runtime_status_target_id: identity?.runtime_status_target_id }; + platformReason = "never_read"; + } catch { platform = {}; platformReason = "read_failed"; } + } + return buildReadback({ target, observed, platform, platformReason, now: Date.now(), cleanRatio: cleanCurrentRatio, splitSelectorValues }); +} + +export async function effectiveAccountSettings(env, observed) { + const broker = observed.identity?.broker_environment === "live" || observed.identity?.broker_environment === "paper" + ? { status: "known", value: observed.identity.broker_environment } + : { status: "unknown" }; + // Keep the legacy value shape: non-DCA settings remain absent for old clients. + const effective = { + strategy_profile: { status: "unknown" }, + broker_environment: broker, + income_layer_enabled: { status: "unknown" }, + option_overlay_enabled: { status: "unknown" }, + reserved_cash_floor: { status: "unknown" }, + reserved_cash_ratio: { status: "unknown" }, + }; + const fields = ["strategy_profile", "dca_mode", "dca_base_investment_usd", "income_layer_enabled", + "option_overlay_enabled", "reserved_cash_floor", "reserved_cash_ratio"]; + // This names the resolver path, not a particular variable, deployment or broker readback. + const finish = (reason = "runtime_setting_unresolved", dcaReason = reason) => ({ + effective, + effectiveRead: Object.fromEntries(fields.map(field => [field, { + status: effective[field]?.status === "known" ? "known" : "unknown", + source: "github_runtime_configuration", + source_as_of: null, + reason: effective[field]?.status === "known" ? null : field.startsWith("dca_") ? dcaReason : reason, + }])), + }); + const token = env.RUNTIME_SETTINGS_DISPATCH_TOKEN; + const repository = platformRepositories(env)?.[observed.platform]; + if (!token) return finish("reader_not_configured"); + if (!repository) return finish("repository_not_configured"); + if (!observed.config) return finish("account_config_unavailable"); + const options = Array.isArray(observed.account_options?.[observed.platform]) ? observed.account_options[observed.platform] : []; + const variableCache = new Map(); + let readFailed = false; + const readVariable = async (repo, scope, githubEnvironment, name) => { + const cacheKey = [repo, scope, githubEnvironment || ""].join("|"); + if (!variableCache.has(cacheKey)) variableCache.set(cacheKey, fetchGithubVariables(token, repo, scope, githubEnvironment, { reportReadErrors: true })); + const values = await variableCache.get(cacheKey); + if (values === null) readFailed = true; + return values?.get(name) || ""; + }; + let current = null; + try { + current = await resolveCurrentStrategyForAccount({ + platform: observed.platform, + option: observed.config, + optionsCount: options.length || 1, + repository, + readVariable, + }); + } catch { + return finish("runtime_configuration_read_failed"); + } + // A failed later page must discard earlier values, never certify missing settings. + if (readFailed) return finish("runtime_configuration_read_failed"); + if (!current || typeof current !== "object") current = {}; + if (typeof current.strategy_profile === "string" && current.strategy_profile) { + effective.strategy_profile = { status: "known", value: current.strategy_profile }; + } + if (typeof current.income_layer_enabled === "boolean") effective.income_layer_enabled = { status: "known", value: current.income_layer_enabled }; + if (typeof current.option_overlay_enabled === "boolean") effective.option_overlay_enabled = { status: "known", value: current.option_overlay_enabled }; + if (typeof current.min_reserved_cash_usd === "string" && current.min_reserved_cash_usd) { + effective.reserved_cash_floor = { status: "known", value: current.min_reserved_cash_usd }; + } + if (typeof current.reserved_cash_ratio === "string") { + const ratio = cleanCurrentRatio(current.reserved_cash_ratio); + if (ratio) effective.reserved_cash_ratio = { status: "known", value: ratio }; + } + const profile = effective.strategy_profile.status === "known" ? effective.strategy_profile.value : ""; + if (isDcaProfile(profile) && DCA_SUPPORTED_PLATFORMS.has(observed.platform)) { + const mode = current.dca_mode === "fixed" || current.dca_mode === "smart" ? current.dca_mode : ""; + const amount = typeof current.dca_base_investment_usd === "string" ? current.dca_base_investment_usd : ""; + effective.dca_mode = mode ? { status: "known", value: mode } : { status: "unknown" }; + effective.dca_base_investment_usd = amount ? { status: "known", value: amount } : { status: "unknown" }; + return finish(); + } + return finish("runtime_setting_unresolved", !DCA_SUPPORTED_PLATFORMS.has(observed.platform) || profile + ? "dca_not_applicable" : "strategy_profile_unresolved"); +} + +export async function accountSettingsProfileRuntimeReadback(env, observed, resolved = null) { + const token = env.RUNTIME_SETTINGS_DISPATCH_TOKEN; + const repository = resolved?.repository ?? platformRepositories(env)?.[observed.platform]; + if (!token || !repository || !observed.config) return null; + const options = Array.isArray(observed.account_options?.[observed.platform]) + ? observed.account_options[observed.platform] : []; + const variableCache = new Map(); + const readVariable = async (repo, scope, githubEnvironment, name) => { + const cacheKey = [repo, scope, githubEnvironment || ""].join("|"); + if (!variableCache.has(cacheKey)) { + variableCache.set(cacheKey, fetchGithubVariables(token, repo, scope, githubEnvironment)); + } + return (await variableCache.get(cacheKey)).get(name) || ""; + }; + try { + const current = await resolveCurrentStrategyForAccount({ + platform: observed.platform, + option: observed.config, + optionsCount: options.length || 1, + repository, + readVariable, + }); + if (!current) return null; + const variableScope = resolved?.variableScope ?? resolveVariableScope(observed.platform, observed.config); + const githubEnvironment = resolved?.githubEnvironment ?? resolveGithubEnvironment(observed.platform, observed.config, variableScope); + const variables = await variableCache.get([repository, variableScope, githubEnvironment || ""].join("|")); + return { + strategy_profile: current.strategy_profile || null, + runtime_target_enabled: variables?.get(RUNTIME_TARGET_ENABLED_VARIABLE) || null, + }; + } catch { + return null; + } +} + +export function hasConfigStore(env) { + return Boolean(configStore(env)); +} + +export function configStore(env) { + const store = env.STRATEGY_SWITCH_CONFIG; + if (!store || typeof store.get !== "function" || typeof store.put !== "function") return null; + return store; +} + +export async function readConfigJson(env, key) { + const store = configStore(env); + if (!store) return null; + const text = await store.get(key); + if (!text) return null; + try { + return JSON.parse(text); + } catch (error) { + throw new Error(`STRATEGY_SWITCH_CONFIG.${key} must be valid JSON`); + } +} + +export async function writeConfigJson(env, key, value) { + const store = configStore(env); + if (!store) throw new Error("STRATEGY_SWITCH_CONFIG KV binding is required"); + const text = JSON.stringify(value, null, 2); + if (indexedKey(key)) await directoryCommand(env, { action: "put", key, value: text }); + else await store.put(key, text); +} + +export async function loadAuditLog(env) { + if (!hasConfigStore(env)) return []; + const payload = await readConfigJson(env, AUDIT_LOG_KEY); + if (!Array.isArray(payload)) return []; + return payload + .filter((entry) => entry && !Array.isArray(entry) && typeof entry === "object") + .slice(0, AUDIT_LOG_LIMIT); +} + +export async function appendAuditLog(env, entry) { + if (!hasConfigStore(env)) return; + let current = []; + try { + current = await loadAuditLog(env); + } catch (error) { + current = []; + } + await writeConfigJson(env, AUDIT_LOG_KEY, [entry, ...current].slice(0, AUDIT_LOG_LIMIT)); +} + +export function accountCounts(accountOptions) { + const counts = {}; + for (const platform of SUPPORTED_PLATFORMS) { + counts[platform] = Array.isArray(accountOptions[platform]) ? accountOptions[platform].length : 0; + } + return counts; +} + +export function uniqueStrings(items) { + const result = []; + const seen = new Set(); + for (const item of items) { + const text = String(item || "").trim().toLowerCase(); + if (!text || seen.has(text)) continue; + seen.add(text); + result.push(text); + } + return result; +} + +export function randomToken() { + const bytes = new Uint8Array(24); + crypto.getRandomValues(bytes); + return base64UrlEncodeBytes(bytes); +} + +export function base64UrlEncodeJson(value) { + return base64UrlEncodeBytes(new TextEncoder().encode(JSON.stringify(value))); +} + +export function base64UrlEncodeBytes(bytes) { + let binary = ""; + for (const byte of bytes) binary += String.fromCharCode(byte); + return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", ""); +} + +export function base64UrlDecode(value) { + const base64 = value.replaceAll("-", "+").replaceAll("_", "/").padEnd(Math.ceil(value.length / 4) * 4, "="); + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index); + return new TextDecoder().decode(bytes); +} + +export function parseCookies(header) { + const result = {}; + for (const part of header.split(";")) { + const [name, ...rest] = part.trim().split("="); + if (!name) continue; + result[name] = decodeURIComponent(rest.join("=")); + } + return result; +} + +export function cookie(name, value, maxAge) { + return `${name}=${encodeURIComponent(value)}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=${maxAge}`; +} + +export function clearCookie(name) { + return `${name}=; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=0`; +} + +export function clearOAuthCookie() { + return { "Set-Cookie": clearCookie(OAUTH_STATE_COOKIE) }; +} + + +export function requireMarketBenchmarkSyncToken(request, env) { + const expected = String(env.MARKET_BENCHMARK_SYNC_TOKEN || env.STRATEGY_SWITCH_SYNC_TOKEN || env.RUNTIME_SETTINGS_DISPATCH_TOKEN || ""); + if (!expected) throw new HttpError("market_benchmark_sync_token_missing", 503); + const token = (request.headers.get("Authorization") || "").match(/^Bearer\s+(.+)$/i)?.[1] || ""; + if (!token || token !== expected) throw new HttpError("unauthorized", 401); + // Fail closed if the bearer equals another dedicated sync token family when those are set differently. + const forbidden = [ + env.ACCOUNT_FACTS_SYNC_TOKEN, + env.IBKR_ACCOUNT_FACTS_SYNC_TOKEN, + env.SCHWAB_ACCOUNT_FACTS_SYNC_TOKEN, + env.FIRSTRADE_ACCOUNT_FACTS_SYNC_TOKEN, + env.BINANCE_ACCOUNT_FACTS_SYNC_TOKEN, + env.EXECUTION_EVIDENCE_SYNC_TOKEN, + ].filter((value) => typeof value === "string" && value && value !== expected); + if (forbidden.includes(token)) throw new HttpError("unauthorized", 401); +} + +export async function marketBenchmarkResponse(request, env) { + if (request.method !== "GET" && request.method !== "HEAD") { + return json({ ok: false, error: "method_not_allowed" }, 405); + } + if (!hasRuntimeInstanceStore(env)) { + return json({ ok: false, error: "market_benchmark_store_unavailable" }, 503); + } + try { + const stored = await runtimeInstanceCommand(env, { + action: "market_benchmark_read", + }); + const body = { + ok: true, + schema_version: MARKET_BENCHMARK_SCHEMA, + inception_date: stored.inception_date ?? null, + timezone: stored.timezone || "Asia/Shanghai", + points: Array.isArray(stored.points) ? stored.points : [], + }; + const headers = responseHeaders({ "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" }); + return new Response(request.method === "HEAD" ? null : JSON.stringify(body), { status: 200, headers }); + } catch (error) { + if (error instanceof HttpError) return json({ ok: false, error: error.message }, error.status || 500); + return json({ ok: false, error: "market_benchmark_unavailable" }, 503); + } +} + + +export function requireMarketBenchmarkRevisionToken(request, env) { + // Dedicated secret only — never fall back to sync/dispatch tokens. + const expected = String(env.MARKET_BENCHMARK_REVISION_TOKEN || ""); + if (!expected) throw new HttpError("market_benchmark_revision_token_missing", 503); + const token = (request.headers.get("Authorization") || "").match(/^Bearer\s+(.+)$/i)?.[1] || ""; + if (!token || token !== expected) throw new HttpError("unauthorized", 401); + const forbidden = [ + env.MARKET_BENCHMARK_SYNC_TOKEN, + env.STRATEGY_SWITCH_SYNC_TOKEN, + env.RUNTIME_SETTINGS_DISPATCH_TOKEN, + env.ACCOUNT_FACTS_SYNC_TOKEN, + env.IBKR_ACCOUNT_FACTS_SYNC_TOKEN, + env.SCHWAB_ACCOUNT_FACTS_SYNC_TOKEN, + env.FIRSTRADE_ACCOUNT_FACTS_SYNC_TOKEN, + env.BINANCE_ACCOUNT_FACTS_SYNC_TOKEN, + env.EXECUTION_EVIDENCE_SYNC_TOKEN, + ].filter((value) => typeof value === "string" && value && value !== expected); + if (forbidden.includes(token)) throw new HttpError("unauthorized", 401); +} + +export async function reviseMarketBenchmarkResponse(request, env) { + try { + requireMarketBenchmarkRevisionToken(request, env); + } catch (error) { + if (error instanceof HttpError) return json({ ok: false, error: error.message }, error.status || 401); + return json({ ok: false, error: "unauthorized" }, 401); + } + if (!hasRuntimeInstanceStore(env)) { + return json({ ok: false, error: "market_benchmark_store_unavailable" }, 503); + } + let raw; + try { + raw = await readBoundedJson(request, 64 * 1024); + } catch (error) { + if (error instanceof HttpError) return json({ ok: false, error: error.message }, error.status || 400); + return json({ ok: false, error: "invalid_market_benchmark_revision_payload" }, 400); + } + try { + const stored = await runtimeInstanceCommand(env, { + action: "market_benchmark_read", + }); + const appliedAt = typeof raw.applied_at === "string" && !Number.isNaN(Date.parse(raw.applied_at)) + ? raw.applied_at + : new Date().toISOString(); + const digestHex = async (value) => sha256Hex(String(value)); + // planRevisionApply expects sync digest; pre-hash with Web Crypto then use a map. + const strings = new Set(); + const proposal = raw.proposal; + if (proposal && Array.isArray(proposal.entries)) { + strings.add(JSON.stringify(proposal.entries)); + for (const entry of proposal.entries) { + if (entry && typeof entry.series_id_sha256 === "string") strings.add(entry.series_id_sha256); + } + } + for (const point of (raw.replacements || [])) { + if (point?.seriesId) strings.add(point.seriesId); + if (point?.close) strings.add(point.close); + } + for (const point of (Array.isArray(stored.points) ? stored.points : [])) { + if (point?.close) strings.add(point.close); + if (point?.seriesId) strings.add(point.seriesId); + } + const cache = new Map(); + for (const value of strings) cache.set(value, await digestHex(value)); + // Also hash every seriesId/close used by planRevisionApply via digest() + const digest = (value) => { + const text = String(value); + if (cache.has(text)) return cache.get(text); + throw new HttpError("benchmark_revision_digest_cache_miss", 500); + }; + // Ensure proposal.entries JSON and all replacement/stored closes/seriesIds are cached — + // re-walk after building any missing by computing individually. + async function ensure(value) { + const text = String(value); + if (!cache.has(text)) cache.set(text, await digestHex(text)); + return cache.get(text); + } + await ensure(JSON.stringify(proposal?.entries || [])); + for (const point of (raw.replacements || [])) { + await ensure(point.seriesId); + await ensure(point.close); + } + for (const point of (Array.isArray(stored.points) ? stored.points : [])) { + await ensure(point.seriesId); + await ensure(point.close); + } + const plan = planRevisionApply({ + proposal: raw.proposal, + approvedProposalSha256: raw.approved_proposal_sha256, + replacements: raw.replacements || [], + storedPoints: Array.isArray(stored.points) ? stored.points : [], + digest: (value) => cache.get(String(value)) || (() => { throw new HttpError("benchmark_revision_digest_cache_miss", 500); })(), + appliedAt, + previouslyAppliedProposalSha256s: [], + }); + const result = await runtimeInstanceCommand(env, { + action: "market_benchmark_revise", + plan: { + proposal_sha256: plan.proposal_sha256, + overwrites: plan.overwrites, + deletes: plan.deletes, + updates: plan.updates, + ledger_entries: plan.ledger_entries, + }, + applied_at: appliedAt, + }); + return json({ + ok: true, + applied: true, + proposal_sha256: result.proposal_sha256 || plan.proposal_sha256, + overwrites: result.overwrites || plan.overwrites, + deletes: 0, + ledger_entries: result.ledger_entries || plan.ledger_entries.length, + log: revisionApplyLogSummary(plan), + }); + } catch (error) { + if (error instanceof HttpError) return json({ ok: false, error: error.message }, error.status || 500); + if (error instanceof MarketBenchmarkRevisionError) return json({ ok: false, error: error.code }, error.status || 400); + if (error instanceof MarketBenchmarkError) return json({ ok: false, error: error.code }, error.status || 400); + return json({ ok: false, error: "market_benchmark_revision_failed" }, 502); + } +} + +export async function syncMarketBenchmarkResponse(request, env) { + try { + requireMarketBenchmarkSyncToken(request, env); + } catch (error) { + if (error instanceof HttpError) return json({ ok: false, error: error.message }, error.status || 401); + return json({ ok: false, error: "unauthorized" }, 401); + } + if (!hasRuntimeInstanceStore(env)) { + return json({ ok: false, error: "market_benchmark_store_unavailable" }, 503); + } + let raw; + try { + raw = await readBoundedJson(request, 64 * 1024); + } catch (error) { + if (error instanceof HttpError) return json({ ok: false, error: error.message }, error.status || 400); + return json({ ok: false, error: "invalid_market_benchmark_payload" }, 400); + } + try { + const result = await runtimeInstanceCommand(env, { + action: "market_benchmark_put", + payload: raw, + }); + return json({ + ok: true, + stored: true, + schema_version: MARKET_BENCHMARK_SCHEMA, + inception_date: result.inception_date ?? null, + written: result.written || 0, + unchanged: result.unchanged || 0, + points: result.points || 0, + }); + } catch (error) { + if (error instanceof HttpError) return json({ ok: false, error: error.message }, error.status || 500); + if (error instanceof MarketBenchmarkError) return json({ ok: false, error: error.code }, error.status || 400); + return json({ ok: false, error: "market_benchmark_sync_failed" }, 502); + } +} + +export async function overviewFxResponse(request, fetchImpl = fetch) { + return overviewFxResponseImpl(request, { + fetchImpl, + fetchWithTimeout, + json, + responseHeaders, + }); +} + +export function json(payload, status = 200, headers = {}) { + return new Response(JSON.stringify(payload, null, 2), { + status, + headers: responseHeaders({ + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "no-store", + }, headers), + }); +} + +export function html(body, status = 200, headers = {}) { + return new Response(body, { + status, + headers: responseHeaders({ + "Content-Type": "text/html; charset=utf-8", + "Cache-Control": "no-store", + }, headers), + }); +} + +export function redirect(location, headers = {}) { + return new Response(null, { + status: 302, + headers: responseHeaders({ Location: location }, headers), + }); +} + +export function responseHeaders(base = {}, extra = {}) { + const headers = new Headers(SECURITY_HEADERS); + appendHeaderEntries(headers, base); + appendHeaderEntries(headers, extra); + return headers; +} + +export function appendHeaderEntries(headers, values) { + for (const [name, value] of Object.entries(values)) { + if (Array.isArray(value)) { + for (const item of value) headers.append(name, item); + } else { + headers.set(name, value); + } + } +} + +export function renderMessage(title, message) { + return `${escapeHtml(title)} + +

${escapeHtml(title)}

${escapeHtml(message)}

返回控制台

`; +} + +export function escapeHtml(value) { + return String(value) + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """); +} diff --git a/web/strategy-switch-console/worker.js b/web/strategy-switch-console/worker.js index 407d74ac..deb8b7a6 100644 --- a/web/strategy-switch-console/worker.js +++ b/web/strategy-switch-console/worker.js @@ -162,636 +162,216 @@ import { researchPromotionApplicationResponse as researchPromotionApplicationResponseImpl, researchPromotionApplicationInternalResponse as researchPromotionApplicationInternalResponseImpl, } from "./application/research_promotion/applications.js"; - -const CYCLE_HEALTH_HISTORY_PAGE_SIZE = 50; -const CYCLE_HEALTH_SUMMARY_SCHEMA_VERSION = "qsl_runtime_cycle_health_state_summary.v1"; -const DEFAULT_REPOSITORY = "QuantStrategyLab/QuantRuntimeSettings"; -const DEFAULT_WORKFLOW = "manual-strategy-switch.yml"; -const ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_PATH = "/api/internal/account-settings/profile-application/claim"; -const ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_AUDIENCE = "qrs-profile-application"; -const ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_REPOSITORY = "QuantStrategyLab/QuantRuntimeSettings"; -const ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_REF = "refs/heads/main"; -const ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_WORKFLOW_REF = "QuantStrategyLab/QuantRuntimeSettings/.github/workflows/apply-approved-hk-profile.yml@refs/heads/main"; -const ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_JWKS_URL = "https://token.actions.githubusercontent.com/.well-known/jwks"; -const ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_MAX_TOKEN_BYTES = 8192; -const ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_MAX_JWKS_BYTES = 32 * 1024; -const ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_TIMEOUT_MS = 5000; -const ACCOUNT_SETTINGS_PROFILE_APPLICATION_REQUEST_TIMEOUT_MS = 1000; -const ACCOUNT_SETTINGS_PROFILE_APPLICATION_MAX_BODY_BYTES = 1024; -const SESSION_COOKIE = "qsl_switch_session"; -const OAUTH_STATE_COOKIE = "qsl_switch_oauth_state"; -const SESSION_TTL_SECONDS = 8 * 60 * 60; -const AUTH_CONFIG_KEY = "auth_config"; -const ACCOUNT_OPTIONS_KEY = "account_options"; -const STRATEGY_PROFILES_KEY = "strategy_profiles"; -// Risk preferences are a separate, low-frequency owner-intent record. They -// must never be merged into a strategy switch input, runtime target, or any -// execution credential/policy path. -const RISK_PROFILE_BINDINGS_KEY = "risk_profile_bindings"; -const RISK_PROFILE_BINDING_REGISTRY_SCHEMA_VERSION = "qsl.risk_profile_binding_registry.v1"; -const RISK_PROFILE_BINDING_SCHEMA_VERSION = "qsl.risk_profile_binding.v1"; -const RISK_PROFILE_SELECTION_SCHEMA_VERSION = "qsl.risk_profile_selection.v1"; -const RISK_PROFILE_IDS = { - CAPITAL_PRESERVATION: "capital_preservation_v1", - BALANCED_COMPOUNDING: "balanced_compounding_v1", - GROWTH_COMPOUNDING: "growth_compounding_v1", -}; -const RISK_PROFILE_PREFERENCES = Object.keys(RISK_PROFILE_IDS); -const DECISION_ACTION_SCHEMA_VERSION = "qsl.decision_action_result.v1"; -const OPERATION_SCHEMA_VERSION = "qsl.operation.v1"; -const AUDIT_EVENTS_SCHEMA_VERSION = "qsl.audit_events.v1"; -const DECISION_ACTION_MAX_BODY_BYTES = 8 * 1024; -const AUDIT_EVENTS_MAX_ROWS = 101; -const AUDIT_LOG_KEY = "audit_log"; -const AUDIT_LOG_LIMIT = 50; -const CURRENT_STRATEGIES_TIMEOUT_MS = 25000; -const CURRENT_STRATEGIES_CACHE_KEY = "current_strategies_cache"; -// The keep-alive bump stays below the KV fresh window so one missed cron tick -// (the schedule is */5) still leaves the entry inside it; a cold isolate then -// never has to fan out to GitHub just because the stamp aged out. -const CURRENT_STRATEGIES_MEMO_TTL_MS = 60_000; // 60 sec — a warm isolate re-checks GitHub after this -const CURRENT_STRATEGIES_KV_FRESH_MS = 1_800_000; // 30 min — a cold isolate serves the KV entry without any GitHub fan-out -const CURRENT_STRATEGIES_STALE_TTL_MS = 3_600_000; // 60 min — return stale plus background refresh before blocking on GitHub -const CURRENT_STRATEGIES_KV_BUMP_MS = 900_000; // 15 min — ceiling for ts-only rewrites when the content did not change -const GITHUB_API_TIMEOUT_MS = 8000; -const GITHUB_LOGIN_REQUEST_TIMEOUT_MS = 20000; -const UX1_CALCULATOR_DEADLINE_MS = 30000; -const UX1_CALCULATOR_MAX_BYTES = 65536; -const UX1_RESEARCH_REPOSITORY = "QuantStrategyLab/UsEquityStrategies"; -const UX1_RESEARCH_WORKFLOW_FILE = "ux1-research-preview.yml"; -const UX1_RESEARCH_WORKFLOW_PATH = ".github/workflows/ux1-research-preview.yml"; -const UX1_JOB_BODY_MAX_BYTES = 80 * 1024; -const UX1_GITHUB_DEADLINE_MS = 8000; -const UX1_GITHUB_MAX_BYTES = 65536; -const UX1_JOB_UNKNOWN_AFTER_MS = 30 * 60 * 1000; -const UX1_PUBLIC_JOB_REASONS = new Set([ - "dispatch_rejected", - "dispatch_unconfirmed", - "input_unavailable", - "calculator_failed", - "check_required", - "config_changed", -]); -const STRATEGY_HEALTH_SNAPSHOT_KEY = "strategy_health_snapshot"; -const STRATEGY_HEALTH_MAX_BODY_BYTES = 256 * 1024; -const STRATEGY_HEALTH_DEFAULT_STALE_TTL_SECONDS = 2 * 60 * 60; -const STRATEGY_HEALTH_STATUSES = ["healthy", "watch", "review", "critical"]; -// Derived from the generated platform config so a domain is declared once. -const SUPPORTED_STRATEGY_DOMAINS = Object.freeze([ - ...new Set(Object.values(PLATFORM_CONFIG).flatMap((platform) => platform.supported_domains || [])), -]); -const STRATEGY_HEALTH_DOMAINS = SUPPORTED_STRATEGY_DOMAINS; -const STRATEGY_HEALTH_DATA_STATUSES = ["ready", "unavailable", "stale"]; -const CONTROL_PLANE_SNAPSHOT_KEY = "control_plane_snapshot"; -const CONTROL_PLANE_SOURCE_PREFIX = "control_plane_source:"; -const CONTROL_PLANE_SOURCE_SCHEMA_VERSION = "qsl_control_plane_source_snapshot.v1"; -const CONTROL_PLANE_SOURCE_RETIREMENTS_KEY = "control_plane_source_retirements"; -const CONTROL_PLANE_ATTENTION_STATUSES = ["research_only", "attention_required", "unavailable"]; -const CONTROL_PLANE_MAX_SOURCES = 100; -const CONTROL_PLANE_MAX_BODY_BYTES = 256 * 1024; -// The current source is a once-per-trading-day P1/P3 lane, rather than a live -// execution heartbeat. Freshness therefore preserves the normal TTL, but also -// waits for the next scheduled research window across the weekend before it -// calls an otherwise usable Friday/Saturday snapshot stale. -const CONTROL_PLANE_DEFAULT_STALE_TTL_SECONDS = 36 * 60 * 60; -const CONTROL_PLANE_RESEARCH_SCHEDULE_UTC_DAYS = new Set([2, 3, 4, 5, 6]); -const CONTROL_PLANE_RESEARCH_SCHEDULE_UTC_HOUR = 3; -const CONTROL_PLANE_RESEARCH_DELIVERY_GRACE_MS = 4 * 60 * 60 * 1000; -const CONTROL_PLANE_CANDIDATE_KINDS = ["individual", "portfolio", "plugin"]; -const CONTROL_PLANE_STAGES = ["P1", "P2", "P3", "P4", "P5", "P6"]; -const CONTROL_PLANE_LIFECYCLE_STATUSES = [ - "research", "evidence_pending", "verified", "deferred", "parked", "paper", "shadow", "owner_decision_required", -]; -const CONTROL_PLANE_RECOMMENDATIONS = [ - "none", "keep_research", "defer", "park", "auto_paper_evaluation", "auto_shadow_evaluation", "owner_live_decision", -]; -const CONTROL_PLANE_AUTOMATION_STATES = ["not_configured", "configured", "active"]; -const FORWARD_OBSERVATION_STATES = [ - "PARKED", "FORWARD_ACTIVE", "PAUSED", "FORWARD_COMPLETE_HUMAN_REVIEW", "MANUAL_HOLD", "IDENTITY_MISMATCH", "RISK_BLOCKED", "REVOKED", "SUPERSEDED", -]; -const FORWARD_OBSERVATION_RESEARCH_STAGES = new Set(["P1", "P2", "P3", "P4"]); -// M1 adaptive selections are a separate read-only projection. They are not -// lifecycle evidence, an account instruction, or an execution permission. -const ADAPTIVE_SELECTION_SOURCE_PREFIX = "adaptive_selection_source:"; -const ADAPTIVE_SELECTION_SOURCE_SCHEMA_VERSION = "qsl.adaptive_selection_source_snapshot.v1"; -const ADAPTIVE_SELECTION_DASHBOARD_SCHEMA_VERSION = "qsl.adaptive_selection_dashboard.v1"; -const ADAPTIVE_SELECTION_DECISION_SCHEMA_VERSION = "qsl.selection_decision.v1"; -const ADAPTIVE_SELECTION_MAX_SOURCES = 100; -const ADAPTIVE_SELECTION_MAX_BODY_BYTES = 256 * 1024; -const ADAPTIVE_SELECTION_DEFAULT_STALE_TTL_SECONDS = 36 * 60 * 60; -const ADAPTIVE_SELECTION_AUTHORITY = "shadow_only"; -// M0 research arrives through a separate, signed-at-transport-boundary -// snapshot. It is deliberately neither an M1 selection input nor a research -// task: this Worker only retains a closed, no-order ledger for authenticated -// readers. No strategy/platform/runtime/broker helper may consume it here. -const M0_RESEARCH_TRANSPORT_SCHEMA_VERSION = "qsl_m0_research_publisher_envelope.v1"; -const M0_RESEARCH_LEDGER_SCHEMA_VERSION = "qsl_m0_research_ledger.v1"; -const M0_RESEARCH_DASHBOARD_SCHEMA_VERSION = "qsl_m0_research_dashboard.v1"; -const M0_RESEARCH_STORAGE_SCHEMA_VERSION = "qsl_m0_research_ledger_storage.v1"; -const M0_RESEARCH_CURRENT_KEY = "m0_research_ledger_current"; -const M0_RESEARCH_ARCHIVE_PREFIX = "m0_research_ledger_archive:"; -const M0_RESEARCH_MAX_BODY_BYTES = 256 * 1024; -const M0_RESEARCH_RETENTION_SECONDS = 14 * 24 * 60 * 60; -const M0_RESEARCH_ALLOWED_SOURCE_REPOSITORY = "QuantStrategyLab/QuantAdvisorResearch"; -const M0_RESEARCH_ALLOWED_PRODUCER_REPOSITORY = "QuantStrategyLab/QuantRuntimeSettings"; -const M0_RESEARCH_SUBJECT_KINDS = ["asset_idea", "theme_context", "strategy_hypothesis", "risk_context"]; -const M0_RESEARCH_HORIZONS = ["short", "medium", "long", "not_applicable"]; -const M0_RESEARCH_STATES = ["candidate", "source_verification_required", "deferred", "context_only"]; -const M0_RESEARCH_CONFIDENCE = ["high", "medium", "low", "mixed", "no_event", "unknown"]; -const M0_RESEARCH_STYLES = ["event_driven", "long_horizon_growth", "value_quality", "macro_context", "mixed_research"]; -const M0_RESEARCH_SUMMARY_FIELDS = [ - "subject_count", - "observation_count", - "fresh_observation_count", - "stale_observation_count", - "unknown_observation_count", - "horizon_conflict_count", - "historical_stale_horizon_drift_count", -]; -// A console decision is intentionally an auditable owner intent, not an -// execution permit. It remains separate from workflow dispatch credentials, -// broker credentials, and any future deterministic execution gateway. -const OWNER_DECISION_INTENT_PREFIX = "owner_decision_intent:"; -const OWNER_DECISION_CURRENT_PREFIX = "owner_decision_current:"; -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_CONTROLLER_READ_SCHEMA_VERSION = "qsl_reconciliation_recovery_controller_read.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_MAX_SAMPLE_WINDOW_MS = 15 * 60 * 1000; -const RECONCILIATION_RECOVERY_PLATFORMS = OBSERVABILITY_PLATFORM_IDS; -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; -// Private Binance holdings use one isolated KV record. They are never folded -// into public health, recovery, control-plane, or admin aggregates. -const BINANCE_PRIVATE_SCOPE_KEY = "private_binance_scope_report"; -const BINANCE_PRIVATE_SCOPE_MAX_BODY_BYTES = 256 * 1024; -const BINANCE_PRIVATE_SCOPE_MAX_ASSETS = 5000; -const BINANCE_PRIVATE_SCOPE_WRITE_TTL_SECONDS = 24 * 60 * 60; -const BINANCE_PRIVATE_SCOPE_INGEST_MAX_AGE_MS = 10 * 60 * 1000; -const BINANCE_PRIVATE_SCOPE_FUTURE_SKEW_MS = 60 * 1000; -const BINANCE_PRIVATE_SCOPE_READ_MAX_AGE_MS = 24 * 60 * 60 * 1000; -// 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. -// Keeping this contract separate prevents a P1/P3 source from accidentally -// turning into an execution or P6 authority source. -const EXECUTION_EVIDENCE_SOURCE_PREFIX = "execution_evidence_source:"; -const RUNTIME_DAILY_KEY_PREFIX = "runtime_daily:"; -const RUNTIME_DAILY_MAX_BODY_BYTES = 64 * 1024; -const RUNTIME_DAILY_MAX_RUNS = 20; -const RUNTIME_DAILY_MAX_LIST = 20; -// History coverage lists the already-stored business dates for one target. It -// never reads record bodies and stays bounded so a large backlog cannot stall -// the read model; hitting the page cap only marks the listing as truncated. -const RUNTIME_DAILY_HISTORY_PAGE = 100; -const RUNTIME_DAILY_HISTORY_MAX_PAGES = 10; -const RUNTIME_DAILY_STALE_MS = 36 * 60 * 60 * 1000; -const RUNTIME_DAILY_FUTURE_SKEW_MS = 5 * 60 * 1000; -const RUNTIME_DAILY_STATUSES = [ - "no_submission", "no_signal", "no_rebalance", "submitted", "broker_acknowledged", "partially_filled", "filled", - "reconciliation_required", "unknown", "failed", "blocked", "dry_run", "shadow", "validation", - "not_due", "market_closed", "outside_window", "within_grace", "missing_report", "read_incomplete", "insufficient", "conflict", -]; -const RUNTIME_DAILY_SCHEDULE_ONLY = ["not_due", "market_closed", "outside_window", "within_grace"]; -const RUNTIME_DAILY_ANOMALY_STATUSES = [ - "reconciliation_required", "unknown", "failed", "blocked", "conflict", "missing_report", "read_incomplete", "insufficient", - "dry_run", "shadow", "validation", -]; -const RUNTIME_DAILY_ACTIVITIES = [ - "no_submission", "no_signal", "no_rebalance", "submitted", "broker_acknowledged", "partially_filled", "filled", - "reconciliation_required", "unknown", "failed", "blocked", "previewed", "insufficient", -]; -const RUNTIME_DAILY_LANES = ["paper", "live", "dry_run", "shadow", "validation", "insufficient"]; -const RUNTIME_DAILY_SCHEDULE_STATES = ["unevaluable", "not_due", "market_closed", "within_grace", "due", "outside_window"]; -const RUNTIME_DAILY_KINDS = ["run", "schedule", "incomplete"]; -const EXECUTION_EVIDENCE_SOURCE_SCHEMA_VERSION = "qsl_execution_evidence_source_snapshot.v1"; -const EXECUTION_EVIDENCE_DASHBOARD_SCHEMA_VERSION = "qsl_execution_evidence_dashboard.v1"; -const EXECUTION_EVIDENCE_MAX_SOURCES = 100; -const EXECUTION_EVIDENCE_MAX_BODY_BYTES = 256 * 1024; -const EXECUTION_EVIDENCE_DEFAULT_STALE_TTL_SECONDS = 36 * 60 * 60; -const EXECUTION_EVIDENCE_PLATFORMS = OBSERVABILITY_PLATFORM_IDS; -const EXECUTION_EVIDENCE_ENVIRONMENTS = ["shadow", "paper", "live"]; -const EXECUTION_EVIDENCE_CAPABILITIES = ["available", "unavailable", "unknown"]; -const EXECUTION_EVIDENCE_STATUSES = ["verified", "pending", "unavailable", "not_applicable"]; -const EXECUTION_RECEIPT_OUTCOMES = [ - "not_due", "no_action", "no_signal", "no_rebalance", "risk_blocked", "submitted", "broker_acknowledged", "partially_filled", "filled", "reconciliation_required", "failed", -]; -const EXECUTION_RECEIPT_CONFIRMATIONS = [ - "not_applicable", "not_observed", "acknowledged", "partially_filled", "filled", "reconciliation_required", -]; -const EXECUTION_EVIDENCE_RECOMMENDATIONS = [ - "continue_autonomous_shadow", "run_autonomous_paper", "owner_limited_live_canary_decision", "parked", -]; -const EXECUTION_EVIDENCE_REASON_CODES = [ - "none", "target_execution_evidence_missing", "paper_not_supported", "paper_execution_evidence_needed", - "policy_not_active", "source_stale", "manual_live_decision_required", "target_execution_receipt_observed", - "target_execution_reconciliation_required", "target_execution_receipt_failed", -]; -// Runtime target lifecycle is intentionally separate from execution evidence: -// it records whether a target is enabled and whether its no-order monitors are -// healthy. A disabled target is not a missing broker or a paper/live result. -const RUNTIME_TARGET_LIFECYCLE_SOURCE_PREFIX = "runtime_target_lifecycle_source:"; -const RUNTIME_TARGET_LIFECYCLE_SOURCE_SCHEMA_VERSION = "qsl_runtime_target_lifecycle_source_snapshot.v1"; -const RUNTIME_TARGET_LIFECYCLE_DASHBOARD_SCHEMA_VERSION = "qsl_runtime_target_lifecycle_dashboard.v1"; -const RUNTIME_TARGET_LIFECYCLE_MAX_SOURCES = 100; -const RUNTIME_TARGET_LIFECYCLE_MAX_BODY_BYTES = 256 * 1024; -const RUNTIME_TARGET_LIFECYCLE_CONFIGURED_STATES = ["enabled", "disabled"]; -const RUNTIME_TARGET_LIFECYCLE_EXECUTION_MODES = ["dry_run", "paper", "live"]; -const RUNTIME_TARGET_LIFECYCLE_CHECK_STATUSES = ["pass", "attention", "not_due", "not_applicable", "unavailable"]; -const RUNTIME_TARGET_LIFECYCLE_DISPOSITIONS = ["continue_enabled_monitoring", "continue_disabled_validation", "parked"]; -const RUNTIME_TARGET_LIFECYCLE_EXECUTION_OBSERVATIONS = ["not_due", "monitoring_only", "not_applicable", "attention", "unavailable"]; -const RUNTIME_TARGET_LIFECYCLE_REASON_CODES = [ - "none", "target_intentionally_disabled", "runtime_guard_attention", "execution_heartbeat_attention", "monitoring_unavailable", -]; -const ACCOUNT_DIAGNOSIS_REQUEST_PATH = "/api/account-diagnosis"; -const ACCOUNT_DIAGNOSIS_INTERNAL_PREFIX = "/api/internal/account-diagnosis/"; -const ACCOUNT_DIAGNOSIS_TARGET_ID = "binance.crypto_live_pool_rotation"; -const ACCOUNT_DIAGNOSIS_TARGETS = Object.freeze({ - [ACCOUNT_DIAGNOSIS_TARGET_ID]: Object.freeze({ - platform: "binance", - recheckRepository: "QuantStrategyLab/BinancePlatform", - recheckWorkflow: "runtime-target-lifecycle.yml", - }), - "longbridge.paper": Object.freeze({ - platform: "longbridge", - recheckRepository: "QuantStrategyLab/LongBridgePlatform", - recheckWorkflow: "runtime-target-lifecycle.yml", - }), - "longbridge.hk": Object.freeze({ - platform: "longbridge", - recheckRepository: "QuantStrategyLab/LongBridgePlatform", - recheckWorkflow: "runtime-target-lifecycle.yml", - }), - "longbridge.sg": Object.freeze({ - platform: "longbridge", - recheckRepository: "QuantStrategyLab/LongBridgePlatform", - recheckWorkflow: "runtime-target-lifecycle.yml", - }), - "schwab.primary": Object.freeze({ - platform: "schwab", - recheckRepository: "QuantStrategyLab/CharlesSchwabPlatform", - recheckWorkflow: "runtime-target-lifecycle.yml", - }), - "firstrade.primary": Object.freeze({ - platform: "firstrade", - recheckRepository: "QuantStrategyLab/FirstradePlatform", - recheckWorkflow: "runtime-target-lifecycle.yml", - }), -}); -const ACCOUNT_DIAGNOSIS_TRIGGERS = ["incident", "manual_check"]; -const ACCOUNT_DIAGNOSIS_STATUSES = ["queued", "running", "succeeded", "failed", "unknown"]; -const ACCOUNT_DIAGNOSIS_REASON_CODES = ["diagnosis_ready", "capacity_unavailable", "codex_unavailable", "invalid_response"]; -const ACCOUNT_DIAGNOSIS_MAX_BODY_BYTES = 16 * 1024; -const ACCOUNT_DIAGNOSIS_SUMMARY_MAX_CHARS = 600; -const ACCOUNT_DIAGNOSIS_DEDUPE_WINDOW_MS = 24 * 60 * 60 * 1000; -const ACCOUNT_DIAGNOSIS_AUTO_ENABLED_ENV = "ACCOUNT_DIAGNOSIS_AUTO_ENABLED"; -const ACCOUNT_DIAGNOSIS_DO_ACTIONS = new Set([ - "diagnosis_create", "diagnosis_latest", "diagnosis_read", "diagnosis_mark_dispatch", "diagnosis_claim", "diagnosis_result", "diagnosis_mark_recheck", "diagnosis_native_intent", "diagnosis_native_save", "diagnosis_native_cancel", -]); -/** Stable 409/qualification reason codes for /api/account-diagnosis (P0-08). */ -const ACCOUNT_DIAGNOSIS_CONFLICT_REASON_TEXT = Object.freeze({ - account_diagnosis_config_unavailable: "账户选项配置不可用,无法绑定诊断身份。", - account_diagnosis_account_not_found: "未找到匹配的账户选项。", - account_diagnosis_account_not_unique: "账户选项不唯一,无法安全绑定诊断身份。", - account_diagnosis_target_not_admitted: "该 target 未纳入账户诊断准入清单。", - account_diagnosis_identity_conflict: "target 与账户身份冲突或不唯一。", - account_diagnosis_lifecycle_store_unavailable: "生命周期配置存储不可用。", - account_diagnosis_lifecycle_match_missing: "缺少匹配的生命周期观测来源。", - account_diagnosis_lifecycle_match_ambiguous: "生命周期观测来源不唯一。", - account_diagnosis_source_unavailable: "生命周期来源不可用(非 ready/stale)。", - account_diagnosis_observation_missing: "缺少观测时间,无法确认诊断资格。", - account_diagnosis_incident_requires_attention: "incident 触发需要已确认的监测关注项。", - account_diagnosis_native_binding_conflict: "诊断原生任务身份或材料绑定冲突。", - account_diagnosis_workflow_binding_conflict: "诊断工作流运行绑定冲突。", - account_diagnosis_schema_incompatible: "诊断上下文 schema 不兼容。", -}); -function accountDiagnosisConflict(reasonCode, status = 409) { - const reason = ACCOUNT_DIAGNOSIS_CONFLICT_REASON_TEXT[reasonCode] || ""; - return new HttpError(reasonCode, status, { reason_code: reasonCode, reason }); -} -function accountDiagnosisErrorPayload(error) { - const reason_code = String(error?.reason_code || error?.message || "account_diagnosis_unavailable"); - const payload = { ok: false, error: reason_code, reason_code }; - const reason = String(error?.reason || ACCOUNT_DIAGNOSIS_CONFLICT_REASON_TEXT[reason_code] || "").trim(); - if (reason) payload.reason = reason; - return payload; -} -const HUMAN_DECISION_DO_ACTIONS = new Set([ - "owner_sync_materials", "recovery_sync_materials", "promotion_sync_materials", - "owner_decide", "recovery_decide", "promotion_decide", - "owner_read", "recovery_read", "promotion_read", -]); -const ACCOUNT_SETTINGS_DO_ACTIONS = new Set([ - "risk_profile_read", "risk_profile_replace", "risk_profile_set", - "account_settings_read", "account_settings_save", "account_declarations_read", - "account_settings_profile_application_lookup", "account_settings_profile_application_approve", - "account_settings_profile_application_claim", -]); -const ACCOUNT_FACTS_DO_ACTIONS = new Set([ - "account_external_flow_put", - "account_executions_put", "account_executions_read", - "account_period_return_put", "account_period_return_read", "account_daily_native_return_research_read", - "account_facts_put", "account_facts_read", "account_facts_list", "account_facts_history_read", "account_facts_history_put", - "binance_wallet_history_put", "binance_wallet_history_read", - "binance_detail_put", "binance_detail_read", -]); -const MARKET_BENCHMARK_DO_ACTIONS = new Set([ - "market_benchmark_put", "market_benchmark_read", "market_benchmark_revise", -]); -const HK_STOP_DO_ACTIONS = new Set(["hk_stop_claim", "hk_stop_record", "hk_stop_read", "hk_stop_accept_result"]); -const RUNTIME_CONTROL_DO_ACTIONS = new Set(["runtime_control_claim", "runtime_control_record", "runtime_control_read", "runtime_control_resolve", "runtime_control_capture", "runtime_control_begin_check", "runtime_control_record_check"]); -const HK_STOP_TARGET_ID = "longbridge/hk"; -const HK_STOP_REQUEST_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/; -const ACCOUNT_SETTING_OVERRIDE_FIELDS = ["strategy_profile", "income_layer_enabled", "option_overlay_enabled", "reserved_cash_floor", "reserved_cash_ratio", "dca_mode", "dca_base_investment_usd"]; -const ACCOUNT_SETTINGS_PROFILE_APPLICATION_REQUEST_KEY = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/; -// Research tasks are a separate, immutable and no-order index. They do not -// share storage or a sync credential with candidate lifecycle snapshots. -const RESEARCH_TASK_SOURCE_PREFIX = "research_task_source:"; -const RESEARCH_TASK_SOURCE_SCHEMA_VERSION = "qsl_research_task_source_snapshot.v1"; -const RESEARCH_TASK_SCHEMA_VERSION = "qsl.research_task.v1"; -const RESEARCH_TASK_DASHBOARD_SCHEMA_VERSION = "qsl_research_task_dashboard.v1"; -const RESEARCH_TASK_MAX_SOURCES = 100; -const RESEARCH_TASK_MAX_BODY_BYTES = 256 * 1024; -const RESEARCH_TASK_DEFAULT_STALE_TTL_SECONDS = 36 * 60 * 60; -const RESEARCH_TASK_TYPES = [ - "strategy_diagnosis", "hypothesis_evaluation", "parameter_challenge", "strategy_candidate", "portfolio_candidate", "plugin_candidate", -]; -const RESEARCH_TASK_OBJECTIVES = ["diagnose_degradation", "test_hypothesis", "challenge_parameters", "evaluate_candidate"]; - -// Research promotion tickets mirror QPK research_promotion_cycle.to_dict(). -// Console may record human intent only; live_authority_granted stays false. -const RESEARCH_PROMOTION_TICKET_PREFIX = "research_promotion_ticket:"; -const RESEARCH_PROMOTION_TICKET_SCHEMA = "qsl.research_promotion_ticket.v1"; -const RESEARCH_PROMOTION_QUEUE_SCHEMA = "qsl.research_promotion_ticket_queue.v1"; -const RESEARCH_PROMOTION_MAX_TICKETS = 100; -const RESEARCH_PROMOTION_MAX_BODY_BYTES = 64 * 1024; -const RESEARCH_PROMOTION_STATES = [ - "parked", - "bounded_reopt", - "shadow_recorded", - "awaiting_human", - "human_accepted", - "human_rejected", -]; -const RESEARCH_PROMOTION_RISK_PROFILES = [ - "CAPITAL_PRESERVATION", - "BALANCED_COMPOUNDING", - "GROWTH_COMPOUNDING", -]; -const RESEARCH_PROMOTION_EXECUTION_MODES = ["live", "paper"]; -const DEFAULT_RESEARCH_PROMOTION_RISK_PROFILE = "CAPITAL_PRESERVATION"; -const V7_PAPER_PREVIEW_PROFILE = "soxl_soxx_core_only_p2_v7_longterm_compounding_cash_reserve"; -const V7_PAPER_APPLICATION_WORKFLOW_REPOSITORY = "QuantStrategyLab/LongBridgePlatform"; -const V7_PAPER_APPLICATION_WORKFLOW = "apply-paper-candidate.yml"; -const V7_PAPER_APPLICATION_UES_REVISION = "b83ef4b3ae67c47d132ddd660ba3ccc60d474c85"; -// Reviewed LongBridge application merge SHA for the paused validation slice. -// This remains a server constant and is never caller input or an enablement. -const V7_PAPER_APPLICATION_SOURCE_COMMIT = "dcdcb579001e0be5579810121fc7894206e06445"; -const V7_PAPER_APPLICATION_TICKET_RE = /^rpt_[0-9a-f]{64}$/; -const V7_PAPER_APPLICATION_ACCOUNT_KEY = "paper"; -const V7_PAPER_APPLICATION_ACCOUNT_SCOPE = "PAPER"; -const V7_PAPER_APPLICATION_SERVICE = "longbridge-quant-paper-service"; -const V7_PAPER_APPLICATION_MAX_BODY_BYTES = 16 * 1024; -const V7_PAPER_APPLICATION_STATUSES = ["approved", "claimed", "applied_paused", "rejected", "uncertain"]; -const V7_PAPER_APPLICATION_DISPATCH_STATES = ["pending", "sent", "unknown"]; -const V7_PAPER_APPLICATION_REASON_CODES = ["", "readback_mismatch", "workflow_rejected", "workflow_uncertain", "dispatch_unverified", "account_changed", "qualification_failed"]; -const V7_PAPER_APPLICATION_DO_ACTIONS = new Set([ - "application_create", "application_latest", "application_read", "application_mark_dispatch", "application_claim", "application_result", -]); -// The workflow has an explicit fail-closed guard until immutable activation -// and durable single-use consumption are connected. Keep the console from -// dispatching a workflow that cannot apply the requested target. -const SWITCH_ACTIVATION_CONNECTED = false; - - -const RISK_ENVELOPE_PREFERENCE_META = { - CAPITAL_PRESERVATION: { - short_zh: "保全", - short_en: "Preserve", - composer_mdd_multiple: 1.0, - promotion_size_scale: 0.5, - }, - BALANCED_COMPOUNDING: { - short_zh: "均衡", - short_en: "Balance", - composer_mdd_multiple: 1.25, - promotion_size_scale: 0.75, - }, - GROWTH_COMPOUNDING: { - short_zh: "增长", - short_en: "Growth", - composer_mdd_multiple: 1.5, - promotion_size_scale: 1.0, - }, -}; - -/** Read-only console view: preference / capital band / status lamp. No KV writes. */ -function buildRiskEnvelopeView({ riskPreference = null, equityUsd = null, status = null } = {}) { - const preferenceRaw = String(riskPreference || "").trim().toUpperCase(); - const preference = RESEARCH_PROMOTION_RISK_PROFILES.includes(preferenceRaw) ? preferenceRaw : null; - const meta = preference ? RISK_ENVELOPE_PREFERENCE_META[preference] : null; - const hasEquity = equityUsd != null && Number.isFinite(Number(equityUsd)); - // Equity injection is not wired yet — band/status/capital_scale stay unknown. - const source = hasEquity ? "reconciled_equity" : "design_preview"; - const statusId = hasEquity && status ? String(status) : "unknown"; - const statusLabels = { - normal: { zh: "正常", en: "Normal" }, - stepped_down: { zh: "已降档", en: "Stepped down" }, - new_risk_blocked: { zh: "禁止新增风险", en: "New risk blocked" }, - unknown: { zh: "待对账权益注入", en: "Awaiting reconciled equity" }, - }; - const statusLabel = statusLabels[statusId] || statusLabels.unknown; - return { - schema: "qsl.risk_envelope_view.v1", - source, - preference: preference - ? { - id: preference, - label_zh: meta.short_zh, - label_en: meta.short_en, - } - : { - id: "unknown", - label_zh: "未设定", - label_en: "Unset", - }, - capital_band: { - id: "unknown", - label_zh: "待对账权益注入", - label_en: "Awaiting reconciled equity", - }, - status: { - id: statusId, - label_zh: statusLabel.zh, - label_en: statusLabel.en, - }, - scales: { - composer_mdd_multiple: meta ? meta.composer_mdd_multiple : null, - promotion_size_scale: meta ? meta.promotion_size_scale : null, - capital_scale: null, - vol_scale: null, - dd_scale: null, - }, - detail: { - dual_scale_note_zh: - "双口径:Composer 相对无杠杆基准 MDD 天花板为 1.00 / 1.25 / 1.50;晋级仓位缩放为 0.50 / 0.75 / 1.00(仅新晋级/材料变更)。资金信封 combined_scale = capital_scale × vol_scale × dd_scale(各因子 ≤1),由系统按权益/波动/回撤计算,禁止自动升档。", - dual_scale_note_en: - "Dual scale: Composer unlevered-benchmark MDD caps are 1.00 / 1.25 / 1.50; promotion size scales are 0.50 / 0.75 / 1.00 (new promotion / material change only). Envelope combined_scale = capital_scale × vol_scale × dd_scale (each ≤1), system-computed from equity/vol/drawdown; auto step-up is forbidden.", - }, - live_authority_granted: false, - }; -} - -function attachRiskEnvelopeView(ticket) { - if (!ticket || typeof ticket !== "object") return ticket; - return { - ...ticket, - risk_envelope_view: buildRiskEnvelopeView({ - riskPreference: ticket.suggested_risk_profile, - equityUsd: null, - }), - }; -} - - - -const SUPPORTED_PLATFORMS = Object.keys(PLATFORM_CONFIG); - -function validateAccountOptionsSchemaOrThrow(payload, fieldName = "account_options") { - return normalizeAccountOptionsSchemaPayload(payload, fieldName); -} - - -const LIVE_CONTINUITY_STATES = [ - "NONE", - "ACTIVE_LKG", - "ACTIVE_REDUCED", - "RECONCILE_ONLY", - "RISK_REDUCTION_ONLY", - "PAUSED", - "ROLLBACK_LKG", -]; -const LEGACY_CASH_ONLY_EXECUTION_VARIABLE = "CASH_ONLY_EXECUTION"; -const CASH_ONLY_EXECUTION_MODES = ["current", "enabled", "disabled"]; -const INCOME_LAYER_ENABLED_VARIABLE = "INCOME_LAYER_ENABLED"; -const INCOME_LAYER_START_USD_VARIABLE = "INCOME_LAYER_START_USD"; -const INCOME_LAYER_MAX_RATIO_VARIABLE = "INCOME_LAYER_MAX_RATIO"; -const OPTION_OVERLAY_ENABLED_VARIABLE = "OPTION_OVERLAY_ENABLED"; -const RUNTIME_TARGET_ENABLED_VARIABLE = "RUNTIME_TARGET_ENABLED"; -const DCA_MODE_VARIABLE = "DCA_MODE"; -const DCA_BASE_INVESTMENT_VARIABLE = "DCA_BASE_INVESTMENT_USD"; -const IBIT_ZSCORE_EXIT_MODE_VARIABLE = "IBIT_ZSCORE_EXIT_MODE"; -const IBIT_ZSCORE_EXIT_ENABLED_VARIABLE = "IBIT_ZSCORE_EXIT_ENABLED"; -const IBIT_ZSCORE_EXIT_PARKING_SYMBOL_VARIABLE = "IBIT_ZSCORE_EXIT_PARKING_SYMBOL"; -const LEGACY_INCOME_LAYER_CONTROL_FIELDS = [ - "income_threshold_usd", - "qqqi_income_ratio", - "income_layer_qqqi_weight", - "income_layer_spyi_weight", -]; -const LEGACY_INCOME_LAYER_VARIABLES = [ - "INCOME_THRESHOLD_USD", - "QQQI_INCOME_RATIO", - "INCOME_LAYER_QQQI_WEIGHT", - "INCOME_LAYER_SPYI_WEIGHT", -]; -const OPTION_OVERLAY_CONTROL_FIELDS = [ - "option_overlay_enabled", - "option_growth_overlay_enabled", - "option_growth_overlay_recipe", - "option_growth_overlay_start_usd", - "option_growth_overlay_nav_budget_ratio", - "option_income_overlay_enabled", - "option_income_overlay_recipe", - "option_income_overlay_start_usd", - "option_income_overlay_nav_risk_ratio", -]; -const OPTION_OVERLAY_VARIABLES = OPTION_OVERLAY_CONTROL_FIELDS.map((field) => field.toUpperCase()); -const OPTION_OVERLAY_PROFILE_FIELDS = [ - ...OPTION_OVERLAY_CONTROL_FIELDS, - "option_overlay_live_gate", - "option_overlay_live_status", -]; -const OPTION_OVERLAY_MODES = ["current", "enabled", "disabled"]; -const DCA_PROFILE_CONFIG = Object.fromEntries( - Object.entries(DCA_PROFILE_DEFAULTS).map(([profile, defaults]) => [ - profile, - { - default_mode: defaults.defaultMode || "fixed", - default_base_investment_usd: defaults.defaultBaseInvestmentUsd || "1000", - }, - ]), -); -const SECURITY_HEADERS = { - "Content-Security-Policy": [ - "default-src 'self'", - "base-uri 'none'", - "object-src 'none'", - "frame-ancestors 'none'", - "frame-src https://fred.stlouisfed.org/graph/graph-landing.php", - "form-action 'self'", - "img-src 'self' data:", - "connect-src 'self'", - "script-src 'self'", - "style-src 'self'", - ].join("; "), - "Permissions-Policy": "camera=(), microphone=(), geolocation=(), payment=(), usb=()", - "Referrer-Policy": "no-referrer", - "X-Content-Type-Options": "nosniff", - "X-Frame-Options": "DENY", -}; - -// This is deliberately a same-origin, public configuration bootstrap. It -// contains only the config projections already shown by the public console; -// private account routing still arrives after an authenticated /api/config -// request. Keeping it external preserves the strict CSP without unsafe-inline. -const BOOTSTRAP_CONFIG_JS = [ - "// Generated from non-secret console configuration. Do not edit by hand.", - `window.__PLATFORM_META__ = ${JSON.stringify(PLATFORM_META)};`, - `window.__PLATFORM_REPOSITORIES__ = ${JSON.stringify(PLATFORM_REPOSITORIES)};`, - `window.__PLATFORM_CONFIG__ = ${JSON.stringify(PLATFORM_CONFIG)};`, - `window.__DEFAULT_ACCOUNT_OPTIONS__ = ${JSON.stringify(DEFAULT_ACCOUNT_OPTIONS)};`, - `window.__DOMAIN_LABELS__ = ${JSON.stringify(DOMAIN_LABELS)};`, - `window.__DEFAULT_STRATEGY_PROFILES__ = ${JSON.stringify(DEFAULT_STRATEGY_PROFILES)};`, - `window.__DCA_PROFILE_DEFAULTS__ = ${JSON.stringify(DCA_PROFILE_DEFAULTS)};`, - `window.__INCOME_LAYER_DEFAULTS__ = ${JSON.stringify(FALLBACK_INCOME_LAYER_DEFAULTS)};`, - `window.__OPTION_OVERLAY_DEFAULTS__ = ${JSON.stringify(FALLBACK_OPTION_OVERLAY_DEFAULTS)};`, - `window.__PLATFORM_MIN_RESERVED_CASH_VARIABLES__ = ${JSON.stringify(PLATFORM_MIN_RESERVED_CASH_VARIABLES)};`, - `window.__PLATFORM_RESERVED_CASH_RATIO_VARIABLES__ = ${JSON.stringify(PLATFORM_RESERVED_CASH_RATIO_VARIABLES)};`, -].join("\n"); - -// A secondary external script keeps a failed or stale main bundle from -// trapping a visitor behind the loading screen forever. It does not fetch, -// mutate configuration, or grant access: it only reveals the static login -// surface after the main boot flow has had time to finish its own timeout. -const BOOT_RECOVERY_JS = [ - "(() => {", - " const delayMs = 20_000;", - " window.setTimeout(() => {", - " if (!document.body?.classList.contains('app-loading')) return;", - " document.body.classList.remove('app-loading');", - " const login = document.getElementById('login-link');", - " if (login) login.hidden = false;", - " }, delayMs);", - "})();", -].join("\n"); +import { HttpError } from "./application/http_error.js"; +import { buildRiskEnvelopeView, attachRiskEnvelopeView } from "./application/risk_envelope.js"; +import { + ACCOUNT_DIAGNOSIS_REQUEST_PATH, + ACCOUNT_DIAGNOSIS_INTERNAL_PREFIX, +} from "./application/account_diagnosis/constants.js"; +import { + UX1_CALCULATOR_DEADLINE_MS, + UX1_CALCULATOR_MAX_BYTES, + UX1_JOB_UNKNOWN_AFTER_MS, +} from "./application/ux1/constants.js"; +import { + runtimeInstanceCommand, +} from "./application/runtime_instances/command.js"; +import { readSession, makeSession } from "./application/auth/session.js"; +import { startLogin, finishLogin } from "./application/auth/login.js"; +import { + ux1DraftGet, + ux1DraftPost, + ux1PreviewPost, + ux1IntentPost, + ux1JobClaimPost, + ux1JobResultPost, + presentUx1Preview, + projectUx1Job, + ux1RunMatches, + ux1RuntimeEpoch, + readUx1CalculatorExchange, +} from "./application/ux1/research.js"; + +import * as api from './application/worker/runtime_support.js'; +import { RuntimeInstances } from "./application/runtime_instances/durable_object.js"; +export { RuntimeInstances }; + +const { + ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_PATH, + CURRENT_STRATEGIES_KV_BUMP_MS, + CURRENT_STRATEGIES_KV_FRESH_MS, + CURRENT_STRATEGIES_TIMEOUT_MS, + DEFAULT_REPOSITORY, + acceptHkStopResult, + accountDiagnosisInternalResponse, + accountDiagnosisResponse, + accountDraftStrategyChoices, + accountFactsErrorResponse, + accountFactsHistoryResponse, + accountFactsResponse, + accountOptionMatchesInputs, + accountSettingsAuditChanges, + accountSettingsResponse, + adaptiveSelectionResponse, + adminConfigResponse, + aggregateRuntimeTargetLifecycleSources, + approveAccountSettingsProfileApplication, + assertAccountDraftPatch, + assertConfiguredAccount, + assertStrategyAllowedForAccount, + attachPromotionQueueReadMetadata, + attachResearchPromotionApplicationPreparation, + auditEventAction, + auditEventsResponse, + binanceAccountFactsReadinessResponse, + binanceAccountFactsResponse, + binanceDetailArchiveResponse, + binanceDigestSnapshotResponse, + binancePrivateScopeResponse, + binanceWalletHistoryResponse, + BOOTSTRAP_CONFIG_JS, + BOOT_RECOVERY_JS, + buildRiskProfileBindings, + calculateAdaptiveSelectionDecisionDigest, + calculateM0ResearchLedgerSha256, + calculateReconciliationRecoveryConfirmationSha256, + calculateResearchTaskSha256, + calculateRiskProfileBindingSha256, + calculateRiskProfileSelectionSha256, + canonicalResearchTaskJson, + claimAccountSettingsProfileApplicationFromGitHub, + configPayload, + consoleRunControls, + controlPlaneResearchSnapshotFreshness, + controlPlaneResponse, + credentialMaintenanceResponse, + dcaDraftSupported, + decisionActionResponse, + decisionReviewResponse, + deskAuth, + deskTargetReadback, + dispatchAccountDiagnosis, + dispatchBinanceResume, + dispatchRuntimeStop, + dispatchSwitch, + effectiveAccountSettings, + emptyAdaptiveSelectionPayload, + emptyControlPlanePayload, + emptyExecutionEvidencePayload, + emptyM0ResearchDashboardPayload, + emptyReconciliationRecoveryPayload, + emptyResearchTaskPayload, + emptyStrategyHealthPayload, + executionEvidenceResponse, + fetchResearchPromotionTicketResponse, + fetchWithTimeout, + hasConfigStore, + html, + humanDecisionReceipt, + inferAccountSupportedDomains, + isConfigJsonParseError, + isDegradedCurrentStrategies, + json, + loadAccountOptionsConfig, + loadAuditLog, + loadCurrentStrategies, + loadCurrentStrategiesSafely, + loadPlatformMeta, + loadStrategyProfilesConfig, + logout, + m0ResearchLedgerResponse, + marketBenchmarkResponse, + normalizeAccountOptionsPayload, + normalizeAdaptiveSelectionSourceSnapshot, + normalizeControlPlaneSnapshot, + normalizeControlPlaneSourceSnapshot, + normalizeExecutionEvidenceSourceSnapshot, + normalizeM0ResearchLedgerTransport, + normalizeReconciliationRecoveryConfirmation, + normalizeReconciliationRecoverySourceSnapshot, + normalizeResearchPromotionTicket, + normalizeResearchTask, + normalizeResearchTaskSourceSnapshot, + normalizeRiskProfileBindingRegistry, + normalizeRuntimeTargetLifecycleTarget, + normalizeStrategyHealthSnapshot, + normalizeStrategyProfilesPayload, + normalizeSwitchInputs, + operationResponse, + overviewFxResponse, + ownerDecisionQueueResponse, + platformRepositories, + projectM0ResearchDashboardForRead, + projectRuntimeAccountState, + readBoundAccountExecutions, + readCurrentStrategies, + readCurrentStrategiesCacheEntry, + readRuntimeStop, + reconciliationRecoveryControllerReadResponse, + reconciliationRecoveryResponse, + recordOwnerDecisionResponse, + recordReconciliationRecoveryConfirmationResponse, + recordResearchPromotionDecisionResponse, + requireSameOrigin, + researchPromotionApplicationInternalResponse, + researchPromotionApplicationResponse, + researchPromotionMaterialKey, + researchPromotionReviewBinding, + researchPromotionReviewMaterial, + researchPromotionTicketsResponse, + researchTaskResponse, + resolvedVariableScope, + responseHeaders, + reviseMarketBenchmarkResponse, + riskProfileBindingTargets, + riskProfileBindingsResponse, + runtimeCatalogResponse, + runtimeCycleHealthAdmissionResponse, + runtimeCycleHealthSourceResponse, + runtimeDailyPublicBindings, + runtimeDailyReadModelEnabled, + runtimeDailyRegisteredBindings, + runtimeDailyResponse, + runtimeInstancesResponse, + runtimeReadbackResponse, + runtimeTargetLifecycleResponse, + saveAccountSettings, + saveAdminConfig, + saveCombinedAccountDraft, + saveRiskProfileBindings, + sessionPayload, + sha256Hex, + splitSelectorValues, + strategyHealthResponse, + strategyProfilesPayload, + supportedDomainsForAccount, + syncAccountDefaultResponse, + syncAccountExecutionsResponse, + syncAccountExternalFlowResponse, + syncAccountFactsResponse, + syncAccountPeriodReturnResponse, + syncAdaptiveSelectionSourceResponse, + syncBinanceAccountFactsResponse, + syncBinancePrivateScopeResponse, + syncControlPlaneResponse, + syncControlPlaneSourceResponse, + syncDefaultStrategyForAccount, + syncExecutionEvidenceSourceResponse, + syncM0ResearchLedgerResponse, + syncMarketBenchmarkResponse, + syncReconciliationRecoverySourceResponse, + syncResearchPromotionTicketResponse, + syncResearchTaskSourceResponse, + syncRuntimeDailyResponse, + syncRuntimeTargetLifecycleSourceResponse, + syncStrategyHealthResponse, + syncStrategyProfilesConfig, + syncStrategyProfilesResponse, + updateAccountOptionsDefaultStrategy, + validateResearchPromotionSelectedAccount, + withTimeout, + writeCurrentStrategiesCache, +} = api; export default { async fetch(request, env, ctx) { @@ -1164,17548 +744,6 @@ export default { }, }; -class HttpError extends Error { - constructor(message, status, details = null) { - super(message); - this.status = status; - this.reason_code = (details && details.reason_code) || message; - this.reason = (details && details.reason) || ""; - } -} - -// One fixed object owns instance state and its revision. KV remains the ACL and -// observation store; it is never a CAS substitute or a writable replica here. -function hasRuntimeInstanceStore(env) { - return env.STRATEGY_SWITCH_RUNTIME_INSTANCES !== undefined && env.STRATEGY_SWITCH_RUNTIME_INSTANCES !== null; -} - -async function runtimeInstanceCommand(env, command) { - const namespace = env.STRATEGY_SWITCH_RUNTIME_INSTANCES; - if (!namespace?.idFromName || !namespace?.get) throw new HttpError("runtime_instances_not_bound", 503); - try { - const stub = namespace.get(namespace.idFromName("runtime-instances")); - const response = await stub.fetch("https://runtime-instances/", command ? { - method: "POST", body: JSON.stringify(command), - } : {}); - const result = await response.json(); - if (!response.ok) throw new HttpError(result.error || "runtime_instances_unavailable", response.status); - return result; - } catch (error) { - if (error instanceof HttpError) throw error; - throw new HttpError("runtime_instances_unavailable", 503); - } -} - -async function credentialMaintenanceResponse(request, env, url) { - const machine = url.pathname.startsWith("/api/internal/"); - const suffix = url.pathname.split("/").at(-1); - let action = "credential_maintenance_read"; - if (machine) { - const expected = typeof env.STRATEGY_SWITCH_SYNC_TOKEN === "string" ? env.STRATEGY_SWITCH_SYNC_TOKEN : ""; - // No dispatch fallback, and no alias for broker, control or session authority. - const aliases = Object.entries(env).filter(([name, value]) => name !== "STRATEGY_SWITCH_SYNC_TOKEN" && - (name.endsWith("_TOKEN") || name === "SESSION_SECRET") && typeof value === "string" && value && value === expected); - if (!expected || aliases.length) throw new HttpError("credential_maintenance_auth_unavailable", 503); - const supplied = (request.headers.get("Authorization") || "").match(/^Bearer\s+(.+)$/i)?.[1] || ""; - if (supplied !== expected) throw new HttpError("credential_maintenance_auth_invalid", 401); - if (suffix === "claim" || suffix === "ack") { - if (request.method !== "POST") throw new HttpError("method_not_allowed", 405); - action = `credential_maintenance_${suffix}`; - } else if (request.method !== "GET") throw new HttpError("method_not_allowed", 405); - } else { - const session = await requireUx1Session(request, env); - if (request.method === "POST") { - if (!session.admin) throw new HttpError("admin required", 403); - requireSameOrigin(request, { requireOrigin: true }); - action = "credential_maintenance_save"; - } else if (request.method !== "GET") throw new HttpError("method_not_allowed", 405); - } - let body; - if (request.method === "POST") { - const text = await request.text(); - if (text.length > 1024) throw new HttpError("invalid_credential_maintenance_request", 400); - try { body = JSON.parse(text); } catch { throw new HttpError("invalid_credential_maintenance_request", 400); } - } - const result = await runtimeInstanceCommand(env, { action, body }); - return json(!machine && request.method === "GET" ? { ...result, ...decisionFields(result, "credential_maintenance") } : result); -} - -let ux1ModuleEpoch = ""; - -function ux1RuntimeEpoch(env) { - const configured = env?.UX1_RUNTIME_EPOCH; - if (typeof configured === "string" && /^[a-f0-9]{32}$/.test(configured)) return configured; - if (!ux1ModuleEpoch) { - const bytes = new Uint8Array(16); - crypto.getRandomValues(bytes); - ux1ModuleEpoch = [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join(""); - } - return ux1ModuleEpoch; -} - -function ux1PreviewMatchesEpoch(preview, env) { - return Boolean(preview && preview.runtime_epoch === ux1RuntimeEpoch(env)); -} - -function ux1ExpectedEvidenceMatches(preview, env) { - if (!preview.evidence) return !["computed", "no_advantage", "no_action"].includes(preview.status); - if (!env.UX1_EXPECTED_EVIDENCE_HASHES) return true; - let expected; - try { expected = JSON.parse(env.UX1_EXPECTED_EVIDENCE_HASHES); } catch { return false; } - return expected && typeof expected === "object" && !Array.isArray(expected) - && Object.keys(expected).length === UX1_EVIDENCE_HASH_KEYS.length - && UX1_EVIDENCE_HASH_KEYS.every((key) => /^[a-f0-9]{64}$/.test(expected[key]) && preview.evidence[key] === expected[key]); -} - -function ux1PreviewBindingStale(preview, env) { - if (!preview) return false; - if (!ux1PreviewMatchesEpoch(preview, env)) return true; - if (!preview.evidence || !env?.UX1_EXPECTED_EVIDENCE_HASHES) return false; - return !ux1ExpectedEvidenceMatches(preview, env); -} - -function presentUx1Preview(preview, env) { - if (!preview) return null; - if (!ux1PreviewBindingStale(preview, env)) return preview; - return { - ...preview, - status: "stale", - stale: true, - actionable: false, - recompute_required: true, - }; -} - -function ux1Flags() { - return { - schema_version: UX1_DRAFT_SCHEMA, - study: UX1_STUDY, - account_connection: "native_not_observed", - native_observed: false, - credential_access_unavailable: true, - research_only: true, - no_order: true, - execution_authority_granted: false, - workflow_dispatched: false, - runtime_mutated: false, - d1_native: "NATIVE_PENDING_EXTERNAL", - d1_status: "PARTIAL", - }; -} - -function ux1StatePayload(env, { revision, fingerprint, draft, preview, intent, job = null, duplicate = false, unchanged = false }) { - const publicJob = projectUx1Job(job, Date.now(), revision, env); - const waiting = Boolean(publicJob && ["queued", "running", "unknown"].includes(publicJob.status)); - let shown = waiting ? null : presentUx1Preview(preview, env); - if (shown && ux1TerminalJobConfigStale(job, env)) { - shown = { ...shown, status: "stale", stale: true, actionable: false, recompute_required: true }; - } - return { - ok: true, - ...ux1Flags(), - research_workflow_dispatched: job?.dispatch_state === "dispatched", - revision, - fingerprint, - custom_draft: ux1CustomDraft(draft), - draft, - preview: shown, - preview_stale: Boolean(shown?.stale), - intent: waiting ? null : intent, - job: publicJob, - duplicate, - unchanged, - }; -} - -async function requireUx1Session(request, env) { - const session = await readSession(request, env); - if (!session) throw new HttpError("login required", 401); - if (!session.allowed) throw new HttpError("allowlist required", 403); - return session; -} - -async function readUx1Body(request) { - const text = await request.text(); - if (text.length > 16384) throw new Ux1InputError("ux1_body_too_large"); - try { - return JSON.parse(text); - } catch { - throw new Ux1InputError("ux1_invalid_json"); - } -} - -function parseUx1Slot(result) { - if (!result?.found) return null; - return { - revision: result.revision, - fingerprint: result.fingerprint, - draft: JSON.parse(result.draft_json), - preview: result.preview_json ? JSON.parse(result.preview_json) : null, - intent: result.intent_json ? JSON.parse(result.intent_json) : null, - job: result.job_json ? JSON.parse(result.job_json) : null, - }; -} - -async function ux1ReadSlot(env, login) { - if (!hasRuntimeInstanceStore(env)) throw new HttpError("ux1_research_store_not_bound", 503); - return parseUx1Slot(await runtimeInstanceCommand(env, { action: "ux1_read", login })); -} - -async function ux1DraftGet(request, env) { - const session = await requireUx1Session(request, env); - const slot = await ux1ReadSlot(env, session.login); - if (!slot) { - const draft = defaultUx1Draft(); - return json(ux1StatePayload(env, { revision: 0, fingerprint: await ux1Fingerprint(draft), draft, preview: null, intent: null })); - } - return json(ux1StatePayload(env, slot)); -} - -async function ux1DraftPost(request, env) { - requireSameOrigin(request, { requireOrigin: true }); - const session = await requireUx1Session(request, env); - const parsed = assertUx1DraftPost(await readUx1Body(request)); - if (!hasRuntimeInstanceStore(env)) throw new HttpError("ux1_research_store_not_bound", 503); - const saved = await runtimeInstanceCommand(env, { - action: "ux1_save", - login: session.login, - expected_revision: parsed.expected_revision, - draft_json: canonicalJson(parsed.draft), - fingerprint: await ux1Fingerprint(parsed.draft), - }); - return json(ux1StatePayload(env, { ...parseUx1Slot({ ...saved, found: true }), unchanged: Boolean(saved.unchanged) })); -} - -function ux1AbortError() { - return new DOMException("The operation was aborted", "AbortError"); -} - -function abandonUx1Reader(reader) { - if (!reader) return; - try { - const canceling = reader.cancel(); - if (canceling && typeof canceling.catch === "function") canceling.catch(() => {}); - } catch { - /* cancel failed before returning a promise */ - } -} - -function abandonUx1Response(response) { - if (!response?.body) return; - try { - abandonUx1Reader(response.body.getReader()); - } catch { - /* body already locked or disturbed */ - } -} - -async function readLimitedUtf8Body(response, maxBytes, controller, abortPromise) { - if (!response?.body) return ""; - const reader = response.body.getReader(); - const decoder = new TextDecoder(); - let received = 0; - let text = ""; - const stopReader = () => { - if (!controller.signal.aborted) controller.abort(); - abandonUx1Reader(reader); - }; - const onAbort = () => { abandonUx1Reader(reader); }; - controller.signal.addEventListener("abort", onAbort); - try { - while (true) { - if (controller.signal.aborted) throw ux1AbortError(); - const readPromise = reader.read(); - readPromise.catch(() => {}); - const next = await Promise.race([readPromise, abortPromise]); - if (next.done) break; - const bytes = next.value?.byteLength ?? 0; - received += bytes; - if (received > maxBytes) { - stopReader(); - throw new Error("ux1_calculator_body_limit"); - } - text += decoder.decode(next.value, { stream: true }); - } - text += decoder.decode(); - return text; - } catch (error) { - stopReader(); - throw error; - } finally { - controller.signal.removeEventListener("abort", onAbort); - } -} - -async function readUx1CalculatorExchange(binding, body, limits) { - const deadlineMs = Number.isFinite(limits?.deadlineMs) ? limits.deadlineMs : UX1_CALCULATOR_DEADLINE_MS; - const maxBytes = Number.isFinite(limits?.maxBytes) ? limits.maxBytes : UX1_CALCULATOR_MAX_BYTES; - const controller = new AbortController(); - let rejectAbort = () => {}; - const abortPromise = new Promise((_, reject) => { rejectAbort = reject; }); - abortPromise.catch(() => {}); - const onAbort = () => rejectAbort(ux1AbortError()); - controller.signal.addEventListener("abort", onAbort); - const timer = setTimeout(() => controller.abort(), deadlineMs); - const pending = Promise.resolve().then(() => binding.fetch("https://ux1-research-calculator/preview", { - method: "POST", - headers: { "content-type": "application/json" }, - body, - signal: controller.signal, - })); - let accepted = false; - pending.then((response) => { - if (accepted || !controller.signal.aborted) return; - abandonUx1Response(response); - }, () => {}); - try { - const response = await Promise.race([pending, abortPromise]); - accepted = true; - const text = await readLimitedUtf8Body(response, maxBytes, controller, abortPromise); - return { status: response.status, ok: response.ok, text }; - } finally { - clearTimeout(timer); - controller.signal.removeEventListener("abort", onAbort); - } -} - -async function ux1ClearStoredPreview(env, login, slot) { - await runtimeInstanceCommand(env, { - action: "ux1_clear_preview", login, - expected_revision: slot.revision, - fingerprint: slot.fingerprint, - }); -} - -async function ux1PreviewPost(request, env) { - requireSameOrigin(request, { requireOrigin: true }); - const session = await requireUx1Session(request, env); - const parsed = assertUx1PreviewPost(await readUx1Body(request)); - const slot = await ux1ReadSlot(env, session.login); - if (!slot || slot.revision !== parsed.expected_revision) throw new HttpError("ux1_preview_conflict", 409); - const draft = normalizeUx1Draft(slot.draft); - const fingerprint = await ux1Fingerprint(draft); - if (fingerprint !== slot.fingerprint) throw new HttpError("ux1_preview_conflict", 409); - if (ux1StoredJobBlocks(slot.job)) return json(ux1StatePayload(env, slot)); - const previewMode = ux1PreviewMode(env); - if (previewMode === "rejected") throw new HttpError("ux1_preview_mode_rejected", 400); - const gate = classifyUx1Draft(draft); - let preview; - if (gate.kind === "missing_input" || gate.kind === "unsupported_scope") { - preview = localUx1Preview({ draft, fingerprint, status: gate.kind, reasons: gate.reasons }); - } else if (previewMode === "github_actions") { - return await ux1DispatchPreview(env, session, slot, draft, fingerprint); - } else { - const binding = env.UX1_RESEARCH_CALCULATOR; - if (!binding || typeof binding.fetch !== "function") { - await ux1ClearStoredPreview(env, session.login, slot); - return json({ - ok: false, - error: "calculator_not_connected", - status: "calculator_not_connected", - recompute_required: true, - ...ux1Flags(), - revision: slot.revision, - fingerprint, - custom_draft: ux1CustomDraft(draft), - draft, - preview: null, - decision_preview: null, - historical_execution_check: null, - }, 503); - } - const calculatorRequest = buildUx1PreviewRequest(draft); - let exchange; - try { - exchange = await readUx1CalculatorExchange(binding, canonicalJson(calculatorRequest)); - } catch { - await ux1ClearStoredPreview(env, session.login, slot); - throw new HttpError("calculator_failed", 502); - } - const calculatorText = exchange.text; - if (exchange.status === 429) { - let busy = false; - try { busy = JSON.parse(calculatorText).error === "calculator_busy"; } catch { busy = false; } - if (busy) throw new HttpError("calculator_busy", 429); - } - if (!exchange.ok) { - await ux1ClearStoredPreview(env, session.login, slot); - throw new HttpError("calculator_failed", 502); - } - let raw; - try { - raw = JSON.parse(calculatorText); - } catch { - await ux1ClearStoredPreview(env, session.login, slot); - throw new HttpError("calculator_failed", 502); - } - try { - preview = projectUx1CalculatorResult(raw, calculatorRequest, draft, fingerprint); - if (!ux1ExpectedEvidenceMatches(preview, env)) throw new Ux1InputError("calculator_result_rejected", 502); - } catch (error) { - await ux1ClearStoredPreview(env, session.login, slot); - if (error instanceof Ux1InputError) throw new HttpError(error.message, error.status || 502); - throw new HttpError("calculator_result_rejected", 502); - } - } - preview = { ...preview, runtime_epoch: ux1RuntimeEpoch(env) }; - const stored = await runtimeInstanceCommand(env, { - action: "ux1_store_preview", - login: session.login, - expected_revision: slot.revision, - fingerprint, - preview_json: canonicalJson(preview), - }); - return json(ux1StatePayload(env, parseUx1Slot({ ...stored, found: true }))); -} - -async function ux1IntentPost(request, env) { - requireSameOrigin(request, { requireOrigin: true }); - const session = await requireUx1Session(request, env); - const parsed = assertUx1IntentPost(await readUx1Body(request)); - const slot = await ux1ReadSlot(env, session.login); - if (!slot || slot.revision !== parsed.expected_revision || slot.fingerprint !== parsed.fingerprint) { - throw new HttpError("ux1_intent_conflict", 409); - } - if (ux1StoredJobBlocks(slot.job)) throw new HttpError("ux1_job_active", 409); - if (ux1PreviewBindingStale(slot.preview, env) || ux1TerminalJobConfigStale(slot.job, env)) throw new HttpError("ux1_preview_stale", 409); - if (!ux1IntentAllowed(slot.preview) || slot.preview.fingerprint !== parsed.fingerprint) { - throw new HttpError("ux1_intent_conflict", 409); - } - const intent = ux1IntentRecord({ - login: session.login, - revision: slot.revision, - fingerprint: slot.fingerprint, - receiptId: await ux1ReceiptId(session.login, slot.revision, slot.fingerprint), - }); - const stored = await runtimeInstanceCommand(env, { - action: "ux1_save_intent", - login: session.login, - expected_revision: slot.revision, - fingerprint: slot.fingerprint, - intent_json: canonicalJson(intent), - }); - return json(ux1StatePayload(env, { ...parseUx1Slot({ ...stored, found: true }), duplicate: Boolean(stored.duplicate) })); -} - -function ux1PreviewMode(env) { - const mode = String(env?.UX1_PREVIEW_MODE ?? "").trim(); - if (!mode) return "local"; - if (mode === "github_actions") return "github_actions"; - return "rejected"; -} - -function ux1EvidenceRecord(value) { - if (!value || typeof value !== "object" || Array.isArray(value)) return null; - if (Object.keys(value).length !== UX1_EVIDENCE_HASH_KEYS.length) return null; - const evidence = {}; - for (const key of UX1_EVIDENCE_HASH_KEYS) { - if (typeof value[key] !== "string" || !/^[a-f0-9]{64}$/.test(value[key])) return null; - evidence[key] = value[key]; - } - return evidence; -} - -function ux1AsyncConfig(env) { - const ues = String(env?.UX1_UES_REVISION || ""); - const epoch = String(env?.UX1_RUNTIME_EPOCH || ""); - const token = String(env?.UX1_RESEARCH_JOB_TOKEN || ""); - if (!/^[a-f0-9]{40}$/.test(ues) || !/^[a-f0-9]{32}$/.test(epoch) || token.length < 16 || token.length > 256) return null; - return ux1EvidenceRecord(ux1ParseJsonObject(env?.UX1_EXPECTED_EVIDENCE_HASHES)) - ? { ues, epoch, evidence: ux1EvidenceRecord(ux1ParseJsonObject(env.UX1_EXPECTED_EVIDENCE_HASHES)) } - : null; -} - -function ux1ParseJsonObject(text) { - if (typeof text !== "string" || !text) return null; - try { - const parsed = JSON.parse(text); - return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null; - } catch { - return null; - } -} - -function ux1StoredJobBlocks(job) { - return Boolean(job && (job.status === "queued" || job.status === "running" || job.status === "unknown")); -} - -function ux1PublicJobReason(reason) { - return UX1_PUBLIC_JOB_REASONS.has(reason) ? reason : null; -} - -function ux1JobConfigStale(job, env) { - const config = ux1AsyncConfig(env); - if (!config || !job) return false; - if (job.runtime_epoch !== config.epoch || job.ues_revision !== config.ues) return true; - return UX1_EVIDENCE_HASH_KEYS.some((key) => job.evidence?.[key] !== config.evidence[key]); -} - -function ux1TerminalJobConfigStale(job, env) { - return Boolean(job && !ux1StoredJobBlocks(job) && ux1JobConfigStale(job, env)); -} - -function ux1JobBindingMatchesConfig(job, env) { - return Boolean(job && ux1AsyncConfig(env) && !ux1JobConfigStale(job, env)); -} - -function projectUx1Job(job, now, slotRevision, env) { - if (!job || typeof job !== "object") return null; - const created = Date.parse(job.created_at); - const age = Number.isFinite(created) ? now - created : 0; - let status = job.status; - let reason = job.reason || null; - if (ux1StoredJobBlocks(job) && age >= UX1_JOB_UNKNOWN_AFTER_MS) { - status = "unknown"; - reason = "check_required"; - } else if (!ux1StoredJobBlocks(job) && (job.revision !== slotRevision || ux1JobConfigStale(job, env))) { - status = "superseded"; - reason = "config_changed"; - } - return { - request_id: job.request_id, - status, - dispatch_state: job.dispatch_state, - created_at: job.created_at, - updated_at: job.updated_at, - reason: ux1PublicJobReason(reason), - }; -} - -function ux1BearerMatches(header, secret) { - const match = /^Bearer ([!-~]{16,256})$/.exec(String(header || "")); - if (!match || typeof secret !== "string" || secret.length < 16 || secret.length > 256) return false; - const left = new TextEncoder().encode(match[1]); - const right = new TextEncoder().encode(secret); - let diff = left.length ^ right.length; - const length = Math.max(left.length, right.length); - for (let index = 0; index < length; index += 1) diff |= (left[index] || 0) ^ (right[index] || 0); - return diff === 0; -} - -function ux1ExactKeys(value, keys) { - return Boolean(value && typeof value === "object" && !Array.isArray(value) - && Object.keys(value).length === keys.length - && keys.every((key) => Object.hasOwn(value, key))); -} - -function ux1RequestIdOk(value) { - return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value); -} - -function ux1PositiveInt(value) { - return typeof value === "string" && /^[1-9][0-9]{0,18}$/.test(value); -} - -async function ux1Digest(value) { - const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonicalJson(value))); - return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join(""); -} - -async function readUx1LimitedJson(request, maxBytes) { - const controller = new AbortController(); - let rejectAbort = () => {}; - const abortPromise = new Promise((_, reject) => { rejectAbort = reject; }); - abortPromise.catch(() => {}); - const onAbort = () => rejectAbort(ux1AbortError()); - controller.signal.addEventListener("abort", onAbort); - const timer = setTimeout(() => controller.abort(), UX1_GITHUB_DEADLINE_MS); - try { - const text = await readLimitedUtf8Body(request, maxBytes, controller, abortPromise); - try { - return JSON.parse(text); - } catch { - throw new HttpError("ux1_invalid_body", 400); - } - } catch (error) { - if (error instanceof HttpError) throw error; - if (String(error?.message || "") === "ux1_calculator_body_limit") throw new HttpError("ux1_body_too_large", 400); - throw new HttpError("ux1_invalid_body", 400); - } finally { - clearTimeout(timer); - controller.signal.removeEventListener("abort", onAbort); - } -} - -async function ux1GithubExchange(url, init) { - const controller = new AbortController(); - let rejectAbort = () => {}; - const abortPromise = new Promise((_, reject) => { rejectAbort = reject; }); - abortPromise.catch(() => {}); - const onAbort = () => rejectAbort(ux1AbortError()); - controller.signal.addEventListener("abort", onAbort); - const timer = setTimeout(() => controller.abort(), UX1_GITHUB_DEADLINE_MS); - const pending = Promise.resolve().then(() => fetch(url, { ...init, redirect: "manual", signal: controller.signal })); - let accepted = false; - pending.then((response) => { - if (accepted || !controller.signal.aborted) return; - abandonUx1Response(response); - }, () => {}); - try { - const response = await Promise.race([pending, abortPromise]); - accepted = true; - const text = await readLimitedUtf8Body(response, UX1_GITHUB_MAX_BYTES, controller, abortPromise); - return { status: response.status, ok: response.ok, text }; - } finally { - clearTimeout(timer); - controller.signal.removeEventListener("abort", onAbort); - } -} - -async function ux1DispatchResearchJob(env, requestId) { - if (!env.RUNTIME_SETTINGS_DISPATCH_TOKEN) return "unknown"; - try { - const exchange = await ux1GithubExchange( - `https://api.github.com/repos/${UX1_RESEARCH_REPOSITORY}/actions/workflows/${UX1_RESEARCH_WORKFLOW_FILE}/dispatches`, - { - method: "POST", - headers: githubHeaders(env.RUNTIME_SETTINGS_DISPATCH_TOKEN), - body: JSON.stringify({ ref: "main", inputs: { ux1_request_id: requestId } }), - }, - ); - if (exchange.status >= 200 && exchange.status < 300) return "dispatched"; - if (exchange.status >= 400 && exchange.status < 500) return "rejected"; - return "unknown"; - } catch { - return "unknown"; - } -} - -async function ux1DispatchPreview(env, session, slot, draft, fingerprint) { - const config = ux1AsyncConfig(env); - if (!config || !env.RUNTIME_SETTINGS_DISPATCH_TOKEN) { - await ux1ClearStoredPreview(env, session.login, slot); - return json({ - ok: false, - error: "ux1_async_not_connected", - status: "ux1_async_not_connected", - recompute_required: true, - ...ux1Flags(), - research_workflow_dispatched: false, - revision: slot.revision, - fingerprint, - custom_draft: ux1CustomDraft(draft), - draft, - preview: null, - job: projectUx1Job(slot.job, Date.now(), slot.revision, env), - decision_preview: null, - historical_execution_check: null, - }, 503); - } - const begun = await runtimeInstanceCommand(env, { - action: "ux1_begin_job", - login: session.login, - revision: slot.revision, - fingerprint, - epoch: config.epoch, - ues: config.ues, - evidence: config.evidence, - request: buildUx1PreviewRequest(draft), - }); - const job = JSON.parse(begun.job_json); - if (begun.created) { - const outcome = await ux1DispatchResearchJob(env, job.request_id); - await runtimeInstanceCommand(env, { - action: "ux1_record_dispatch", - login: session.login, - request_id: job.request_id, - outcome, - }); - } - return json(ux1StatePayload(env, await ux1ReadSlot(env, session.login))); -} - -function ux1RunMatches(run, job, runId, runAttempt) { - if (!run || typeof run !== "object" || Array.isArray(run) || !job) return false; - const repository = run.repository; - const headRepository = run.head_repository; - if (!repository || typeof repository !== "object" || !headRepository || typeof headRepository !== "object") return false; - return String(run.id) === runId - && run.event === "workflow_dispatch" - && run.path === UX1_RESEARCH_WORKFLOW_PATH - && run.head_sha === job.ues_revision - && run.display_title === `UX1 preview ${job.request_id}` - && String(run.run_attempt) === runAttempt - && run.head_branch === "main" - && repository.full_name === UX1_RESEARCH_REPOSITORY - && headRepository.full_name === UX1_RESEARCH_REPOSITORY - && repository.fork !== true - && headRepository.fork !== true; -} - -async function ux1FetchRun(env, runId) { - let exchange; - try { - exchange = await ux1GithubExchange( - `https://api.github.com/repos/${UX1_RESEARCH_REPOSITORY}/actions/runs/${runId}`, - { method: "GET", headers: githubHeaders(env.RUNTIME_SETTINGS_DISPATCH_TOKEN) }, - ); - } catch { - throw new HttpError("ux1_run_unverified", 502); - } - if (!exchange.ok) throw new HttpError("ux1_run_unverified", 502); - try { - return JSON.parse(exchange.text); - } catch { - throw new HttpError("ux1_run_unverified", 502); - } -} - -function ux1ClaimDenied(requestId, runId, runAttempt) { - return json({ ok: true, claimed: false, request_id: requestId, run_id: runId, run_attempt: runAttempt }); -} - -async function ux1JobClaimPost(request, env) { - if (!ux1BearerMatches(request.headers.get("Authorization"), env.UX1_RESEARCH_JOB_TOKEN)) { - throw new HttpError("ux1_job_unauthorized", 401); - } - const body = await readUx1LimitedJson(request, UX1_JOB_BODY_MAX_BYTES); - if (!ux1ExactKeys(body, ["request_id", "run_id", "run_attempt"]) || !ux1RequestIdOk(body.request_id) || !ux1PositiveInt(body.run_id) || !ux1PositiveInt(body.run_attempt)) { - throw new HttpError("ux1_invalid_body", 400); - } - const looked = await runtimeInstanceCommand(env, { action: "ux1_lookup_job", request_id: body.request_id }); - if (!looked.found) throw new HttpError("ux1_job_not_found", 404); - const job = JSON.parse(looked.job_json); - if (job.claimed_run_id || (job.status !== "queued" && job.status !== "unknown")) { - return ux1ClaimDenied(body.request_id, body.run_id, body.run_attempt); - } - const draft = { - objective: job.request?.objective, - research_case_id: job.request?.research_case_id, - advanced_settings: job.request?.advanced_settings, - }; - try { - if (!job.request || canonicalJson(buildUx1PreviewRequest(draft)) !== canonicalJson(job.request) || await ux1Fingerprint(draft) !== job.fingerprint) { - throw new HttpError("ux1_run_unverified", 502); - } - } catch (error) { - if (error instanceof HttpError) throw error; - throw new HttpError("ux1_run_unverified", 502); - } - const run = await ux1FetchRun(env, body.run_id); - if (!ux1RunMatches(run, job, body.run_id, body.run_attempt)) throw new HttpError("ux1_run_unverified", 502); - const claimed = await runtimeInstanceCommand(env, { - action: "ux1_claim_job", - request_id: body.request_id, - run_id: body.run_id, - run_attempt: body.run_attempt, - }); - if (!claimed.claimed) return ux1ClaimDenied(body.request_id, body.run_id, body.run_attempt); - return json({ - ok: true, - claimed: true, - request_id: body.request_id, - run_id: body.run_id, - run_attempt: body.run_attempt, - request: claimed.request, - }); -} - -async function ux1JobResultPost(request, env) { - if (!ux1BearerMatches(request.headers.get("Authorization"), env.UX1_RESEARCH_JOB_TOKEN)) { - throw new HttpError("ux1_job_unauthorized", 401); - } - const body = await readUx1LimitedJson(request, UX1_JOB_BODY_MAX_BYTES); - if (!ux1ExactKeys(body, ["request_id", "run_id", "run_attempt", "result", "error_code"]) || !ux1RequestIdOk(body.request_id) || !ux1PositiveInt(body.run_id) || !ux1PositiveInt(body.run_attempt)) { - throw new HttpError("ux1_invalid_body", 400); - } - if (!(body.error_code === null || body.error_code === "input_unavailable" || body.error_code === "calculator_failed")) { - throw new HttpError("ux1_invalid_body", 400); - } - if (body.error_code === null ? body.result === null : body.result !== null) throw new HttpError("ux1_invalid_body", 400); - const looked = await runtimeInstanceCommand(env, { action: "ux1_lookup_job", request_id: body.request_id }); - if (!looked.found) throw new HttpError("ux1_job_not_found", 404); - const job = JSON.parse(looked.job_json); - if (job.claimed_run_id !== body.run_id || job.claimed_run_attempt !== body.run_attempt) { - throw new HttpError("ux1_result_conflict", 409); - } - let previewJson = null; - if (!body.error_code) { - const draft = { - objective: job.request?.objective, - research_case_id: job.request?.research_case_id, - advanced_settings: job.request?.advanced_settings, - }; - try { - if (canonicalJson(buildUx1PreviewRequest(draft)) !== canonicalJson(job.request)) throw new Ux1InputError("calculator_result_rejected", 502); - const preview = projectUx1CalculatorResult(body.result, job.request, draft, job.fingerprint); - const evidence = { evidence: Object.fromEntries(UX1_EVIDENCE_HASH_KEYS.map((key) => [key, preview.evidence?.[key]])) }; - if (!preview.evidence || !ux1ExpectedEvidenceMatches(evidence, { UX1_EXPECTED_EVIDENCE_HASHES: JSON.stringify(job.evidence) })) { - throw new Ux1InputError("calculator_result_rejected", 502); - } - if (ux1JobBindingMatchesConfig(job, env)) previewJson = canonicalJson({ ...preview, runtime_epoch: job.runtime_epoch }); - } catch (error) { - if (error instanceof Ux1InputError && error.message === "calculator_failed") throw new HttpError("calculator_failed", 502); - throw new HttpError("calculator_result_rejected", 502); - } - } - const finished = await runtimeInstanceCommand(env, { - action: "ux1_finish_job", - request_id: body.request_id, - run_id: body.run_id, - run_attempt: body.run_attempt, - error_code: body.error_code, - preview_json: previewJson, - terminal_digest: await ux1Digest({ error_code: body.error_code, result: body.result }), - }); - return json({ ok: true, accepted: true, duplicate: Boolean(finished.duplicate) }); -} - -function ux1SelectSlot(sql, login) { - return sql.exec( - "SELECT revision, draft_json, fingerprint, preview_json, intent_json, job_json FROM ux1_research_slot WHERE login = ?", - login, - ).toArray()[0] || null; -} - -function ux1SlotPayload(row, extra = {}) { - return { - ok: true, - found: Boolean(row), - revision: row ? Number(row.revision) : 0, - draft_json: row?.draft_json || null, - fingerprint: row?.fingerprint || null, - preview_json: row?.preview_json || null, - intent_json: row?.intent_json || null, - job_json: row?.job_json || null, - ...extra, - }; -} - -function ux1ParseStoredJob(text) { - if (!text) return null; - try { - const job = JSON.parse(text); - return job && typeof job === "object" && !Array.isArray(job) ? job : null; - } catch { - return null; - } -} - -function ux1SelectJob(sql, requestId) { - return sql.exec( - `SELECT login, revision, draft_json, fingerprint, preview_json, intent_json, job_json - FROM ux1_research_slot WHERE json_extract(job_json, '$.request_id') = ?`, - requestId, - ).toArray()[0] || null; -} - -function ux1WriteJob(sql, login, job) { - const text = JSON.stringify(job); - if (text.length > 65536) throw new HttpError("ux1_invalid_body", 400); - sql.exec("UPDATE ux1_research_slot SET job_json = ? WHERE login = ?", text, login); - return text; -} - -function ux1SameSucceededBinding(job, command, row) { - const evidence = ux1EvidenceRecord(command.evidence); - if (!job || job.status !== "succeeded" || !evidence || !row) return false; - return job.revision === Number(row.revision) - && job.fingerprint === row.fingerprint - && job.revision === command.revision - && job.fingerprint === command.fingerprint - && job.runtime_epoch === command.epoch - && job.ues_revision === command.ues - && UX1_EVIDENCE_HASH_KEYS.every((key) => job.evidence?.[key] === evidence[key]); -} - -function ux1MutateJob(sql, command) { - if (command.action === "ux1_lookup_job") { - if (!ux1RequestIdOk(command.request_id)) throw new HttpError("ux1_invalid_body", 400); - const row = ux1SelectJob(sql, command.request_id); - return row ? ux1SlotPayload(row) : { ok: true, found: false }; - } - if (command.action === "ux1_claim_job" || command.action === "ux1_finish_job") { - if (!ux1RequestIdOk(command.request_id) || !ux1PositiveInt(command.run_id) || !ux1PositiveInt(command.run_attempt)) { - throw new HttpError("ux1_invalid_body", 400); - } - const row = ux1SelectJob(sql, command.request_id); - if (!row) throw new HttpError("ux1_job_not_found", 404); - const job = ux1ParseStoredJob(row.job_json); - if (!job) throw new HttpError("ux1_job_not_found", 404); - const now = new Date().toISOString(); - if (command.action === "ux1_claim_job") { - if (job.claimed_run_id || (job.status !== "queued" && job.status !== "unknown")) { - return { ok: true, claimed: false }; - } - job.status = "running"; - job.claimed_run_id = command.run_id; - job.claimed_run_attempt = command.run_attempt; - job.updated_at = now; - ux1WriteJob(sql, row.login, job); - return { ok: true, claimed: true, request: job.request }; - } - if (job.claimed_run_id !== command.run_id || job.claimed_run_attempt !== command.run_attempt) { - throw new HttpError("ux1_result_conflict", 409); - } - if (job.terminal_digest) { - if (job.terminal_digest === command.terminal_digest) return { ok: true, duplicate: true }; - throw new HttpError("ux1_result_conflict", 409); - } - if (job.status !== "running") throw new HttpError("ux1_result_conflict", 409); - if (typeof command.terminal_digest !== "string" || !/^[a-f0-9]{64}$/.test(command.terminal_digest)) { - throw new HttpError("ux1_invalid_body", 400); - } - job.status = command.error_code ? "failed" : "succeeded"; - job.reason = command.error_code || null; - job.terminal_digest = command.terminal_digest; - job.updated_at = now; - ux1WriteJob(sql, row.login, job); - const sameSlot = Number(row.revision) === job.revision && row.fingerprint === job.fingerprint; - if (command.error_code && sameSlot) { - sql.exec( - "UPDATE ux1_research_slot SET preview_json = NULL, intent_json = NULL WHERE login = ? AND revision = ? AND fingerprint = ?", - row.login, Number(row.revision), row.fingerprint, - ); - } else if (!command.error_code && sameSlot && typeof command.preview_json === "string" && command.preview_json.length <= 65536) { - sql.exec( - "UPDATE ux1_research_slot SET preview_json = ? WHERE login = ? AND revision = ? AND fingerprint = ?", - command.preview_json, row.login, Number(row.revision), row.fingerprint, - ); - } - return { ok: true, duplicate: false }; - } - const login = String(command?.login || ""); - if (!/^[a-z0-9-]{1,39}$/.test(login)) throw new HttpError("ux1_login_invalid", 400); - const row = ux1SelectSlot(sql, login); - if (!row) throw new HttpError("ux1_preview_conflict", 409); - const job = ux1ParseStoredJob(row.job_json); - if (command.action === "ux1_begin_job") { - if (ux1StoredJobBlocks(job)) return { ...ux1SlotPayload(row), created: false }; - if (ux1SameSucceededBinding(job, command, row)) return { ...ux1SlotPayload(row), created: false }; - const evidence = ux1EvidenceRecord(command.evidence); - if (!evidence || command.revision !== Number(row.revision) || command.fingerprint !== row.fingerprint) { - throw new HttpError("ux1_preview_conflict", 409); - } - if (!/^[a-f0-9]{32}$/.test(String(command.epoch || "")) || !/^[a-f0-9]{40}$/.test(String(command.ues || ""))) { - throw new HttpError("ux1_invalid_body", 400); - } - if (!command.request || command.request.schema !== "qsl.ux1.preview_request.v1" || JSON.stringify(command.request).length > 16384) { - throw new HttpError("ux1_invalid_body", 400); - } - const now = new Date().toISOString(); - const next = { - request_id: crypto.randomUUID(), - status: "queued", - dispatch_state: "pending", - revision: Number(row.revision), - fingerprint: row.fingerprint, - runtime_epoch: command.epoch, - ues_revision: command.ues, - evidence, - request: command.request, - created_at: now, - updated_at: now, - claimed_run_id: null, - claimed_run_attempt: null, - reason: null, - terminal_digest: null, - }; - const jobJson = ux1WriteJob(sql, login, next); - sql.exec("UPDATE ux1_research_slot SET preview_json = NULL, intent_json = NULL WHERE login = ?", login); - return { ...ux1SlotPayload(row), preview_json: null, intent_json: null, job_json: jobJson, created: true }; - } - if (command.action === "ux1_record_dispatch") { - if (!job || job.request_id !== command.request_id || job.dispatch_state !== "pending") { - return { ...ux1SlotPayload(row), recorded: false }; - } - if (command.outcome === "dispatched") job.dispatch_state = "dispatched"; - else if (command.outcome === "rejected") { - job.dispatch_state = "rejected"; - if (job.status === "queued") job.status = "failed"; - job.reason = "dispatch_rejected"; - } else { - job.dispatch_state = "unknown"; - if (job.status === "queued") job.status = "unknown"; - job.reason = "dispatch_unconfirmed"; - } - job.updated_at = new Date().toISOString(); - const jobJson = ux1WriteJob(sql, login, job); - return { ...ux1SlotPayload(row), job_json: jobJson, recorded: true }; - } - throw new HttpError("unsupported_ux1_action", 400); -} - -function runtimeInstanceAccountOptions(instances) { - const options = {}; - for (const instance of instances) { - if (instance.kind !== "existing") continue; - (options[instance.platform] ||= []).push(instance.config); - } - return options; -} - -function runtimeInstanceConflict(instances, platform, config, exceptKey = null) { - const selectors = (value) => String(value || "").split(",").map((item) => item.trim().toLowerCase()).filter(Boolean); - for (const item of instances) { - if (item.platform !== platform || item.key === exceptKey) continue; - const other = item.config; - if (item.key === config.key || other.target_name === config.target_name || - (other.service_name && other.service_name === config.service_name) || - selectors(other.account_selector).some((value) => selectors(config.account_selector).includes(value))) { - throw new HttpError("runtime_instance_identity_conflict", 409); - } - } -} - -function runtimeInstanceFields(raw, allowed) { - if (!raw || typeof raw !== "object" || Array.isArray(raw) || Object.keys(raw).some((key) => !allowed.includes(key))) { - throw new HttpError("invalid_runtime_instance_fields", 400); - } -} - -function accountDiagnosisTaskFromRow(row) { - if (!row) return null; - return { - request_id: row.request_id, - platform: row.platform, - key: row.account_key, - target_id: row.target_id, - trigger: row.trigger, - observed_at: row.observed_at, - checks: JSON.parse(row.checks_json), - state_fingerprint: row.state_fingerprint, - status: row.status, - native: row.native_json ? JSON.parse(row.native_json) : null, - backend_kind: row.native_json ? "native" : "legacy", - workflow_run_id: row.workflow_run_id || null, - workflow_run_attempt: row.workflow_run_attempt || null, - job_id: row.job_id || null, - summary: row.summary || "", - reason_code: row.reason_code || "", - dispatch_state: row.dispatch_state, - recheck_dispatch_state: row.recheck_dispatch_state, - auto_source: row.auto_source || "manual", - created_at: row.created_at, - updated_at: row.updated_at, - }; -} - -function researchPromotionApplicationFromRow(row) { - if (!row) return null; - return { - application_id: row.application_id, - ticket_id: row.ticket_id, - status: row.status, - dispatch_state: row.dispatch_state, - claim_token: row.claim_token, - platform_id: row.platform_id, - account_key: row.account_key, - account_scope: row.account_scope, - account_selector: row.account_selector, - service_name: row.service_name, - broker_environment: row.broker_environment, - expected_revision: Number(row.expected_revision), - expected_strategy_profile: row.expected_strategy_profile, - strategy_profile: row.strategy_profile, - candidate_id: row.candidate_id, - config_sha256: row.config_sha256, - source_commit: row.source_commit, - approved_ues_revision: row.approved_ues_revision, - desired_state: row.desired_state, - workflow_run_id: row.workflow_run_id || null, - workflow_run_attempt: row.workflow_run_attempt || null, - reason_code: row.reason_code || "", - readback: row.readback_json ? JSON.parse(row.readback_json) : null, - created_at: row.created_at, - updated_at: row.updated_at, - }; -} - -async function runtimeInstancesResponse(request, env) { - const session = await readSession(request, env); - if (!session) return json({ ok: false, error: "login required" }, 401); - if (!session.admin) return json({ ok: false, error: "admin required" }, 403); - if (request.method === "GET") return json(await runtimeInstanceCommand(env)); - if (request.method !== "POST") return json({ ok: false, error: "method_not_allowed" }, 405); - requireSameOrigin(request, { requireOrigin: true }); - let raw; - try { - const text = await request.text(); - if (text.length > 16384) throw new Error(); - raw = JSON.parse(text); - } catch { - throw new HttpError("invalid_runtime_instance_request", 400); - } - runtimeInstanceFields(raw, ["action", "expected_revision", "platform", "key", "config", "broker_environment", "confirm"]); - const command = { action: raw.action, expected_revision: raw.expected_revision, actor: session.login }; - if (raw.action === "initialize") { - if (raw.confirm !== "IMPORT_EXISTING_CONFIG") throw new HttpError("runtime_instance_import_confirmation_required", 400); - // Explicit, one-time import only. A deployment must freeze legacy writers - // and review this input before enabling the binding and importing it. - command.account_options = (await loadLegacyAccountOptionsConfig(env)).options || {}; - } else if (raw.action === "create" || raw.action === "edit") { - try { - if (!SUPPORTED_PLATFORMS.includes(raw.platform) || PLATFORM_CONFIG[raw.platform].dry_run_only) throw new Error(); - runtimeInstanceFields(raw.config, ["key", "label", "target_name", "account_selector", "deployment_selector", "account_scope", "service_name", "github_environment", "variable_scope", "cash_currency", "supported_domains", "broker_environment", "default_strategy_profile", "default_execution_mode"]); - const option = cleanAccountOption(raw.config, raw.platform, 0); - if (!option.account_selector || !option.deployment_selector || !option.service_name) throw new Error(); - option.default_execution_mode = raw.config.default_execution_mode || "dry_run"; - option.default_strategy_profile = cleanSlug(raw.config.default_strategy_profile, "default_strategy_profile"); - assertStrategyAllowedForAccount({ platform: raw.platform, strategy_profile: option.default_strategy_profile, execution_mode: option.default_execution_mode }, option, await loadStrategyProfilesConfig(env)); - command.platform = raw.platform; - command.config = option; - if (raw.action === "edit") command.key = cleanSlug(raw.key, "key"); - } catch { - throw new HttpError("invalid_runtime_instance_config", 400); - } - } else if (raw.action === "set_broker_environment") { - try { - if (!SUPPORTED_PLATFORMS.includes(raw.platform) || PLATFORM_CONFIG[raw.platform].dry_run_only) throw new Error(); - command.platform = cleanChoice(raw.platform, SUPPORTED_PLATFORMS, "platform"); - command.key = cleanSlug(raw.key, "key"); - command.broker_environment = normalizeBrokerEnvironment(raw.broker_environment); - if (!supportedBrokerEnvironmentsForPlatform(command.platform).includes(command.broker_environment)) throw new Error(); - } catch { - throw new HttpError("invalid_broker_environment", 400); - } - } else if (raw.action === "request_retirement") { - command.platform = raw.platform; - command.key = raw.key; - } else { - throw new HttpError("unsupported_runtime_instance_action", 400); - } - return json(await runtimeInstanceCommand(env, command)); -} - -import { dateOnlyExternalFlow, externalFlowEnabled, initializeExternalFlow, normalizeExternalFlow, putExternalFlow, readExternalFlow, readExternalFlowCoverage } from "./application/account_facts/external_flow.js"; -import { initializeExecutions, normalizeExecutions, putExecutions, readExecutions } from "./application/account_facts/executions.js"; - -export class RuntimeInstances { - constructor(ctx, env) { - this.directory = new KvDirectory(ctx.storage, env?.STRATEGY_SWITCH_CONFIG, env); - this.storage = ctx.storage; - this.sql = ctx.storage.sql; - initializeExecutions(this.sql); - this.externalFlowEnabled = externalFlowEnabled(env); - initializeExternalFlow(this.sql); - initializeCredentialMaintenance(this.sql); - initializeSecretRotation(this.sql); - this.rotationEnv = env; - this.deskReadbacks = new DeskReadbackStore(ctx.storage, async (id, actor) => { - const state = this.read(); - const target = readbackTargets({ options: runtimeInstanceAccountOptions(state.instances), revision: state.revision }).find(target => target.id === id); - if (!target) throw new DeskError("not_found", 404); - let observed = null; - try { observed = this.readAccountSettings({ platform: target.platform, key: target.key, legacy: await readRiskProfileLegacy(env) }); } catch { /* Degrade saved independently. */ } - return deskTargetReadback(env, target, actor, observed); - }); - // Native intervals remain in the existing private account read-model store. - this.sql.exec(`CREATE TABLE IF NOT EXISTS account_period_return ( - account_key TEXT PRIMARY KEY, payload_json TEXT NOT NULL - )`); - this.sql.exec(`CREATE TABLE IF NOT EXISTS account_daily_native_return ( - account_key TEXT NOT NULL, source_binding_id TEXT NOT NULL, currency TEXT NOT NULL, - observation_date TEXT NOT NULL, payload_json TEXT NOT NULL, - PRIMARY KEY(account_key, source_binding_id, currency, observation_date) - )`); - // Dormant: only an independent protected provisioning step may seed admission. - // There is deliberately no ingress/admin command that writes an admission. - this.sql.exec(`CREATE TABLE IF NOT EXISTS runtime_cycle_health_source ( - source_id TEXT PRIMARY KEY, target_id TEXT NOT NULL UNIQUE, binding_fingerprint TEXT NOT NULL, - configuration_sha256 TEXT NOT NULL, authority_revision INTEGER NOT NULL, - required_from TEXT NOT NULL, observed_at TEXT, observation_json TEXT, - observation_sha256 TEXT, received_at TEXT, checkpoint_revision INTEGER - )`); - this.sql.exec(`CREATE TABLE IF NOT EXISTS runtime_cycle_health_checkpoint ( - target_id TEXT NOT NULL, binding_fingerprint TEXT NOT NULL, source_id TEXT NOT NULL, - revision INTEGER NOT NULL, payload_json TEXT NOT NULL, - PRIMARY KEY (target_id, binding_fingerprint) - )`); - this.sql.exec(`CREATE TABLE IF NOT EXISTS runtime_cycle_health_incident ( - target_id TEXT NOT NULL, binding_fingerprint TEXT NOT NULL, source_id TEXT NOT NULL, - cycle_id TEXT NOT NULL, cycle_json TEXT NOT NULL, category TEXT NOT NULL, - active_category TEXT, latest_fault_at TEXT NOT NULL, attempt_count INTEGER NOT NULL, - resolution_history_json TEXT NOT NULL, - PRIMARY KEY (target_id, binding_fingerprint, source_id, cycle_id) - )`); - this.sql.exec(`CREATE TABLE IF NOT EXISTS runtime_cycle_health_attempt ( - history_id INTEGER PRIMARY KEY AUTOINCREMENT, - target_id TEXT NOT NULL, binding_fingerprint TEXT NOT NULL, source_id TEXT NOT NULL, - cycle_id TEXT NOT NULL, attempt_key TEXT NOT NULL, fault_at TEXT NOT NULL, - category TEXT NOT NULL, configuration_sha256 TEXT NOT NULL, cycle_json TEXT NOT NULL, - resolution_ref TEXT, - UNIQUE (target_id, binding_fingerprint, source_id, attempt_key) - )`); - this.sql.exec("CREATE INDEX IF NOT EXISTS runtime_cycle_health_history_page ON runtime_cycle_health_attempt (target_id, source_id, history_id)"); - this.sql.exec("CREATE TABLE IF NOT EXISTS instance_state (id INTEGER PRIMARY KEY CHECK (id = 1), revision INTEGER NOT NULL, payload TEXT NOT NULL)"); - this.sql.exec("CREATE TABLE IF NOT EXISTS instance_history (revision INTEGER PRIMARY KEY, entry TEXT NOT NULL)"); - this.sql.exec(`CREATE TABLE IF NOT EXISTS account_diagnosis_tasks ( - request_id TEXT PRIMARY KEY, - platform TEXT NOT NULL, - account_key TEXT NOT NULL, - target_id TEXT NOT NULL, - trigger TEXT NOT NULL, - observed_at TEXT NOT NULL, - checks_json TEXT NOT NULL, - state_fingerprint TEXT NOT NULL, - status TEXT NOT NULL, - workflow_run_id TEXT, - workflow_run_attempt TEXT, - job_id TEXT, - summary TEXT NOT NULL, - reason_code TEXT NOT NULL, - dispatch_state TEXT NOT NULL, - recheck_dispatch_state TEXT NOT NULL, - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL - )`); - try { this.sql.exec("ALTER TABLE account_diagnosis_tasks ADD COLUMN auto_source TEXT NOT NULL DEFAULT 'manual'"); } catch { /* already present */ } - try { this.sql.exec("ALTER TABLE account_diagnosis_tasks ADD COLUMN native_json TEXT"); } catch { /* already present */ } - this.sql.exec("CREATE INDEX IF NOT EXISTS account_diagnosis_tasks_fingerprint ON account_diagnosis_tasks (target_id, state_fingerprint, updated_at)"); - this.sql.exec(`CREATE TABLE IF NOT EXISTS research_promotion_applications ( - application_id TEXT PRIMARY KEY, - ticket_id TEXT NOT NULL, - status TEXT NOT NULL, - dispatch_state TEXT NOT NULL, - claim_token TEXT NOT NULL, - platform_id TEXT NOT NULL, - account_key TEXT NOT NULL, - account_scope TEXT NOT NULL, - account_selector TEXT NOT NULL, - service_name TEXT NOT NULL, - broker_environment TEXT NOT NULL, - expected_revision INTEGER NOT NULL, - expected_strategy_profile TEXT NOT NULL, - strategy_profile TEXT NOT NULL, - candidate_id TEXT NOT NULL, - config_sha256 TEXT NOT NULL, - source_commit TEXT NOT NULL, - approved_ues_revision TEXT NOT NULL, - desired_state TEXT NOT NULL, - workflow_run_id TEXT, - workflow_run_attempt TEXT, - reason_code TEXT NOT NULL, - readback_json TEXT, - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL - )`); - // A rejected application is a terminal record, but an explicitly - // re-qualified user request may create a new attempt. Keep history while - // deduplicating all still-active/uncertain attempts. - this.sql.exec("DROP INDEX IF EXISTS research_promotion_applications_ticket_account"); - this.sql.exec("CREATE INDEX IF NOT EXISTS research_promotion_applications_ticket_account_idx ON research_promotion_applications (ticket_id, platform_id, account_key, status)"); - this.sql.exec(`CREATE TABLE IF NOT EXISTS ux1_research_slot ( - login TEXT PRIMARY KEY, - revision INTEGER NOT NULL, - draft_json TEXT NOT NULL, - fingerprint TEXT NOT NULL, - preview_json TEXT, - intent_json TEXT, - job_json TEXT - )`); - try { - this.sql.exec("ALTER TABLE ux1_research_slot ADD COLUMN job_json TEXT"); - } catch { - /* already migrated */ - } - this.sql.exec(`CREATE TABLE IF NOT EXISTS human_decision_material ( - kind TEXT NOT NULL, - subject_id TEXT NOT NULL, - source_id TEXT NOT NULL, - material_key TEXT NOT NULL, - summary_json TEXT NOT NULL, - eligible INTEGER NOT NULL, - expires_at_ms INTEGER, - legacy_state TEXT NOT NULL, - legacy_block TEXT NOT NULL, - updated_at TEXT NOT NULL, - PRIMARY KEY (kind, subject_id) - )`); - this.sql.exec(`CREATE TABLE IF NOT EXISTS human_decision_record ( - request_key TEXT PRIMARY KEY, - kind TEXT NOT NULL, - subject_id TEXT NOT NULL, - material_key TEXT NOT NULL, - action TEXT NOT NULL, - actor TEXT NOT NULL, - decided_at TEXT NOT NULL, - target_json TEXT NOT NULL, - payload_json TEXT NOT NULL - )`); - this.sql.exec("CREATE INDEX IF NOT EXISTS human_decision_record_subject ON human_decision_record (kind, subject_id, material_key)"); - this.sql.exec(`CREATE TABLE IF NOT EXISTS human_decision_source_material ( - kind TEXT NOT NULL, - subject_id TEXT NOT NULL, - source_id TEXT NOT NULL, - material_key TEXT NOT NULL, - summary_json TEXT NOT NULL, - eligible INTEGER NOT NULL, - expires_at_ms INTEGER, - updated_at TEXT NOT NULL, - PRIMARY KEY (kind, subject_id, source_id) - )`); - this.sql.exec(`CREATE TABLE IF NOT EXISTS risk_profile_authority ( - id INTEGER PRIMARY KEY CHECK (id = 1), - revision INTEGER NOT NULL, - imported INTEGER NOT NULL, - bindings_json TEXT NOT NULL - )`); - this.sql.exec(`CREATE TABLE IF NOT EXISTS account_setting_draft ( - platform TEXT NOT NULL, - account_key TEXT NOT NULL, - revision INTEGER NOT NULL, - identity_json TEXT NOT NULL, - overrides_json TEXT NOT NULL, - updated_at TEXT NOT NULL, - PRIMARY KEY (platform, account_key) - )`); - this.sql.exec(`CREATE TABLE IF NOT EXISTS account_setting_declaration ( - platform TEXT NOT NULL, account_key TEXT NOT NULL, revision INTEGER NOT NULL, - identity_json TEXT NOT NULL, declaration_json TEXT NOT NULL, - PRIMARY KEY (platform, account_key) - )`); - this.sql.exec(`CREATE TABLE IF NOT EXISTS account_setting_profile_application ( - request_key TEXT PRIMARY KEY, - platform TEXT NOT NULL, - account_key TEXT NOT NULL, - identity_json TEXT NOT NULL, - instance_revision INTEGER NOT NULL, - draft_revision INTEGER NOT NULL, - old_profile TEXT NOT NULL, - new_profile TEXT NOT NULL, - approved_by TEXT NOT NULL, - approved_at TEXT NOT NULL, - status TEXT NOT NULL, - workflow_run_id TEXT, - workflow_run_attempt TEXT, - claimed_at TEXT - )`); - this.sql.exec("CREATE INDEX IF NOT EXISTS account_setting_profile_application_target ON account_setting_profile_application (platform, account_key, status)"); - this.sql.exec(`CREATE TABLE IF NOT EXISTS account_facts_observation ( - platform TEXT NOT NULL, - account_key TEXT NOT NULL, - payload_json TEXT NOT NULL, - observed_finished_at TEXT NOT NULL, - observation_date TEXT NOT NULL, - target_id TEXT NOT NULL, - source_binding_id TEXT NOT NULL, - account_scope TEXT NOT NULL, - updated_at TEXT NOT NULL, - PRIMARY KEY (platform, account_key) - )`); - this.sql.exec(`CREATE TABLE IF NOT EXISTS account_facts_daily ( - platform TEXT NOT NULL, - account_key TEXT NOT NULL, - observation_date TEXT NOT NULL, - payload_json TEXT NOT NULL, - observed_finished_at TEXT NOT NULL, - target_id TEXT NOT NULL, - source_binding_id TEXT NOT NULL, - account_scope TEXT NOT NULL, - updated_at TEXT NOT NULL, - PRIMARY KEY (platform, account_key, observation_date) - )`); - this.sql.exec("CREATE INDEX IF NOT EXISTS account_facts_daily_account ON account_facts_daily (platform, account_key, observation_date)"); - this.sql.exec(`CREATE TABLE IF NOT EXISTS market_benchmark_daily ( - series_id TEXT NOT NULL, - observation_date TEXT NOT NULL, - close TEXT NOT NULL, - source TEXT, - updated_at TEXT NOT NULL, - PRIMARY KEY (series_id, observation_date) - )`); - this.sql.exec("CREATE INDEX IF NOT EXISTS market_benchmark_daily_date ON market_benchmark_daily (observation_date)"); - this.sql.exec(`CREATE TABLE IF NOT EXISTS market_benchmark_revision_ledger ( - proposal_sha256 TEXT NOT NULL, - entry_index INTEGER NOT NULL, - series_id_sha256 TEXT NOT NULL, - observation_date TEXT NOT NULL, - prior_close_sha256 TEXT NOT NULL, - new_close_sha256 TEXT NOT NULL, - candidate_source TEXT NOT NULL, - reason TEXT NOT NULL, - applied_at TEXT NOT NULL, - PRIMARY KEY (proposal_sha256, entry_index) - )`); - this.sql.exec("CREATE INDEX IF NOT EXISTS market_benchmark_revision_ledger_date ON market_benchmark_revision_ledger (observation_date)"); - this.sql.exec(`CREATE TABLE IF NOT EXISTS hk_stop_request ( - request_id TEXT PRIMARY KEY, - target_id TEXT NOT NULL, - identity_json TEXT NOT NULL, - source_revision INTEGER NOT NULL, - action TEXT NOT NULL, - requested_at TEXT NOT NULL, - phase TEXT NOT NULL, - dispatch_result TEXT, - workflow_run_id TEXT, - workflow_run_attempt TEXT, - updated_at TEXT NOT NULL - )`); - this.sql.exec("CREATE INDEX IF NOT EXISTS hk_stop_request_target ON hk_stop_request (target_id, requested_at)"); - for (const column of ["source_identity_sha256", "runtime_identity_sha256", "result_json", "stopped_at"]) { - try { this.sql.exec(`ALTER TABLE hk_stop_request ADD COLUMN ${column} TEXT`); } catch { /* already present */ } - } - } - - emptyCycleHealthState(targetId, bindingId) { - return { - schema_version: CYCLE_HEALTH_SUMMARY_SCHEMA_VERSION, - target_id: targetId, source_binding_id: bindingId, - incident_count: 0, fault_attempt_count: 0, unresolved_count: 0, - highest_severity: null, active_category: null, latest_fault_at: null, - resolution_history_count: 0, history_complete: false, - execution_authority_granted: false, - }; - } - - cycleHealthHistoryCursor(sourceId, targetId, bindingFingerprint, throughId, afterId) { - return [sourceId, targetId, bindingFingerprint, throughId, afterId].join(":"); - } - - cycleHealthHistoryPage(sourceId, targetId, bindingFingerprint, cursor = null) { - let throughId, afterId; - if (cursor) { - const parts = cursor.split(":"); - if (parts.length !== 5 || parts[0] !== sourceId || parts[1] !== targetId || parts[2] !== bindingFingerprint) { - throw new HttpError("cycle_health_history_cursor_mismatch", 400); - } - throughId = Number(parts[3]); afterId = Number(parts[4]); - if (!Number.isSafeInteger(throughId) || throughId < 0 || !Number.isSafeInteger(afterId) || afterId < 0 || afterId > throughId) { - throw new HttpError("cycle_health_history_cursor_invalid", 400); - } - } else { - throughId = Number(this.sql.exec("SELECT COALESCE(MAX(history_id), 0) AS highwater FROM runtime_cycle_health_attempt WHERE target_id = ? AND source_id = ?", targetId, sourceId).toArray()[0]?.highwater || 0); - afterId = 0; - } - const rows = this.sql.exec(`SELECT a.history_id, a.binding_fingerprint, a.cycle_id, a.attempt_key, a.fault_at, - a.category, a.configuration_sha256, a.cycle_json, a.resolution_ref, i.resolution_history_json - FROM runtime_cycle_health_attempt a JOIN runtime_cycle_health_incident i - ON i.target_id = a.target_id AND i.binding_fingerprint = a.binding_fingerprint - AND i.source_id = a.source_id AND i.cycle_id = a.cycle_id - WHERE a.target_id = ? AND a.source_id = ? AND a.history_id > ? AND a.history_id <= ? - ORDER BY a.history_id ASC LIMIT ?`, targetId, sourceId, afterId, throughId, CYCLE_HEALTH_HISTORY_PAGE_SIZE + 1).toArray(); - const hasMore = rows.length > CYCLE_HEALTH_HISTORY_PAGE_SIZE; - const selected = rows.slice(0, CYCLE_HEALTH_HISTORY_PAGE_SIZE); - const items = selected.map(row => { - let cycle, resolutionHistory; - try { cycle = JSON.parse(row.cycle_json); resolutionHistory = JSON.parse(row.resolution_history_json); } - catch { throw new HttpError("cycle_health_history_stored_invalid", 409); } - return { - history_id: row.history_id, binding_fingerprint: row.binding_fingerprint, - continuity: row.binding_fingerprint === bindingFingerprint ? "same_binding" : "unconfirmed", - cycle_id: row.cycle_id, attempt_key: row.attempt_key, fault_at: row.fault_at, - category: row.category, configuration_sha256: row.configuration_sha256, - cycle, resolution_ref: row.resolution_ref, resolution_history: resolutionHistory, - }; - }); - const nextCursor = hasMore && selected.length - ? this.cycleHealthHistoryCursor(sourceId, targetId, bindingFingerprint, throughId, selected.at(-1).history_id) - : null; - return { - items, snapshot_through_id: throughId, complete: !hasMore, - next_cursor: nextCursor, - }; - } - - cycleHealthRecordAttempt({ targetId, bindingFingerprint, sourceId, configurationSha, cycle, resolutionRef = null, resolutionHistory = null, state }) { - const category = cycleHealthFaultCategory(cycle); - const attemptKey = cycleHealthAttemptKey(cycle); - const cycleJson = canonicalCycleJson(cycle); - const oldAttempt = this.sql.exec(`SELECT cycle_json FROM runtime_cycle_health_attempt - WHERE target_id = ? AND binding_fingerprint = ? AND source_id = ? AND attempt_key = ?`, - targetId, bindingFingerprint, sourceId, attemptKey).toArray()[0]; - if (oldAttempt) { - if (oldAttempt.cycle_json !== cycleJson) throw new HttpError("cycle_health_fault_receipt_conflict", 409); - return { state, inserted: false }; - } - if (!category) return { state, inserted: false }; - const faultAt = cycleHealthFaultTime(cycle); - const prior = this.sql.exec(`SELECT * FROM runtime_cycle_health_incident - WHERE target_id = ? AND binding_fingerprint = ? AND source_id = ? AND cycle_id = ?`, - targetId, bindingFingerprint, sourceId, cycle.cycle_id).toArray()[0] || null; - let incidentCycle = cycle, incidentCategory = category, latestFaultAt = faultAt; - let activeCategory = resolutionRef === null ? category : null; - let attemptCount = 1; - let history = resolutionHistory || []; - if (prior) { - let oldCycle; - try { oldCycle = JSON.parse(prior.cycle_json); history = JSON.parse(prior.resolution_history_json); } - catch { throw new HttpError("cycle_health_checkpoint_invalid", 409); } - const oldRank = prior.category === "execution_uncertainty" ? 2 : 1; - const newRank = category === "execution_uncertainty" ? 2 : 1; - const timeOrder = compareCycleInstants(faultAt, prior.latest_fault_at); - if (newRank > oldRank || (newRank === oldRank && (timeOrder > 0 || (timeOrder === 0 && attemptKey > cycleHealthAttemptKey(oldCycle))))) { - incidentCycle = cycle; - } else { - incidentCycle = oldCycle; - } - incidentCategory = newRank > oldRank ? category : prior.category; - latestFaultAt = timeOrder > 0 ? faultAt : prior.latest_fault_at; - attemptCount = prior.attempt_count + 1; - if (resolutionRef === null) { - const priorActiveRank = prior.active_category === "execution_uncertainty" ? 2 : prior.active_category === "operational" ? 1 : 0; - activeCategory = newRank > priorActiveRank ? category : prior.active_category; - } else activeCategory = prior.active_category; - } - this.sql.exec(`INSERT INTO runtime_cycle_health_attempt ( - target_id, binding_fingerprint, source_id, cycle_id, attempt_key, fault_at, - category, configuration_sha256, cycle_json, resolution_ref - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, targetId, bindingFingerprint, sourceId, - cycle.cycle_id, attemptKey, faultAt, category, configurationSha, cycleJson, resolutionRef); - this.sql.exec(`INSERT INTO runtime_cycle_health_incident ( - target_id, binding_fingerprint, source_id, cycle_id, cycle_json, category, - active_category, latest_fault_at, attempt_count, resolution_history_json - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT(target_id, binding_fingerprint, source_id, cycle_id) DO UPDATE SET - cycle_json = excluded.cycle_json, category = excluded.category, - active_category = excluded.active_category, latest_fault_at = excluded.latest_fault_at, - attempt_count = excluded.attempt_count, resolution_history_json = excluded.resolution_history_json`, - targetId, bindingFingerprint, sourceId, cycle.cycle_id, canonicalCycleJson(incidentCycle), incidentCategory, - activeCategory, latestFaultAt, attemptCount, JSON.stringify(history)); - const next = { ...state }; - if (!prior) next.incident_count += 1; - next.fault_attempt_count += 1; - if (activeCategory !== null && (!prior || prior.active_category === null)) next.unresolved_count += 1; - if (!prior && Array.isArray(history)) next.resolution_history_count += history.length; - if (activeCategory !== null && !next.active_category) next.active_category = activeCategory; - const stateRank = next.highest_severity === "execution_uncertainty" ? 2 : next.highest_severity === "operational" ? 1 : 0; - const incidentRank = incidentCategory === "execution_uncertainty" ? 2 : 1; - if (incidentRank > stateRank) next.highest_severity = incidentCategory; - const activeRank = next.active_category === "execution_uncertainty" ? 2 : next.active_category === "operational" ? 1 : 0; - const nextActiveRank = activeCategory === "execution_uncertainty" ? 2 : activeCategory === "operational" ? 1 : 0; - if (nextActiveRank > activeRank) next.active_category = activeCategory; - if (!next.latest_fault_at || compareCycleInstants(faultAt, next.latest_fault_at) > 0) next.latest_fault_at = faultAt; - next.history_complete = false; - return { state: next, inserted: true }; - } - - migrateCycleHealthCheckpoint(row) { - let checkpoint; - try { checkpoint = JSON.parse(row.payload_json); } catch { throw new HttpError("cycle_health_checkpoint_invalid", 409); } - if (checkpoint?.state?.schema_version === CYCLE_HEALTH_SUMMARY_SCHEMA_VERSION) return checkpoint; - const oldState = checkpoint?.state; - if (!oldState || !Array.isArray(oldState.incidents)) throw new HttpError("cycle_health_checkpoint_invalid", 409); - let state = this.emptyCycleHealthState(row.target_id, oldState.source_binding_id); - for (const incident of oldState.incidents) { - if (!incident || !Array.isArray(incident.fault_attempts) || !incident.fault_attempts.length) throw new HttpError("cycle_health_checkpoint_invalid", 409); - for (const attempt of incident.fault_attempts) { - if (!attempt?.cycle || typeof attempt.configuration_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(attempt.configuration_sha256) - || (attempt.resolution_ref !== null && (typeof attempt.resolution_ref !== "string" || !/^[a-f0-9]{64}$/.test(attempt.resolution_ref)))) { - throw new HttpError("cycle_health_checkpoint_invalid", 409); - } - state = this.cycleHealthRecordAttempt({ targetId: row.target_id, bindingFingerprint: row.binding_fingerprint, - sourceId: row.source_id, configurationSha: attempt.configuration_sha256, cycle: attempt.cycle, - resolutionRef: attempt.resolution_ref, resolutionHistory: incident.resolution_history || [], state }).state; - } - } - checkpoint.state = state; - checkpoint.schema_version = "qsl_runtime_cycle_health_checkpoint_state.v2"; - checkpoint.covered_through = null; - checkpoint.coverage_complete = false; - checkpoint.coverage_incomplete_reason = "legacy_watermark_unavailable"; - const source = this.sql.exec("SELECT binding_fingerprint, configuration_sha256, required_from, observed_at, observation_json FROM runtime_cycle_health_source WHERE source_id = ?", row.source_id).toArray()[0]; - if (row.binding_fingerprint === source?.binding_fingerprint && checkpoint.baseline_established === true - && checkpoint.configuration_sha256 === source.configuration_sha256 && checkpoint.required_from === source.required_from - && source.observation_json && source.observed_at) { - try { - const observation = JSON.parse(source.observation_json); - const coverage = observation?.targets?.[0]?.cycle_health?.coverage; - if (observation.computed_at === source.observed_at && compareCycleInstants(coverage?.through, observation.computed_at) <= 0 - && cycleCoverageComplete(coverage, { since: source.required_from, through: source.required_from })) { - checkpoint.covered_through = coverage.through; - checkpoint.coverage_complete = true; - checkpoint.coverage_incomplete_reason = null; - } - } catch { /* Keep legacy coverage unproven when the stored source record is invalid. */ } - } - this.sql.exec("UPDATE runtime_cycle_health_checkpoint SET payload_json = ? WHERE target_id = ? AND binding_fingerprint = ? AND source_id = ? AND revision = ?", - JSON.stringify(checkpoint), row.target_id, row.binding_fingerprint, row.source_id, row.revision); - return checkpoint; - } - - cycleHealthCommand(command) { - const rows = [...this.sql.exec("SELECT * FROM runtime_cycle_health_source WHERE source_id = ?", command.source_id)]; - const a = rows[0]; - if (!a || a.target_id !== command.target_id || a.binding_fingerprint !== command.binding_fingerprint - || !/^[a-f0-9]{64}$/.test(a.configuration_sha256) - || !Number.isSafeInteger(a.authority_revision) || a.authority_revision < 1) throw new HttpError("cycle_health_source_not_admitted", 409); - try { normalizeCycleInstant(a.required_from); } catch { throw new HttpError("cycle_health_baseline_not_admitted", 409); } - if (command.action === "cycle_health_admission") return { - authority_revision: a.authority_revision, configuration_sha256: a.configuration_sha256, required_from: a.required_from, - }; - if (command.authority_revision !== a.authority_revision || command.configuration_sha256 !== a.configuration_sha256 - || command.required_from !== a.required_from) throw new HttpError("cycle_health_admission_changed", 409); - let partitions = [...this.sql.exec("SELECT * FROM runtime_cycle_health_checkpoint WHERE target_id = ? AND source_id = ?", a.target_id, a.source_id)]; - const owner = [...this.sql.exec("SELECT source_id FROM runtime_cycle_health_checkpoint WHERE target_id = ? AND binding_fingerprint = ?", a.target_id, a.binding_fingerprint)][0]; - if (owner && owner.source_id !== a.source_id) throw new HttpError("cycle_health_source_partition_conflict", 409); - partitions = partitions.map(row => ({ ...row, checkpoint: this.migrateCycleHealthCheckpoint(row) })); - const row = partitions.find(p => p.binding_fingerprint === a.binding_fingerprint) || null; - const previous = row?.checkpoint || null; - const priorPartitions = partitions.filter(p => p.binding_fingerprint !== a.binding_fingerprint).map(p => ({ - checkpoint_revision: p.revision, checkpoint: p.checkpoint, - })); - if (row && a.checkpoint_revision !== row.revision) throw new HttpError("cycle_health_checkpoint_revision_conflict", 409); - const state = previous?.state || this.emptyCycleHealthState(a.target_id, command.source_binding_id); - const admissionTransition = previous && (previous.configuration_sha256 !== a.configuration_sha256 || previous.required_from !== a.required_from); - const effectiveCheckpoint = admissionTransition ? { - ...previous, baseline_established: false, covered_through: null, coverage_complete: false, - coverage_incomplete_reason: "admission_changed", - } : previous; - const result = (status, checkpoint = previous, revision = row?.revision || 0) => ({ - ok: true, schema_version: "qsl_runtime_cycle_health_checkpoint.v2", status, - source_id: a.source_id, target_id: a.target_id, authority_revision: a.authority_revision, - configuration_sha256: a.configuration_sha256, source_binding_id: command.source_binding_id, - checkpoint_revision: revision, observed_at: a.observed_at, observation_sha256: a.observation_sha256, - received_at: a.received_at, required_from: a.required_from, checkpoint, - covered_through: checkpoint?.covered_through || null, - coverage_complete: checkpoint?.coverage_complete === true, - coverage_incomplete_reason: checkpoint?.coverage_incomplete_reason || null, - ...(command.action === "cycle_health_read" - ? { history_page: this.cycleHealthHistoryPage(a.source_id, a.target_id, a.binding_fingerprint, command.history_cursor || null) } - : {}), - prior_partitions: priorPartitions, continuity: priorPartitions.length ? "unconfirmed" : "same_binding", - adopted: false, no_order: true, execution_authority_granted: false, - }); - if (command.action === "cycle_health_read") { - const r = result(!previous ? "uninitialized" : state.unresolved_count || priorPartitions.length ? "blocked" - : !effectiveCheckpoint.baseline_established || effectiveCheckpoint.coverage_complete !== true ? "incomplete" : "complete", effectiveCheckpoint); - if (a.observed_at && (Date.now() - Date.parse(a.observed_at) > ACCOUNT_FACTS_STALE_MS || Date.parse(a.observed_at) - Date.now() > ACCOUNT_FACTS_FUTURE_SKEW_MS)) r.status = "stale"; - if (previous && (previous.configuration_sha256 !== a.configuration_sha256 || previous.required_from !== a.required_from)) r.status = "incomplete"; - if (priorPartitions.length || state.unresolved_count) r.status = "blocked"; - return r; - } - if (command.action !== "cycle_health_put") throw new HttpError("unsupported_cycle_health_action", 400); - const packet = command.observation; - if (packet.targets[0].cycle_health.configuration_sha256 !== a.configuration_sha256) throw new HttpError("cycle_health_configuration_not_admitted", 409); - const order = a.observed_at ? compareCycleInstants(packet.computed_at, a.observed_at) : 1; - if (order < 0) throw new HttpError("cycle_health_stale_observation", 409); - if (order === 0) { - if (command.observation_json !== a.observation_json || command.observation_sha256 !== a.observation_sha256) throw new HttpError("cycle_health_observation_conflict", 409); - return { ...result("unchanged"), schema_version: "qsl_runtime_cycle_health_ack.v1", result: "unchanged" }; - } - const h = packet.targets[0].cycle_health; - const transition = previous && (previous.configuration_sha256 !== a.configuration_sha256 || previous.required_from !== a.required_from); - const expectedCoverageFrom = !transition && previous?.covered_through ? previous.covered_through : a.required_from; - const scanComplete = cycleCoverageComplete(h.coverage); - const coverageContinuous = scanComplete - && compareCycleInstants(h.coverage.from, expectedCoverageFrom) <= 0 - && compareCycleInstants(h.coverage.through, expectedCoverageFrom) >= 0; - const coveredThrough = coverageContinuous ? h.coverage.through : transition ? null : previous?.covered_through || null; - const incompleteReason = coverageContinuous ? null : !scanComplete ? "scan_incomplete" - : compareCycleInstants(h.coverage.from, expectedCoverageFrom) > 0 ? "coverage_gap" : "coverage_not_advanced"; - let nextState = structuredClone(state); - for (const cycle of h.cycles) { - nextState = this.cycleHealthRecordAttempt({ targetId: a.target_id, bindingFingerprint: a.binding_fingerprint, - sourceId: a.source_id, configurationSha: h.configuration_sha256, cycle, state: nextState }).state; - } - const baselineEstablished = coverageContinuous - ? (!previous || transition || previous.baseline_established || !previous.covered_through) - : Boolean(previous && !transition && previous.baseline_established); - const checkpoint = { - schema_version: "qsl_runtime_cycle_health_checkpoint_state.v2", - configuration_sha256: a.configuration_sha256, required_from: a.required_from, - baseline_established: baselineEstablished, - covered_through: coveredThrough, coverage_complete: coverageContinuous, - coverage_incomplete_reason: incompleteReason, - state: nextState, - }; - const revision = (row?.revision || 0) + 1; - this.sql.exec("UPDATE runtime_cycle_health_source SET observed_at = ?, observation_json = ?, observation_sha256 = ?, received_at = ?, checkpoint_revision = ? WHERE source_id = ?", - packet.computed_at, command.observation_json, command.observation_sha256, command.received_at, revision, a.source_id); - this.sql.exec(`INSERT INTO runtime_cycle_health_checkpoint (target_id, binding_fingerprint, source_id, revision, payload_json) - VALUES (?, ?, ?, ?, ?) ON CONFLICT(target_id, binding_fingerprint) DO UPDATE SET revision = excluded.revision, payload_json = excluded.payload_json`, - a.target_id, a.binding_fingerprint, a.source_id, revision, JSON.stringify(checkpoint)); - a.observed_at = packet.computed_at; a.observation_sha256 = command.observation_sha256; a.received_at = command.received_at; - return { ...result("stored", checkpoint, revision), schema_version: "qsl_runtime_cycle_health_ack.v1", result: "stored" }; - } - - cycleHealthProvisionCommand(command) { - const { source_id: sourceId, target_id: targetId, binding_fingerprint: bindingFingerprint, - configuration_sha256: configurationSha, required_from: requiredFrom, - expected_authority_revision: expectedRevision } = command || {}; - if (targetId !== "longbridge.paper" || !/^[a-f0-9]{64}$/.test(bindingFingerprint || "") - || !/^[a-f0-9]{64}$/.test(configurationSha || "") - || !Number.isSafeInteger(expectedRevision) || expectedRevision < 0) { - throw new HttpError("cycle_health_admission_request_invalid", 400); - } - const normalizedSourceId = normalizeControlPlaneIdentifier(sourceId, "source_id", false); - let normalizedRequiredFrom; - try { normalizedRequiredFrom = normalizeCycleInstant(requiredFrom); } - catch { throw new HttpError("cycle_health_baseline_not_admitted", 400); } - const current = [...this.sql.exec("SELECT * FROM runtime_cycle_health_source WHERE target_id = ?", targetId)][0] || null; - const sourceOwner = [...this.sql.exec("SELECT * FROM runtime_cycle_health_source WHERE source_id = ?", normalizedSourceId)][0] || null; - if (sourceOwner && sourceOwner.target_id !== targetId) throw new HttpError("cycle_health_source_target_conflict", 409); - if (current && current.source_id !== normalizedSourceId) throw new HttpError("cycle_health_source_id_change_requires_support", 409); - if (current?.binding_fingerprint !== bindingFingerprint - && [...this.sql.exec("SELECT 1 AS found FROM runtime_cycle_health_checkpoint WHERE target_id = ? AND binding_fingerprint = ?", targetId, bindingFingerprint)][0]) { - throw new HttpError("cycle_health_binding_partition_reuse_requires_support", 409); - } - const currentRevision = current?.authority_revision || 0; - const sameAdmission = current && current.binding_fingerprint === bindingFingerprint - && current.configuration_sha256 === configurationSha && current.required_from === normalizedRequiredFrom; - if (sameAdmission) { - if (expectedRevision > currentRevision || expectedRevision < currentRevision - 1) { - throw new HttpError("cycle_health_admission_revision_conflict", 409); - } - return { - ok: true, schema_version: "qsl_runtime_cycle_health_admission.v1", result: "unchanged", - source_id: normalizedSourceId, target_id: targetId, authority_revision: currentRevision, - configuration_sha256: configurationSha, required_from: normalizedRequiredFrom, - status: "admission_unchanged", adopted: false, no_order: true, - execution_authority_granted: false, - }; - } - if (expectedRevision !== currentRevision) throw new HttpError("cycle_health_admission_revision_conflict", 409); - if (current && compareCycleInstants(normalizedRequiredFrom, current.required_from) > 0) { - throw new HttpError("cycle_health_baseline_range_shrink_rejected", 409); - } - const nextRevision = currentRevision + 1; - if (!Number.isSafeInteger(nextRevision)) throw new HttpError("cycle_health_admission_revision_exhausted", 409); - if (!current) { - this.sql.exec(`INSERT INTO runtime_cycle_health_source ( - source_id, target_id, binding_fingerprint, configuration_sha256, - authority_revision, required_from, observed_at, observation_json, - observation_sha256, received_at, checkpoint_revision - ) VALUES (?, ?, ?, ?, ?, ?, NULL, NULL, NULL, NULL, NULL)`, - normalizedSourceId, targetId, bindingFingerprint, configurationSha, nextRevision, normalizedRequiredFrom); - } else { - const bindingChanged = current.binding_fingerprint !== bindingFingerprint; - this.sql.exec(`UPDATE runtime_cycle_health_source SET binding_fingerprint = ?, configuration_sha256 = ?, - authority_revision = ?, required_from = ?, observed_at = ?, observation_json = ?, observation_sha256 = ?, - received_at = ?, checkpoint_revision = ? WHERE source_id = ? AND authority_revision = ?`, - bindingFingerprint, configurationSha, nextRevision, normalizedRequiredFrom, - bindingChanged ? null : current.observed_at, - bindingChanged ? null : current.observation_json, - bindingChanged ? null : current.observation_sha256, - bindingChanged ? null : current.received_at, - bindingChanged ? null : current.checkpoint_revision, - normalizedSourceId, currentRevision); - if (this.sql.exec("SELECT changes() AS changed").toArray()[0]?.changed !== 1) { - throw new HttpError("cycle_health_admission_revision_conflict", 409); - } - } - return { - ok: true, schema_version: "qsl_runtime_cycle_health_admission.v1", result: "stored", - source_id: normalizedSourceId, target_id: targetId, authority_revision: nextRevision, - configuration_sha256: configurationSha, required_from: normalizedRequiredFrom, - status: "admission_configured", adopted: false, no_order: true, - execution_authority_granted: false, - }; - } - - accountFactsCommand(command) { - if (!command || typeof command.action !== "string") throw new HttpError("unsupported_account_facts_action", 400); - if (command.action === "account_daily_native_return_research_read") return this.researchNativeDailyReturns(command); - if (["binance_detail_put", "binance_detail_read"].includes(command.action)) return binanceDetailCommand(this.sql, command); - if (command.action === "binance_wallet_history_put" || command.action === "binance_wallet_history_read") { - return this.binanceWalletHistoryCommand(command); - } - if (command.action === "account_period_return_put" || command.action === "account_period_return_read") return this.accountPeriodReturnCommand(command); - if (["account_executions_put", "account_executions_read"].includes(command.action)) { - const context = this.accountFactsStorageContext(command); - if (context.platform !== "ibkr") throw new HttpError("invalid_execution_platform", 400); - return command.action === "account_executions_read" - ? { ok: true, executions: readExecutions(this.sql, context) } - : { ok: true, ...putExecutions(this.sql, context, command.payload) }; - } - if (command.action === "account_external_flow_put") { - const context = this.accountFactsStorageContext(command); - const flow = normalizeExternalFlow(command.externalFlow); - if (!this.externalFlowEnabled && !dateOnlyExternalFlow(flow)) throw new HttpError("external_flow_disabled", 409); - const source = { ibkr: "ibkr_flex", schwab: "schwab_transactions", longbridge: "longbridge_cashflow" }[context.platform]; - if (!source || flow.windows.some(w => w.source !== source)) throw new HttpError("external_flow_source_mismatch", 409); - const stored = putExternalFlow(this.sql, context, flow); - const readback = readExternalFlow(this.sql, context); - const readbackVerified = flow.windows.every(w => readback?.windows.some(saved => JSON.stringify(saved) === JSON.stringify(w))); - if (!readbackVerified) throw new HttpError("external_flow_readback_failed", 503); - return { ok: true, ...stored, readback_verified: true }; - } - if (command.action === "account_facts_put") return this.putAccountFactsObservation(command); - if (command.action === "account_facts_history_put") return this.putAccountFactsHistoricalObservation(command); - if (command.action === "account_facts_read") return this.readAccountFactsObservation(command); - if (command.action === "account_facts_list") return this.listAccountFactsObservations(command); - if (command.action === "account_facts_history_read") return this.readAccountFactsDailyHistory(command); - throw new HttpError("unsupported_account_facts_action", 400); - } - - - marketBenchmarkCommand(command) { - if (!command || typeof command.action !== "string") throw new HttpError("unsupported_market_benchmark_action", 400); - if (command.action === "market_benchmark_read") return this.readMarketBenchmarkPoints(command); - if (command.action === "market_benchmark_put") return this.putMarketBenchmarkPoints(command); - if (command.action === "market_benchmark_revise") return this.reviseMarketBenchmarkPoints(command); - throw new HttpError("unsupported_market_benchmark_action", 400); - } - - readMarketBenchmarkPoints(command = {}) { - const rows = this.sql.exec( - `SELECT series_id, observation_date, close, source FROM market_benchmark_daily - ORDER BY series_id ASC, observation_date ASC`, - ).toArray(); - const points = rows.map((row) => ({ - seriesId: row.series_id, - observationDate: row.observation_date, - close: row.close, - ...(row.source ? { source: row.source } : {}), - })); - if (command.performance === true) return { ok: true, points }; - return { ok: true, ...publicMarketBenchmarkReadModel(points) }; - } - - putMarketBenchmarkPoints(command) { - let normalized; - try { - normalized = normalizeMarketBenchmarkSyncPayload(command.payload || command); - } catch (error) { - if (error instanceof MarketBenchmarkError) throw new HttpError(error.code, error.status || 400); - throw error; - } - const updatedAt = new Date().toISOString(); - let written = 0; - let unchanged = 0; - for (const point of normalized.points) { - const existing = this.sql.exec( - `SELECT close, source FROM market_benchmark_daily WHERE series_id = ? AND observation_date = ?`, - point.seriesId, - point.observationDate, - ).toArray()[0]; - if (existing && existing.close === point.close - && (existing.source || null) === (point.source || null)) { - unchanged += 1; - continue; - } - if (existing && existing.close !== point.close) { - throw new HttpError("market_benchmark_point_conflict", 409); - } - this.sql.exec( - `INSERT INTO market_benchmark_daily (series_id, observation_date, close, source, updated_at) - VALUES (?, ?, ?, ?, ?) - ON CONFLICT(series_id, observation_date) DO UPDATE SET - close = excluded.close, - source = excluded.source, - updated_at = excluded.updated_at`, - point.seriesId, - point.observationDate, - point.close, - point.source || null, - updatedAt, - ); - written += 1; - } - return { - ok: true, - stored: true, - schema_version: MARKET_BENCHMARK_SCHEMA, - inception_date: this.readMarketBenchmarkPoints().inception_date, - written, - unchanged, - points: normalized.points.length, - }; - } - - reviseMarketBenchmarkPoints(command) { - const appliedAt = typeof command.applied_at === "string" && !Number.isNaN(Date.parse(command.applied_at)) - ? command.applied_at - : new Date().toISOString(); - // Plan is validated by the HTTP layer (hashes + concrete updates); DO only applies. - const plan = command.plan; - if (!plan || !Array.isArray(plan.updates) || !Array.isArray(plan.ledger_entries)) { - throw new HttpError("benchmark_revision_plan_required", 400); - } - if (plan.overwrites !== plan.updates.length || plan.deletes !== 0) { - throw new HttpError("benchmark_revision_plan_invalid", 400); - } - const existingProposal = this.sql.exec( - `SELECT proposal_sha256 FROM market_benchmark_revision_ledger WHERE proposal_sha256 = ? LIMIT 1`, - plan.proposal_sha256, - ).toArray()[0]; - if (existingProposal) throw new HttpError("benchmark_revision_already_applied", 409); - let written = 0; - for (const point of plan.updates) { - const existing = this.sql.exec( - `SELECT close, source FROM market_benchmark_daily WHERE series_id = ? AND observation_date = ?`, - point.seriesId, - point.observationDate, - ).toArray()[0]; - if (!existing) throw new HttpError("benchmark_revision_target_missing", 404); - if (existing.close === point.close) throw new HttpError("benchmark_revision_noop", 400); - this.sql.exec( - `UPDATE market_benchmark_daily SET close = ?, source = ?, updated_at = ? WHERE series_id = ? AND observation_date = ?`, - point.close, - point.source || null, - appliedAt, - point.seriesId, - point.observationDate, - ); - written += 1; - } - for (const entry of plan.ledger_entries) { - this.sql.exec( - `INSERT INTO market_benchmark_revision_ledger ( - proposal_sha256, entry_index, series_id_sha256, observation_date, - prior_close_sha256, new_close_sha256, candidate_source, reason, applied_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, - entry.proposal_sha256, - entry.entry_index, - entry.series_id_sha256, - entry.observation_date, - entry.prior_close_sha256, - entry.new_close_sha256, - entry.candidate_source, - entry.reason, - entry.applied_at || appliedAt, - ); - } - return { - ok: true, - applied: true, - proposal_sha256: plan.proposal_sha256, - overwrites: written, - deletes: 0, - ledger_entries: plan.ledger_entries.length, - }; - } - - - binanceWalletHistoryContext(command) { - const accountKey = command?.account_key; - const sourceBindingId = command?.source_binding_id; - if (typeof accountKey !== "string" || !accountKey - || typeof sourceBindingId !== "string" || !/^[a-f0-9]{64}$/.test(sourceBindingId) - || command.storage_account_key !== `${accountKey}:${sourceBindingId}` - || typeof command.target_id !== "string" || !command.target_id - || typeof command.account_scope !== "string" || !command.account_scope) { - throw new HttpError("invalid_binance_wallet_history_account", 400); - } - return { - account_key: command.storage_account_key, - target_id: command.target_id, - account_scope: command.account_scope, - source_binding_id: sourceBindingId, - binding: { - target_id: command.target_id, - account_scope: command.account_scope, - source_binding: { kind: "binance_readonly_scope_revision", id: sourceBindingId }, - }, - }; - } - - binanceWalletHistoryCommand(command) { - const context = this.binanceWalletHistoryContext(command); - if (command.action === "binance_wallet_history_read") { - const rows = this.sql.exec( - `SELECT payload_json, target_id, source_binding_id, account_scope FROM account_facts_daily - WHERE platform = 'binance' AND account_key = ? ORDER BY observation_date ASC`, - context.account_key, - ).toArray(); - const days = []; - for (const row of rows) { - if (row.target_id !== context.target_id || row.source_binding_id !== context.source_binding_id - || row.account_scope !== context.account_scope) { - throw new HttpError("binance_wallet_history_identity_mismatch", 409); - } - let payload; - try { payload = JSON.parse(row.payload_json); } - catch { throw new HttpError("binance_wallet_history_stored_invalid", 409); } - try { days.push(normalizeBinanceWalletHistoryEntry(payload, context.binding)); } - catch { throw new HttpError("binance_wallet_history_stored_invalid", 409); } - } - return { ok: true, days }; - } - - let history; - try { history = normalizeBinanceWalletHistoryEntry(command.history, context.binding); } - catch { throw new HttpError("invalid_binance_wallet_history", 400); } - const row = this.sql.exec( - `SELECT payload_json, target_id, source_binding_id, account_scope FROM account_facts_daily - WHERE platform = 'binance' AND account_key = ? AND observation_date = ?`, - context.account_key, - history.observation_date, - ).toArray()[0]; - let existing = null; - if (row) { - if (row.target_id !== context.target_id || row.source_binding_id !== context.source_binding_id - || row.account_scope !== context.account_scope) { - throw new HttpError("binance_wallet_history_identity_mismatch", 409); - } - try { existing = normalizeBinanceWalletHistoryEntry(JSON.parse(row.payload_json), context.binding); } - catch { throw new HttpError("binance_wallet_history_stored_invalid", 409); } - } - if (existing) { - const existingTime = Date.parse(existing.report_observed_finished_at); - const incomingTime = Date.parse(history.report_observed_finished_at); - if (incomingTime < existingTime) return { ok: true, unchanged: true }; - if (incomingTime === existingTime) { - if (JSON.stringify(existing) !== JSON.stringify(history)) { - throw new HttpError("binance_wallet_history_conflict", 409); - } - return { ok: true, unchanged: true }; - } - } - const updatedAt = new Date().toISOString(); - this.sql.exec( - `INSERT INTO account_facts_daily ( - platform, account_key, observation_date, payload_json, observed_finished_at, - target_id, source_binding_id, account_scope, updated_at - ) VALUES ('binance', ?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT(platform, account_key, observation_date) DO UPDATE SET - payload_json = excluded.payload_json, - observed_finished_at = excluded.observed_finished_at, - target_id = excluded.target_id, - source_binding_id = excluded.source_binding_id, - account_scope = excluded.account_scope, - updated_at = excluded.updated_at`, - context.account_key, - history.observation_date, - JSON.stringify(history), - history.report_observed_finished_at, - context.target_id, - context.source_binding_id, - context.account_scope, - updatedAt, - ); - const latestDate = this.sql.exec( - "SELECT MAX(observation_date) AS latest_date FROM account_facts_daily WHERE platform = 'binance' AND account_key = ?", - context.account_key, - ).toArray()[0]?.latest_date; - const cutoff = historyRetentionCutoff(latestDate, BINANCE_WALLET_HISTORY_MAX_DAYS); - if (cutoff) { - this.sql.exec("DELETE FROM account_facts_daily WHERE platform = 'binance' AND account_key = ? AND observation_date < ?", - context.account_key, cutoff); - } - return { ok: true, unchanged: false }; - } - - accountPeriodReturnCommand(command) { - const context = this.accountFactsStorageContext(command); - if (context.platform !== "ibkr") throw new HttpError("invalid_account_period_return", 400); - const binding = { ...context, source_binding: { id: context.source_binding_id } }; - const sourceIds = [context.source_binding_id, ...(context.history_source_binding_ids || [])]; - const sources = new Set(sourceIds); - const readStored = (payloadJson) => { - const payload = JSON.parse(payloadJson); - if (!sources.has(payload?.source_binding_id)) throw new HttpError("account_period_return_source_unqualified", 409); - return normalizeIbkrPeriodReturn(payload, { - binding: { ...binding, source_binding: { id: payload.source_binding_id } }, enforceFresh: false, - }); - }; - const existing = this.sql.exec("SELECT payload_json FROM account_period_return WHERE account_key = ?", context.account_key).toArray()[0]; - let prior = null; - if (existing) { - try { prior = readStored(existing.payload_json); } - catch { /* Keep the latest interval unavailable; independently qualify daily rows below. */ } - } - if (command.action === "account_period_return_read") { - const dailyByDay = new Map(); - let conflictingDailyValues = false; - const conflictingNav = new Set(); - const navByDay = new Map(); - const addDaily = (row) => { - const key = `${row.currency}:${row.period.to}`; - const old = dailyByDay.get(key); - if (old && Number(old.value) !== Number(row.value)) { - conflictingDailyValues = true; - return; - } - if (row.nav_change) { - const nav = canonicalResearchTaskJson(row.nav_change); - if (navByDay.has(key) && navByDay.get(key) !== nav) conflictingNav.add(key); - else navByDay.set(key, nav); - } - if (!old || row.source_binding_id === context.source_binding_id - || (old.source_binding_id !== context.source_binding_id - && Date.parse(row.observed_at) > Date.parse(old.observed_at))) dailyByDay.set(key, row); - }; - const rows = this.sql.exec(`SELECT payload_json FROM account_daily_native_return - WHERE account_key = ? AND source_binding_id IN (${sourceIds.map(() => "?").join(",")}) - ORDER BY observation_date DESC LIMIT ?`, context.account_key, ...sourceIds, 366 * sourceIds.length); - for (const stored of rows) { - let row; - try { row = readStored(stored.payload_json); } catch { continue; } - addDaily(row); - } - if (prior && prior.period.from === prior.period.to) addDaily(prior); - if (conflictingDailyValues) return { ok: true, return: { - ...ACCOUNT_FACTS_RETURN_UNAVAILABLE, reason: "native_return_history_conflict", - } }; - const daily = [...dailyByDay.values()].sort((a, b) => b.period.to.localeCompare(a.period.to)).slice(0, 366); - const latest = prior ? publicIbkrPeriodReturn(prior) : { ...ACCOUNT_FACTS_RETURN_UNAVAILABLE }; - if (prior && prior.period.from === prior.period.to && conflictingNav.has(`${prior.currency}:${prior.period.to}`)) delete latest.nav_change; - return { ok: true, return: { ...latest, - daily: daily.sort((a, b) => a.period.to.localeCompare(b.period.to)).map(row => { - const projected = publicIbkrPeriodReturn(row); - if (conflictingNav.has(`${row.currency}:${row.period.to}`)) delete projected.nav_change; - return projected; - }) } }; - } - const incoming = normalizeIbkrPeriodReturn(command.payload, { binding }); - const historical = command.historical === true; - if (historical && (incoming.period.from !== incoming.period.to || Date.now() - Date.parse(incoming.period.to + "T00:00:00Z") > 366 * 86400000)) throw new HttpError("invalid_account_daily_return", 400); - if (prior && !historical) { - if (incoming.period.to < prior.period.to || Date.parse(incoming.observed_at) < Date.parse(prior.observed_at)) throw new HttpError("account_period_return_stale_write", 409); - if (incoming.observed_at === prior.observed_at && canonicalResearchTaskJson(incoming) !== canonicalResearchTaskJson(prior)) throw new HttpError("account_period_return_conflict", 409); - } - if (incoming.period.from === incoming.period.to) { - const oldDay = this.sql.exec("SELECT payload_json FROM account_daily_native_return WHERE account_key = ? AND source_binding_id = ? AND currency = ? AND observation_date = ?", context.account_key, context.source_binding_id, incoming.currency, incoming.period.to).toArray()[0]; - if (oldDay) { - const old = normalizeIbkrPeriodReturn(JSON.parse(oldDay.payload_json), { binding, enforceFresh: false }); - if (Date.parse(incoming.observed_at) < Date.parse(old.observed_at)) throw new HttpError("account_daily_return_stale_write", 409); - if (incoming.observed_at === old.observed_at && canonicalResearchTaskJson(incoming) !== canonicalResearchTaskJson(old)) throw new HttpError("account_daily_return_conflict", 409); - } - this.sql.exec("INSERT INTO account_daily_native_return (account_key, source_binding_id, currency, observation_date, payload_json) VALUES (?, ?, ?, ?, ?) ON CONFLICT(account_key, source_binding_id, currency, observation_date) DO UPDATE SET payload_json = excluded.payload_json", context.account_key, context.source_binding_id, incoming.currency, incoming.period.to, JSON.stringify(incoming)); - } - if (historical && prior && (incoming.period.to < prior.period.to || Date.parse(incoming.observed_at) < Date.parse(prior.observed_at))) return { ok: true, unchanged: false }; - if (prior && canonicalResearchTaskJson(incoming) === canonicalResearchTaskJson(prior)) return { ok: true, unchanged: true }; - this.sql.exec("INSERT INTO account_period_return (account_key, payload_json) VALUES (?, ?) ON CONFLICT(account_key) DO UPDATE SET payload_json = excluded.payload_json", context.account_key, JSON.stringify(incoming)); - return { ok: true, unchanged: false }; - } - - // Internal only: a future authorized caller must supply verified account and - // source bindings. The browser/session and publisher routes do not call this. - researchNativeDailyReturns(command) { - const context = this.accountFactsStorageContext(command); - const from = runtimeDailyCalendarDate(command.from), to = runtimeDailyCalendarDate(command.to); - const start = Date.parse(`${from}T00:00:00Z`), end = Date.parse(`${to}T00:00:00Z`); - if (context.platform !== "ibkr" || !/^[a-f0-9]{64}$/.test(context.source_binding_id) - || !context.account_scope || !context.account_selector || !context.target_id - || typeof command.currency !== "string" || !/^[A-Z]{3}$/.test(command.currency) - || !from || !to || end < start || (end - start) / 86400000 >= 366) - throw new HttpError("invalid_native_return_research_range", 400); - const sources = new Set([context.source_binding_id, ...(context.history_source_binding_ids || [])]); - let stored; - try { - stored = this.sql.exec(`SELECT source_binding_id, currency, observation_date, payload_json - FROM account_daily_native_return WHERE account_key = ? AND currency = ? AND observation_date >= ? AND observation_date <= ? - ORDER BY observation_date, source_binding_id LIMIT ?`, context.account_key, command.currency, from, to, 366 * 65 + 1).toArray(); - } catch { throw new HttpError("native_return_read_failed", 503); } - if (stored.length > 366 * 65) throw new HttpError("native_return_read_limit_exceeded", 503); - const byDate = new Map(); - const navByDate = new Map(); - const decimal = text => { - const [whole, fraction = ""] = text.split("."); - return { number: BigInt(whole + fraction), scale: fraction.length }; - }; - const decimalEqual = (left, right) => { - const a = decimal(left), b = decimal(right); - return a.number * 10n ** BigInt(b.scale) === b.number * 10n ** BigInt(a.scale); - }; - const navFields = ["startingValue", "endingValue", "depositsWithdrawals", "internalCashTransfers", "assetTransfers", "linkingAdjustments"]; - for (const item of stored) { - const date = runtimeDailyCalendarDate(item.observation_date); - if (!date) return { ok: false, error: "native_return_record_invalid", date: null }; - if (!sources.has(item.source_binding_id)) return { ok: false, error: "native_return_source_unqualified", date }; - let raw; - try { - raw = JSON.parse(item.payload_json); - const validated = normalizeIbkrPeriodReturn(raw, { binding: { ...context, source_binding: { id: item.source_binding_id } }, enforceFresh: false }); - if (validated.currency !== item.currency || validated.period.from !== date || validated.period.to !== date) throw Error(); - } catch { return { ok: false, error: "native_return_record_invalid", date }; } - const previous = byDate.get(date); - if (previous) { - if (!decimalEqual(previous.value, raw.value)) - return { ok: false, error: "native_return_history_conflict", date }; - } - if (raw.nav_change) { - const priorNav = navByDate.get(date), nav = raw.nav_change; - if (priorNav && (!decimalEqual(priorNav.amount, nav.amount) - || navFields.some(field => !decimalEqual(priorNav.components[field], nav.components[field])))) - return { ok: false, error: "native_return_nav_conflict", date }; - navByDate.set(date, nav); - } - // SQL order is deterministic; prefer the currently configured source. - if (!previous || raw.source_binding_id === context.source_binding_id) byDate.set(date, raw); - } - const rows = [...byDate.values()]; - const datesWithoutRecords = []; - for (let day = start; day <= end; day += 86400000) { - const date = new Date(day).toISOString().slice(0, 10); - if (!byDate.has(date)) datesWithoutRecords.push(date); - } - return { ok: true, status: "ok", currency: command.currency, from, to, rows, - source_row_count: stored.length, duplicate_row_count: stored.length - rows.length, - returned_from: rows[0]?.period.to || null, returned_to: rows.at(-1)?.period.to || null, - dates_without_records: datesWithoutRecords, calendar_qualification: "unknown", published_at: null, historical_pit: "unknown" }; - } - - accountFactsStorageContext(command) { - if (!ACCOUNT_FACTS_SUPPORTED_PLATFORMS.includes(command?.platform) - || typeof command.account_key !== "string" || !command.account_key - || typeof command.account_scope !== "string" - || typeof command.account_selector !== "string" - || typeof command.target_id !== "string" - || typeof command.source_binding_id !== "string") { - throw new HttpError("invalid_account_facts_account", 400); - } - const historyIds = command.history_source_binding_ids; - if (historyIds !== undefined && (!["account_facts_history_read", "account_facts_history_put", "account_period_return_read", "account_period_return_put", "account_daily_native_return_research_read", "account_executions_read", "account_executions_put"].includes(command.action) - || !Array.isArray(historyIds) || historyIds.length < 1 || historyIds.length > 64 - || new Set(historyIds).size !== historyIds.length - || historyIds.some(id => typeof id !== "string" || !/^[a-f0-9]{64}$/.test(id) - || id === command.source_binding_id))) throw new HttpError("invalid_account_facts_history_sources", 400); - const brokerAccountId = command.platform === FIRSTRADE_ACCOUNT_FACTS_PLATFORM ? command.broker_account_id : null; - if (command.platform === FIRSTRADE_ACCOUNT_FACTS_PLATFORM && !validFirstradeAccountId(brokerAccountId)) { - throw new HttpError("invalid_account_facts_account", 400); - } - const brokerAccountHash = command.platform === SCHWAB_ACCOUNT_FACTS_PLATFORM - ? command.broker_account_hash - : null; - if (command.platform === SCHWAB_ACCOUNT_FACTS_PLATFORM - && (typeof brokerAccountHash !== "string" || !brokerAccountHash || brokerAccountHash.length > 256)) { - throw new HttpError("invalid_account_facts_account", 400); - } - return { - platform: command.platform, - ...(command.platform === FIRSTRADE_ACCOUNT_FACTS_PLATFORM ? { broker_account_id: brokerAccountId } : {}), - account_key: command.account_key, - account_scope: command.account_scope, - account_selector: command.account_selector, - target_id: command.target_id, - source_binding_id: command.source_binding_id, - ...(historyIds ? { history_source_binding_ids: [...historyIds] } : {}), - ...(command.platform === SCHWAB_ACCOUNT_FACTS_PLATFORM - ? { broker_account_hash: brokerAccountHash } - : {}), - }; - } - - validateAccountFactsStoragePayload(payload, context, { historical = false } = {}) { - try { - const normalize = historical ? normalizeAccountFactsHistoricalPayload : normalizeAccountFactsHistoryPayload; - return normalize(payload, { - expectedPlatform: context.platform, - expectedTargetId: context.target_id, - expectedBindingId: context.source_binding_id, - expectedOptionScope: context.account_scope, - expectedAccountSelector: context.platform === IBKR_ACCOUNT_FACTS_PLATFORM - ? context.account_selector - : null, - expectedBrokerAccountHash: context.platform === SCHWAB_ACCOUNT_FACTS_PLATFORM - ? context.broker_account_hash - : null, - expectedBrokerAccountId: context.platform === FIRSTRADE_ACCOUNT_FACTS_PLATFORM ? context.broker_account_id : null, - enforceObservationWindow: false, - }); - } catch (error) { - if (error instanceof AccountFactsError) throw new HttpError(error.code, error.status || 400); - throw error; - } - } - - validatePriorAccountFactsForAccount(row, context) { - if (!row) return null; - if (row.platform !== context.platform || row.account_key !== context.account_key - || row.account_scope !== context.account_scope || row.target_id !== context.target_id) { - throw new HttpError("account_facts_identity_mismatch", 409); - } - const priorBindingId = row.payload?.source_binding?.id; - if (typeof priorBindingId !== "string") throw new HttpError("account_facts_stored_invalid", 409); - const priorContext = { ...context, source_binding_id: priorBindingId }; - const payload = this.validateAccountFactsStoragePayload(row.payload, priorContext); - if (row.source_binding_id !== priorBindingId - || payload.target_id !== row.target_id - || payload.source_binding.id !== row.source_binding_id) { - throw new HttpError("account_facts_identity_mismatch", 409); - } - return { ...row, payload }; - } - - readAccountFactsRow(platform, accountKey, context = null) { - const row = this.sql.exec( - "SELECT payload_json, observed_finished_at, observation_date, target_id, source_binding_id, account_scope FROM account_facts_observation WHERE platform = ? AND account_key = ?", - platform, - accountKey, - ).toArray()[0]; - if (!row) return null; - let payload; - try { payload = JSON.parse(row.payload_json); } catch { throw new HttpError("account_facts_stored_invalid", 409); } - if (context) { - if (platform !== context.platform || accountKey !== context.account_key - || row.target_id !== context.target_id - || row.source_binding_id !== context.source_binding_id - || row.account_scope !== context.account_scope) { - throw new HttpError("account_facts_identity_mismatch", 409); - } - payload = this.validateAccountFactsStoragePayload(payload, context); - } - return { - platform, - account_key: accountKey, - payload, - observed_finished_at: row.observed_finished_at, - observation_date: row.observation_date, - target_id: row.target_id, - source_binding_id: row.source_binding_id, - account_scope: row.account_scope, - }; - } - - putAccountFactsObservation(command) { - const context = this.accountFactsStorageContext(command); - if (!command.history || typeof command.history !== "object" || Array.isArray(command.history)) { - throw new HttpError("invalid_account_facts_history", 400); - } - let history; - try { - history = this.validateAccountFactsStoragePayload(command.history, context); - } catch (error) { - if (error instanceof AccountFactsError) throw new HttpError(error.code, error.status || 400); - throw error; - } - const existingRow = this.validatePriorAccountFactsForAccount( - this.readAccountFactsRow(command.platform, command.account_key), - context, - ); - const decision = decideAccountFactsPut(existingRow?.payload || null, history); - if (decision.action === "reject") throw new HttpError(decision.reason, decision.status || 409); - let flowChanged = false; - if (command.externalFlow !== undefined) { - if (!this.externalFlowEnabled) throw new HttpError("external_flow_disabled", 409); - const flow = normalizeExternalFlow(command.externalFlow); - const source = { ibkr: "ibkr_flex", schwab: "schwab_transactions", longbridge: "longbridge_cashflow" }[context.platform]; - const currencies = new Set(history.broker_reported_balances.map(b => b.currency)); - if (flow.windows.some(w => w.source !== source || !currencies.has(w.currency) - || Date.parse(w.period_end) > Date.parse(history.observed_finished_at))) { - throw new HttpError("external_flow_observation_mismatch", 409); - } - flowChanged = !putExternalFlow(this.sql, context, flow).unchanged; - } - if (decision.action === "keep") { - // Latest table unchanged; still ensure the accepted day is retained. - this.upsertAccountFactsDaily(context, history); - return { - ok: true, - stored: true, - unchanged: !flowChanged, - platform: command.platform, - account_key: command.account_key, - observation_date: existingRow.observation_date, - observed_finished_at: existingRow.observed_finished_at, - }; - } - const updatedAt = new Date().toISOString(); - const payloadJson = JSON.stringify(history); - this.sql.exec( - `INSERT INTO account_facts_observation ( - platform, account_key, payload_json, observed_finished_at, observation_date, - target_id, source_binding_id, account_scope, updated_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT(platform, account_key) DO UPDATE SET - payload_json = excluded.payload_json, - observed_finished_at = excluded.observed_finished_at, - observation_date = excluded.observation_date, - target_id = excluded.target_id, - source_binding_id = excluded.source_binding_id, - account_scope = excluded.account_scope, - updated_at = excluded.updated_at`, - command.platform, - command.account_key, - payloadJson, - history.observed_finished_at, - history.observation_date, - history.target_id, - history.source_binding.id, - command.account_scope, - updatedAt, - ); - this.upsertAccountFactsDaily(context, history); - return { - ok: true, - stored: true, - unchanged: false, - platform: command.platform, - account_key: command.account_key, - observation_date: history.observation_date, - observed_finished_at: history.observed_finished_at, - }; - } - - putAccountFactsHistoricalObservation(command) { - const context = this.accountFactsStorageContext(command); - const sourceId = command.history?.source_binding?.id; - if (![context.source_binding_id, ...(context.history_source_binding_ids || [])].includes(sourceId)) { - throw new HttpError("account_facts_binding_unmatched", 409); - } - const history = this.validateAccountFactsStoragePayload(command.history, - { ...context, source_binding_id: sourceId }, { historical: true }); - // Deliberately bypass latest observation and external-flow writes. Reuse - // daily storage with replacement and retention deletion disabled. - return { ok: true, stored: true, ...this.upsertAccountFactsDaily(context, history, { appendOnly: true }) }; - } - - readAccountFactsDailyRow(platform, accountKey, observationDate, context = null) { - const row = this.sql.exec( - `SELECT payload_json, observed_finished_at, observation_date, target_id, source_binding_id, account_scope - FROM account_facts_daily WHERE platform = ? AND account_key = ? AND observation_date = ?`, - platform, - accountKey, - observationDate, - ).toArray()[0]; - if (!row) return null; - let payload; - try { payload = JSON.parse(row.payload_json); } catch { throw new HttpError("account_facts_stored_invalid", 409); } - if (context) { - if (platform !== context.platform || accountKey !== context.account_key - || row.target_id !== context.target_id - || row.source_binding_id !== context.source_binding_id - || row.account_scope !== context.account_scope) { - throw new HttpError("account_facts_identity_mismatch", 409); - } - payload = this.validateAccountFactsStoragePayload(payload, context); - } - return { - platform, - account_key: accountKey, - observation_date: row.observation_date, - payload, - observed_finished_at: row.observed_finished_at, - target_id: row.target_id, - source_binding_id: row.source_binding_id, - account_scope: row.account_scope, - }; - } - - upsertAccountFactsDaily(context, history, { appendOnly = false } = {}) { - const { platform, account_key: accountKey, account_scope: accountScope } = context; - const existing = this.validatePriorAccountFactsForAccount( - this.readAccountFactsDailyRow(platform, accountKey, history.observation_date), - context, - ); - if (appendOnly && existing) { - if (existing.observed_finished_at !== history.observed_finished_at - || canonicalizeAccountFactsHistory(existing.payload) !== canonicalizeAccountFactsHistory(history)) { - throw new HttpError("account_facts_daily_conflict", 409); - } - return { unchanged: true }; - } - let decision; - try { - decision = decideAccountFactsDailyUpsert(existing?.payload || null, history); - } catch (error) { - if (error instanceof AccountFactsError) throw new HttpError(error.code, error.status || 400); - throw error; - } - if (decision.action === "reject") throw new HttpError(decision.reason, decision.status || 409); - if (decision.action === "skip" || decision.action === "keep") { - this.pruneAccountFactsDaily(platform, accountKey, history.observation_date); - return { unchanged: true }; - } - const updatedAt = new Date().toISOString(); - this.sql.exec( - `INSERT INTO account_facts_daily ( - platform, account_key, observation_date, payload_json, observed_finished_at, - target_id, source_binding_id, account_scope, updated_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) - ${appendOnly ? "" : `ON CONFLICT(platform, account_key, observation_date) DO UPDATE SET - payload_json = excluded.payload_json, - observed_finished_at = excluded.observed_finished_at, - target_id = excluded.target_id, - source_binding_id = excluded.source_binding_id, - account_scope = excluded.account_scope, - updated_at = excluded.updated_at`}`, - platform, - accountKey, - history.observation_date, - JSON.stringify(history), - history.observed_finished_at, - history.target_id, - history.source_binding.id, - accountScope, - updatedAt, - ); - if (!appendOnly) this.pruneAccountFactsDaily(platform, accountKey, history.observation_date); - return { unchanged: false }; - } - - pruneAccountFactsDaily(platform, accountKey, latestDate) { - const cutoff = historyRetentionCutoff(latestDate, ACCOUNT_FACTS_HISTORY_MAX_DAYS); - if (!cutoff) return; - this.sql.exec( - "DELETE FROM account_facts_daily WHERE platform = ? AND account_key = ? AND observation_date < ?", - platform, - accountKey, - cutoff, - ); - } - - readAccountFactsDailyHistory(command) { - const context = this.accountFactsStorageContext(command); - const { platform, account_key: accountKey } = context; - if (command.projection === "external-flow-coverage") { - return { ok: true, ...readExternalFlowCoverage(this.sql, context) }; - } - if (command.projection !== undefined) { - const dates = accountFactsIdentityDates(command.from, command.to); - if (command.projection !== "identity" || !dates) { - throw new HttpError("invalid_account_facts_identity_query", 400); - } - // Deliberately exclude payload_json: identity inspection must not read assets. - const rows = this.sql.exec( - `SELECT observation_date, observed_finished_at, target_id, source_binding_id, account_scope - FROM account_facts_daily WHERE platform = ? AND account_key = ? - AND observation_date >= ? AND observation_date <= ? ORDER BY observation_date ASC`, - platform, accountKey, dates[0], dates.at(-1), - ).toArray(); - const same = (value, expected) => typeof value === "string" && value.length > 0 && value.length <= 512 - ? value === expected : null; - return { ok: true, records: dates.map(date => { - const matches = rows.filter(row => row.observation_date === date); - const row = matches.length === 1 ? matches[0] : null; - const source = row?.source_binding_id; - return { - observation_date: date, observed_finished_at: row?.observed_finished_at ?? null, - target_id_matches: same(row?.target_id, context.target_id), - source_binding_id_matches: same(source, context.source_binding_id), - account_scope_matches: same(row?.account_scope, context.account_scope), - record_revision: null, - row_present: matches.length > 1 ? null : Boolean(row), - source_binding_id_trusted: typeof source === "string" && source.length > 0 && source.length <= 512 - ? [context.source_binding_id, ...(context.history_source_binding_ids || [])].includes(source) : null, - stored_source_binding_id: typeof source === "string" && /^[a-f0-9]{64}$/.test(source) ? source : null, - }; - }) }; - } - const rows = this.sql.exec( - `SELECT payload_json, observed_finished_at, observation_date, target_id, source_binding_id, account_scope - FROM account_facts_daily WHERE platform = ? AND account_key = ? ORDER BY observation_date ASC`, - platform, - accountKey, - ).toArray(); - const days = []; - for (const row of rows) { - // Same-account history may use an explicitly qualified prior source. - // Current observations and ordinary sync still require the current source. - if (row.target_id !== context.target_id - || ![context.source_binding_id, ...(context.history_source_binding_ids || [])].includes(row.source_binding_id) - || row.account_scope !== context.account_scope) continue; - let payload; - try { payload = JSON.parse(row.payload_json); } catch { throw new HttpError("account_facts_stored_invalid", 409); } - payload = this.validateAccountFactsStoragePayload(payload, { ...context, source_binding_id: row.source_binding_id }); - days.push({ - observation_date: row.observation_date, - payload, - observed_finished_at: row.observed_finished_at, - target_id: row.target_id, - source_binding_id: row.source_binding_id, - account_scope: row.account_scope, - }); - } - return { ok: true, days, - externalFlow: readExternalFlow(this.sql, context, { dateOnly: !this.externalFlowEnabled }), - }; - } - - readAccountFactsObservation(command) { - const context = this.accountFactsStorageContext(command); - const row = this.readAccountFactsRow(context.platform, context.account_key, context); - return { ok: true, observation: row }; - } - - listAccountFactsObservations(command) { - const wanted = Array.isArray(command.accounts) ? command.accounts : []; - const observations = []; - for (const item of wanted) { - if (!item || typeof item.platform !== "string" || typeof item.account_key !== "string") continue; - const context = this.accountFactsStorageContext(item); - try { - const row = this.readAccountFactsRow(context.platform, context.account_key, context); - if (row) observations.push(row); - } catch (error) { - // A trusted binding rotation makes older rows ineligible for display. - // Keep them stored for audit, but treat them as absent until refreshed. - if (!(error instanceof HttpError) || error.message !== "account_facts_identity_mismatch") throw error; - } - } - return { ok: true, observations }; - } - - accountSettingsCommand(command) { - if (!command || typeof command.actor !== "string" || !command.actor) throw new HttpError("runtime_instance_actor_required", 400); - if (command.action === "risk_profile_read") return this.readRiskProfileAuthority(command.legacy); - if (command.action === "risk_profile_replace") return this.replaceRiskProfileAuthority(command); - if (command.action === "risk_profile_set") return this.setRiskProfileAuthority(command); - if (command.action === "account_declarations_read") { - const state = this.read(); - if (!state.initialized) throw new HttpError("runtime_instances_not_initialized", 409); - return { declarations: state.instances.filter(item => item.kind === "existing").map(item => ({ - platform: item.platform, key: item.key, binding_environment: accountSettingsIdentity(item).broker_environment, - ...this.accountSettingDeclaration(item, accountSettingsIdentity(item)), - })) }; - } - if (command.action === "account_settings_read") return this.readAccountSettings(command); - if (command.action === "account_settings_save") return this.saveAccountSettings(command); - if (command.action === "account_settings_profile_application_lookup") return this.readAccountSettingsProfileApplication(command); - if (command.action === "account_settings_profile_application_approve") return this.approveAccountSettingsProfileApplication(command); - if (command.action === "account_settings_profile_application_claim") return this.claimAccountSettingsProfileApplication(command); - throw new HttpError("unsupported_account_settings_action", 400); - } - - ensureRiskProfileAuthority(legacy) { - const row = this.sql.exec("SELECT revision, imported, bindings_json FROM risk_profile_authority WHERE id = 1").toArray()[0]; - if (row) return row; - if (legacy?.state === "blocked") throw new HttpError("risk_profile_bindings_invalid", 409); - const bindings = legacy?.state === "valid" && Array.isArray(legacy.bindings) ? legacy.bindings : []; - const payload = JSON.stringify(bindings); - this.sql.exec("INSERT INTO risk_profile_authority (id, revision, imported, bindings_json) VALUES (1, 0, 1, ?)", payload); - return { revision: 0, imported: 1, bindings_json: payload }; - } - - storedRiskBindings(row) { - let bindings; - try { bindings = JSON.parse(row.bindings_json); } catch { throw new HttpError("risk_profile_bindings_invalid", 409); } - if (!Array.isArray(bindings)) throw new HttpError("risk_profile_bindings_invalid", 409); - return bindings; - } - - assertStoredRiskBinding(binding, accountOptions) { - if (!binding || Array.isArray(binding) || typeof binding !== "object") throw new HttpError("invalid_risk_profile_binding", 400); - if (binding.no_order !== true || binding.execution_authority_granted !== false) throw new HttpError("risk_profile_execution_authority_rejected", 400); - if (binding.scope_id !== riskProfileScopeId(binding.platform, binding.target_name)) throw new HttpError("invalid_risk_profile_binding", 400); - if (!RISK_PROFILE_PREFERENCES.includes(binding.profile_selection?.risk_preference)) throw new HttpError("invalid_risk_profile_binding", 400); - const policy = RISK_TIER_POLICY_BINDINGS[binding.profile_selection?.risk_preference]; - if (!policy || binding.profile_selection?.profile_id !== policy.profile_id) { - throw new HttpError("risk_profile_policy_binding_invalid", 409); - } - if (!riskProfileBindingTargets(accountOptions).some((target) => target.platform === binding.platform && target.target_name === binding.target_name)) { - throw new HttpError("risk_profile_binding_target_not_configured", 409); - } - } - - writeRiskProfileAuthority(row, bindings) { - const current = this.storedRiskBindings(row); - const same = canonicalResearchTaskJson(current) === canonicalResearchTaskJson(bindings); - const revision = same ? Number(row.revision) : Number(row.revision) + 1; - if (!same) this.sql.exec("UPDATE risk_profile_authority SET revision = ?, bindings_json = ? WHERE id = 1", revision, JSON.stringify(bindings)); - const state = this.read(); - return { - ok: true, - revision, - bindings, - account_options: runtimeInstanceAccountOptions(state.instances), - instance_revision: state.revision, - initialized: state.initialized, - no_order: true, - execution_authority_granted: false, - }; - } - - readRiskProfileAuthority(legacy) { - const row = this.ensureRiskProfileAuthority(legacy); - const state = this.read(); - return { - ok: true, - revision: Number(row.revision), - bindings: this.storedRiskBindings(row), - account_options: runtimeInstanceAccountOptions(state.instances), - instance_revision: state.revision, - initialized: state.initialized, - no_order: true, - execution_authority_granted: false, - }; - } - - requireRiskRevision(command, row) { - if (!Number.isSafeInteger(command.expected_revision) || command.expected_revision !== Number(row.revision)) { - throw new HttpError("risk_profile_revision_conflict", 409); - } - } - - replaceRiskProfileAuthority(command) { - const row = this.ensureRiskProfileAuthority(command.legacy); - this.requireRiskRevision(command, row); - const options = runtimeInstanceAccountOptions(this.read().instances); - if (!Array.isArray(command.bindings)) throw new HttpError("invalid_risk_profile_binding", 400); - for (const binding of command.bindings) this.assertStoredRiskBinding(binding, options); - const bindings = [...command.bindings].sort((left, right) => left.scope_id.localeCompare(right.scope_id)); - return this.writeRiskProfileAuthority(row, bindings); - } - - setRiskProfileAuthority(command) { - const row = this.ensureRiskProfileAuthority(command.legacy); - this.requireRiskRevision(command, row); - const options = runtimeInstanceAccountOptions(this.read().instances); - if (!riskProfileBindingTargets(options).some((target) => target.platform === command.platform && target.target_name === command.target_name)) { - throw new HttpError("risk_profile_binding_target_not_configured", 409); - } - const scopeId = riskProfileScopeId(command.platform, command.target_name); - const current = this.storedRiskBindings(row).filter((binding) => binding.scope_id !== scopeId); - if (command.risk_change === "clear") return this.writeRiskProfileAuthority(row, current); - if (command.risk_change !== "set") throw new HttpError("invalid_risk_profile_change", 400); - this.assertStoredRiskBinding(command.binding, options); - if (command.binding.scope_id !== scopeId) throw new HttpError("invalid_risk_profile_binding", 400); - current.push(command.binding); - current.sort((left, right) => left.scope_id.localeCompare(right.scope_id)); - return this.writeRiskProfileAuthority(row, current); - } - - existingAccountInstance(platform, key) { - const state = this.read(); - if (!state.initialized) throw new HttpError("runtime_instances_not_initialized", 409); - const item = state.instances.find((candidate) => candidate.kind === "existing" && candidate.platform === platform && candidate.key === key); - if (!item) throw new HttpError("account_settings_not_found", 404); - return { state, item, identity: accountSettingsIdentity(item) }; - } - - accountSettingDraftView(item, identity) { - const row = this.sql.exec( - "SELECT revision, identity_json, overrides_json FROM account_setting_draft WHERE platform = ? AND account_key = ?", - item.platform, item.key, - ).toArray()[0]; - if (!row) return { status: "empty", revision: 0, identity: null, current_identity: identity, overrides: {} }; - let storedIdentity = null; - let overrides = {}; - try { - storedIdentity = JSON.parse(row.identity_json); - overrides = JSON.parse(row.overrides_json); - } catch { - throw new HttpError("account_settings_draft_invalid", 409); - } - const conflict = canonicalResearchTaskJson(storedIdentity) !== canonicalResearchTaskJson(identity); - return { - status: conflict ? "identity_conflict" : "current", - revision: Number(row.revision), - identity: storedIdentity, - current_identity: identity, - overrides: overrides && typeof overrides === "object" && !Array.isArray(overrides) ? overrides : {}, - }; - } - - accountSettingDeclaration(item, identity) { - const row = this.sql.exec("SELECT revision, identity_json, declaration_json FROM account_setting_declaration WHERE platform = ? AND account_key = ?", item.platform, item.key).toArray()[0]; - if (!row) return { status: "empty", revision: 0, environment: null, account_type: null, source: "user_declared" }; - const matches = canonicalResearchTaskJson(JSON.parse(row.identity_json)) === canonicalResearchTaskJson(identity); - const value = JSON.parse(row.declaration_json); - return { status: matches ? "current" : "identity_conflict", revision: Number(row.revision), - environment: matches ? value.environment : null, account_type: matches ? value.account_type : null, source: "user_declared" }; - } - - saveAccountSettingDeclaration(item, identity, command) { - if (!command.identity || canonicalResearchTaskJson(command.identity) !== canonicalResearchTaskJson(identity)) throw new HttpError("account_settings_identity_conflict", 409); - const current = this.accountSettingDeclaration(item, identity); - if (!Number.isSafeInteger(command.expected_declaration_revision) || command.expected_declaration_revision !== current.revision) throw new HttpError("account_settings_declaration_revision_conflict", 409); - if (current.status === "identity_conflict") throw new HttpError("account_settings_identity_conflict", 409); - const value = command.declaration; - if (!value || Array.isArray(value) || typeof value !== "object" || Object.keys(value).some(k => !["environment", "account_type"].includes(k)) - || ![null, "live", "paper"].includes(value.environment) || ![null, "cash", "margin"].includes(value.account_type)) throw new HttpError("invalid_account_settings_declaration", 400); - if (current.status === "current" && current.environment === value.environment && current.account_type === value.account_type) return; - this.sql.exec("INSERT INTO account_setting_declaration (platform, account_key, revision, identity_json, declaration_json) VALUES (?, ?, ?, ?, ?) ON CONFLICT (platform, account_key) DO UPDATE SET revision = excluded.revision, identity_json = excluded.identity_json, declaration_json = excluded.declaration_json", item.platform, item.key, current.revision + 1, JSON.stringify(identity), JSON.stringify(value)); - } - - readAccountSettings(command) { - const { state, item, identity } = this.existingAccountInstance(command.platform, command.key); - const risk = this.readRiskProfileAuthority(command.legacy); - const scopeId = riskProfileScopeId(item.platform, identity.target_name); - const binding = risk.bindings.find((entry) => entry.scope_id === scopeId) || null; - return { - ok: true, - found: true, - platform: item.platform, - key: item.key, - instance_revision: state.revision, - enabled: item.enabled, - identity, - config: item.config, - draft: this.accountSettingDraftView(item, identity), - declaration: this.accountSettingDeclaration(item, identity), - risk: { - revision: risk.revision, - scope_id: scopeId, - preference: binding?.profile_selection?.risk_preference || null, - binding, - }, - bindings: risk.bindings, - account_options: risk.account_options, - no_order: true, - execution_authority_granted: false, - }; - } - - saveAccountSettingDraft(item, identity, command) { - if (!command.identity || Array.isArray(command.identity) || typeof command.identity !== "object") { - throw new HttpError("account_settings_identity_required", 400); - } - if (canonicalResearchTaskJson(command.identity) !== canonicalResearchTaskJson(identity)) { - throw new HttpError("account_settings_identity_conflict", 409); - } - if (!Number.isSafeInteger(command.expected_draft_revision) || command.expected_draft_revision < 0) { - throw new HttpError("account_settings_draft_revision_conflict", 409); - } - const row = this.sql.exec( - "SELECT revision, identity_json, overrides_json FROM account_setting_draft WHERE platform = ? AND account_key = ?", - item.platform, item.key, - ).toArray()[0]; - const now = new Date().toISOString(); - if (!row) { - if (command.expected_draft_revision !== 0) throw new HttpError("account_settings_draft_revision_conflict", 409); - const overrides = applyAccountSettingOverrides({}, command.overrides); - this.sql.exec( - "INSERT INTO account_setting_draft (platform, account_key, revision, identity_json, overrides_json, updated_at) VALUES (?, ?, 1, ?, ?, ?)", - item.platform, item.key, JSON.stringify(identity), JSON.stringify(overrides), now, - ); - return; - } - if (command.expected_draft_revision !== Number(row.revision)) throw new HttpError("account_settings_draft_revision_conflict", 409); - const storedIdentity = JSON.parse(row.identity_json); - const conflict = canonicalResearchTaskJson(storedIdentity) !== canonicalResearchTaskJson(identity); - if (conflict && command.acknowledge_identity_conflict !== true) throw new HttpError("account_settings_identity_conflict", 409); - const storedOverrides = JSON.parse(row.overrides_json); - const base = conflict ? {} : storedOverrides; - const overrides = applyAccountSettingOverrides(base, command.overrides); - if (!conflict && canonicalResearchTaskJson(overrides) === canonicalResearchTaskJson(storedOverrides)) return; - this.sql.exec( - "UPDATE account_setting_draft SET revision = ?, identity_json = ?, overrides_json = ?, updated_at = ? WHERE platform = ? AND account_key = ?", - Number(row.revision) + 1, JSON.stringify(identity), JSON.stringify(overrides), now, item.platform, item.key, - ); - } - - saveAccountSettings(command) { - const { item, identity } = this.existingAccountInstance(command.platform, command.key); - const hasDraft = Object.prototype.hasOwnProperty.call(command, "overrides"); - const hasRisk = Object.prototype.hasOwnProperty.call(command, "risk_change"); - const hasDeclaration = Object.prototype.hasOwnProperty.call(command, "declaration"); - if (!hasDraft && !hasRisk && !hasDeclaration) throw new HttpError("account_settings_change_required", 400); - if (hasDeclaration) this.saveAccountSettingDeclaration(item, identity, command); - if (hasDraft) this.saveAccountSettingDraft(item, identity, command); - if (hasRisk) { - if (!command.identity || Array.isArray(command.identity) || typeof command.identity !== "object") { - throw new HttpError("account_settings_identity_required", 400); - } - if (canonicalResearchTaskJson(command.identity) !== canonicalResearchTaskJson(identity)) { - throw new HttpError("account_settings_identity_conflict", 409); - } - this.setRiskProfileAuthority({ - ...command, - platform: identity.platform, - target_name: identity.target_name, - expected_revision: command.expected_risk_revision, - }); - } - return { ...this.readAccountSettings(command), adopted: false }; - } - - accountSettingsProfileApplicationFromRow(row) { - if (!row) return null; - let identity; - try { identity = JSON.parse(row.identity_json); } catch { throw new HttpError("account_settings_profile_application_invalid", 409); } - return { - request_key: row.request_key, - platform: row.platform, - key: row.account_key, - identity, - instance_revision: Number(row.instance_revision), - draft_revision: Number(row.draft_revision), - old_profile: row.old_profile, - new_profile: row.new_profile, - approved_by: row.approved_by, - approved_at: row.approved_at, - status: row.status, - workflow_run_id: row.workflow_run_id || null, - workflow_run_attempt: row.workflow_run_attempt || null, - claimed_at: row.claimed_at || null, - runtime_applied: false, - variables_updated: false, - no_order: true, - execution_authority_granted: false, - }; - } - - readAccountSettingsProfileApplication(command) { - const requestKey = String(command.request_key || ""); - if (!ACCOUNT_SETTINGS_PROFILE_APPLICATION_REQUEST_KEY.test(requestKey)) { - throw new HttpError("account_settings_profile_application_request_invalid", 400); - } - const row = this.sql.exec("SELECT * FROM account_setting_profile_application WHERE request_key = ?", requestKey).toArray()[0]; - return { ok: true, application: this.accountSettingsProfileApplicationFromRow(row) }; - } - - approveAccountSettingsProfileApplication(command) { - const requestKey = String(command.request_key || ""); - if (!ACCOUNT_SETTINGS_PROFILE_APPLICATION_REQUEST_KEY.test(requestKey)) { - throw new HttpError("account_settings_profile_application_request_invalid", 400); - } - if (!SUPPORTED_PLATFORMS.includes(command.platform) || typeof command.key !== "string" || !command.key) { - throw new HttpError("account_settings_account_required", 400); - } - if (!Number.isSafeInteger(command.expected_instance_revision) || command.expected_instance_revision < 0 - || !Number.isSafeInteger(command.expected_draft_revision) || command.expected_draft_revision < 0) { - throw new HttpError("account_settings_profile_application_revision_invalid", 400); - } - const identity = command.identity; - if (!accountSettingsIdentityIsComplete(identity)) throw new HttpError("account_settings_identity_required", 400); - const identityJson = canonicalResearchTaskJson(identity); - const oldProfile = accountSettingsProfileName(command.old_profile); - const newProfile = accountSettingsProfileName(command.new_profile); - if (!oldProfile || !newProfile || oldProfile === newProfile) throw new HttpError("account_settings_profile_application_profile_invalid", 400); - - const existing = this.sql.exec("SELECT * FROM account_setting_profile_application WHERE request_key = ?", requestKey).toArray()[0]; - if (existing) { - const sameRequest = existing.platform === command.platform - && existing.account_key === command.key - && existing.identity_json === identityJson - && Number(existing.instance_revision) === command.expected_instance_revision - && Number(existing.draft_revision) === command.expected_draft_revision - && existing.old_profile === oldProfile - && existing.new_profile === newProfile - && existing.approved_by === command.actor; - if (!sameRequest) throw new HttpError("account_settings_profile_application_conflict", 409); - return { ok: true, replayed: true, application: this.accountSettingsProfileApplicationFromRow(existing) }; - } - - if (command.current_profile !== oldProfile || command.runtime_target_enabled !== false) { - throw new HttpError("account_settings_profile_application_runtime_conflict", 409); - } - const { state, item, identity: currentIdentity } = this.existingAccountInstance(command.platform, command.key); - if (state.revision !== command.expected_instance_revision || identityJson !== canonicalResearchTaskJson(currentIdentity)) { - throw new HttpError("account_settings_profile_application_identity_conflict", 409); - } - const draft = this.accountSettingDraftView(item, currentIdentity); - if (draft.status !== "current" || draft.revision !== command.expected_draft_revision - || canonicalResearchTaskJson(draft.identity) !== identityJson - || Object.keys(draft.overrides).length !== 1 - || draft.overrides.strategy_profile !== newProfile) { - throw new HttpError("account_settings_profile_application_draft_conflict", 409); - } - if (isDcaProfile(oldProfile) || isDcaProfile(newProfile)) { - throw new HttpError("account_settings_profile_application_profile_only_required", 409); - } - - const priorApprovals = this.sql.exec( - "SELECT request_key, identity_json, instance_revision, draft_revision, old_profile, new_profile FROM account_setting_profile_application WHERE platform = ? AND account_key = ? AND status = 'approved'", - command.platform, command.key, - ).toArray(); - for (const prior of priorApprovals) { - const stale = prior.identity_json !== identityJson - || Number(prior.instance_revision) !== state.revision - || Number(prior.draft_revision) !== draft.revision - || prior.old_profile !== oldProfile - || prior.new_profile !== newProfile; - if (stale) { - this.sql.exec( - "UPDATE account_setting_profile_application SET status = 'invalidated' WHERE request_key = ? AND status = 'approved'", - prior.request_key, - ); - } - } - const active = this.sql.exec( - "SELECT request_key FROM account_setting_profile_application WHERE platform = ? AND account_key = ? AND status IN ('approved', 'claimed') LIMIT 1", - command.platform, command.key, - ).toArray()[0]; - if (active) throw new HttpError("account_settings_profile_application_pending", 409); - - const approvedAt = new Date().toISOString(); - this.sql.exec( - `INSERT INTO account_setting_profile_application - (request_key, platform, account_key, identity_json, instance_revision, draft_revision, - old_profile, new_profile, approved_by, approved_at, status, workflow_run_id, - workflow_run_attempt, claimed_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'approved', NULL, NULL, NULL)`, - requestKey, command.platform, command.key, identityJson, command.expected_instance_revision, - command.expected_draft_revision, oldProfile, newProfile, command.actor, approvedAt, - ); - const row = this.sql.exec("SELECT * FROM account_setting_profile_application WHERE request_key = ?", requestKey).toArray()[0]; - return { ok: true, replayed: false, application: this.accountSettingsProfileApplicationFromRow(row) }; - } - - claimAccountSettingsProfileApplication(command) { - const requestKey = String(command.request_key || ""); - if (!ACCOUNT_SETTINGS_PROFILE_APPLICATION_REQUEST_KEY.test(requestKey)) { - throw new HttpError("account_settings_profile_application_request_invalid", 400); - } - const row = this.sql.exec("SELECT * FROM account_setting_profile_application WHERE request_key = ?", requestKey).toArray()[0]; - if (!row) throw new HttpError("account_settings_profile_application_not_found", 404); - const workflowRunId = String(command.workflow_run_id || ""); - const workflowRunAttempt = String(command.workflow_run_attempt || ""); - if (!/^\d{1,20}$/.test(workflowRunId) || !/^[1-9]\d{0,5}$/.test(workflowRunAttempt)) { - throw new HttpError("account_settings_profile_application_run_invalid", 400); - } - const identityJson = accountSettingsIdentityIsComplete(command.identity) - ? canonicalResearchTaskJson(command.identity) - : ""; - const safeResult = { no_order: true, execution_authority_granted: false, runtime_applied: false, variables_updated: false }; - const boundRequest = identityJson === row.identity_json - && Number(command.expected_instance_revision) === Number(row.instance_revision) - && Number(command.expected_draft_revision) === Number(row.draft_revision) - && command.old_profile === row.old_profile - && command.new_profile === row.new_profile - && command.current_profile === row.old_profile - && command.runtime_target_enabled === false; - if (!boundRequest) throw new HttpError("account_settings_profile_application_conflict", 409); - if (row.status === "claimed") { - if (row.workflow_run_id !== workflowRunId || row.workflow_run_attempt !== workflowRunAttempt) { - throw new HttpError("account_settings_profile_application_already_claimed", 409); - } - return { ok: true, claimed: false, replayed: true, application: this.accountSettingsProfileApplicationFromRow(row), ...safeResult }; - } - if (row.status !== "approved") throw new HttpError("account_settings_profile_application_conflict", 409); - if (!Number.isSafeInteger(command.expected_instance_revision) || !Number.isSafeInteger(command.expected_draft_revision)) { - throw new HttpError("account_settings_profile_application_revision_invalid", 409); - } - const { state, item, identity: currentIdentity } = this.existingAccountInstance(row.platform, row.account_key); - if (state.revision !== Number(row.instance_revision) - || canonicalResearchTaskJson(currentIdentity) !== row.identity_json) { - throw new HttpError("account_settings_profile_application_identity_conflict", 409); - } - const draft = this.accountSettingDraftView(item, currentIdentity); - if (draft.status !== "current" || draft.revision !== Number(row.draft_revision) - || canonicalResearchTaskJson(draft.identity) !== row.identity_json - || Object.keys(draft.overrides).length !== 1 - || draft.overrides.strategy_profile !== row.new_profile) { - throw new HttpError("account_settings_profile_application_draft_conflict", 409); - } - const claimedAt = new Date().toISOString(); - this.sql.exec( - `UPDATE account_setting_profile_application - SET status = 'claimed', workflow_run_id = ?, workflow_run_attempt = ?, claimed_at = ? - WHERE request_key = ? AND status = 'approved'`, - workflowRunId, workflowRunAttempt, claimedAt, requestKey, - ); - const claimedRow = this.sql.exec("SELECT * FROM account_setting_profile_application WHERE request_key = ?", requestKey).toArray()[0]; - return { ok: true, claimed: true, replayed: false, application: this.accountSettingsProfileApplicationFromRow(claimedRow), ...safeResult }; - } - - runtimeControlCommand(command) { - const platform = command?.platform; - const key = command?.key; - if (!["ibkr", "schwab"].includes(platform) || typeof key !== "string" || !key) throw new HttpError("runtime_control_target_invalid", 400); - const found = this.existingAccountInstance(platform, key); - const identity = found.identity; - if (!runtimeControlIdentityIsComplete(identity)) throw new HttpError("runtime_control_identity_incomplete", 409); - const targetId = `${platform}/${identity.target_name}`; - const latest = () => { - const row = this.sql.exec("SELECT * FROM hk_stop_request WHERE target_id = ? ORDER BY requested_at DESC, rowid DESC LIMIT 1", targetId).toArray()[0]; - return row ? { ...this.hkStopPublic(row), result_json: row.result_json || null } : null; - }; - if (command.action === "runtime_control_read") return { ok: true, record: latest() }; - const requestId = String(command.request_id || ""); - if (!HK_STOP_REQUEST_ID.test(requestId)) throw new HttpError("runtime_control_request_invalid", 400); - const stored = this.hkStopStored(requestId); - if (command.action === "runtime_control_claim") { - if (!["enable", "disable"].includes(command.operation) || !Number.isSafeInteger(command.source_revision) - || found.state.revision !== command.source_revision - || canonicalResearchTaskJson(identity) !== canonicalResearchTaskJson(command.identity)) throw new HttpError("runtime_control_identity_conflict", 409); - if (stored) { - if (stored.target_id !== targetId || stored.action !== command.operation || stored.identity_json !== canonicalResearchTaskJson(identity)) throw new HttpError("runtime_control_identity_conflict", 409); - return { ok: true, state: "replay", record: stored }; - } - const open = this.hkStopPublic(this.sql.exec( - "SELECT * FROM hk_stop_request WHERE target_id = ? AND phase IN ('reserved', 'unknown', 'accepted', 'partial') ORDER BY requested_at ASC LIMIT 1", targetId, - ).toArray()[0]); - if (open) return { ok: true, state: "blocked", record: open }; - const now = new Date().toISOString(); - this.sql.exec(`INSERT INTO hk_stop_request - (request_id, target_id, identity_json, source_revision, action, requested_at, phase, updated_at) - VALUES (?, ?, ?, ?, ?, ?, 'reserved', ?)`, requestId, targetId, canonicalResearchTaskJson(identity), command.source_revision, command.operation, now, now); - return { ok: true, state: "reserved", record: this.hkStopStored(requestId) }; - } - if (!stored || stored.target_id !== targetId || stored.identity_json !== canonicalResearchTaskJson(identity)) throw new HttpError("runtime_control_identity_conflict", 409); - if (command.action === "runtime_control_record") { - if (!["accepted", "unknown", "rejected"].includes(command.phase)) throw new HttpError("runtime_control_result_invalid", 400); - if (command.run_id != null && !/^[1-9][0-9]*$/.test(String(command.run_id))) throw new HttpError("runtime_control_result_invalid", 400); - if (stored.workflow_run_id && command.run_id && String(stored.workflow_run_id) !== String(command.run_id)) throw new HttpError("runtime_control_result_invalid", 409); - if (stored.phase === "reserved") this.sql.exec( - "UPDATE hk_stop_request SET phase = ?, dispatch_result = ?, workflow_run_id = ?, updated_at = ? WHERE request_id = ? AND phase = 'reserved'", - command.phase, command.phase, command.run_id || null, new Date().toISOString(), requestId, - ); - else if (stored.phase === "unknown" && !stored.workflow_run_id && command.run_id) this.sql.exec( - "UPDATE hk_stop_request SET workflow_run_id = ?, updated_at = ? WHERE request_id = ? AND phase = 'unknown' AND workflow_run_id IS NULL", - String(command.run_id), new Date().toISOString(), requestId, - ); - return { ok: true, record: this.hkStopStored(requestId) }; - } - if (command.action === "runtime_control_capture") { - if (!stored.workflow_run_id || String(stored.workflow_run_id) !== String(command.run_id) - || !/^[1-9][0-9]*$/.test(String(command.platform_run_id || ""))) throw new HttpError("runtime_control_result_invalid", 400); - let details = {}; - try { details = JSON.parse(latest()?.result_json || "{}"); } catch { throw new HttpError("runtime_control_result_invalid", 409); } - if (details.platform_run_id && details.platform_run_id !== String(command.platform_run_id)) throw new HttpError("runtime_control_result_invalid", 409); - details.platform_run_id = String(command.platform_run_id); - if (["platform_result_unavailable", "platform_dispatch_unverified"].includes(command.reason)) details.reason = command.reason; - this.sql.exec("UPDATE hk_stop_request SET result_json = ?, updated_at = ? WHERE request_id = ?", JSON.stringify(details), new Date().toISOString(), requestId); - return { ok: true, record: latest() }; - } - if (command.action === "runtime_control_begin_check") { - let details = {}; - try { details = JSON.parse(latest()?.result_json || "{}"); } catch { throw new HttpError("runtime_control_result_invalid", 409); } - if (!["unknown", "accepted", "partial"].includes(stored.phase) || !details.platform_run_id) throw new HttpError("runtime_control_check_unavailable", 409); - if (details.check_id && !details.check_complete) return { ok: true, state: "replay", record: latest() }; - if (!HK_STOP_REQUEST_ID.test(String(command.check_id || ""))) throw new HttpError("runtime_control_request_invalid", 400); - Object.assign(details, { check_id: command.check_id, check_run_id: null, check_complete: false, check_requested_at: new Date().toISOString() }); - this.sql.exec("UPDATE hk_stop_request SET result_json = ?, updated_at = ? WHERE request_id = ?", JSON.stringify(details), new Date().toISOString(), requestId); - return { ok: true, state: "reserved", record: latest() }; - } - if (command.action === "runtime_control_record_check") { - let details = {}; - try { details = JSON.parse(latest()?.result_json || "{}"); } catch { throw new HttpError("runtime_control_result_invalid", 409); } - if (!details.check_id || details.check_id !== command.check_id) throw new HttpError("runtime_control_result_invalid", 409); - if (command.run_id != null && !/^[1-9][0-9]*$/.test(String(command.run_id))) throw new HttpError("runtime_control_result_invalid", 400); - if (details.check_run_id && command.run_id && details.check_run_id !== String(command.run_id)) throw new HttpError("runtime_control_result_invalid", 409); - if (command.run_id) details.check_run_id = String(command.run_id); - if (command.complete === true) details.check_complete = true; - if (command.reason === "readback_dispatch_rejected") details.reason = command.reason; - this.sql.exec("UPDATE hk_stop_request SET result_json = ?, updated_at = ? WHERE request_id = ?", JSON.stringify(details), new Date().toISOString(), requestId); - return { ok: true, record: latest() }; - } - if (command.action === "runtime_control_resolve") { - if (!["applied", "partial", "protected", "rejected"].includes(command.phase) || !command.run_id - || String(stored.workflow_run_id) !== String(command.run_id)) throw new HttpError("runtime_control_result_invalid", 400); - if (command.check_id) { - let check = {}; - try { check = JSON.parse(latest()?.result_json || "{}"); } catch { throw new HttpError("runtime_control_result_invalid", 409); } - if (check.check_id !== command.check_id || check.check_run_id !== String(command.check_run_id)) throw new HttpError("runtime_control_result_invalid", 409); - } - const reason = ["runtime_control_workflow_permission_denied", "runtime_control_workflow_unavailable", "runtime_control_target_unsupported", "stop_source_permission_denied", "stop_source_not_accessible", "platform_dispatch_rejected", "platform_result_unavailable", "platform_dispatch_unverified"].includes(command.reason) ? command.reason : null; - let details = {}; - try { details = JSON.parse(latest()?.result_json || "{}"); } catch { throw new HttpError("runtime_control_result_invalid", 409); } - if (["accepted", "unknown", "partial"].includes(stored.phase)) this.sql.exec( - "UPDATE hk_stop_request SET phase = ?, result_json = ?, updated_at = ? WHERE request_id = ?", command.phase, JSON.stringify({ ...details, reason }), new Date().toISOString(), requestId, - ); - return { ok: true, record: latest() }; - } - throw new HttpError("runtime_control_action_invalid", 400); - } - - hkStopCommand(command) { - if (command?.action === "hk_stop_read") return { ok: true, record: this.hkStopOpen() || this.hkStopLatest() }; - if (command?.action === "hk_stop_claim") return this.hkStopClaim(command); - if (command?.action === "hk_stop_record") return this.hkStopRecord(command); - if (command?.action === "hk_stop_accept_result") return this.hkStopAcceptResult(command); - throw new HttpError("unsupported_hk_stop_action", 400); - } - - hkStopPublic(row) { - if (!row) return null; - return { - target_id: row.target_id, - request_id: row.request_id, - identity_json: row.identity_json, - source_revision: Number(row.source_revision), - action: row.action, - requested_at: row.requested_at, - phase: row.phase, - dispatch_result: row.dispatch_result || null, - workflow_run_id: row.workflow_run_id || null, - workflow_run_attempt: row.workflow_run_attempt || null, - source_identity_sha256: row.source_identity_sha256 || null, - runtime_identity_sha256: row.runtime_identity_sha256 || null, - updated_at: row.updated_at, - }; - } - - hkStopStored(requestId) { - return this.hkStopPublic(this.sql.exec("SELECT * FROM hk_stop_request WHERE request_id = ?", requestId).toArray()[0]); - } - - hkStopOpen() { - return this.hkStopPublic(this.sql.exec( - "SELECT * FROM hk_stop_request WHERE target_id = ? AND phase IN ('reserved', 'unknown', 'accepted', 'stopped') ORDER BY requested_at ASC", - HK_STOP_TARGET_ID, - ).toArray()[0]); - } - - hkStopLatest() { - return this.hkStopPublic(this.sql.exec( - "SELECT * FROM hk_stop_request WHERE target_id = ? ORDER BY requested_at DESC", - HK_STOP_TARGET_ID, - ).toArray()[0]); - } - - hkStopLiveIdentity(sourceRevision, identity) { - let found; - try { - found = this.existingAccountInstance("longbridge", "hk"); - } catch { - throw new HttpError("hk_stop_source_conflict", 409); - } - if (found.state.revision !== sourceRevision) throw new HttpError("hk_stop_source_conflict", 409); - const live = hkStopSourceIdentity(found.item.config, found.item); - if (!hkStopIdentityComplete(live) || canonicalResearchTaskJson(live) !== canonicalResearchTaskJson(identity)) { - throw new HttpError("hk_stop_source_conflict", 409); - } - return live; - } - - hkStopClaim(command) { - const requestId = String(command.request_id || "").toLowerCase(); - if (!HK_STOP_REQUEST_ID.test(requestId)) throw new HttpError("invalid_hk_stop_request_id", 400); - if (command.action_name !== "stop") throw new HttpError("hk_stop_action_conflict", 409); - const identity = command.identity; - if (!identity || Array.isArray(identity) || typeof identity !== "object") throw new HttpError("hk_stop_identity_incomplete", 400); - if (!Number.isSafeInteger(command.source_revision)) throw new HttpError("hk_stop_source_conflict", 409); - const storedIdentity = canonicalResearchTaskJson(identity); - const existing = this.hkStopStored(requestId); - if (existing) { - if (existing.action !== "stop" || existing.identity_json !== storedIdentity) { - throw new HttpError("hk_stop_identity_conflict", 409); - } - return { ok: true, state: "replay", record: existing }; - } - const open = this.hkStopOpen(); - if (open) { - if (open.identity_json !== storedIdentity) throw new HttpError("hk_stop_identity_conflict", 409); - return { ok: true, state: "blocked", record: open }; - } - this.hkStopLiveIdentity(command.source_revision, identity); - const sourceHash = hkStopDigestOrEmpty(command.source_identity_sha256); - const runtimeHash = hkStopDigestOrEmpty(command.runtime_identity_sha256); - if ((sourceHash === "") !== (runtimeHash === "")) throw new HttpError("hk_stop_identity_incomplete", 400); - const now = new Date().toISOString(); - this.sql.exec( - `INSERT INTO hk_stop_request ( - request_id, target_id, identity_json, source_revision, action, requested_at, phase, dispatch_result, workflow_run_id, workflow_run_attempt, source_identity_sha256, runtime_identity_sha256, updated_at - ) VALUES (?, ?, ?, ?, 'stop', ?, 'reserved', NULL, NULL, NULL, ?, ?, ?)`, - requestId, HK_STOP_TARGET_ID, storedIdentity, command.source_revision, now, sourceHash || null, runtimeHash || null, now, - ); - return { ok: true, state: "reserved", record: this.hkStopStored(requestId) }; - } - - hkStopRecord(command) { - const requestId = String(command.request_id || "").toLowerCase(); - const phase = command.phase; - if (!HK_STOP_REQUEST_ID.test(requestId) || !["accepted", "rejected", "unknown"].includes(phase)) { - throw new HttpError("hk_stop_result_rejected", 400); - } - const existing = this.hkStopStored(requestId); - if (!existing) throw new HttpError("hk_stop_request_conflict", 409); - if (existing.phase === "stopped" || existing.phase !== "reserved") return { ok: true, recorded: false, record: existing }; - const now = new Date().toISOString(); - this.sql.exec( - "UPDATE hk_stop_request SET phase = ?, dispatch_result = ?, updated_at = ? WHERE request_id = ? AND phase = 'reserved'", - phase, phase, now, requestId, - ); - return { ok: true, recorded: true, record: this.hkStopStored(requestId) }; - } - - hkStopAcceptResult(command) { - const result = command?.result; - const now = Date.parse(command?.now); - if (!result?.producer || !result.readback) throw new HttpError("hk_stop_result_rejected", 400); - const requestId = String(result?.request_id || "").toLowerCase(); - if (!HK_STOP_REQUEST_ID.test(requestId) || !Number.isFinite(now)) throw new HttpError("hk_stop_result_rejected", 400); - const row = this.sql.exec("SELECT * FROM hk_stop_request WHERE request_id = ?", requestId).toArray()[0]; - if (!row) throw new HttpError("hk_stop_request_conflict", 409); - if (!row.source_identity_sha256 || !row.runtime_identity_sha256) { - throw new HttpError("hk_stop_result_unlinked", 409); - } - const requestedAt = Date.parse(row.requested_at); - const observedAt = Date.parse(result.observed_at); - if (!Number.isFinite(requestedAt) || !Number.isFinite(observedAt)) throw new HttpError("hk_stop_result_rejected", 400); - if (result.source_revision !== Number(row.source_revision) - || result.source_identity_sha256 !== row.source_identity_sha256 - || result.runtime_identity_sha256 !== row.runtime_identity_sha256 - || result.target_id !== row.target_id) { - throw new HttpError("hk_stop_identity_conflict", 409); - } - let live; - try { - live = this.existingAccountInstance("longbridge", "hk"); - } catch { - throw new HttpError("hk_stop_identity_conflict", 409); - } - const current = hkStopSourceIdentity(live.item.config, live.item); - if (!hkStopIdentityComplete(current) || canonicalResearchTaskJson(current) !== row.identity_json) { - throw new HttpError("hk_stop_identity_conflict", 409); - } - if (observedAt < requestedAt || observedAt > now + 60000) throw new HttpError("hk_stop_result_stale", 409); - const canonical = canonicalResearchTaskJson(result); - const sameRun = row.workflow_run_id === result.producer.run_id && String(row.workflow_run_attempt) === String(result.producer.run_attempt); - if (row.phase === "stopped") { - if (row.result_json === canonical && sameRun) return { ok: true, replayed: true, record: this.hkStopStored(requestId) }; - throw new HttpError("hk_stop_result_conflict", 409); - } - if (!["reserved", "accepted", "unknown"].includes(row.phase)) throw new HttpError("hk_stop_result_conflict", 409); - if (now - requestedAt > 24 * 60 * 60 * 1000) throw new HttpError("hk_stop_result_stale", 409); - const stamp = new Date(now).toISOString(); - this.sql.exec( - `UPDATE hk_stop_request - SET phase = 'stopped', workflow_run_id = ?, workflow_run_attempt = ?, result_json = ?, stopped_at = ?, updated_at = ? - WHERE request_id = ? AND phase IN ('reserved', 'accepted', 'unknown')`, - result.producer.run_id, String(result.producer.run_attempt), canonical, stamp, stamp, requestId, - ); - const stored = this.hkStopStored(requestId); - if (stored?.phase !== "stopped") throw new HttpError("hk_stop_result_conflict", 409); - return { ok: true, replayed: false, record: stored }; - } - - read() { - const row = this.sql.exec("SELECT revision, payload FROM instance_state WHERE id = 1").toArray()[0]; - return row ? { revision: row.revision, instances: JSON.parse(row.payload), initialized: true } : { revision: 0, instances: [], initialized: false }; - } - - response(state) { - return { - ok: true, ...state, account_options: runtimeInstanceAccountOptions(state.instances), - history: this.sql.exec("SELECT entry FROM instance_history ORDER BY revision DESC LIMIT 50").toArray().map((row) => JSON.parse(row.entry)), - no_order: true, execution_authority_granted: false, - }; - } - - async alarm() { await this.deskReadbacks.alarm(); } - - async fetch(request) { - try { - if (new URL(request.url).pathname === DIRECTORY_PATH) { - if (request.method !== "POST") throw new HttpError("method_not_allowed", 405); - const result = await this.directory.command(await request.json()); - return json(result, result.ok ? 200 : 503); - } - if (request.method === "GET") return json(this.response(this.read())); - if (request.method !== "POST") throw new HttpError("method_not_allowed", 405); - let command; - try { command = await request.json(); } catch { throw new HttpError("invalid_runtime_instance_request", 400); } - if (["desk_readback_start", "desk_readback_get", "desk_readback_fresh"].includes(command?.action)) { - try { return json(await this.deskReadbacks.command(command)); } - catch (error) { return deskErrorResponse(error); } - } - if (typeof command?.action === "string" && command.action.startsWith("rotation_")) { - return json(await durableRotationCommand(this.storage, this.sql, this.rotationEnv, command)); - } - if (["credential_maintenance_read", "credential_maintenance_save", "credential_maintenance_claim", "credential_maintenance_ack"].includes(command?.action)) { - return json(this.storage.transactionSync(() => credentialMaintenanceCommand(this.sql, command))); - } - if (ACCOUNT_DIAGNOSIS_DO_ACTIONS.has(command?.action)) { - return json(await this.accountDiagnosisCommand(command)); - } - if (["console_decision_history", "console_settings_history", "console_account_metadata", "console_audit_events"].includes(command?.action)) { - return json(this.consoleReadCommand(command)); - } - if (HUMAN_DECISION_DO_ACTIONS.has(command?.action)) { - return json(this.humanDecisionCommand(command)); - } - if (V7_PAPER_APPLICATION_DO_ACTIONS.has(command?.action)) { - return json(await this.applicationCommand(command)); - } - if (typeof command?.action === "string" && command.action.startsWith("ux1_")) { - return json(this.ux1ResearchCommand(command)); - } - if (["cycle_health_admission", "cycle_health_put", "cycle_health_read", "cycle_health_provision"].includes(command?.action)) { - if (command.action === "cycle_health_provision") { - return json(this.storage.transactionSync(() => this.cycleHealthProvisionCommand(command))); - } - return json(this.storage.transactionSync(() => this.cycleHealthCommand(command))); - } - if (ACCOUNT_SETTINGS_DO_ACTIONS.has(command?.action)) { - return json(this.storage.transactionSync(() => this.accountSettingsCommand(command))); - } - if (ACCOUNT_FACTS_DO_ACTIONS.has(command?.action)) { - const result = this.storage.transactionSync(() => this.accountFactsCommand(command)); - return json(result, command.action === "account_daily_native_return_research_read" && !result.ok ? 409 : 200); - } - if (MARKET_BENCHMARK_DO_ACTIONS.has(command?.action)) { - return json(this.storage.transactionSync(() => this.marketBenchmarkCommand(command))); - } - if (RUNTIME_CONTROL_DO_ACTIONS.has(command?.action)) { - return json(this.storage.transactionSync(() => this.runtimeControlCommand(command))); - } - if (HK_STOP_DO_ACTIONS.has(command?.action)) { - return json(this.storage.transactionSync(() => this.hkStopCommand(command))); - } - // No awaits or external I/O inside this SQLite transaction. Every state - // change, version advance, and history row commits together or rolls back. - return json(this.storage.transactionSync(() => { - const state = this.read(); - if (!command || typeof command.actor !== "string" || !command.actor) throw new HttpError("runtime_instance_actor_required", 400); - if (command.action !== "sync_defaults") { - if (!Number.isSafeInteger(command.expected_revision) || command.expected_revision < 0) throw new HttpError("runtime_instance_revision_required", 400); - if (command.expected_revision !== state.revision) throw new HttpError("runtime_instance_revision_conflict", 409); - } - const ts = new Date().toISOString(); - let changed = true; - let before = null; - let after = null; - if (command.action === "initialize") { - if (state.initialized) throw new HttpError("runtime_instances_already_initialized", 409); - for (const [platform, options] of Object.entries(command.account_options)) { - for (const config of options) { - if (state.instances.some((item) => item.platform === platform && (item.key === config.key || item.config.target_name === config.target_name))) throw new HttpError("runtime_instance_identity_conflict", 409); - state.instances.push({ platform, key: config.key, kind: "existing", config, enabled: null, platform_applied: null, application_status: "unknown", retirement_status: "none", created_at: ts, updated_at: ts }); - } - } - state.initialized = true; - } else { - if (!state.initialized) throw new HttpError("runtime_instances_not_initialized", 409); - const item = state.instances.find((candidate) => candidate.platform === command.platform && candidate.key === command.key); - if (command.action === "create") { - if (state.instances.length >= 120) throw new HttpError("runtime_instance_limit_reached", 409); - runtimeInstanceConflict(state.instances, command.platform, command.config); - after = { platform: command.platform, key: command.config.key, kind: "draft", config: command.config, enabled: false, platform_applied: false, application_status: "unapplied", retirement_status: "none", created_at: ts, updated_at: ts }; - state.instances.push(after); - } else if (command.action === "edit") { - if (!item || item.kind !== "draft" || item.retirement_status !== "none") throw new HttpError("runtime_instance_draft_edit_required", 409); - if (item.key !== command.config.key) throw new HttpError("runtime_instance_key_immutable", 409); - runtimeInstanceConflict(state.instances, command.platform, command.config, item.key); - before = structuredClone(item); - item.config = command.config; - item.updated_at = ts; - after = item; - } else if (command.action === "request_retirement") { - if (!item) throw new HttpError("runtime_instance_not_found", 404); - before = structuredClone(item); - changed = item.retirement_status !== "requested"; - item.retirement_status = "requested"; - if (changed) item.updated_at = ts; - after = item; - } else if (command.action === "set_broker_environment") { - if (!item || item.kind !== "existing") throw new HttpError("runtime_instance_existing_required", 409); - if (!BROKER_ENVIRONMENTS.includes(command.broker_environment) - || !supportedBrokerEnvironmentsForPlatform(command.platform).includes(command.broker_environment)) { - throw new HttpError("invalid_broker_environment", 400); - } - before = structuredClone(item); - changed = item.config.broker_environment !== command.broker_environment; - if (changed) { - item.config = { ...item.config, broker_environment: command.broker_environment }; - item.updated_at = ts; - } - after = item; - } else if (command.action === "sync_defaults") { - const current = runtimeInstanceAccountOptions(state.instances); - const update = updateAccountOptionsDefaultStrategy(current, command.inputs); - changed = update.changed; - for (const instance of state.instances) { - if (instance.kind !== "existing") continue; - const config = update.options[instance.platform]?.find((option) => option.key === instance.key); - if (config) { - const defaults = {}; - for (const field of ["default_execution_mode", "default_strategy_profile"]) { - if (instance.config[field] !== undefined) defaults[field] = instance.config[field]; - } - instance.config = { ...config, ...defaults }; - } - } - } else { - throw new HttpError("unsupported_runtime_instance_action", 400); - } - } - if (changed) { - state.revision += 1; - this.sql.exec("INSERT INTO instance_state (id, revision, payload) VALUES (1, ?, ?) ON CONFLICT(id) DO UPDATE SET revision = excluded.revision, payload = excluded.payload", state.revision, JSON.stringify(state.instances)); - this.sql.exec("INSERT INTO instance_history (revision, entry) VALUES (?, ?)", state.revision, JSON.stringify({ revision: state.revision, ts, login: command.actor, action: command.action, before, after })); - } - return this.response(state); - })); - } catch (error) { - return json({ ok: false, error: error instanceof HttpError || error instanceof CycleHealthError || error instanceof CredentialMaintenanceError || error instanceof RotationError ? error.message : "runtime_instance_transaction_failed" }, error instanceof HttpError || error instanceof CycleHealthError || error instanceof CredentialMaintenanceError || error instanceof RotationError ? error.status : 409); - } - } - - accountDiagnosisCommand(command) { - return this.storage.transactionSync(() => { - if (!command || typeof command.actor !== "string" || !command.actor) { - throw new HttpError("runtime_instance_actor_required", 400); - } - if (command.action === "diagnosis_create") { - const autoSource = command.auto_source === "source_sync" ? "source_sync" : "manual"; - // Unclosed work keeps its original obligation regardless of age or fingerprint. - const unresolved = this.sql.exec("SELECT * FROM account_diagnosis_tasks WHERE target_id = ? AND status IN ('queued', 'running', 'unknown') ORDER BY updated_at DESC LIMIT 1", command.target_id).toArray()[0]; - if (unresolved) return { ok: true, created: false, task: accountDiagnosisTaskFromRow(unresolved) }; - const existing = autoSource === "source_sync" - ? this.sql.exec( - "SELECT * FROM account_diagnosis_tasks WHERE target_id = ? AND auto_source = ? AND updated_at >= ? ORDER BY updated_at DESC LIMIT 1", - command.target_id, autoSource, new Date(Number(command.now) - ACCOUNT_DIAGNOSIS_DEDUPE_WINDOW_MS).toISOString(), - ).toArray()[0] - : this.sql.exec( - "SELECT * FROM account_diagnosis_tasks WHERE target_id = ? AND state_fingerprint = ? ORDER BY updated_at DESC LIMIT 1", - command.target_id, command.state_fingerprint, - ).toArray()[0]; - const existingAge = existing ? Date.parse(existing.updated_at) : Number.NaN; - const existingIsRecent = Number.isFinite(existingAge) - && existingAge >= Number(command.now) - ACCOUNT_DIAGNOSIS_DEDUPE_WINDOW_MS; - if (existing && existingIsRecent) return { ok: true, created: false, task: accountDiagnosisTaskFromRow(existing) }; - const task = { - request_id: command.request_id, - platform: command.platform, - key: command.key, - target_id: command.target_id, - trigger: command.trigger, - observed_at: command.observed_at, - checks: command.checks, - state_fingerprint: command.state_fingerprint, - status: "queued", - workflow_run_id: null, - workflow_run_attempt: null, - job_id: null, - summary: "", - reason_code: "", - dispatch_state: "pending", - recheck_dispatch_state: "not_requested", - created_at: command.created_at, - updated_at: command.created_at, - }; - this.sql.exec( - `INSERT INTO account_diagnosis_tasks - (request_id, platform, account_key, target_id, trigger, observed_at, checks_json, state_fingerprint, - status, workflow_run_id, workflow_run_attempt, job_id, summary, reason_code, dispatch_state, - recheck_dispatch_state, created_at, updated_at, auto_source) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, - task.request_id, task.platform, task.key, task.target_id, task.trigger, task.observed_at, - JSON.stringify(task.checks), task.state_fingerprint, task.status, null, null, null, - task.summary, task.reason_code, task.dispatch_state, task.recheck_dispatch_state, - task.created_at, task.updated_at, autoSource, - ); - return { ok: true, created: true, task }; - } - if (command.action === "diagnosis_latest") { - const row = this.sql.exec( - "SELECT * FROM account_diagnosis_tasks WHERE platform = ? AND account_key = ? ORDER BY updated_at DESC LIMIT 1", - command.platform, command.key, - ).toArray()[0]; - return { ok: true, task: accountDiagnosisTaskFromRow(row) }; - } - const requestId = command.request_id; - const row = this.sql.exec("SELECT * FROM account_diagnosis_tasks WHERE request_id = ?", requestId).toArray()[0]; - if (!row) throw new HttpError("account_diagnosis_request_not_found", 404); - if (command.action === "diagnosis_read") return { ok: true, task: accountDiagnosisTaskFromRow(row) }; - if (command.action === "diagnosis_native_intent") { - if (row.native_json) { - const old = JSON.parse(row.native_json); - if (canonicalResearchTaskJson(old.binding) !== canonicalResearchTaskJson(command.native.binding) - || old.request_sha256 !== command.native.request_sha256) throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); - return { ok: true, created: false, task: accountDiagnosisTaskFromRow(row) }; - } - if (row.workflow_run_id || row.workflow_run_attempt || row.status !== "queued" || row.dispatch_state !== "pending") { - throw new HttpError("account_diagnosis_legacy_retired", 409); - } - this.sql.exec("UPDATE account_diagnosis_tasks SET native_json = ?, updated_at = ? WHERE request_id = ?", - JSON.stringify(command.native), command.updated_at, requestId); - return { ok: true, created: true, task: accountDiagnosisTaskFromRow(this.sql.exec("SELECT * FROM account_diagnosis_tasks WHERE request_id = ?", requestId).toArray()[0]) }; - } - if (command.action === "diagnosis_native_cancel") { - if (!row.native_json) throw new HttpError("account_diagnosis_legacy_retired", 409); - const native = JSON.parse(row.native_json); - // The first durable terminal receipt or cancellation wins atomically. - if (native.receipt) return { ok: true, send_cancel: false, task: accountDiagnosisTaskFromRow(row) }; - const send = Boolean(native.task_id && !native.cancel_sent); - native.cancel_requested = true; - native.cancel_sent = native.cancel_sent || send; - native.phase = "hold"; - this.sql.exec("UPDATE account_diagnosis_tasks SET native_json = ?, status = 'unknown', summary = '', reason_code = '', recheck_dispatch_state = 'not_requested', updated_at = ? WHERE request_id = ?", - JSON.stringify(native), command.updated_at, requestId); - return { ok: true, send_cancel: send, task: accountDiagnosisTaskFromRow(this.sql.exec("SELECT * FROM account_diagnosis_tasks WHERE request_id = ?", requestId).toArray()[0]) }; - } - if (command.action === "diagnosis_native_save") { - if (!row.native_json) throw new HttpError("account_diagnosis_legacy_retired", 409); - const old = JSON.parse(row.native_json), next = command.native; - for (const field of ["version", "binding", "request", "input_sha256", "request_sha256", "operation_key", "deadline"]) { - if (canonicalResearchTaskJson(old[field]) !== canonicalResearchTaskJson(next[field])) throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); - } - if ((old.task_id && old.task_id !== next.task_id) || (old.task_deadline !== null && old.task_deadline !== next.task_deadline) - || (old.claim && canonicalResearchTaskJson(old.claim) !== canonicalResearchTaskJson(next.claim)) - || (old.cancel_sent && !next.cancel_sent) || (old.cancel_requested && !next.cancel_requested) - || !ACCOUNT_DIAGNOSIS_STATUSES.includes(command.status)) { - throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); - } - if (old.receipt) { - if (old.receipt !== next.receipt) throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); - return { ok: true, changed: false, task: accountDiagnosisTaskFromRow(row) }; - } - if (command.status === "succeeded" && (old.cancel_requested || old.cancel_sent - || Date.now() / 1000 >= Math.min(old.deadline, next.task_deadline ?? Infinity))) { - throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); - } - const recheck = command.status === "succeeded" ? "pending" : row.recheck_dispatch_state; - this.sql.exec("UPDATE account_diagnosis_tasks SET native_json = ?, status = ?, summary = ?, reason_code = ?, dispatch_state = 'sent', recheck_dispatch_state = ?, updated_at = ? WHERE request_id = ?", - JSON.stringify(next), command.status, command.summary, command.reason_code, recheck, command.updated_at, requestId); - return { ok: true, changed: true, task: accountDiagnosisTaskFromRow(this.sql.exec("SELECT * FROM account_diagnosis_tasks WHERE request_id = ?", requestId).toArray()[0]) }; - } - if (command.action === "diagnosis_mark_dispatch") { - if (row.dispatch_state !== "pending") return { ok: true, changed: false, task: accountDiagnosisTaskFromRow(row) }; - if (command.dispatch_state === "unknown" && row.native_json) { - const native = { ...JSON.parse(row.native_json), phase: "hold" }; - this.sql.exec("UPDATE account_diagnosis_tasks SET dispatch_state = 'unknown', status = 'unknown', native_json = ?, updated_at = ? WHERE request_id = ?", - JSON.stringify(native), command.updated_at, requestId); - } else { - this.sql.exec("UPDATE account_diagnosis_tasks SET dispatch_state = ?, updated_at = ? WHERE request_id = ?", command.dispatch_state, command.updated_at, requestId); - } - return { ok: true, changed: true, task: accountDiagnosisTaskFromRow(this.sql.exec("SELECT * FROM account_diagnosis_tasks WHERE request_id = ?", requestId).toArray()[0]) }; - } - if (command.action === "diagnosis_claim" || command.action === "diagnosis_result") { - throw new HttpError("account_diagnosis_legacy_retired", 409); - } - if (command.action === "diagnosis_mark_recheck") { - if (row.recheck_dispatch_state !== "pending") return { ok: true, changed: false, task: accountDiagnosisTaskFromRow(row) }; - this.sql.exec("UPDATE account_diagnosis_tasks SET recheck_dispatch_state = ?, updated_at = ? WHERE request_id = ?", command.recheck_dispatch_state, command.updated_at, requestId); - return { ok: true, changed: true, task: accountDiagnosisTaskFromRow(this.sql.exec("SELECT * FROM account_diagnosis_tasks WHERE request_id = ?", requestId).toArray()[0]) }; - } - throw new HttpError("unsupported_account_diagnosis_action", 400); - }); - } - - consoleReadCommand(command) { - if (!command.actor) throw new HttpError("runtime_instance_actor_required", 400); - if (command.action === "console_audit_events") { - const limit = command.limit; - if (!Number.isSafeInteger(limit) || limit < 1 || limit > AUDIT_EVENTS_MAX_ROWS) throw new HttpError("invalid_pagination", 400); - const { snapshot, before } = command; - // Normalize time once, and use the same binary id tie-break as the HTTP merge. - return { rows: this.sql.exec(` - WITH events AS ( - SELECT 'decision' AS source, kind, subject_id, material_key AS revision, - action, actor, strftime('%Y-%m-%dT%H:%M:%fZ', decided_at) AS occurred_at, - request_key AS ref, 'decided' AS status, 'durable_human_decision:' || request_key AS event_id - FROM human_decision_record - UNION ALL - SELECT 'promotion_application', 'promotion', ticket_id, '', status, '', - strftime('%Y-%m-%dT%H:%M:%fZ', updated_at), application_id, status, - 'research_promotion_application:' || application_id FROM research_promotion_applications - UNION ALL - SELECT 'account_settings_application', 'account_settings', platform || ':' || account_key, '', - status, approved_by, strftime('%Y-%m-%dT%H:%M:%fZ', approved_at), request_key, status, - 'account_settings_application:' || request_key FROM account_setting_profile_application - UNION ALL - SELECT 'credential_rotation', 'credential', 'ibkr_flex', '', action, actor, - occurred_at, operation_id, outcome, 'credential_rotation:' || event_id - FROM credential_rotation_audit - ) SELECT * FROM events WHERE occurred_at <= ? - AND (? IS NULL OR occurred_at < ? OR (occurred_at = ? AND event_id > ?)) - ORDER BY occurred_at DESC, event_id ASC LIMIT ?`, snapshot, - before?.at ?? null, before?.at ?? null, before?.at ?? null, before?.id ?? null, limit).toArray() }; - } - if (command.action === "console_decision_history") { - const { limit, offset } = command; - if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100 || !Number.isSafeInteger(offset) || offset < 0 || offset > 100000) throw new HttpError("invalid_pagination", 400); - return { rows: this.sql.exec("SELECT request_key, kind, subject_id, material_key, action, actor, decided_at, target_json, json_extract(payload_json, '$.ticket.confirmation_execution_mode') AS confirmation_execution_mode FROM human_decision_record ORDER BY decided_at DESC, request_key DESC LIMIT ? OFFSET ?", limit + 1, offset).toArray() }; - } - if (command.action === "console_settings_history") { - this.existingAccountInstance(command.platform, command.key); - return { approvals: this.sql.exec("SELECT request_key, status, identity_json, instance_revision, old_profile, new_profile, approved_at, approved_by, draft_revision FROM account_setting_profile_application WHERE platform = ? AND account_key = ? ORDER BY approved_at DESC", command.platform, command.key).toArray() }; - } - const drafts = this.sql.exec("SELECT platform, account_key, overrides_json, updated_at FROM account_setting_draft").toArray(); - const approvals = this.sql.exec("SELECT platform, account_key, MAX(approved_at) AS approved_at FROM account_setting_profile_application GROUP BY platform, account_key").toArray(); - return { rows: this.read().instances.map(item => { - const draft = drafts.find(row => row.platform === item.platform && row.account_key === item.key); - const approval = approvals.find(row => row.platform === item.platform && row.account_key === item.key); - return { platform: item.platform, account_key: item.key, - pending_draft_count: draft ? new Set(Object.keys(JSON.parse(draft.overrides_json)).map(field => field.startsWith("dca_") ? "dca" : field.startsWith("reserved_") ? "cash" : field)).size : 0, - last_change_at: [draft?.updated_at, approval?.approved_at, item.updated_at].filter(Boolean).sort().at(-1) || null, - }; - }) }; - } - - humanDecisionCommand(command) { - return this.storage.transactionSync(() => { - if (!command || typeof command.actor !== "string" || !command.actor) { - throw new HttpError("runtime_instance_actor_required", 400); - } - if (command.action === "owner_sync_materials") return this.syncHumanDecisionMaterials(command, "owner"); - if (command.action === "recovery_sync_materials") return this.syncHumanDecisionMaterials(command, "recovery"); - if (command.action === "promotion_sync_materials") return this.syncHumanDecisionMaterials(command, "promotion"); - if (command.action === "owner_decide") return this.writeHumanDecision(command, "owner"); - if (command.action === "recovery_decide") return this.writeHumanDecision(command, "recovery"); - if (command.action === "promotion_decide") return this.writeHumanDecision(command, "promotion"); - if (command.action === "owner_read") return this.readHumanDecision(command, "owner"); - if (command.action === "recovery_read") return this.readHumanDecision(command, "recovery"); - if (command.action === "promotion_read") return this.readHumanDecision(command, "promotion"); - throw new HttpError("unsupported_human_decision_action", 400); - }); - } - - humanDecisionMaterialRow(kind, subjectId) { - return this.sql.exec( - "SELECT * FROM human_decision_material WHERE kind = ? AND subject_id = ?", - kind, subjectId, - ).toArray()[0] || null; - } - - humanDecisionTerminalRow(kind, subjectId, materialKey) { - return this.sql.exec( - "SELECT * FROM human_decision_record WHERE kind = ? AND subject_id = ? AND material_key = ? ORDER BY decided_at ASC LIMIT 1", - kind, subjectId, materialKey, - ).toArray()[0] || null; - } - - humanDecisionRecordPayload(row) { - let payload = {}; - try { - payload = JSON.parse(row.payload_json); - } catch { - throw new HttpError("human_decision_record_invalid", 409); - } - return { - request_key: row.request_key, - kind: row.kind, - subject_id: row.subject_id, - material_key: row.material_key, - action: row.action, - actor: row.actor, - decided_at: row.decided_at, - target_json: row.target_json, - payload, - }; - } - - humanDecisionSourceRow(kind, subjectId, sourceId) { - return this.sql.exec( - "SELECT * FROM human_decision_source_material WHERE kind = ? AND subject_id = ? AND source_id = ?", - kind, subjectId, sourceId, - ).toArray()[0] || null; - } - - upsertHumanDecisionSource(kind, subjectId, sourceId, item, eligible, updatedAt) { - if (this.humanDecisionSourceRow(kind, subjectId, sourceId)) { - this.sql.exec( - `UPDATE human_decision_source_material - SET material_key = ?, summary_json = ?, eligible = ?, expires_at_ms = ?, updated_at = ? - WHERE kind = ? AND subject_id = ? AND source_id = ?`, - item.material_key, item.summary_json || "{}", eligible, item.expires_at_ms ?? null, updatedAt, kind, subjectId, sourceId, - ); - return; - } - this.sql.exec( - `INSERT INTO human_decision_source_material - (kind, subject_id, source_id, material_key, summary_json, eligible, expires_at_ms, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, - kind, subjectId, sourceId, item.material_key, item.summary_json || "{}", eligible, item.expires_at_ms ?? null, updatedAt, - ); - } - - recomputeHumanDecisionSubject(kind, subjectId, updatedAt) { - const sources = this.sql.exec( - "SELECT * FROM human_decision_source_material WHERE kind = ? AND subject_id = ?", - kind, subjectId, - ).toArray(); - let projection = this.humanDecisionMaterialRow(kind, subjectId); - if (!projection && !sources.length) return; - if (!projection) { - this.insertHumanDecisionMaterial(kind, subjectId, sources[0].source_id, sources[0], 0, { state: "none", block: "" }, updatedAt); - projection = this.humanDecisionMaterialRow(kind, subjectId); - } - if (projection.legacy_state === "blocked") { - this.sql.exec( - "UPDATE human_decision_material SET eligible = 0, updated_at = ? WHERE kind = ? AND subject_id = ?", - updatedAt, kind, subjectId, - ); - return; - } - if (sources.length !== 1) { - const keys = new Set(sources.map((row) => row.material_key)); - const materialKey = keys.size === 1 ? sources[0].material_key : ""; - this.sql.exec( - "UPDATE human_decision_material SET source_id = ?, material_key = ?, eligible = 0, updated_at = ? WHERE kind = ? AND subject_id = ?", - "", materialKey, updatedAt, kind, subjectId, - ); - return; - } - const only = sources[0]; - this.sql.exec( - `UPDATE human_decision_material - SET source_id = ?, material_key = ?, summary_json = ?, eligible = ?, expires_at_ms = ?, updated_at = ? - WHERE kind = ? AND subject_id = ?`, - only.source_id, only.material_key, only.summary_json, only.eligible, only.expires_at_ms, updatedAt, kind, subjectId, - ); - } - - insertHumanDecisionMaterial(kind, subjectId, sourceId, item, eligible, legacyState, updatedAt) { - this.sql.exec( - `INSERT INTO human_decision_material - (kind, subject_id, source_id, material_key, summary_json, eligible, expires_at_ms, legacy_state, legacy_block, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, - kind, subjectId, sourceId, item.material_key, item.summary_json || "{}", eligible, - item.expires_at_ms ?? null, legacyState.state, legacyState.block, updatedAt, - ); - } - - insertHumanDecisionRecord(record) { - this.sql.exec( - `INSERT INTO human_decision_record - (request_key, kind, subject_id, material_key, action, actor, decided_at, target_json, payload_json) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, - record.request_key, record.kind, record.subject_id, record.material_key, record.action, - record.actor, record.decided_at, record.target_json, record.payload_json, - ); - } - - importHumanDecisionLegacy(kind, subjectId, item) { - const legacy = item.legacy; - if (!legacy) return { state: "none", block: "" }; - const record = legacy.record; - if (legacy.blocked || !record || record.material_key !== item.material_key || !record.action || !record.actor || !record.decided_at || !record.payload || typeof record.payload !== "object") { - return { state: "blocked", block: "unexplained_legacy" }; - } - this.insertHumanDecisionRecord({ - request_key: `legacy:${kind}:${subjectId}:${item.material_key}`, - kind, - subject_id: subjectId, - material_key: item.material_key, - action: record.action, - actor: record.actor, - decided_at: record.decided_at, - target_json: record.target_json || "{}", - payload_json: JSON.stringify(record.payload), - }); - return { state: "imported", block: "" }; - } - - syncHumanDecisionMaterials(command, kind) { - const sourceId = String(command.source_id || ""); - const updatedAt = String(command.updated_at || ""); - const materials = Array.isArray(command.materials) ? command.materials : []; - const seen = new Set(); - const affected = new Set(); - let terminal = false; - let terminalState = ""; - let identityConflict = false; - let legacyBlocked = false; - for (const item of materials) { - const subjectId = String(item.subject_id || ""); - if (!subjectId || seen.has(subjectId)) continue; - seen.add(subjectId); - const sourceRow = this.humanDecisionSourceRow(kind, subjectId, sourceId); - const incomingTerminal = this.humanDecisionTerminalRow(kind, subjectId, item.material_key); - if ( - kind === "promotion" && sourceRow && sourceRow.material_key !== item.material_key && !incomingTerminal - && !this.humanDecisionTerminalRow(kind, subjectId, sourceRow.material_key) - ) { - identityConflict = true; - continue; - } - if (kind === "promotion" && incomingTerminal) { - terminal = true; - terminalState = incomingTerminal.action === "accept" ? "human_accepted" : "human_rejected"; - } - let projection = this.humanDecisionMaterialRow(kind, subjectId); - if (!projection) { - const legacyState = this.importHumanDecisionLegacy(kind, subjectId, item); - this.insertHumanDecisionMaterial(kind, subjectId, sourceId, item, 0, legacyState, updatedAt); - if (legacyState.state === "blocked") legacyBlocked = true; - const imported = this.humanDecisionTerminalRow(kind, subjectId, item.material_key); - if (imported && kind === "promotion") { - terminal = true; - terminalState = imported.action === "accept" ? "human_accepted" : "human_rejected"; - } - projection = this.humanDecisionMaterialRow(kind, subjectId); - } else if (projection.legacy_state === "blocked") { - legacyBlocked = true; - } - const blocked = projection?.legacy_state === "blocked"; - this.upsertHumanDecisionSource(kind, subjectId, sourceId, item, blocked ? 0 : (item.eligible ? 1 : 0), updatedAt); - affected.add(subjectId); - } - if (command.replace_source) { - const rows = this.sql.exec( - "SELECT subject_id FROM human_decision_source_material WHERE kind = ? AND source_id = ?", - kind, sourceId, - ).toArray(); - for (const row of rows) { - if (seen.has(row.subject_id)) continue; - this.sql.exec( - "DELETE FROM human_decision_source_material WHERE kind = ? AND subject_id = ? AND source_id = ?", - kind, row.subject_id, sourceId, - ); - affected.add(row.subject_id); - } - } - for (const subjectId of command.duplicate_subject_ids || []) { - if (typeof subjectId !== "string" || !subjectId) continue; - this.sql.exec( - "UPDATE human_decision_source_material SET eligible = 0, updated_at = ? WHERE kind = ? AND subject_id = ? AND source_id = ?", - updatedAt, kind, subjectId, sourceId, - ); - affected.add(subjectId); - } - for (const subjectId of affected) this.recomputeHumanDecisionSubject(kind, subjectId, updatedAt); - return { ok: true, terminal, terminal_state: terminalState, identity_conflict: identityConflict, legacy_blocked: legacyBlocked }; - } - - writeHumanDecision(command, kind) { - if (kind === "owner" && command.action_name === "retire_candidate") { - throw new HttpError("owner_retirement_proposal_required", 409); - } - const subjectId = String(command.subject_id || ""); - const materialKey = String(command.material_key || ""); - const actionName = String(command.action_name || ""); - const requestKey = String(command.request_key || ""); - const targetJson = String(command.target_json || "{}"); - if (!subjectId || !materialKey || !actionName || !requestKey) throw new HttpError("human_decision_request_invalid", 400); - let row = this.humanDecisionMaterialRow(kind, subjectId); - if (!row && command.legacy) { - const legacyState = this.importHumanDecisionLegacy(kind, subjectId, { material_key: materialKey, legacy: command.legacy }); - this.insertHumanDecisionMaterial(kind, subjectId, String(command.source_id || kind), { - material_key: materialKey, - summary_json: command.summary_json || "{}", - expires_at_ms: command.expires_at_ms ?? null, - }, legacyState.state === "blocked" ? 0 : 1, legacyState, String(command.decided_at || "")); - row = this.humanDecisionMaterialRow(kind, subjectId); - } - if (!row) throw new HttpError("human_decision_material_unavailable", 409); - if (row.legacy_state === "blocked") throw new HttpError("human_decision_legacy_blocked", 409); - if (row.material_key !== materialKey) throw new HttpError("human_decision_material_conflict", 409); - if (Number(row.eligible) !== 1) throw new HttpError("human_decision_material_ineligible", 409); - if (row.expires_at_ms != null && Number(command.now_ms) >= Number(row.expires_at_ms)) { - throw new HttpError("human_decision_material_expired", 409); - } - if (kind === "promotion" && command.review_binding !== undefined - && !promotionReviewBindingsMatch(command.review_binding, promotionMaterialReviewBinding(row))) { - throw new HttpError("human_decision_review_material_conflict", 409); - } - const assertReview = terminal => { - if (kind === "promotion" && command.review_binding !== undefined - && !promotionReviewBindingsMatch(command.review_binding, this.humanDecisionRecordPayload(terminal).payload?.review_binding)) { - throw new HttpError("human_decision_review_binding_unqualified", 409); - } - }; - const byRequest = this.sql.exec("SELECT * FROM human_decision_record WHERE request_key = ?", requestKey).toArray()[0]; - if (byRequest) { - if (byRequest.kind !== kind || byRequest.subject_id !== subjectId || byRequest.material_key !== materialKey || byRequest.action !== actionName || byRequest.target_json !== targetJson) { - throw new HttpError("human_decision_conflict", 409); - } - assertReview(byRequest); - return { ok: true, replayed: true, record: this.humanDecisionRecordPayload(byRequest) }; - } - const terminal = this.humanDecisionTerminalRow(kind, subjectId, materialKey); - if (terminal) { - if (terminal.action !== actionName || terminal.target_json !== targetJson) throw new HttpError("human_decision_conflict", 409); - assertReview(terminal); - return { ok: true, replayed: true, record: this.humanDecisionRecordPayload(terminal) }; - } - const payload = command.payload && typeof command.payload === "object" ? structuredClone(command.payload) : {}; - if (kind === "promotion" && actionName === "accept") { - const instances = this.read(); - if (instances.initialized) { - const preflightRevision = Number(command.preflight_revision); - if (!Number.isSafeInteger(preflightRevision) || preflightRevision !== instances.revision) { - throw new HttpError("runtime_instance_revision_conflict", 409); - } - payload.account_revision = instances.revision; - } else { - payload.account_revision = null; - } - } - const record = { - request_key: requestKey, - kind, - subject_id: subjectId, - material_key: materialKey, - action: actionName, - actor: String(command.actor || ""), - decided_at: String(command.decided_at || ""), - target_json: targetJson, - payload_json: JSON.stringify(payload), - }; - this.insertHumanDecisionRecord(record); - return { ok: true, replayed: false, record: this.humanDecisionRecordPayload(this.humanDecisionTerminalRow(kind, subjectId, materialKey)) }; - } - - readHumanDecision(command, kind) { - const subjectId = String(command.subject_id || ""); - const now = Number.isFinite(Number(command.now_ms)) ? Number(command.now_ms) : Date.now(); - const row = this.humanDecisionMaterialRow(kind, subjectId); - const expiry = (material) => material.expires_at_ms == null ? null : new Date(Number(material.expires_at_ms)).toISOString(); - const expired = (material) => material.expires_at_ms != null && now >= Number(material.expires_at_ms); - // Historical receipt lookup deliberately does not substitute the current material. - // It grants no eligibility or authority to act on the current material. - if (command.exact_material_key !== undefined) { - const materialKey = command.exact_material_key; - if (!/^[0-9a-f]{64}$/.test(materialKey) || !subjectId || subjectId.length > 256) { - throw new HttpError("human_decision_lookup_invalid", 400); - } - const terminal = this.humanDecisionTerminalRow(kind, subjectId, materialKey); - return { - ok: true, - exact_material_key: materialKey, - decision: terminal ? this.humanDecisionRecordPayload(terminal) : null, - current_material: row ? { - matches: row.material_key === materialKey, - eligible: Number(row.eligible) === 1 && !expired(row), - expired: expired(row), - expires_at: expiry(row), - blocked: row.legacy_state === "blocked", - ...(kind === "promotion" ? { review_binding: promotionMaterialReviewBinding(row) } : {}), - } : null, - }; - } - if (!row) return { ok: true, migrated: false, blocked: false, eligible: false, expired: false, expires_at: null, material_key: null, decision: null }; - const terminal = this.humanDecisionTerminalRow(kind, subjectId, row.material_key); - return { - ok: true, - migrated: true, - blocked: row.legacy_state === "blocked", - eligible: Number(row.eligible) === 1 && !expired(row), - expired: expired(row), - expires_at: expiry(row), - material_key: row.material_key, - ...(kind === "promotion" ? { review_binding: promotionMaterialReviewBinding(row) } : {}), - decision: terminal ? this.humanDecisionRecordPayload(terminal) : null, - }; - } - - applicationCommand(command) { - return this.storage.transactionSync(() => { - if (!command || typeof command.actor !== "string" || !command.actor) { - throw new HttpError("runtime_instance_actor_required", 400); - } - if (command.action === "application_create") { - const currentMaterial = this.humanDecisionMaterialRow("promotion", command.ticket_id); - if (!currentMaterial || currentMaterial.legacy_state === "blocked" || Number(currentMaterial.eligible) !== 1 || currentMaterial.material_key !== command.promotion_material_key) { - throw new HttpError("research candidate is not human_accepted", 409); - } - const decisionRow = this.humanDecisionTerminalRow("promotion", command.ticket_id, currentMaterial.material_key); - if (!decisionRow || decisionRow.action !== "accept") throw new HttpError("research candidate is not human_accepted", 409); - let decisionPayload = {}; - try { decisionPayload = JSON.parse(decisionRow.payload_json); } catch { throw new HttpError("research candidate is not human_accepted", 409); } - const selected = decisionPayload.selected_account || {}; - if (selected.platform !== command.platform_id || selected.key !== command.account_key) { - throw new HttpError("promotion_account_binding_conflict", 409); - } - const instances = this.read(); - if (!instances.initialized || instances.revision !== Number(command.expected_revision) || !Number.isSafeInteger(decisionPayload.account_revision) || decisionPayload.account_revision !== instances.revision) { - throw new HttpError("runtime_instance_revision_conflict", 409); - } - const existing = this.sql.exec( - "SELECT * FROM research_promotion_applications WHERE ticket_id = ? AND platform_id = ? AND account_key = ? AND status <> 'rejected' ORDER BY created_at DESC LIMIT 1", - command.ticket_id, command.platform_id, command.account_key, - ).toArray()[0]; - if (existing) return { ok: true, created: false, application: researchPromotionApplicationFromRow(existing) }; - const application = { - application_id: command.application_id, - ticket_id: command.ticket_id, - status: "approved", - dispatch_state: "pending", - claim_token: command.claim_token, - platform_id: command.platform_id, - account_key: command.account_key, - account_scope: command.account_scope, - account_selector: command.account_selector, - service_name: command.service_name, - broker_environment: command.broker_environment, - expected_revision: command.expected_revision, - expected_strategy_profile: command.expected_strategy_profile, - strategy_profile: command.strategy_profile, - candidate_id: command.candidate_id, - config_sha256: command.config_sha256, - source_commit: command.source_commit, - approved_ues_revision: command.approved_ues_revision, - desired_state: "paused", - workflow_run_id: null, - workflow_run_attempt: null, - reason_code: "", - readback: null, - created_at: command.created_at, - updated_at: command.created_at, - }; - this.sql.exec( - `INSERT INTO research_promotion_applications - (application_id, ticket_id, status, dispatch_state, claim_token, platform_id, account_key, - account_scope, account_selector, service_name, broker_environment, expected_revision, - expected_strategy_profile, strategy_profile, candidate_id, config_sha256, source_commit, - approved_ues_revision, desired_state, workflow_run_id, workflow_run_attempt, reason_code, - readback_json, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, - application.application_id, application.ticket_id, application.status, application.dispatch_state, - application.claim_token, application.platform_id, application.account_key, application.account_scope, - application.account_selector, application.service_name, application.broker_environment, - application.expected_revision, application.expected_strategy_profile, application.strategy_profile, - application.candidate_id, application.config_sha256, application.source_commit, - application.approved_ues_revision, application.desired_state, null, null, application.reason_code, - null, application.created_at, application.updated_at, - ); - return { ok: true, created: true, application }; - } - if (command.action === "application_latest") { - const row = this.sql.exec( - "SELECT * FROM research_promotion_applications WHERE ticket_id = ? ORDER BY created_at DESC LIMIT 1", - command.ticket_id, - ).toArray()[0]; - return { ok: true, application: researchPromotionApplicationFromRow(row) }; - } - const applicationId = command.application_id; - const row = this.sql.exec("SELECT * FROM research_promotion_applications WHERE application_id = ?", applicationId).toArray()[0]; - if (!row) throw new HttpError("research_promotion_application_not_found", 404); - if (command.action === "application_read") return { ok: true, application: researchPromotionApplicationFromRow(row) }; - if (command.action === "application_mark_dispatch") { - if (row.status !== "approved" || row.dispatch_state !== "pending") return { ok: true, changed: false, application: researchPromotionApplicationFromRow(row) }; - this.sql.exec("UPDATE research_promotion_applications SET dispatch_state = ?, updated_at = ? WHERE application_id = ?", command.dispatch_state, command.updated_at, applicationId); - return { ok: true, changed: true, application: researchPromotionApplicationFromRow(this.sql.exec("SELECT * FROM research_promotion_applications WHERE application_id = ?", applicationId).toArray()[0]) }; - } - if (command.action === "application_claim") { - const state = this.read(); - const instance = state.instances.find((item) => item.kind === "existing" - && item.platform === row.platform_id && item.key === row.account_key); - const config = instance?.config || {}; - const bindingStillMatches = state.revision === Number(row.expected_revision) - && String(config.default_strategy_profile || "") === row.expected_strategy_profile - && String(config.broker_environment || "") === row.broker_environment - && String(config.account_selector || "") === row.account_selector - && String(config.service_name || "") === row.service_name; - const claimed = row.status === "approved" && ["pending", "sent", "unknown"].includes(row.dispatch_state) - && row.claim_token === command.claim_token && bindingStillMatches; - if (claimed) { - this.sql.exec( - "UPDATE research_promotion_applications SET status = 'claimed', workflow_run_id = ?, workflow_run_attempt = ?, updated_at = ? WHERE application_id = ? AND status = 'approved'", - command.workflow_run_id, command.workflow_run_attempt, command.updated_at, applicationId, - ); - } - return { ok: true, claimed, reason: claimed ? "" : (bindingStillMatches ? "already_claimed_or_invalid_token" : "account_changed") }; - } - if (command.action === "application_result") { - const sameRun = row.workflow_run_id === command.workflow_run_id - && row.workflow_run_attempt === command.workflow_run_attempt - && row.claim_token === command.claim_token; - if (["applied_paused", "rejected", "uncertain"].includes(row.status)) { - if (!sameRun) throw new HttpError("research_promotion_application_workflow_binding_conflict", 409); - return { ok: true, changed: false, application: researchPromotionApplicationFromRow(row) }; - } - if (row.status !== "claimed" || !sameRun) throw new HttpError("research_promotion_application_workflow_binding_conflict", 409); - let effectiveStatus = command.status; - let effectiveReasonCode = command.reason_code; - if (command.status === "applied_paused") { - const state = this.read(); - const instance = state.instances.find((item) => item.kind === "existing" - && item.platform === row.platform_id && item.key === row.account_key); - const config = instance?.config || {}; - const bindingStillMatches = state.revision === Number(row.expected_revision) - && String(config.default_strategy_profile || "") === row.expected_strategy_profile - && String(config.broker_environment || "") === row.broker_environment - && String(config.account_selector || "") === row.account_selector - && String(config.service_name || "") === row.service_name; - if (!bindingStillMatches) { - effectiveStatus = "uncertain"; - effectiveReasonCode = "account_changed"; - } else { - const before = structuredClone(instance); - instance.config = { ...instance.config, default_strategy_profile: row.strategy_profile }; - instance.enabled = false; - instance.platform_applied = true; - instance.application_status = "applied_paused"; - instance.updated_at = command.updated_at; - state.revision += 1; - this.sql.exec("INSERT INTO instance_state (id, revision, payload) VALUES (1, ?, ?) ON CONFLICT(id) DO UPDATE SET revision = excluded.revision, payload = excluded.payload", state.revision, JSON.stringify(state.instances)); - this.sql.exec("INSERT INTO instance_history (revision, entry) VALUES (?, ?)", state.revision, JSON.stringify({ - revision: state.revision, - ts: command.updated_at, - login: command.actor, - action: "research_promotion_application_applied_paused", - before, - after: instance, - })); - } - } - this.sql.exec( - `UPDATE research_promotion_applications - SET status = ?, reason_code = ?, readback_json = ?, updated_at = ? - WHERE application_id = ?`, - effectiveStatus, effectiveReasonCode, command.readback ? JSON.stringify(command.readback) : null, - command.updated_at, applicationId, - ); - return { ok: true, changed: true, application: researchPromotionApplicationFromRow(this.sql.exec("SELECT * FROM research_promotion_applications WHERE application_id = ?", applicationId).toArray()[0]) }; - } - throw new HttpError("unsupported_research_promotion_application_action", 400); - }); - } - - ux1ResearchCommand(command) { - return this.storage.transactionSync(() => { - if (["ux1_begin_job", "ux1_record_dispatch", "ux1_lookup_job", "ux1_claim_job", "ux1_finish_job"].includes(command?.action)) { - return ux1MutateJob(this.sql, command); - } - const login = String(command?.login || ""); - if (!/^[a-z0-9-]{1,39}$/.test(login)) throw new HttpError("ux1_login_invalid", 400); - const row = ux1SelectSlot(this.sql, login); - const found = ux1SlotPayload(row); - if (command.action === "ux1_read") return found; - if (command.action === "ux1_save") { - if (!Number.isSafeInteger(command.expected_revision) || command.expected_revision < 0) throw new HttpError("ux1_revision_required", 400); - if ((row ? Number(row.revision) : 0) !== command.expected_revision) throw new HttpError("ux1_revision_conflict", 409); - if (typeof command.draft_json !== "string" || typeof command.fingerprint !== "string" || command.draft_json.length > 16384) { - throw new HttpError("ux1_invalid_body", 400); - } - if (row && row.draft_json === command.draft_json && row.fingerprint === command.fingerprint) return { ...found, unchanged: true }; - const revision = (row ? Number(row.revision) : 0) + 1; - this.sql.exec( - `INSERT INTO ux1_research_slot (login, revision, draft_json, fingerprint, preview_json, intent_json) - VALUES (?, ?, ?, ?, NULL, NULL) - ON CONFLICT(login) DO UPDATE SET revision = excluded.revision, draft_json = excluded.draft_json, - fingerprint = excluded.fingerprint, preview_json = NULL, intent_json = NULL`, - login, revision, command.draft_json, command.fingerprint, - ); - return { ...ux1SlotPayload(ux1SelectSlot(this.sql, login)), unchanged: false }; - } - if (command.action === "ux1_store_preview") { - if (!row || Number(row.revision) !== command.expected_revision || row.fingerprint !== command.fingerprint) throw new HttpError("ux1_preview_conflict", 409); - if (typeof command.preview_json !== "string" || command.preview_json.length > 65536) throw new HttpError("ux1_invalid_body", 400); - this.sql.exec("UPDATE ux1_research_slot SET preview_json = ? WHERE login = ? AND revision = ?", command.preview_json, login, Number(row.revision)); - return { ...found, preview_json: command.preview_json }; - } - if (command.action === "ux1_save_intent") { - if (!row || Number(row.revision) !== command.expected_revision || row.fingerprint !== command.fingerprint || !row.preview_json) { - throw new HttpError("ux1_intent_conflict", 409); - } - if (row.intent_json) { - const existing = JSON.parse(row.intent_json); - if (existing.revision === Number(row.revision) && existing.fingerprint === row.fingerprint) return { ...found, duplicate: true }; - } - if (typeof command.intent_json !== "string" || command.intent_json.length > 4096) throw new HttpError("ux1_invalid_body", 400); - this.sql.exec("UPDATE ux1_research_slot SET intent_json = ? WHERE login = ? AND revision = ?", command.intent_json, login, Number(row.revision)); - return { ...found, intent_json: command.intent_json, duplicate: false }; - } - if (command.action === "ux1_clear_preview") { - if (!row) return found; - if (Number(row.revision) !== command.expected_revision || row.fingerprint !== command.fingerprint) return found; - this.sql.exec("UPDATE ux1_research_slot SET preview_json = NULL WHERE login = ? AND revision = ? AND fingerprint = ?", login, Number(row.revision), row.fingerprint); - return { ...found, preview_json: null }; - } - throw new HttpError("unsupported_ux1_action", 400); - }); - } -} - -async function startLogin(request, env) { - requireEnv(env, "GITHUB_CLIENT_ID"); - const url = new URL(request.url); - const state = randomToken(); - const authorizeUrl = new URL("https://github.com/login/oauth/authorize"); - authorizeUrl.searchParams.set("client_id", env.GITHUB_CLIENT_ID); - authorizeUrl.searchParams.set("redirect_uri", `${url.origin}/callback`); - authorizeUrl.searchParams.set("scope", "read:user read:org"); - authorizeUrl.searchParams.set("state", state); - return redirect(authorizeUrl.toString(), { - "Set-Cookie": cookie(OAUTH_STATE_COOKIE, state, 600), - }); -} - -async function finishLogin(request, env) { - requireEnv(env, "GITHUB_CLIENT_ID"); - requireEnv(env, "GITHUB_CLIENT_SECRET"); - requireEnv(env, "SESSION_SECRET"); - - const url = new URL(request.url); - const code = url.searchParams.get("code") || ""; - const state = url.searchParams.get("state") || ""; - const cookies = parseCookies(request.headers.get("Cookie") || ""); - if (!code || !state || cookies[OAUTH_STATE_COOKIE] !== state) { - return html(renderMessage("登录失败", "OAuth state 校验失败,请重新登录。"), 400, clearOAuthCookie()); - } - - const tokenStage = await runLoginGithubStage("token_exchange", () => fetchWithTimeout("https://github.com/login/oauth/access_token", { - method: "POST", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - }, - body: JSON.stringify({ - client_id: env.GITHUB_CLIENT_ID, - client_secret: env.GITHUB_CLIENT_SECRET, - code, - redirect_uri: `${url.origin}/callback`, - }), - }, GITHUB_LOGIN_REQUEST_TIMEOUT_MS)); - if (tokenStage.timedOut) return loginTimeoutResponse("token_exchange"); - const tokenResponse = tokenStage.value; - const tokenPayload = await tokenResponse.json(); - const tokenPayloadIsObject = tokenPayload !== null && typeof tokenPayload === "object"; - if (!tokenResponse.ok || !tokenPayloadIsObject || !tokenPayload.access_token) { - const status = Number.isSafeInteger(tokenResponse?.status) ? tokenResponse.status : null; - const allowedOauthErrors = new Set([ - "incorrect_client_credentials", - "redirect_uri_mismatch", - "bad_verification_code", - "unverified_user_email", - ]); - const errorCategory = tokenPayloadIsObject && typeof tokenPayload.error === "string" - && allowedOauthErrors.has(tokenPayload.error) - ? tokenPayload.error - : "unknown"; - console.warn("github_token_exchange_failed", { - http_status: status, - error_category: errorCategory, - }); - const statusLabel = status === null ? "不可用" : String(status); - return html( - renderMessage("登录失败", `GitHub 凭证交换未成功(HTTP ${statusLabel},类别:${errorCategory})。请从登录入口重新开始。`), - 502, - clearOAuthCookie(), - ); - } - - const userStage = await runLoginGithubStage("user_lookup", () => fetchWithTimeout("https://api.github.com/user", { - headers: githubHeaders(tokenPayload.access_token), - }, GITHUB_LOGIN_REQUEST_TIMEOUT_MS)); - if (userStage.timedOut) return loginTimeoutResponse("user_lookup"); - const userResponse = userStage.value; - const user = await userResponse.json(); - const login = String(user.login || "").toLowerCase(); - if (!userResponse.ok || !login) { - return html(renderMessage("登录失败", "无法读取 GitHub 用户。"), 502, clearOAuthCookie()); - } - - const authConfig = await loadAuthConfig(env); - const orgStage = await runLoginGithubStage("organization_lookup", () => fetchGithubOrgLogins( - tokenPayload.access_token, - GITHUB_LOGIN_REQUEST_TIMEOUT_MS, - )); - if (orgStage.timedOut) return loginTimeoutResponse("organization_lookup"); - const orgLogins = orgStage.value; - if (!isAllowedPrincipal(login, orgLogins, authConfig)) { - return html(renderMessage("没有权限", `${login} 不在允许登录名单或组织中。`), 403, clearOAuthCookie()); - } - - const session = await makeSession(login, authorizedOrgLogins(orgLogins, authConfig), env); - return redirect("/", { - "Set-Cookie": [ - cookie(SESSION_COOKIE, session, SESSION_TTL_SECONDS), - clearCookie(OAUTH_STATE_COOKIE), - ], - }); -} - -async function runLoginGithubStage(stage, operation) { - const startedAt = Date.now(); - try { - return { timedOut: false, value: await operation() }; - } catch (error) { - if (error?.message !== "GitHub request timed out") throw error; - console.warn("github_login_request_timed_out", { - stage, - timeout_ms: GITHUB_LOGIN_REQUEST_TIMEOUT_MS, - elapsed_ms: Math.max(0, Date.now() - startedAt), - }); - return { timedOut: true }; - } -} - -function loginTimeoutResponse(stage) { - const stageLabels = { - token_exchange: "登录凭证交换", - user_lookup: "读取用户身份", - organization_lookup: "核对组织权限", - }; - const stageLabel = stageLabels[stage] || "登录请求处理"; - return html( - renderMessage("登录超时", `${stageLabel}超时,请从登录入口重新开始。此回调链接已失效,请勿复用。`), - 504, - clearOAuthCookie(), - ); -} - -const deskAuth = createDeskAuth({ readSession, requireSameOrigin }); - -async function sessionPayload(request, env) { - const session = await readSession(request, env); - return { - authenticated: Boolean(session), - login: session?.login || null, - allowed: Boolean(session?.allowed), - admin: Boolean(session?.admin), - server_time: new Date().toISOString(), - }; -} - -async function adminConfigResponse(request, env) { - const session = await readSession(request, env); - if (!session) return json({ ok: false, error: "login required" }, 401); - if (!session.admin) return json({ ok: false, error: "admin required" }, 403); - return json(await buildAdminState(session, env)); -} - -async function saveAdminConfig(request, env) { - requireSameOrigin(request, { requireOrigin: true }); - const session = await readSession(request, env); - if (!session) 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 to save admin config" }, 400); - } - - let raw; - try { - raw = await request.json(); - } catch (error) { - return json({ ok: false, error: "request body must be valid JSON" }, 400); - } - const bootstrapAdmins = parseLoginList(env.STRATEGY_SWITCH_ADMIN_LOGINS || "", "STRATEGY_SWITCH_ADMIN_LOGINS"); - const bootstrapAdminOrgs = parseOrgList(env.STRATEGY_SWITCH_ADMIN_ORGS || "", "STRATEGY_SWITCH_ADMIN_ORGS"); - const allowedLogins = normalizeLoginList(raw.allowed_logins, "allowed_logins"); - const allowedOrgs = normalizeOrgList(raw.allowed_orgs, "allowed_orgs"); - const submittedAdmins = normalizeLoginList(raw.admin_logins, "admin_logins"); - const submittedAdminOrgs = normalizeOrgList(raw.admin_orgs, "admin_orgs"); - const effectiveAdmins = uniqueStrings([...bootstrapAdmins, ...submittedAdmins]); - const effectiveAdminOrgs = uniqueStrings([...bootstrapAdminOrgs, ...submittedAdminOrgs]); - if (!effectiveAdmins.includes(session.login) && !hasOrgMatch(session.orgs, effectiveAdminOrgs)) { - throw new Error("current admin login or org must remain in admin config"); - } - const authConfig = { - allowed_logins: uniqueStrings([...allowedLogins, ...effectiveAdmins]), - allowed_orgs: allowedOrgs, - admin_logins: effectiveAdmins, - admin_orgs: effectiveAdminOrgs, - }; - const accountOptions = normalizeAccountOptionsInput(raw.account_options, "account_options"); - - if (hasRuntimeInstanceStore(env)) { - const instances = await runtimeInstanceCommand(env); - if (!instances.initialized || canonicalResearchTaskJson(accountOptions) !== canonicalResearchTaskJson(normalizeAccountOptionsPayload(instances.account_options))) { - throw new HttpError("runtime_instances_managed_separately", 409); - } - } - await writeConfigJson(env, AUTH_CONFIG_KEY, authConfig); - if (!hasRuntimeInstanceStore(env)) await writeConfigJson(env, ACCOUNT_OPTIONS_KEY, validateAccountOptionsSchemaOrThrow(accountOptions)); - await appendAuditLog(env, { - ts: new Date().toISOString(), - login: session.login, - action: "save_config", - allowed_count: authConfig.allowed_logins.length, - allowed_org_count: authConfig.allowed_orgs.length, - admin_count: authConfig.admin_logins.length, - admin_org_count: authConfig.admin_orgs.length, - account_counts: accountCounts(accountOptions), - }); - return json(await buildAdminState(session, env)); -} - -async function riskProfileBindingsResponse(request, env) { - const session = await readSession(request, env); - if (!session) return json({ ok: false, error: "login required" }, 401); - if (!session.admin) return json({ ok: false, error: "admin required" }, 403); - const bindingState = await loadRiskProfileBindings(env); - if (bindingState.error) { - return json({ - ok: false, - error: "risk profile bindings are unavailable", - reason: bindingState.error, - no_order: true, - execution_authority_granted: false, - }, bindingState.status || 409); - } - return json({ - ok: true, - revision: bindingState.revision, - bindings: bindingState.bindings, - tier_bindings: bindingState.bindings.map((binding) => riskTierBindingView({ - scope_id: binding.scope_id, - platform: binding.platform, - target_name: binding.target_name, - binding, - revision: bindingState.revision, - })), - configured_targets: riskProfileBindingTargets(bindingState.account_options || {}), - no_order: true, - execution_authority_granted: false, - }); -} - -async function saveRiskProfileBindings(request, env) { - requireSameOrigin(request, { requireOrigin: true }); - const session = await readSession(request, env); - if (!session) return json({ ok: false, error: "login required" }, 401); - if (!session.admin) return json({ ok: false, error: "admin required" }, 403); - let raw; - try { - raw = await request.json(); - } catch { - return json({ ok: false, error: "request body must be valid JSON" }, 400); - } - if (!hasRuntimeInstanceStore(env)) { - try { - await buildRiskProfileBindings({ bindings: raw?.bindings }, (await loadLegacyAccountOptionsConfig(env)).options || {}, session.login); - } catch (error) { - return json({ ok: false, error: error.message || "risk profile bindings are invalid" }, 400); - } - return json({ ok: false, error: "runtime_instances_not_bound", no_order: true, execution_authority_granted: false }, 503); - } - const legacy = await readRiskProfileLegacy(env); - let observed; - try { - observed = await runtimeInstanceCommand(env, { action: "risk_profile_read", actor: session.login, legacy }); - } catch (error) { - const status = error instanceof HttpError ? error.status : 503; - return json({ ok: false, error: error instanceof HttpError ? error.message : "runtime_instances_unavailable", no_order: true, execution_authority_granted: false }, status); - } - if (!Number.isSafeInteger(raw?.expected_revision)) return json({ ok: false, error: "risk_profile_revision_required" }, 400); - if (raw.expected_revision !== observed.revision) return json({ ok: false, error: "risk_profile_revision_conflict" }, 409); - let bindings; - try { - bindings = await buildRiskProfileBindings({ bindings: raw.bindings }, observed.account_options, session.login); - } catch (error) { - return json({ ok: false, error: error.message || "risk profile bindings are invalid" }, 400); - } - let saved; - try { - saved = await runtimeInstanceCommand(env, { - action: "risk_profile_replace", - actor: session.login, - expected_revision: observed.revision, - bindings, - legacy, - }); - } catch (error) { - const status = error instanceof HttpError ? error.status : 503; - return json({ ok: false, error: error instanceof HttpError ? error.message : "runtime_instances_unavailable", no_order: true, execution_authority_granted: false }, status); - } - if (hasConfigStore(env)) { - await mirrorConfigJson(env, RISK_PROFILE_BINDINGS_KEY, { - schema_version: RISK_PROFILE_BINDING_REGISTRY_SCHEMA_VERSION, - bindings: saved.bindings, - }); - } - try { - await appendAuditLog(env, { - ts: new Date().toISOString(), - login: session.login, - action: "save_risk_profile_bindings", - binding_count: saved.bindings.length, - }); - } catch { /* audit failure does not undo the durable object decision */ } - return json({ - ok: true, - revision: saved.revision, - bindings: saved.bindings, - configured_targets: riskProfileBindingTargets(saved.account_options || {}), - no_order: true, - execution_authority_granted: false, - }); -} - -async function consoleRunControls(_request, env, account, session) { - return runControls(account, session, { - dispatchAvailable: Boolean(env.RUNTIME_SETTINGS_DISPATCH_TOKEN), - platformControl: ["ibkr", "schwab"].includes(account.platform) - ? () => readPlatformRuntimeControl(env, account.platform, account.target_name) : null, - stopReady: async () => { - if (account.platform === "longbridge" && account.target_name === "hk") { - if (account.key !== "hk") return false; - await loadHkStopAccount(env); - const { record } = await runtimeInstanceCommand(env, { action: "hk_stop_read" }); - return !record || !["reserved", "accepted", "unknown", "stopped"].includes(record.phase); - } - const config = await loadAccountOptionsConfig(env); - if (!account.target_name || (config.options?.[account.platform] || []).filter(a => a.target_name === account.target_name).length !== 1) return false; - const inputs = { ...account, platform: account.platform }; - const values = Object.fromEntries(["deployment_selector", "account_scope", "account_selector", "service_name"] - .map(field => [field, inputs[field] || defaultInputValue(field, inputs)])); - return Object.values(values).every(Boolean) && String(values.account_selector).split(",").some(value => value.trim()); - }, - resumeTarget: async () => { - const repository = platformRepositories(env).binance; - const config = await loadAccountOptionsConfig(env); - if (repository !== "QuantStrategyLab/BinancePlatform" || !account.target_name || - (config.options?.binance || []).filter(a => a.target_name === account.target_name).length !== 1 || - resolvedVariableScope(account.variable_scope, { ...account, platform: "binance" }) !== "repository") return null; - const variables = await fetchGithubVariables(env.RUNTIME_SETTINGS_DISPATCH_TOKEN, repository, "repository", ""); - return binanceResumeTarget(Object.fromEntries(variables), account); - }, - profileState: async () => { - if (!hasRuntimeInstanceStore(env)) return { reason: "runtime_instances_not_bound" }; - const observed = await runtimeInstanceCommand(env, { action: "account_settings_read", actor: session.login, platform: account.platform, key: account.key }); - const draft = observed.draft; - if (draft?.status === "identity_conflict") return { reason: "account_settings_identity_conflict" }; - const next = draft?.overrides?.strategy_profile; - if (draft?.status !== "current" || typeof next !== "string" || Object.keys(draft.overrides).length !== 1 || - !accountSettingsIdentityIsComplete(observed.identity) || - canonicalResearchTaskJson(draft.identity) !== canonicalResearchTaskJson(observed.identity)) return { reason: "profile_draft_required" }; - if (isDcaProfile(next)) return { reason: "profile_only_required" }; - const profiles = await loadAccountDraftProfiles(env); - if (!accountDraftStrategyChoices(account.platform, observed.config, profiles).some(p => p.profile === next && !p.dca_supported)) return { reason: "profile_not_selectable" }; - const readback = await accountSettingsProfileRuntimeReadback(env, observed); - if (!readback) return { reason: "runtime_readback_unavailable" }; - if (readback.runtime_target_enabled !== "false") return { reason: "runtime_target_not_disabled" }; - if (!readback.strategy_profile || readback.strategy_profile === next || isDcaProfile(readback.strategy_profile)) return { reason: "current_profile_conflict" }; - const history = await runtimeInstanceCommand(env, { action: "console_settings_history", actor: session.login, platform: account.platform, key: account.key }); - if (history.approvals.some(row => row.status === "claimed" || (row.status === "approved" && - row.identity_json === canonicalResearchTaskJson(observed.identity) && Number(row.instance_revision) === observed.instance_revision && - Number(row.draft_revision) === draft.revision && row.old_profile === readback.strategy_profile && row.new_profile === next))) - return { reason: "profile_application_pending" }; - return { ready: true }; - }, - }); -} - -function accountSettingsOperations(admin, optionSupported) { - const allowed = Boolean(admin); - return { - save_draft: allowed, - save_draft_reason: allowed ? "available" : "admin_required", - save_risk_preference: allowed, - save_risk_preference_reason: allowed ? "available" : "admin_required", - save_declaration: allowed, - save_declaration_reason: allowed ? "available" : "admin_required", - save_option_draft: allowed && optionSupported === true, - save_option_draft_reason: !allowed ? "admin_required" : optionSupported === true ? "available" : "option_overlay_not_defined", - apply_strategy: false, - activation: false, - reason: "strategy_application_not_connected", - apply_strategy_reason: "strategy_application_not_connected", - activation_reason: "strategy_application_not_connected", - }; -} - -function accountSettingsPayload(observed, effective, admin, extras = {}) { - const optionSupported = extras.pendingStrategy ? extras.pendingOptionSupported === true : extras.currentOptionSupported === true; - return { - ok: true, - platform: observed.platform, - key: observed.key, - instance_revision: observed.instance_revision, - identity: observed.identity, - effective, - effective_read: extras.effectiveRead, - draft: observed.draft, - declaration: observed.declaration, - strategy_options: Array.isArray(extras.strategyOptions) ? extras.strategyOptions : [], - current_option_supported: extras.currentOptionSupported === true, - risk: { - revision: observed.risk.revision, - scope_id: observed.risk.scope_id, - preference: observed.risk.preference, - }, - tier_binding: riskTierBindingView({ - scope_id: observed.risk.scope_id, - platform: observed.platform, - target_name: observed.identity?.target_name || null, - binding: observed.risk.binding || null, - revision: observed.risk.revision, - }), - operations: accountSettingsOperations(admin, optionSupported), - adopted: false, - no_order: true, - execution_authority_granted: false, - current_dca_supported: extras.currentDcaSupported === true, - }; -} - -async function accountSettingsResponse(request, env) { - const session = await readSession(request, env); - if (!session) return json({ ok: false, error: "login required" }, 401); - const url = new URL(request.url); - const platform = url.searchParams.get("platform") || ""; - const key = url.searchParams.get("key") || ""; - if (!SUPPORTED_PLATFORMS.includes(platform) || !key) return json({ ok: false, error: "account_settings_account_required" }, 400); - if (!hasRuntimeInstanceStore(env)) return json({ ok: false, error: "runtime_instances_not_bound", no_order: true, execution_authority_granted: false }, 503); - const legacy = await readRiskProfileLegacy(env); - let observed; - try { - observed = await runtimeInstanceCommand(env, { action: "account_settings_read", actor: session.login, platform, key, legacy }); - } catch (error) { - const status = error instanceof HttpError ? error.status : 503; - return json({ ok: false, error: error instanceof HttpError ? error.message : "runtime_instances_unavailable", no_order: true, execution_authority_granted: false }, status); - } - return json(await presentAccountSettings(env, observed, session.admin)); -} - -async function presentAccountSettings(env, observed, admin) { - const { effective, effectiveRead } = await effectiveAccountSettings(env, observed); - const profiles = await loadAccountDraftProfiles(env); - const strategyOptions = profiles ? accountDraftStrategyChoices(observed.platform, observed.config, profiles) : []; - const conflict = observed.draft?.status === "identity_conflict"; - const pending = !conflict && typeof observed.draft?.overrides?.strategy_profile === "string" ? observed.draft.overrides.strategy_profile : ""; - const trusted = effective.strategy_profile?.status === "known" && typeof effective.strategy_profile.value === "string" ? effective.strategy_profile.value : ""; - return accountSettingsPayload(observed, effective, admin, { - effectiveRead, - strategyOptions, - pendingStrategy: Boolean(pending), - pendingOptionSupported: Boolean(pending) && optionOverlayDraftAllowed(observed.platform, profiles, pending), - currentOptionSupported: Boolean(trusted) && optionOverlayDraftAllowed(observed.platform, profiles, trusted), - currentDcaSupported: dcaDraftSupported(observed.platform, profiles, trusted), - }); -} - -async function loadAccountDraftProfiles(env) { - try { - return await loadStrategyProfilesConfig(env); - } catch { - return null; - } -} - -function profileDefinesOptionOverlay(profiles, profileId) { - if (!Array.isArray(profiles) || typeof profileId !== "string" || !profileId) return false; - const found = profiles.find((item) => sameStrategyProfile(item?.profile, profileId)); - return found?.option_overlay_enabled === true; -} - -function optionOverlayDraftAllowed(platform, profiles, profileId) { - return PLATFORM_CONFIG[platform]?.option_overlay === true && profileDefinesOptionOverlay(profiles, profileId); -} - -function dcaDraftSupported(platform, profiles, profileId) { - if (!profileId || !Array.isArray(profiles)) return false; - if (!DCA_SUPPORTED_PLATFORMS.has(platform) || !isDcaProfile(profileId)) return false; - return profiles.some((item) => sameStrategyProfile(item?.profile, profileId)); -} - -function accountDraftStrategyChoices(platform, accountOption, profiles) { - if (!Array.isArray(profiles)) return []; - let domains = []; - try { - domains = supportedDomainsForAccount(platform, accountOption || {}); - } catch { - return []; - } - const choices = []; - const allowsDryRunOnly = accountOption?.default_execution_mode === "dry_run" - && supportedExecutionModesForPlatform(platform).includes("dry_run"); - for (const profile of profiles) { - if (!profile || typeof profile.profile !== "string" || !profile.profile) continue; - if (profile.domain_explicit !== true || profile.lifecycle_stage === "frozen" || isFrozenStrategyProfile(profile)) continue; - if (!profile.domain || !domains.includes(profile.domain)) continue; - const modes = Array.isArray(profile.allowed_execution_modes) ? profile.allowed_execution_modes : []; - if (!modes.some((mode) => mode === "paper" || mode === "live" - || (allowsDryRunOnly && mode === "dry_run"))) continue; - try { - assertDcaPlatform(platform, profile.profile); - } catch { - continue; - } - choices.push({ - profile: profile.profile, - label: profile.label || profile.profile, - label_zh: profile.label_zh || profile.label || profile.profile, - label_en: profile.label_en || profile.label || profile.profile, - domain: profile.domain, - // Existing catalog declarations only, never broker or execution permission. - lifecycle_stage: typeof profile.lifecycle_stage === "string" ? profile.lifecycle_stage : null, - allowed_execution_modes: Array.isArray(profile.allowed_execution_modes) && profile.allowed_execution_modes.length ? [...profile.allowed_execution_modes] : null, - can_switch_live: typeof profile.can_switch_live === "boolean" ? profile.can_switch_live : null, - blocked_live_reason: typeof profile.blocked_live_reason === "string" ? profile.blocked_live_reason : null, - option_overlay_enabled: profile.option_overlay_enabled === true, - dca_supported: dcaDraftSupported(platform, profiles, profile.profile), - }); - } - return choices; -} - -function assertAccountDraftPatch(profiles, observed, patch, trustedProfile, options = {}) { - if (!patch || Array.isArray(patch) || typeof patch !== "object") throw new HttpError("invalid_account_setting_overrides", 400); - const choices = profiles ? accountDraftStrategyChoices(observed.platform, observed.config, profiles) : []; - const rebuilding = observed.draft?.status === "identity_conflict" && options.acknowledgeIdentityConflict === true; - const stored = observed.draft?.overrides && typeof observed.draft.overrides === "object" && !Array.isArray(observed.draft.overrides) ? observed.draft.overrides : {}; - const current = rebuilding ? {} : stored; - const touchesStrategy = Object.prototype.hasOwnProperty.call(patch, "strategy_profile"); - const touchesOption = Object.prototype.hasOwnProperty.call(patch, "option_overlay_enabled"); - const touchesDcaMode = Object.prototype.hasOwnProperty.call(patch, "dca_mode"); - const touchesDcaBase = Object.prototype.hasOwnProperty.call(patch, "dca_base_investment_usd"); - if (touchesDcaMode !== touchesDcaBase) throw new HttpError("account_settings_dca_rejected", 400); - if (touchesDcaMode && (patch.dca_mode === null) !== (patch.dca_base_investment_usd === null)) { - throw new HttpError("account_settings_dca_rejected", 400); - } - if (touchesDcaMode && patch.dca_mode !== null && patch.dca_mode !== "fixed" && patch.dca_mode !== "smart") { - throw new HttpError("invalid_account_setting_overrides", 400); - } - if (touchesDcaBase && patch.dca_base_investment_usd !== null && !accountSettingPositiveAmount(patch.dca_base_investment_usd)) { - throw new HttpError("invalid_account_setting_overrides", 400); - } - if (!profiles && (touchesDcaMode || touchesDcaBase)) throw new HttpError("account_settings_dca_rejected", 400); - if (touchesStrategy && patch.strategy_profile !== null) { - const allowedChoice = choices.some((item) => sameStrategyProfile(item.profile, patch.strategy_profile)); - const currentEditable = sameStrategyProfile(patch.strategy_profile, trustedProfile) && dcaDraftSupported(observed.platform, profiles, patch.strategy_profile); - if (typeof patch.strategy_profile !== "string" || (!allowedChoice && !currentEditable)) { - throw new HttpError("account_settings_strategy_rejected", 400); - } - } - if (!touchesStrategy && !touchesOption && !touchesDcaMode && !touchesDcaBase) return; - const bound = touchesStrategy - ? patch.strategy_profile || trustedProfile || "" - : (typeof current.strategy_profile === "string" && current.strategy_profile ? current.strategy_profile : trustedProfile || ""); - const supports = optionOverlayDraftAllowed(observed.platform, profiles, bound); - if (touchesOption && patch.option_overlay_enabled !== null && (typeof patch.option_overlay_enabled !== "boolean" || !supports)) { - throw new HttpError("account_settings_option_rejected", 400); - } - const nextOption = touchesOption - ? patch.option_overlay_enabled - : (Object.prototype.hasOwnProperty.call(current, "option_overlay_enabled") ? current.option_overlay_enabled : undefined); - if ((nextOption === true || nextOption === false) && !supports) throw new HttpError("account_settings_option_strategy_conflict", 400); - // Other setting groups can be edited on a DCA account without rebinding its strategy. - if (!touchesStrategy && !touchesDcaMode && !touchesDcaBase) return; - const previous = typeof current.strategy_profile === "string" && current.strategy_profile ? current.strategy_profile : trustedProfile || ""; - const next = touchesStrategy - ? (patch.strategy_profile === null ? trustedProfile || "" : String(patch.strategy_profile || "")) - : previous; - const mergedMode = touchesDcaMode ? patch.dca_mode : (Object.prototype.hasOwnProperty.call(current, "dca_mode") ? current.dca_mode : null); - const mergedBase = touchesDcaBase ? patch.dca_base_investment_usd : (Object.prototype.hasOwnProperty.call(current, "dca_base_investment_usd") ? current.dca_base_investment_usd : null); - const hasMergedDca = (mergedMode !== null && mergedMode !== undefined) || (mergedBase !== null && mergedBase !== undefined); - if (!dcaDraftSupported(observed.platform, profiles, next)) { - if (hasMergedDca) throw new HttpError("account_settings_dca_rejected", 400); - if (!sameStrategyProfile(previous, next) && dcaDraftSupported(observed.platform, profiles, previous) - && (!touchesDcaMode || patch.dca_mode !== null || !touchesDcaBase || patch.dca_base_investment_usd !== null)) { - throw new HttpError("account_settings_dca_rejected", 400); - } - return; - } - if (touchesDcaMode || touchesDcaBase) { - const clearingDca = patch.dca_mode === null && patch.dca_base_investment_usd === null; - const boundToProfile = sameStrategyProfile(patch.strategy_profile, next); - const clearingCurrentBinding = patch.strategy_profile === null && clearingDca; - if (!touchesStrategy || (!boundToProfile && !clearingCurrentBinding)) { - throw new HttpError("account_settings_dca_rejected", 400); - } - } - if (!sameStrategyProfile(previous, next) || touchesDcaMode || touchesDcaBase) { - if (!touchesDcaMode || !touchesDcaBase) throw new HttpError("account_settings_dca_rejected", 400); - } -} - -async function saveCombinedAccountDraft(request, env) { - requireSameOrigin(request, { requireOrigin: true }); - const session = await readSession(request, env); - if (!session) return json({ ok: false, error: "login required" }, 401); - if (!session.admin) return json({ ok: false, error: "admin required" }, 403); - let raw; - try { raw = await readBoundedJson(request, 16 * 1024); } catch (error) { return json({ ok: false, error: "invalid_account_setting_overrides" }, error.status || 400); } - const allowed = ["platform", "key", "identity", "expected_draft_revision", "acknowledge_identity_conflict", "overrides"]; - if (!raw || Array.isArray(raw) || Object.keys(raw).some(key => !allowed.includes(key)) || !Object.hasOwn(raw, "overrides")) return json({ ok: false, error: "invalid_account_setting_overrides" }, 400); - return saveAccountSettings(new Request(request.url, { method: "POST", headers: request.headers, body: JSON.stringify(raw) }), env); -} - -async function saveAccountSettings(request, env) { - requireSameOrigin(request, { requireOrigin: true }); - const session = await readSession(request, env); - if (!session) return json({ ok: false, error: "login required" }, 401); - if (!session.admin) return json({ ok: false, error: "admin required" }, 403); - let raw; - try { raw = await request.json(); } catch { return json({ ok: false, error: "request body must be valid JSON" }, 400); } - const platform = raw?.platform; - const key = raw?.key; - if (!SUPPORTED_PLATFORMS.includes(platform) || typeof key !== "string" || !key) return json({ ok: false, error: "account_settings_account_required" }, 400); - if (!hasRuntimeInstanceStore(env)) return json({ ok: false, error: "runtime_instances_not_bound", no_order: true, execution_authority_granted: false }, 503); - const legacy = await readRiskProfileLegacy(env); - let observed; - try { - observed = await runtimeInstanceCommand(env, { action: "account_settings_read", actor: session.login, platform, key, legacy }); - } catch (error) { - const status = error instanceof HttpError ? error.status : 503; - return json({ ok: false, error: error instanceof HttpError ? error.message : "runtime_instances_unavailable", no_order: true, execution_authority_granted: false }, status); - } - const wantsDraft = Object.prototype.hasOwnProperty.call(raw, "overrides"); - const wantsRisk = Object.prototype.hasOwnProperty.call(raw, "risk_preference"); - const wantsDeclaration = Object.prototype.hasOwnProperty.call(raw, "declaration"); - if ((wantsDraft || wantsRisk || wantsDeclaration) && (!raw.identity || Array.isArray(raw.identity) || typeof raw.identity !== "object")) { - return json({ ok: false, error: "account_settings_identity_required" }, 400); - } - if ((wantsDraft || wantsRisk || wantsDeclaration) && canonicalResearchTaskJson(raw.identity) !== canonicalResearchTaskJson(observed.identity)) { - return json({ ok: false, error: "account_settings_identity_conflict" }, 409); - } - const command = { action: "account_settings_save", actor: session.login, platform, key, legacy }; - if (wantsDraft || wantsRisk || wantsDeclaration) command.identity = raw.identity; - if (wantsDeclaration) { - if (!Number.isSafeInteger(raw.expected_declaration_revision) || raw.expected_declaration_revision !== observed.declaration.revision) return json({ ok: false, error: "account_settings_declaration_revision_conflict" }, 409); - command.declaration = raw.declaration; - command.expected_declaration_revision = raw.expected_declaration_revision; - } - if (wantsDraft) { - if (!Number.isSafeInteger(raw.expected_draft_revision)) return json({ ok: false, error: "account_settings_draft_revision_required" }, 400); - if (raw.expected_draft_revision !== observed.draft.revision) return json({ ok: false, error: "account_settings_draft_revision_conflict" }, 409); - command.overrides = raw.overrides; - command.expected_draft_revision = observed.draft.revision; - if (raw.acknowledge_identity_conflict === true) command.acknowledge_identity_conflict = true; - const profiles = await loadAccountDraftProfiles(env); - const { effective: beforeEffective } = await effectiveAccountSettings(env, observed); - const trusted = beforeEffective.strategy_profile?.status === "known" && typeof beforeEffective.strategy_profile.value === "string" ? beforeEffective.strategy_profile.value : ""; - try { - assertAccountDraftPatch(profiles, observed, raw.overrides, trusted, { acknowledgeIdentityConflict: raw.acknowledge_identity_conflict === true }); - } catch (error) { - const status = error instanceof HttpError ? error.status : 400; - return json({ ok: false, error: error instanceof HttpError ? error.message : "invalid_account_setting_overrides", no_order: true, execution_authority_granted: false }, status); - } - } - if (wantsRisk) { - if (!Number.isSafeInteger(raw.expected_risk_revision)) return json({ ok: false, error: "risk_profile_revision_required" }, 400); - if (raw.expected_risk_revision !== observed.risk.revision) return json({ ok: false, error: "risk_profile_revision_conflict" }, 409); - command.expected_risk_revision = observed.risk.revision; - if (raw.risk_preference === null) command.risk_change = "clear"; - else { - let built; - try { - built = await buildRiskProfileBindings( - { bindings: [{ platform, target_name: observed.identity.target_name, risk_preference: raw.risk_preference }] }, - observed.account_options, - session.login, - ); - } catch (error) { - return json({ ok: false, error: error.message || "risk profile bindings are invalid" }, 400); - } - command.risk_change = "set"; - command.binding = built[0]; - } - } - if (!Object.prototype.hasOwnProperty.call(command, "overrides") && !Object.prototype.hasOwnProperty.call(command, "risk_change") && !Object.prototype.hasOwnProperty.call(command, "declaration")) { - return json({ ok: false, error: "account_settings_change_required" }, 400); - } - let saved; - try { - saved = await runtimeInstanceCommand(env, command); - } catch (error) { - const status = error instanceof HttpError ? error.status : 503; - return json({ ok: false, error: error instanceof HttpError ? error.message : "runtime_instances_unavailable", no_order: true, execution_authority_granted: false }, status); - } - if (command.risk_change && hasConfigStore(env)) { - await mirrorConfigJson(env, RISK_PROFILE_BINDINGS_KEY, { - schema_version: RISK_PROFILE_BINDING_REGISTRY_SCHEMA_VERSION, - bindings: saved.bindings, - }); - } - const changes = accountSettingsAuditChanges(observed, saved, command); - let auditLogged = false; - if (changes.length && hasConfigStore(env)) { - const entry = { - ts: new Date().toISOString(), - login: session.login, - action: "save_account_settings", - platform, - key, - changes, - }; - if (changes.some((kind) => kind === "cash_draft" || kind === "income_draft" || kind === "option_draft" || kind === "strategy_draft")) entry.draft_revision = saved.draft.revision; - if (changes.includes("risk_saved") || changes.includes("risk_cleared")) entry.risk_revision = saved.risk.revision; - if (changes.includes("account_declaration_saved")) entry.declaration_revision = saved.declaration.revision; - try { - await appendAuditLog(env, entry); - auditLogged = true; - } catch { /* the setting is already stored; a missed audit line must not be saved again */ } - } - const presented = await presentAccountSettings(env, saved, true); - presented.audit_logged = auditLogged; - return json(presented); -} - -async function approveAccountSettingsProfileApplication(request, env) { - requireSameOrigin(request, { requireOrigin: true }); - const session = await readSession(request, env); - if (!session) return json({ ok: false, error: "login required" }, 401); - if (!session.admin) return json({ ok: false, error: "admin required" }, 403); - let raw; - try { raw = await request.json(); } catch { return json({ ok: false, error: "request body must be valid JSON" }, 400); } - const allowedFields = new Set([ - "platform", "key", "request_key", "identity", "expected_instance_revision", - "expected_draft_revision", "old_profile", "new_profile", - ]); - if (!raw || Array.isArray(raw) || typeof raw !== "object" || Object.keys(raw).some((key) => !allowedFields.has(key))) { - return json({ ok: false, error: "account_settings_profile_application_request_invalid" }, 400); - } - if (!SUPPORTED_PLATFORMS.includes(raw.platform) || typeof raw.key !== "string" || !raw.key) { - return json({ ok: false, error: "account_settings_account_required" }, 400); - } - if (!ACCOUNT_SETTINGS_PROFILE_APPLICATION_REQUEST_KEY.test(String(raw.request_key || "")) - || !Number.isSafeInteger(raw.expected_instance_revision) || raw.expected_instance_revision < 0 - || !Number.isSafeInteger(raw.expected_draft_revision) || raw.expected_draft_revision < 0 - || !accountSettingsIdentityIsComplete(raw.identity)) { - return json({ ok: false, error: "account_settings_profile_application_request_invalid" }, 400); - } - const oldProfile = accountSettingsProfileName(raw.old_profile); - const newProfile = accountSettingsProfileName(raw.new_profile); - if (!oldProfile || !newProfile || oldProfile === newProfile) { - return json({ ok: false, error: "account_settings_profile_application_profile_invalid" }, 400); - } - if (!hasRuntimeInstanceStore(env)) return json({ ok: false, error: "runtime_instances_not_bound" }, 503); - - const command = { - action: "account_settings_profile_application_approve", - actor: session.login, - platform: raw.platform, - key: raw.key, - request_key: raw.request_key, - identity: raw.identity, - expected_instance_revision: raw.expected_instance_revision, - expected_draft_revision: raw.expected_draft_revision, - old_profile: oldProfile, - new_profile: newProfile, - }; - try { - const prior = await runtimeInstanceCommand(env, { - action: "account_settings_profile_application_lookup", - actor: session.login, - request_key: raw.request_key, - }); - if (prior.application) { - const replay = await runtimeInstanceCommand(env, command); - return json({ - ok: true, - replayed: true, - application: replay.application, - runtime_applied: false, - variables_updated: false, - no_order: true, - execution_authority_granted: false, - }); - } - - const observed = await runtimeInstanceCommand(env, { - action: "account_settings_read", actor: session.login, platform: raw.platform, key: raw.key, - }); - if (observed.instance_revision !== raw.expected_instance_revision - || canonicalResearchTaskJson(observed.identity) !== canonicalResearchTaskJson(raw.identity)) { - return json({ ok: false, error: "account_settings_profile_application_identity_conflict" }, 409); - } - const draft = observed.draft; - if (draft?.status !== "current" || draft.revision !== raw.expected_draft_revision - || canonicalResearchTaskJson(draft.identity) !== canonicalResearchTaskJson(observed.identity) - || Object.keys(draft.overrides || {}).length !== 1 - || draft.overrides?.strategy_profile !== newProfile) { - return json({ ok: false, error: "account_settings_profile_application_draft_conflict" }, 409); - } - if (isDcaProfile(oldProfile) || isDcaProfile(newProfile)) { - return json({ ok: false, error: "account_settings_profile_application_profile_only_required" }, 409); - } - const profiles = await loadAccountDraftProfiles(env); - const selectable = accountDraftStrategyChoices(raw.platform, observed.config, profiles); - if (!selectable.some((profile) => profile.profile === newProfile && profile.dca_supported !== true)) { - return json({ ok: false, error: "account_settings_profile_application_profile_not_selectable" }, 409); - } - const readback = await accountSettingsProfileRuntimeReadback(env, observed); - if (!readback) return json({ ok: false, error: "account_settings_runtime_readback_unavailable" }, 503); - if (readback.runtime_target_enabled !== "false") { - return json({ ok: false, error: "account_settings_runtime_target_not_disabled" }, 409); - } - if (readback.strategy_profile !== oldProfile) { - return json({ ok: false, error: "account_settings_profile_application_current_profile_conflict" }, 409); - } - command.current_profile = readback.strategy_profile; - command.runtime_target_enabled = false; - const approved = await runtimeInstanceCommand(env, command); - let auditLogged = false; - if (!approved.replayed && hasConfigStore(env)) { - try { - await appendAuditLog(env, { - ts: approved.application.approved_at, - login: session.login, - action: "approve_account_settings_profile_application", - platform: raw.platform, - key: raw.key, - request_key: raw.request_key, - instance_revision: raw.expected_instance_revision, - draft_revision: raw.expected_draft_revision, - old_profile: oldProfile, - new_profile: newProfile, - }); - auditLogged = true; - } catch { /* the durable approval is authoritative if its audit mirror is unavailable */ } - } - return json({ - ok: true, - replayed: approved.replayed === true, - application: approved.application, - audit_logged: auditLogged, - runtime_applied: false, - variables_updated: false, - no_order: true, - execution_authority_granted: false, - }); - } catch (error) { - const status = error instanceof HttpError ? error.status : 503; - return json({ ok: false, error: error instanceof HttpError ? error.message : "runtime_instances_unavailable", no_order: true, execution_authority_granted: false }, status); - } -} - -function decodeProfileApplicationJwtPart(value, maxBytes, parseJson = true) { - if (typeof value !== "string" || value.length === 0 || value.length > maxBytes * 2 - || !/^[A-Za-z0-9_-]+$/.test(value)) throw new HttpError("github_oidc_token_invalid", 401); - const padded = value.replaceAll("-", "+").replaceAll("_", "/") + "=".repeat((4 - value.length % 4) % 4); - let binary; - try { binary = atob(padded); } catch { throw new HttpError("github_oidc_token_invalid", 401); } - if (binary.length > maxBytes) throw new HttpError("github_oidc_token_invalid", 401); - const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0)); - if (!parseJson) return { bytes }; - try { return { bytes, value: JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes)) }; } - catch { throw new HttpError("github_oidc_token_invalid", 401); } -} - -async function readGitHubProfileApplicationJwks() { - const controller = new AbortController(); - let rejectAbort = () => {}; - const abortPromise = new Promise((_, reject) => { rejectAbort = reject; }); - abortPromise.catch(() => {}); - const onAbort = () => rejectAbort(new HttpError("github_oidc_jwks_unavailable", 503)); - controller.signal.addEventListener("abort", onAbort); - const timeout = setTimeout(() => controller.abort(), ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_TIMEOUT_MS); - let response; - try { - response = await Promise.race([ - fetch(ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_JWKS_URL, { - method: "GET", - headers: { Accept: "application/json" }, - redirect: "manual", - signal: controller.signal, - }), - abortPromise, - ]); - if (response.status !== 200 || response.redirected) throw new HttpError("github_oidc_jwks_unavailable", 503); - const contentLength = response.headers.get("Content-Length"); - if (contentLength && (!/^\d+$/.test(contentLength) || Number(contentLength) > ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_MAX_JWKS_BYTES)) { - throw new HttpError("github_oidc_jwks_unavailable", 503); - } - const contentType = response.headers.get("Content-Type") || ""; - if (!/^application\/json(?:\s*;|$)/i.test(contentType)) throw new HttpError("github_oidc_jwks_unavailable", 503); - const text = await readLimitedUtf8Body(response, ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_MAX_JWKS_BYTES, controller, abortPromise); - const jwks = JSON.parse(text); - if (!jwks || Array.isArray(jwks) || typeof jwks !== "object" || !Array.isArray(jwks.keys) || jwks.keys.length < 1 || jwks.keys.length > 16) { - throw new HttpError("github_oidc_jwks_unavailable", 503); - } - return jwks.keys; - } catch (error) { - if (error instanceof HttpError) throw error; - throw new HttpError("github_oidc_jwks_unavailable", 503); - } finally { - clearTimeout(timeout); - controller.signal.removeEventListener("abort", onAbort); - } -} - -async function verifyGitHubProfileApplicationToken(request, env) { - const workflowSha = env.ACCOUNT_SETTINGS_PROFILE_APPLICATION_WORKFLOW_SHA; - if (typeof workflowSha !== "string" || !/^[a-f0-9]{40}$/.test(workflowSha)) { - throw new HttpError("github_oidc_workflow_sha_not_configured", 503); - } - const authorization = request.headers.get("Authorization") || ""; - const match = /^Bearer ([A-Za-z0-9._~-]+)$/.exec(authorization); - if (!match || match[1].length > ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_MAX_TOKEN_BYTES) { - throw new HttpError("github_oidc_token_invalid", 401); - } - const parts = match[1].split("."); - if (parts.length !== 3) throw new HttpError("github_oidc_token_invalid", 401); - const header = decodeProfileApplicationJwtPart(parts[0], 2048).value; - const claims = decodeProfileApplicationJwtPart(parts[1], 4096).value; - const signature = decodeProfileApplicationJwtPart(parts[2], 1024, false).bytes; - if (!header || Array.isArray(header) || typeof header !== "object" - || header.alg !== "RS256" || header.typ !== "JWT" - || typeof header.kid !== "string" || !/^[A-Za-z0-9._-]{1,128}$/.test(header.kid) - || Object.hasOwn(header, "jku") || Object.hasOwn(header, "x5u") || Object.hasOwn(header, "crit")) { - throw new HttpError("github_oidc_token_invalid", 401); - } - if (!claims || Array.isArray(claims) || typeof claims !== "object" - || claims.iss !== "https://token.actions.githubusercontent.com" - || claims.aud !== ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_AUDIENCE - || claims.repository !== ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_REPOSITORY - || claims.ref !== ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_REF - || claims.workflow_ref !== ACCOUNT_SETTINGS_PROFILE_APPLICATION_OIDC_WORKFLOW_REF - || claims.workflow_sha !== workflowSha - || Object.hasOwn(claims, "job_workflow_ref") || Object.hasOwn(claims, "job_workflow_sha") - || claims.event_name !== "workflow_dispatch" - || typeof claims.run_id !== "string" || !/^[1-9]\d{0,19}$/.test(claims.run_id) - || typeof claims.run_attempt !== "string" || !/^[1-9]\d{0,5}$/.test(claims.run_attempt)) { - throw new HttpError("github_oidc_token_invalid", 401); - } - const now = Math.floor(Date.now() / 1000); - if (![claims.iat, claims.nbf, claims.exp].every(Number.isSafeInteger) - || claims.iat > now + 30 || claims.iat < now - 600 - || claims.nbf > now + 30 || claims.nbf > claims.exp - || claims.exp <= now || claims.exp <= claims.iat || claims.exp - claims.iat > 600) { - throw new HttpError("github_oidc_token_invalid", 401); - } - const keys = await readGitHubProfileApplicationJwks(); - const matches = keys.filter((key) => key?.kid === header.kid); - if (matches.length !== 1) throw new HttpError("github_oidc_token_invalid", 401); - const jwk = matches[0]; - if (jwk.kty !== "RSA" || jwk.use !== "sig" || (jwk.alg && jwk.alg !== "RS256") - || (jwk.key_ops !== undefined && (!Array.isArray(jwk.key_ops) || !jwk.key_ops.includes("verify"))) - || typeof jwk.n !== "string" || typeof jwk.e !== "string") { - throw new HttpError("github_oidc_token_invalid", 401); - } - let valid = false; - try { - const publicKey = await crypto.subtle.importKey( - "jwk", jwk, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, false, ["verify"], - ); - valid = await crypto.subtle.verify( - "RSASSA-PKCS1-v1_5", publicKey, signature, new TextEncoder().encode(`${parts[0]}.${parts[1]}`), - ); - } catch { throw new HttpError("github_oidc_token_invalid", 401); } - if (!valid) throw new HttpError("github_oidc_token_invalid", 401); - return { runId: claims.run_id, runAttempt: claims.run_attempt }; -} - -function accountSettingsProfileApplicationIsFixedHk(application) { - const identity = application?.identity; - return application?.platform === "longbridge" && application?.key === "hk" - && accountSettingsIdentityIsComplete(identity) - && identity.platform === "longbridge" && identity.key === "hk"; -} - -async function claimAccountSettingsProfileApplicationFromGitHub(request, env) { - if (new URL(request.url).search || !hasRuntimeInstanceStore(env)) { - return json({ ok: false, error: "account_settings_profile_application_unavailable", no_order: true, execution_authority_granted: false }, 503); - } - try { - const run = await verifyGitHubProfileApplicationToken(request, env); - const raw = await readGitHubProfileApplicationRequestBody(request); - if (!ux1ExactKeys(raw, ["request_key"]) || !ACCOUNT_SETTINGS_PROFILE_APPLICATION_REQUEST_KEY.test(String(raw.request_key || ""))) { - return json({ ok: false, error: "account_settings_profile_application_request_invalid", no_order: true, execution_authority_granted: false }, 400); - } - const actor = "github-oidc-profile-application"; - const lookup = await runtimeInstanceCommand(env, { - action: "account_settings_profile_application_lookup", actor, request_key: raw.request_key, - }); - const application = lookup.application; - if (!application) return json({ ok: false, error: "account_settings_profile_application_not_found", no_order: true, execution_authority_granted: false }, 404); - if (!accountSettingsProfileApplicationIsFixedHk(application)) { - return json({ ok: false, error: "account_settings_profile_application_target_conflict", no_order: true, execution_authority_granted: false }, 409); - } - const command = { - action: "account_settings_profile_application_claim", - actor, - request_key: application.request_key, - identity: application.identity, - expected_instance_revision: application.instance_revision, - expected_draft_revision: application.draft_revision, - old_profile: application.old_profile, - new_profile: application.new_profile, - current_profile: application.old_profile, - runtime_target_enabled: false, - workflow_run_id: run.runId, - workflow_run_attempt: run.runAttempt, - }; - if (application.status === "claimed") { - const replay = await runtimeInstanceCommand(env, command); - return json({ ...replay, ok: true }); - } - if (application.status !== "approved") { - return json({ ok: false, error: "account_settings_profile_application_conflict", no_order: true, execution_authority_granted: false }, 409); - } - const observed = await runtimeInstanceCommand(env, { - action: "account_settings_read", actor, platform: application.platform, key: application.key, - }); - if (observed.instance_revision !== application.instance_revision - || canonicalResearchTaskJson(observed.identity) !== canonicalResearchTaskJson(application.identity)) { - return json({ ok: false, error: "account_settings_profile_application_identity_conflict", no_order: true, execution_authority_granted: false }, 409); - } - const draft = observed.draft; - if (draft?.status !== "current" || draft.revision !== application.draft_revision - || canonicalResearchTaskJson(draft.identity) !== canonicalResearchTaskJson(application.identity) - || Object.keys(draft.overrides || {}).length !== 1 - || draft.overrides?.strategy_profile !== application.new_profile) { - return json({ ok: false, error: "account_settings_profile_application_draft_conflict", no_order: true, execution_authority_granted: false }, 409); - } - const variableScope = resolveVariableScope(application.platform, observed.config); - const githubEnvironment = resolveGithubEnvironment(application.platform, observed.config, variableScope); - const repository = platformRepositories(env)?.[application.platform]; - if (repository !== "QuantStrategyLab/LongBridgePlatform" - || variableScope !== "environment" - || githubEnvironment !== "longbridge-hk") { - return json({ ok: false, error: "account_settings_profile_application_target_conflict", no_order: true, execution_authority_granted: false }, 409); - } - const readback = await accountSettingsProfileRuntimeReadback(env, observed, { - repository, variableScope, githubEnvironment, - }); - if (!readback) return json({ ok: false, error: "account_settings_runtime_readback_unavailable", no_order: true, execution_authority_granted: false }, 503); - if (readback.runtime_target_enabled !== "false" || readback.strategy_profile !== application.old_profile) { - return json({ ok: false, error: "account_settings_profile_application_runtime_conflict", no_order: true, execution_authority_granted: false }, 409); - } - const claimed = await runtimeInstanceCommand(env, command); - return json({ ...claimed, ok: true }); - } catch (error) { - const status = error instanceof HttpError ? error.status : 503; - return json({ ok: false, error: error instanceof HttpError ? error.message : "account_settings_profile_application_unavailable", no_order: true, execution_authority_granted: false }, status); - } -} - -async function readGitHubProfileApplicationRequestBody(request) { - const contentLength = request.headers.get("Content-Length"); - if (contentLength && (!/^\d+$/.test(contentLength) || Number(contentLength) > ACCOUNT_SETTINGS_PROFILE_APPLICATION_MAX_BODY_BYTES)) { - throw new HttpError("account_settings_profile_application_body_too_large", 413); - } - const controller = new AbortController(); - let rejectAbort = () => {}; - const abortPromise = new Promise((_, reject) => { rejectAbort = reject; }); - abortPromise.catch(() => {}); - const onAbort = () => rejectAbort(new HttpError("account_settings_profile_application_body_timeout", 408)); - controller.signal.addEventListener("abort", onAbort); - const timeout = setTimeout(() => controller.abort(), ACCOUNT_SETTINGS_PROFILE_APPLICATION_REQUEST_TIMEOUT_MS); - try { - const text = await readLimitedUtf8Body(request, ACCOUNT_SETTINGS_PROFILE_APPLICATION_MAX_BODY_BYTES, controller, abortPromise); - try { return JSON.parse(text); } catch { throw new HttpError("request body must be valid JSON", 400); } - } catch (error) { - if (error instanceof HttpError) throw error; - if (String(error?.message || "") === "ux1_calculator_body_limit") { - throw new HttpError("account_settings_profile_application_body_too_large", 413); - } - throw new HttpError("account_settings_profile_application_body_invalid", 400); - } finally { - clearTimeout(timeout); - controller.signal.removeEventListener("abort", onAbort); - } -} - -function accountSettingsAuditChanges(before, after, requested) { - const changes = []; - const left = before?.draft?.overrides && typeof before.draft.overrides === "object" && !Array.isArray(before.draft.overrides) ? before.draft.overrides : {}; - const right = after?.draft?.overrides && typeof after.draft.overrides === "object" && !Array.isArray(after.draft.overrides) ? after.draft.overrides : {}; - const patch = requested?.overrides && typeof requested.overrides === "object" && !Array.isArray(requested.overrides) ? requested.overrides : {}; - const stored = (record, name) => Object.prototype.hasOwnProperty.call(record, name) ? record[name] : undefined; - const asked = (name) => Object.prototype.hasOwnProperty.call(patch, name); - if (["reserved_cash_floor", "reserved_cash_ratio"].some(asked) && ["reserved_cash_floor", "reserved_cash_ratio"].some((name) => stored(left, name) !== stored(right, name))) changes.push("cash_draft"); - if (asked("income_layer_enabled") && stored(left, "income_layer_enabled") !== stored(right, "income_layer_enabled")) changes.push("income_draft"); - if (asked("option_overlay_enabled") && stored(left, "option_overlay_enabled") !== stored(right, "option_overlay_enabled")) changes.push("option_draft"); - const strategyDraftChanged = (asked("strategy_profile") && stored(left, "strategy_profile") !== stored(right, "strategy_profile")) - || ((asked("dca_mode") || asked("dca_base_investment_usd")) && ["dca_mode", "dca_base_investment_usd"].some((name) => stored(left, name) !== stored(right, name))); - if (strategyDraftChanged) changes.push("strategy_draft"); - if (requested?.declaration && before?.declaration?.revision !== after?.declaration?.revision) changes.push("account_declaration_saved"); - const beforePreference = before?.risk?.preference || null; - const afterPreference = after?.risk?.preference || null; - if ((requested?.risk_change === "set" || requested?.risk_change === "clear") && beforePreference !== afterPreference) changes.push(afterPreference ? "risk_saved" : "risk_cleared"); - return changes; -} - -async function buildAdminState(session, env) { - const authConfig = await loadAuthConfig(env); - const accountConfig = await loadAccountOptionsConfig(env); - const riskProfileBindingState = await loadRiskProfileBindings(env); - const runtimeInstances = hasRuntimeInstanceStore(env) ? await runtimeInstanceCommand(env) : null; - let runtimeInstanceStrategies = []; - if (runtimeInstances) { - try { - runtimeInstanceStrategies = (await loadStrategyProfilesConfig(env)).filter((item) => !isFrozenStrategyProfile(item) && (item.allowed_execution_modes || []).includes("dry_run")).map((item) => ({ profile: item.profile, label: item.label, domain: item.domain })); - } catch { - // Catalog failure affects draft authoring, never existing instance - // readback, retirement requests, or `/api/admin/config` readback. - } - } - return { - ok: true, - session: { login: session.login, admin: true }, - kvAvailable: hasConfigStore(env), - authConfig, - accountOptions: accountConfig.options || {}, - accountOptionSource: accountConfig.source, - riskProfileBindings: riskProfileBindingState.bindings, - riskProfileRevision: riskProfileBindingState.revision, - riskProfileBindingsError: riskProfileBindingState.error, - riskProfileBindingTargets: riskProfileBindingTargets(riskProfileBindingState.account_options || accountConfig.options || {}), - auditLog: await loadAuditLog(env), - runtimeInstances, - runtimeInstanceStrategies, - runtimeInstanceStrategiesAvailable: runtimeInstanceStrategies.length > 0, - }; -} - -async function loadPlatformMeta(env = {}) { - // Use the same bundled configuration as routing and capability validation. - // Fetching GitHub main here could mix a newer catalog with an older Worker. - const merged = Object.fromEntries(Object.entries(PLATFORM_META) - .map(([platform, meta]) => [platform, { ...meta }])); - const hidden = new Set(String(env.STRATEGY_SWITCH_HIDDEN_PLATFORMS || "") - .split(",").map((value) => value.trim().toLowerCase()).filter(Boolean)); - for (const [platform, meta] of Object.entries(merged)) { - meta.console_visible = !hidden.has(platform); - } - return merged; -} - -// In-memory cache for the lifetime of this Worker isolate. -let _memCurrentStrategies = null; -let _memCurrentStrategiesTs = 0; -let _memRefreshing = false; // prevent concurrent background refreshes - -// KV free tier caps writes at 1,000/day account-wide and the */5 cron plus every -// background refresh reaches this helper, so a write is paid for only when the -// rendered content changed or the stored entry needs its ts bumped to stay inside -// the fresh window that cold isolates serve from. -// current_strategies_cache carries per-account execution_evidence.observed_at -// stamped with new Date() (see accountExecutionEvidence callers), so whole-payload -// byte equality can never hold; the comparison drops those stamps and keeps the -// stored value untouched for readers. - -// readConfigJson reports a damaged value as `STRATEGY_SWITCH_CONFIG. must be -// valid JSON`. Only that shape is a cache miss; a KV read that failed for any other -// reason is an outage and has to reach the caller instead of being mistaken for an -// empty cache — the difference is what keeps cold isolates from fanning out to -// GitHub on every request while the store is unhealthy. -function isConfigJsonParseError(error) { - return /^STRATEGY_SWITCH_CONFIG\.[^\s]+ must be valid JSON$/.test(String(error?.message || "")); -} - -async function readCurrentStrategiesCacheEntry(env) { - try { - return await readConfigJson(env, CURRENT_STRATEGIES_CACHE_KEY); - } catch (error) { - if (!isConfigJsonParseError(error)) throw error; - return null; - } -} - -async function writeCurrentStrategiesCache(env, payload, ts) { - if (env.STRATEGY_SWITCH_RUNTIME_INSTANCES) { - try { - await directoryCommand(env, { action: "cache", payload, ts }); - return; - } catch (error) { - // Receiver-first compatibility only; a real storage failure remains loud. - if (error.message !== "kv_directory_legacy_receiver" && error.message !== "kv_directory_invalid_request") throw error; - } - } - const signature = currentStrategiesSignature(payload); - // A damaged stored value must not wedge the cache; this is the only path that can - // overwrite it, so the comparison reads through the same repair-aware helper. - const cached = await readCurrentStrategiesCacheEntry(env); - if (cached?.data && currentStrategiesSignature(cached.data) === signature - && cached.ts && ts - cached.ts < CURRENT_STRATEGIES_KV_BUMP_MS) { - return; - } - await writeConfigJson(env, CURRENT_STRATEGIES_CACHE_KEY, { ts, data: payload }); -} - -// loadCurrentStrategiesSafely answers with evidence-only account entries whenever -// GitHub timed out or threw, so a payload whose every account is exactly that -// shape carries no strategy information and must not be served as fresh. -function isDegradedCurrentStrategies(payload) { - const accounts = Object.values(payload || {}).flatMap(options => Object.values(options || {})); - if (!accounts.length) return false; - return accounts.every(account => account && typeof account === "object" - && Object.keys(account).length === 1 - && account.execution_evidence?.status === "read_failed"); -} - -async function configPayload(request, env, ctx) { - const session = await readSession(request, env); - const meta = await loadPlatformMeta(env); - if (!session?.allowed) return { accountOptions: null, platformMeta: meta }; - const accountConfig = await loadAccountOptionsConfig(env); - const strategyProfiles = await loadStrategyProfilesConfig(env); - - const currentStrategies = await readCurrentStrategies(accountConfig.options, env, ctx); - - return { - accountOptions: accountConfig.options, - accountOptionsRevision: accountConfig.revision ?? null, - runtimeDailyBindings: runtimeDailyReadModelEnabled(env) ? await runtimeDailyPublicBindings(env, accountConfig.options) : [], - platformRepositories: platformRepositories(env), - platformMeta: meta, - strategyProfiles, - currentStrategies, - }; -} - -async function readCurrentStrategies(accountOptions, env, ctx) { - let currentStrategies = null; - let cacheFresh = false; - - // 1) In-memory cache - if (_memCurrentStrategies) { - const age = Date.now() - _memCurrentStrategiesTs; - if (age < CURRENT_STRATEGIES_MEMO_TTL_MS) { - currentStrategies = _memCurrentStrategies; - cacheFresh = true; - } else if (age < CURRENT_STRATEGIES_STALE_TTL_MS) { - currentStrategies = _memCurrentStrategies; - // stale — trigger background refresh below - } - } - - // 2) KV cache - if (!currentStrategies && hasConfigStore(env)) { - const cached = await readCurrentStrategiesCacheEntry(env); - if (cached?.ts && cached.data) { - const age = Date.now() - cached.ts; - // A degraded entry stays inside the freshness window but is never *fresh*: - // the next request must revalidate instead of serving a read_failed payload - // for the whole 30 minutes. - if (age < CURRENT_STRATEGIES_KV_FRESH_MS && !isDegradedCurrentStrategies(cached.data)) { - currentStrategies = cached.data; - cacheFresh = true; - } else if (age < CURRENT_STRATEGIES_STALE_TTL_MS) { - currentStrategies = cached.data; - // stale — trigger background refresh below - } - if (currentStrategies && !_memCurrentStrategies) { - _memCurrentStrategies = currentStrategies; - _memCurrentStrategiesTs = cached.ts; - } - } - } - - // 3) Background refresh when stale (return old data immediately) - if (currentStrategies && !cacheFresh && !_memRefreshing && hasConfigStore(env) && ctx) { - _memRefreshing = true; - ctx.waitUntil((async () => { - try { - const fresh = await loadCurrentStrategiesSafely(accountOptions, env); - _memCurrentStrategies = fresh; - _memCurrentStrategiesTs = Date.now(); - await writeCurrentStrategiesCache(env, fresh, _memCurrentStrategiesTs); - } catch { /* keep stale data */ } - finally { _memRefreshing = false; } - })()); - } - - // 4) Complete miss — must wait for GitHub - if (!currentStrategies) { - currentStrategies = await loadCurrentStrategiesSafely(accountOptions, env); - _memCurrentStrategies = currentStrategies; - _memCurrentStrategiesTs = Date.now(); - if (hasConfigStore(env) && ctx) { - ctx.waitUntil(writeCurrentStrategiesCache(env, currentStrategies, _memCurrentStrategiesTs)); - } - } - - return currentStrategies; -} - -async function strategyProfilesPayload(env) { - return { - strategyProfiles: await loadStrategyProfilesConfig(env), - platformMeta: await loadPlatformMeta(env), - }; -} - -async function runtimeCatalogResponse(request, env) { - const session = await readSession(request, env); - if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); - // This is a generated gate catalog, not a deployment observation. Keeping - // the distinction in the response makes it unsuitable as an accidental - // substitute for the independently refreshed control-plane/evidence views. - return json(RUNTIME_CATALOG_PROJECTION); -} - -async function loadCurrentStrategies(accountOptions, env) { - const token = env.RUNTIME_SETTINGS_DISPATCH_TOKEN; - if (!token || !accountOptions) return executionEvidenceFallback(accountOptions, "not_connected"); - const repositories = platformRepositories(env); - - const variableCache = new Map(); - const readVariable = async (repository, scope, githubEnvironment, name) => { - const cacheKey = [repository, scope, githubEnvironment || ""].join("|"); - if (!variableCache.has(cacheKey)) { - variableCache.set(cacheKey, (async () => { - let status = "ok"; - const values = await fetchGithubVariables(token, repository, scope, githubEnvironment, { - reportReadErrors: true, onReadFailure: reason => { status = reason; }, - }); - return { values, status }; - })()); - } - const result = await variableCache.get(cacheKey); - return result.values?.get(name) || ""; - }; - - readVariable.readStatus = async (repository, scope, githubEnvironment) => - (await variableCache.get([repository, scope, githubEnvironment || ""].join("|")))?.status || "ok"; - const currentStrategies = {}; - // Process platforms sequentially: each platform's account list is also - // processed one at a time (not Promise.all). This keeps GitHub API - // concurrency low enough to avoid secondary rate limiting. - for (const platform of SUPPORTED_PLATFORMS) { - const platformStrategies = await loadStrategiesForPlatform(platform, accountOptions, repositories, readVariable); - if (Object.keys(platformStrategies).length) currentStrategies[platform] = platformStrategies; - // 100ms gap between platforms to respect GitHub secondary rate limit - await new Promise((r) => setTimeout(r, 100)); - } - return currentStrategies; -} - -async function loadStrategiesForPlatform(platform, accountOptions, repositories, readVariable) { - const options = Array.isArray(accountOptions[platform]) ? accountOptions[platform] : []; - if (!options.length) return {}; - const repository = repositories[platform]; - if (!repository) return executionEvidenceFallback({ [platform]: options }, "not_connected")[platform]; - - // Process accounts sequentially within each platform to stay within - // GitHub's concurrent request budget per token (~30 burst limit). - const platformStrategies = {}; - for (const option of options) { - let status = "ok"; - const trackedRead = async (...args) => { - const value = await readVariable(...args); - const result = await readVariable.readStatus?.(...args.slice(0, 3)); - if (result && result !== "ok") status = result; - return value; - }; - const current = await resolveCurrentStrategyForAccount({ - platform, - option, - optionsCount: options.length, - repository, - readVariable: trackedRead, - }); - platformStrategies[option.key] = { ...current, - execution_evidence: accountExecutionEvidence(option, current || {}, { status, observedAt: new Date().toISOString() }) }; - } - return platformStrategies; -} - -function executionEvidenceFallback(accountOptions, status) { - return Object.fromEntries(Object.entries(accountOptions || {}).map(([platform, options]) => [platform, - Object.fromEntries(options.map(option => [option.key, { - execution_evidence: accountExecutionEvidence(option, {}, { status, observedAt: new Date().toISOString() }), - }]))])); -} - -async function loadCurrentStrategiesSafely(accountOptions, env) { - try { - return await withTimeout( - loadCurrentStrategies(accountOptions, env), - CURRENT_STRATEGIES_TIMEOUT_MS, - executionEvidenceFallback(accountOptions, "read_failed"), - ); - } catch { - return executionEvidenceFallback(accountOptions, "read_failed"); - } -} - -function withTimeout(promise, timeoutMs, fallback) { - let timeoutId; - const timeout = new Promise((resolve) => { - timeoutId = setTimeout(() => resolve(fallback), timeoutMs); - }); - return Promise.race([promise, timeout]).finally(() => clearTimeout(timeoutId)); -} - -async function fetchWithTimeout(resource, init = {}, timeoutMs = GITHUB_API_TIMEOUT_MS, fetchImpl = fetch) { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), timeoutMs); - try { - return await fetchImpl(resource, { ...init, signal: controller.signal }); - } catch (error) { - if (error?.name === "AbortError") throw new Error("GitHub request timed out"); - throw error; - } finally { - clearTimeout(timeoutId); - } -} - -async function resolveCurrentStrategyForAccount({ platform, option, optionsCount, repository, readVariable }) { - const serviceTargetsValue = usesServiceTargetsAsRuntimeSource(platform) - ? await readVariable(repository, "repository", "", "CLOUD_RUN_SERVICE_TARGETS_JSON") - : ""; - const serviceTarget = runtimeTargetFromServiceTargets(serviceTargetsValue, platform, option); - const serviceTargetProfile = cleanCurrentStrategy(serviceTarget?.strategy_profile); - const serviceTargetReservedCashPayload = reservedCashPayloadFromObject(platform, serviceTarget); - const serviceTargetIncomeLayerPayload = incomeLayerPayloadFromObject(serviceTarget); - const serviceTargetOptionOverlayPayload = optionOverlayPayloadFromObject(serviceTarget); - let serviceTargetRuntimeTargetEnabledPayload = runtimeTargetEnabledPayloadFromObject(serviceTarget); - if (serviceTarget && !Object.keys(serviceTargetRuntimeTargetEnabledPayload).length) { - const variableScope = resolveVariableScope(platform, option); - serviceTargetRuntimeTargetEnabledPayload = await readRuntimeTargetEnabledVariable({ - repository, variableScope, - githubEnvironment: resolveGithubEnvironment(platform, option, variableScope), readVariable, - }); - } - const serviceTargetDcaPayload = dcaPayloadFromObject(serviceTarget); - const serviceTargetCashOnlyPayload = cashOnlyPayloadFromObject(platform, serviceTarget); - if (serviceTargetProfile) { - return { - strategy_profile: serviceTargetProfile, - ...runtimeModePayload(serviceTarget), - strategy_execution: strategyTargetExecution(serviceTarget), - ...serviceTargetReservedCashPayload, - ...serviceTargetIncomeLayerPayload, - ...serviceTargetOptionOverlayPayload, - ...serviceTargetRuntimeTargetEnabledPayload, - ...serviceTargetCashOnlyPayload, - ...dcaPayloadForProfile(serviceTargetProfile, serviceTargetDcaPayload), - source: "CLOUD_RUN_SERVICE_TARGETS_JSON", - variable_scope: "repository", - }; - } - if ( - Object.keys(serviceTargetReservedCashPayload).length || - Object.keys(serviceTargetIncomeLayerPayload).length || - Object.keys(serviceTargetOptionOverlayPayload).length || - Object.keys(serviceTargetCashOnlyPayload).length || - Object.keys(serviceTargetRuntimeTargetEnabledPayload).length - ) { - return { - ...runtimeModePayload(serviceTarget), - strategy_execution: strategyTargetExecution(serviceTarget), - ...serviceTargetReservedCashPayload, - ...serviceTargetIncomeLayerPayload, - ...serviceTargetOptionOverlayPayload, - ...serviceTargetCashOnlyPayload, - ...serviceTargetRuntimeTargetEnabledPayload, - source: "CLOUD_RUN_SERVICE_TARGETS_JSON", - variable_scope: "repository", - }; - } - - const variableScope = resolveVariableScope(platform, option); - const githubEnvironment = resolveGithubEnvironment(platform, option, variableScope); - const reservedCashPayloadPromise = readReservedCashVariables({ - platform, - repository, - variableScope, - githubEnvironment, - readVariable, - }); - const incomeLayerPayloadPromise = readIncomeLayerVariables({ - repository, - variableScope, - githubEnvironment, - readVariable, - }); - const optionOverlayPayloadPromise = readOptionOverlayVariables({ - repository, - variableScope, - githubEnvironment, - readVariable, - }); - const runtimeTargetEnabledPayloadPromise = readRuntimeTargetEnabledVariable({ - repository, - variableScope, - githubEnvironment, - readVariable, - }); - const dcaPayloadPromise = readDcaVariables({ - repository, - variableScope, - githubEnvironment, - readVariable, - }); - const cashOnlyPayloadPromise = readCashOnlyVariables({ - platform, - repository, - variableScope, - githubEnvironment, - readVariable, - }); - // All fields share one scoped variable-list read; missing fields are not retried. - const runtimeTargetValue = await readVariable(repository, variableScope, githubEnvironment, "RUNTIME_TARGET_JSON"); - const [ - reservedCashPayload, - incomeLayerPayload, - optionOverlayPayload, - runtimeTargetEnabledPayload, - dcaPayload, - cashOnlyPayload, - ] = await Promise.all([ - reservedCashPayloadPromise, - incomeLayerPayloadPromise, - optionOverlayPayloadPromise, - runtimeTargetEnabledPayloadPromise, - dcaPayloadPromise, - cashOnlyPayloadPromise, - ]); - const runtimeTarget = parseJsonObject(runtimeTargetValue); - const runtimeTargetMatches = runtimeTarget && runtimeTargetMatchesAccount(runtimeTarget, platform, option); - const runtimeTargetProfile = runtimeTargetMatches ? cleanCurrentStrategy(runtimeTarget.strategy_profile) : ""; - if (runtimeTargetProfile) { - let resumeTarget = null; - if (platform === "binance" && repository === "QuantStrategyLab/BinancePlatform" && variableScope === "repository") { - const values = { RUNTIME_TARGET_JSON: runtimeTargetValue }; - for (const name of ["RUNTIME_TARGET_ENABLED", "BINANCE_RECOVERY_CONTROL_ENABLED", "BINANCE_DRY_RUN", "STRATEGY_PROFILE", "CLOUD_RUN_SERVICE_TARGETS_JSON"]) { - values[name] = await readVariable(repository, variableScope, githubEnvironment, name); - } - resumeTarget = await binanceResumeTarget(values, option); - } - return { - strategy_profile: runtimeTargetProfile, - ...(resumeTarget ? { binance_resume_target_sha256: resumeTarget.runtime_target_sha256 } : {}), - ...runtimeModePayload(runtimeTarget), - strategy_execution: strategyTargetExecution(runtimeTarget), - ...reservedCashPayload, - ...incomeLayerPayload, - ...optionOverlayPayload, - ...runtimeTargetEnabledPayload, - ...dcaPayloadForProfile(runtimeTargetProfile, dcaPayload), - ...cashOnlyPayload, - source: "RUNTIME_TARGET_JSON", - variable_scope: variableScope, - github_environment: githubEnvironment || "", - }; - } - - if (variableScope === "environment" || optionsCount <= 1) { - const profileValue = await readVariable(repository, variableScope, githubEnvironment, "STRATEGY_PROFILE"); - const profile = cleanCurrentStrategy(profileValue); - if (profile) { - const current = { - strategy_profile: profile, - ...reservedCashPayload, - ...incomeLayerPayload, - ...optionOverlayPayload, - ...runtimeTargetEnabledPayload, - ...dcaPayloadForProfile(profile, dcaPayload), - ...cashOnlyPayload, - source: "STRATEGY_PROFILE", - variable_scope: variableScope, - github_environment: githubEnvironment || "", - }; - if (variableScope === "environment" && normalizeMatchValue(option?.target_name) === "paper") { - current.execution_mode = "paper"; - } - return current; - } - } - - if ( - Object.keys(reservedCashPayload).length || - Object.keys(incomeLayerPayload).length || - Object.keys(optionOverlayPayload).length || - Object.keys(runtimeTargetEnabledPayload).length || - Object.keys(cashOnlyPayload).length - ) { - return { - ...reservedCashPayload, - ...incomeLayerPayload, - ...optionOverlayPayload, - ...runtimeTargetEnabledPayload, - ...cashOnlyPayload, - source: Object.keys(reservedCashPayload).length - ? "RESERVED_CASH_VARIABLES" - : (Object.keys(incomeLayerPayload).length - ? "INCOME_LAYER_VARIABLES" - : (Object.keys(runtimeTargetEnabledPayload).length - ? "RUNTIME_TARGET_ENABLED_VARIABLE" - : (Object.keys(optionOverlayPayload).length - ? "OPTION_OVERLAY_VARIABLES" - : "CASH_ONLY_EXECUTION_VARIABLE"))), - variable_scope: variableScope, - github_environment: githubEnvironment || "", - }; - } - - return null; -} - -function usesServiceTargetsAsRuntimeSource(platform) { - return ["ibkr", "schwab", "firstrade"].includes(platform); -} - -function logout(request) { - requireSameOrigin(request, { requireOrigin: true }); - return json({ ok: true }, 200, { - "Set-Cookie": clearCookie(SESSION_COOKIE), - }); -} - -function hkStopSourceIdentity(config, item = {}) { - const source = config || {}; - const selector = source.account_selector; - const selectors = Array.isArray(selector) - ? selector.map((value) => String(value).trim()).filter(Boolean) - : String(selector || "").split(",").map((value) => value.trim()).filter(Boolean); - const broker = source.broker_environment; - return { - key: String(source.key || item.key || ""), - target_name: String(source.target_name || ""), - platform_id: "longbridge", - deployment_selector: String(source.deployment_selector || "").trim(), - account_selector: selectors, - account_scope: String(source.account_scope || "").trim(), - service_name: String(source.service_name || "").trim(), - variable_scope: source.variable_scope === "environment" ? "environment" : "", - github_environment: String(source.github_environment || "").trim(), - broker_environment: broker === "live" || broker === "paper" ? broker : null, - runtime_status_target_id: String(source.runtime_status_target_id || "").trim(), - }; -} - -function hkStopDigestOrEmpty(value) { - return typeof value === "string" && /^[0-9a-f]{64}$/.test(value) ? value : ""; -} - -function hkStopExactObject(value, keys) { - return Boolean(value) && !Array.isArray(value) && typeof value === "object" - && Object.keys(value).length === keys.length - && keys.every((key) => Object.prototype.hasOwnProperty.call(value, key)); -} - -function normalizeHkStopResult(raw, env) { - const expectedProject = env?.HK_STOP_GCP_PROJECT_ID; - if (typeof expectedProject !== "string" || !/^[a-z][a-z0-9-]{4,61}[a-z0-9]$/.test(expectedProject)) { - throw new HttpError("hk_stop_project_not_configured", 503); - } - const top = ["schema_version", "request_id", "source_revision", "source_identity_sha256", "target_id", "runtime_identity_sha256", "producer", "observed_at", "readback", "no_order", "in_flight_state", "retirement_complete"]; - const producerKeys = ["repository", "workflow_path", "run_id", "run_attempt", "head_sha"]; - const readbackKeys = ["project", "region", "service", "revision_name", "runtime_enabled", "scheduler_state", "scheduler_count", "scheduler_set_sha256", "complete"]; - if (!hkStopExactObject(raw, top) - || raw.schema_version !== "qsl_hk_stop_result.v1" - || !HK_STOP_REQUEST_ID.test(String(raw.request_id || "")) - || !Number.isSafeInteger(raw.source_revision) || raw.source_revision < 0 - || !hkStopDigestOrEmpty(raw.source_identity_sha256) || !hkStopDigestOrEmpty(raw.runtime_identity_sha256) - || raw.target_id !== HK_STOP_TARGET_ID - || !hkStopExactObject(raw.producer, producerKeys) - || raw.producer.repository !== "QuantStrategyLab/LongBridgePlatform" - || raw.producer.workflow_path !== ".github/workflows/stop-hk-runtime.yml" - || !/^(0|[1-9][0-9]*)$/.test(raw.producer.run_id) - || raw.producer.run_attempt !== 1 - || !/^[0-9a-f]{40}$/.test(raw.producer.head_sha) - || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/.test(raw.observed_at) - || !hkStopExactObject(raw.readback, readbackKeys) - || raw.readback.project !== expectedProject - || raw.readback.region !== "asia-east2" - || raw.readback.service !== "longbridge-quant-hk-service" - || typeof raw.readback.revision_name !== "string" - || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/.test(raw.readback.revision_name) - || raw.readback.runtime_enabled !== false - || raw.readback.scheduler_state !== "paused" - || !Number.isSafeInteger(raw.readback.scheduler_count) || raw.readback.scheduler_count < 1 - || !hkStopDigestOrEmpty(raw.readback.scheduler_set_sha256) - || raw.readback.complete !== true - || raw.no_order !== true - || raw.in_flight_state !== "unknown" - || raw.retirement_complete !== false) { - throw new HttpError("hk_stop_result_rejected", 400); - } - return { - schema_version: raw.schema_version, - request_id: String(raw.request_id).toLowerCase(), - source_revision: raw.source_revision, - source_identity_sha256: raw.source_identity_sha256, - target_id: raw.target_id, - runtime_identity_sha256: raw.runtime_identity_sha256, - producer: { - repository: raw.producer.repository, - workflow_path: raw.producer.workflow_path, - run_id: raw.producer.run_id, - run_attempt: raw.producer.run_attempt, - head_sha: raw.producer.head_sha, - }, - observed_at: raw.observed_at, - readback: { ...raw.readback }, - no_order: true, - in_flight_state: "unknown", - retirement_complete: false, - }; -} - -async function acceptHkStopResult(request, env) { - requireDedicatedExecutionEvidenceSyncToken(request, env); - if (!hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instances_not_bound", 503); - const result = normalizeHkStopResult(await readBoundedJson(request, 8192), env); - const recorded = await runtimeInstanceCommand(env, { - action: "hk_stop_accept_result", - result, - now: new Date().toISOString(), - }); - return json({ - ok: true, - replayed: recorded.replayed === true, - phase: "stopped", - stop_confirmed: true, - platform_applied: false, - request_succeeded: false, - in_flight_state: "unknown", - }); -} - -function hkStopIdentityComplete(identity) { - return identity?.key === "hk" - && identity.target_name === "hk" - && identity.platform_id === "longbridge" - && identity.deployment_selector - && identity.account_selector?.length - && identity.account_scope === "HK" - && identity.service_name === "longbridge-quant-hk-service" - && identity.variable_scope === "environment" - && identity.github_environment === "longbridge-hk"; -} - -function presentHkStop(record, observation, repository) { - const dispatchResult = record?.dispatch_result || null; - return { - ok: true, - configured: false, - platform_applied: false, - request_succeeded: false, - stop_confirmed: record?.phase === "stopped", - in_flight_state: "unknown", - reused: record?.reused === true, - persisted: record?.persisted !== false, - request_id: record?.request_id || null, - phase: record?.phase || null, - dispatch_result: dispatchResult, - action: record ? "stop" : null, - requested_at: record?.requested_at || null, - updated_at: record?.updated_at || null, - workflow_run_id: null, - workflow_run_attempt: null, - actions_url: dispatchResult === "accepted" - ? `https://github.com/${repository}/actions/workflows/manual-runtime-stop.yml` - : null, - runtime_observation: observation || { status: "unknown", request_succeeded: false }, - }; -} - -async function hkStopObservation() { - // Lifecycle snapshots identify a target id, not this stop request's account. - // A fresh disabled snapshot can belong to an older identity, so it stays unknown. - return { status: "unknown", request_succeeded: false }; -} - -async function loadHkStopAccount(env) { - if (!hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instances_not_bound", 503); - const loaded = await loadAccountOptionsConfig(env); - if (loaded.source !== "durable_object" || !Number.isSafeInteger(loaded.revision)) { - throw new HttpError("hk_stop_source_unavailable", 409); - } - const matches = (loaded.options?.longbridge || []).filter((item) => item.key === "hk" && item.target_name === "hk"); - if (matches.length !== 1) throw new HttpError("stop requires one configured target", 400); - const identity = hkStopSourceIdentity(matches[0], { key: "hk" }); - if (!hkStopIdentityComplete(identity)) throw new HttpError("hk_stop_identity_incomplete", 400); - return { revision: loaded.revision, identity }; -} - -async function runtimeControlSource(env, platform, targetName) { - if (!hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instances_not_bound", 503); - const source = await loadAccountOptionsConfig(env); - if (source.source !== "durable_object" || !Number.isSafeInteger(source.revision)) throw new HttpError("runtime_control_source_unavailable", 409); - const matches = (source.options?.[platform] || []).filter(item => item.target_name === targetName); - if (matches.length !== 1) throw new HttpError("runtime_control_identity_incomplete", 409); - const item = matches[0]; - const identity = accountSettingsIdentity({ platform, key: item.key, config: item }); - if (!runtimeControlIdentityIsComplete(identity)) throw new HttpError("runtime_control_identity_incomplete", 409); - return { item, identity, revision: source.revision }; -} - -function presentRuntimeControl(record) { - let reason = null; - try { reason = record?.result_json ? JSON.parse(record.result_json).reason || null : null; } catch { /* invalid persisted result stays unclassified */ } - return { ok: record?.phase !== "rejected", request_id: record?.request_id || null, action: record?.action || null, - phase: record?.phase || null, updated_at: record?.updated_at || null, - unavailable_reason: reason || (record?.phase === "unknown" ? "runtime_control_result_unavailable" : null), - platform_applied: record?.phase === "applied" }; -} - -async function readPlatformRuntimeControl(env, platform, targetName) { - const source = await runtimeControlSource(env, platform, targetName); - const args = { platform, key: source.item.key }; - let { record } = await runtimeInstanceCommand(env, { action: "runtime_control_read", ...args }); - if (record && ["reserved", "accepted", "unknown", "partial"].includes(record.phase)) { - const result = await readRuntimeControlReceipt({ repository: env.RUNTIME_SETTINGS_REPO || DEFAULT_REPOSITORY, - token: env.RUNTIME_SETTINGS_DISPATCH_TOKEN, requestId: record.request_id, operation: record.action, - runId: record.workflow_run_id, requestedAt: record.requested_at }); - if (result.run_id && !record.workflow_run_id) { - ({ record } = await runtimeInstanceCommand(env, { action: "runtime_control_record", ...args, - request_id: record.request_id, phase: "unknown", run_id: result.run_id })); - } - if (result.platform_run_id && record.workflow_run_id) { - ({ record } = await runtimeInstanceCommand(env, { action: "runtime_control_capture", ...args, - request_id: record.request_id, run_id: record.workflow_run_id, platform_run_id: result.platform_run_id, reason: result.reason })); - } - if (["applied", "partial", "protected", "rejected"].includes(result.status) && record.workflow_run_id) { - ({ record } = await runtimeInstanceCommand(env, { action: "runtime_control_resolve", ...args, - request_id: record.request_id, phase: result.status, run_id: record.workflow_run_id, reason: result.reason })); - } - } - let details = {}; - try { details = JSON.parse(record?.result_json || "{}"); } catch { /* retain unknown */ } - if (record && ["accepted", "unknown", "partial"].includes(record.phase) && details.check_id && !details.check_complete) { - const checked = await readRuntimeControlReceipt({ repository: env.RUNTIME_SETTINGS_REPO || DEFAULT_REPOSITORY, - token: env.RUNTIME_SETTINGS_DISPATCH_TOKEN, requestId: record.request_id, operation: record.action, - checkId: details.check_id, runId: details.check_run_id, requestedAt: details.check_requested_at }); - if (checked.run_id) { - ({ record } = await runtimeInstanceCommand(env, { action: "runtime_control_record_check", ...args, - request_id: record.request_id, check_id: details.check_id, run_id: checked.run_id, complete: checked.complete === true })); - } - if (["applied", "partial", "protected", "rejected"].includes(checked.status) && checked.complete - && checked.platform_run_id === details.platform_run_id) { - ({ record } = await runtimeInstanceCommand(env, { action: "runtime_control_resolve", ...args, - request_id: record.request_id, phase: checked.status, run_id: record.workflow_run_id, - check_id: details.check_id, check_run_id: checked.run_id, reason: checked.reason })); - } - } - return presentRuntimeControl(record); -} - -function runtimeControlStopRequest(env, platform, targetName, source) { - const item = source.item; - const inputs = { ...item, platform, target_name: targetName }; - const github = { repository: platformRepositories(env)[platform], variable_scope: resolvedVariableScope(item.variable_scope, inputs) }; - if (github.variable_scope === "environment") github.environment = item.github_environment; - return { target_id: `${platform}/${targetName}`, github, runtime_target: { - platform_id: platform, deployment_selector: source.identity.deployment_selector, - account_selector: source.identity.account_selector.split(',').map(value => value.trim()).filter(Boolean), - account_scope: source.identity.account_scope, service_name: source.identity.service_name, - } }; -} - -async function dispatchPlatformControlReadback(env, raw) { - if (raw.confirm !== "READ_ONLY" || !["ibkr", "schwab"].includes(raw.platform) - || Object.keys(raw).some(key => !["platform", "target_name", "operation", "confirm"].includes(key))) throw new HttpError("runtime_control_request_invalid", 400); - const targetName = cleanSlug(raw.target_name, "target_name"); - const source = await runtimeControlSource(env, raw.platform, targetName); - const args = { platform: raw.platform, key: source.item.key }; - // Reconcile the original QRS run first. The platform run ID can only come - // from its validated safe artifact and never from the client. - await readPlatformRuntimeControl(env, raw.platform, targetName); - let { record } = await runtimeInstanceCommand(env, { action: "runtime_control_read", ...args }); - if (!record || !["accepted", "unknown", "partial"].includes(record.phase)) return json(presentRuntimeControl(record)); - const checkId = crypto.randomUUID(); - const begun = await runtimeInstanceCommand(env, { action: "runtime_control_begin_check", ...args, request_id: record.request_id, check_id: checkId }); - if (begun.state !== "reserved") return json(presentRuntimeControl(begun.record)); - record = begun.record; - const details = JSON.parse(record.result_json || "{}"); - let runId = null, rejected = false; - try { - const response = await fetchWithTimeout(`https://api.github.com/repos/${env.RUNTIME_SETTINGS_REPO || DEFAULT_REPOSITORY}/actions/workflows/manual-runtime-stop.yml/dispatches`, { - method: "POST", headers: githubHeaders(env.RUNTIME_SETTINGS_DISPATCH_TOKEN), body: JSON.stringify({ ref: "main", return_run_details: true, - inputs: { stop_request: JSON.stringify(runtimeControlStopRequest(env, raw.platform, targetName, source)), apply: "false", confirm: "READ_ONLY", apply_hk_stop: "false", - runtime_control_mode: "readback", runtime_control_operation: record.action, runtime_control_request_id: record.request_id, - runtime_control_check_id: checkId, runtime_control_platform_run_id: details.platform_run_id } }), - }); - if (response.status === 200) { - const body = await response.json(); - if (Number.isSafeInteger(body.workflow_run_id) && body.workflow_run_id > 0) runId = String(body.workflow_run_id); - } else if (response.status >= 400 && response.status < 500 && response.status !== 429) rejected = true; - } catch { /* Readback outcome may be unknown; next explicit refresh reads the same check. */ } - if (runId || rejected) ({ record } = await runtimeInstanceCommand(env, { action: "runtime_control_record_check", ...args, - request_id: record.request_id, check_id: checkId, run_id: runId, complete: rejected, reason: rejected ? "readback_dispatch_rejected" : null })); - return json(presentRuntimeControl(record)); -} - -async function dispatchPlatformRuntimeControl(env, raw) { - if (raw.confirm !== "CONTROL_ONLY" || !["ibkr", "schwab"].includes(raw.platform) - || !["enable", "disable"].includes(raw.operation) - || Object.keys(raw).some(key => !["platform", "target_name", "operation", "request_id", "confirm"].includes(key))) throw new HttpError("runtime_control_request_invalid", 400); - const targetName = cleanSlug(raw.target_name, "target_name"); - const requestId = raw.request_id || crypto.randomUUID(); - if (!HK_STOP_REQUEST_ID.test(requestId)) throw new HttpError("runtime_control_request_invalid", 400); - const source = await runtimeControlSource(env, raw.platform, targetName); - const args = { platform: raw.platform, key: source.item.key }; - const claimed = await runtimeInstanceCommand(env, { action: "runtime_control_claim", ...args, identity: source.identity, - source_revision: source.revision, request_id: requestId, operation: raw.operation }); - if (claimed.state !== "reserved") { - const body = presentRuntimeControl(claimed.record); - return json(claimed.state === "blocked" ? { ...body, ok: false, error: "runtime_control_pending" } : body, claimed.state === "blocked" ? 409 : 200); - } - const stopRequest = runtimeControlStopRequest(env, raw.platform, targetName, source); - let phase = "unknown", runId = null; - try { - const response = await fetchWithTimeout(`https://api.github.com/repos/${env.RUNTIME_SETTINGS_REPO || DEFAULT_REPOSITORY}/actions/workflows/manual-runtime-stop.yml/dispatches`, { - method: "POST", headers: githubHeaders(env.RUNTIME_SETTINGS_DISPATCH_TOKEN), body: JSON.stringify({ ref: "main", return_run_details: true, - inputs: { stop_request: JSON.stringify(stopRequest), apply: "true", confirm: "CONTROL_ONLY", apply_hk_stop: "false", - runtime_control_operation: raw.operation, runtime_control_request_id: requestId } }), - }); - if (response.status === 200) { - const body = await response.json(); - if (Number.isSafeInteger(body.workflow_run_id) && body.workflow_run_id > 0) { phase = "accepted"; runId = String(body.workflow_run_id); } - } else if (response.status >= 400 && response.status < 500 && response.status !== 429) phase = "rejected"; - } catch { /* Outcome may already have occurred; never resend. */ } - try { - const recorded = await runtimeInstanceCommand(env, { action: "runtime_control_record", ...args, request_id: requestId, phase, run_id: runId }); - return json(presentRuntimeControl(recorded.record), recorded.record?.phase === "rejected" ? 502 : 200); - } catch { return json({ ...presentRuntimeControl(claimed.record), phase: "unknown" }); } -} - -async function readRuntimeStop(request, env, url) { - requireSameOrigin(request); - const session = await readSession(request, env); - if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); - const platform = url.searchParams.get("platform"); - const targetName = url.searchParams.get("target_name"); - if (["ibkr", "schwab"].includes(platform) && targetName) return json(await readPlatformRuntimeControl(env, platform, targetName)); - if (platform !== "longbridge" || targetName !== "hk") { - return json({ - ok: true, configured: false, platform_applied: false, request_succeeded: false, - phase: null, runtime_observation: { status: "unknown", request_succeeded: false }, - }); - } - if (!hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instances_not_bound", 503); - const stored = await runtimeInstanceCommand(env, { action: "hk_stop_read" }); - const observation = await hkStopObservation(env, stored.record); - const repository = env.RUNTIME_SETTINGS_REPO || DEFAULT_REPOSITORY; - return json(presentHkStop(stored.record, observation, repository)); -} - -async function dispatchHkRuntimeStop(env, raw) { - if (!hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instances_not_bound", 503); - const requestId = raw.request_id ? String(raw.request_id).trim().toLowerCase() : crypto.randomUUID(); - if (!HK_STOP_REQUEST_ID.test(requestId)) throw new HttpError("invalid_hk_stop_request_id", 400); - const source = await loadHkStopAccount(env); - const identity = source.identity; - const identityJson = canonicalResearchTaskJson(identity); - const runtimeJson = canonicalResearchTaskJson({ - platform_id: identity.platform_id, - deployment_selector: identity.deployment_selector, - account_selector: identity.account_selector, - account_scope: identity.account_scope, - service_name: identity.service_name, - }); - const claimed = await runtimeInstanceCommand(env, { - action: "hk_stop_claim", - request_id: requestId, - action_name: "stop", - source_revision: source.revision, - identity: source.identity, - source_identity_sha256: await sha256Hex(identityJson), - runtime_identity_sha256: await sha256Hex(runtimeJson), - }); - const repository = env.RUNTIME_SETTINGS_REPO || DEFAULT_REPOSITORY; - if (claimed.state !== "reserved") { - const observation = await hkStopObservation(env, claimed.record); - const body = presentHkStop({ ...claimed.record, reused: true }, observation, repository); - if (claimed.state === "blocked") return json({ ...body, ok: false, error: "hk_stop_request_pending" }, 409); - return json(body); - } - const stored = claimed.record; - const stopRequest = { - target_id: HK_STOP_TARGET_ID, - github: { - repository: "QuantStrategyLab/LongBridgePlatform", - variable_scope: "environment", - environment: identity.github_environment, - }, - runtime_target: { - platform_id: "longbridge", - deployment_selector: identity.deployment_selector, - account_selector: identity.account_selector, - account_scope: identity.account_scope, - service_name: identity.service_name, - }, - correlation: { - request_id: stored.request_id, - source_revision: stored.source_revision, - source_identity_sha256: stored.source_identity_sha256, - }, - }; - let outcome = "unknown"; - try { - const response = await fetchWithTimeout(`https://api.github.com/repos/${repository}/actions/workflows/manual-runtime-stop.yml/dispatches`, { - method: "POST", - headers: githubHeaders(env.RUNTIME_SETTINGS_DISPATCH_TOKEN), - body: JSON.stringify({ - ref: env.RUNTIME_SETTINGS_REF || "main", - inputs: { - stop_request: JSON.stringify(stopRequest), - apply: "true", - confirm: "STOP_ONLY", - apply_hk_stop: "true", - }, - }), - }); - if (response.status === 204) outcome = "accepted"; - else if (response.status >= 400 && response.status < 500) outcome = "rejected"; - } catch { - outcome = "unknown"; - } - try { - const recorded = await runtimeInstanceCommand(env, { - action: "hk_stop_record", - request_id: claimed.record.request_id, - phase: outcome, - }); - const observation = await hkStopObservation(env, recorded.record); - return json(presentHkStop(recorded.record, observation, repository)); - } catch { - const observation = { status: "unknown", request_succeeded: false }; - return json(presentHkStop({ ...claimed.record, phase: "reserved", dispatch_result: null, persisted: false }, observation, repository)); - } -} - -async function dispatchRuntimeStop(request, env) { - requireEnv(env, "RUNTIME_SETTINGS_DISPATCH_TOKEN"); - 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); - const raw = await request.json(); - if (raw && !Array.isArray(raw) && typeof raw === "object" && ["ibkr", "schwab"].includes(raw.platform) && raw.operation === "inspect") - return await dispatchPlatformControlReadback(env, raw); - if (raw && !Array.isArray(raw) && typeof raw === "object" && ["ibkr", "schwab"].includes(raw.platform) && raw.operation) - return await dispatchPlatformRuntimeControl(env, raw); - const hkStop = raw && !Array.isArray(raw) && raw.platform === "longbridge" && raw.target_name === "hk"; - const allowedKeys = hkStop ? ["platform", "target_name", "confirm", "request_id"] : ["platform", "target_name", "confirm"]; - if (!raw || Array.isArray(raw) || typeof raw !== "object" - || Object.keys(raw).some((key) => !allowedKeys.includes(key)) - || raw.confirm !== "STOP_ONLY") { - throw new HttpError("stop accepts only platform, target_name and STOP_ONLY confirmation", 400); - } - const platform = cleanChoice(raw.platform, SUPPORTED_PLATFORMS, "platform"); - const targetName = cleanSlug(raw.target_name, "target_name"); - if (platform === "longbridge" && targetName === "hk") return await dispatchHkRuntimeStop(env, raw); - const accountConfig = await loadAccountOptionsConfig(env); - const matches = (accountConfig.options?.[platform] || []).filter((item) => item.target_name === targetName); - if (matches.length !== 1) throw new HttpError("stop requires one configured target", 400); - // Derive routing only from protected account configuration, not form edits. - // The workflow independently checks this identity against current variables. - const inputs = { ...matches[0], platform, target_name: targetName }; - inputs.variable_scope = resolvedVariableScope(inputs.variable_scope, inputs); - for (const field of ["deployment_selector", "account_scope", "account_selector", "service_name", "github_environment"]) { - inputs[field] = inputs[field] || defaultInputValue(field, inputs); - } - const identity = { - platform_id: platform, - deployment_selector: inputs.deployment_selector, - account_selector: String(inputs.account_selector || "").split(",").map((item) => item.trim()).filter(Boolean), - account_scope: inputs.account_scope, - service_name: inputs.service_name, - }; - if (!identity.deployment_selector || !identity.account_selector.length || !identity.account_scope || !identity.service_name) { - throw new HttpError("stop target identity is incomplete", 400); - } - const github = { repository: platformRepositories(env)[platform], variable_scope: inputs.variable_scope }; - if (github.variable_scope === "environment") github.environment = inputs.github_environment; - const stopRequest = { target_id: `${platform}/${targetName}`, github, runtime_target: identity }; - const repository = env.RUNTIME_SETTINGS_REPO || DEFAULT_REPOSITORY; - const workflow = "manual-runtime-stop.yml"; - try { - const response = await fetchWithTimeout(`https://api.github.com/repos/${repository}/actions/workflows/${workflow}/dispatches`, { - method: "POST", - headers: githubHeaders(env.RUNTIME_SETTINGS_DISPATCH_TOKEN), - body: JSON.stringify({ - ref: env.RUNTIME_SETTINGS_REF || "main", - inputs: { stop_request: JSON.stringify(stopRequest), apply: "true", confirm: "STOP_ONLY" }, - }), - }); - if (!response.ok) throw new Error("dispatch failed"); - } catch { - return json({ ok: false, error: "stop dispatch unverified; check the existing run before retrying" }, 502); - } - return json({ - ok: true, configured: false, platform_applied: false, - actions_url: `https://github.com/${repository}/actions/workflows/${workflow}`, - }); -} - -async function binanceResumeTarget(values, option) { - const target = parseJsonObject(values.RUNTIME_TARGET_JSON); - const continuity = target?.live_continuity; - if (values.RUNTIME_TARGET_ENABLED !== "false" || values.BINANCE_RECOVERY_CONTROL_ENABLED !== "true" - || !["", "false"].includes(values.BINANCE_DRY_RUN || "") - || !["", "crypto_live_pool_rotation"].includes(values.STRATEGY_PROFILE || "") - || values.CLOUD_RUN_SERVICE_TARGETS_JSON - || target?.platform_id !== "binance" || target.strategy_profile !== "crypto_live_pool_rotation" - || target.execution_mode !== "live" || target.dry_run_only !== false - || continuity?.baseline_kind !== "legacy_authorized" - || !["RECONCILE_ONLY", "ACTIVE_LKG"].includes(continuity?.state) - || typeof continuity.baseline_id !== "string" || !continuity.baseline_id.trim() - || typeof continuity.captured_at !== "string" || !continuity.captured_at.trim() - || !/^[a-f0-9]{64}$/.test(continuity.baseline_target_sha256 || "")) return null; - const inputs = { ...option, platform: "binance" }; - const identity = { platform_id: "binance" }; - for (const field of ["deployment_selector", "account_selector", "account_scope", "service_name"]) { - const value = inputs[field] || defaultInputValue(field, inputs); - identity[field] = field === "account_selector" - ? String(value || "").split(",").map((item) => item.trim()).filter(Boolean) : value; - if (!value || JSON.stringify(identity[field]) !== JSON.stringify(target[field])) return null; - } - const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(values.RUNTIME_TARGET_JSON)); - return { - runtime_target: identity, - runtime_target_sha256: [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join(""), - }; -} - -async function dispatchBinanceResume(request, env) { - requireEnv(env, "RUNTIME_SETTINGS_DISPATCH_TOKEN"); - 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); - const raw = await request.json(); - if (!raw || Array.isArray(raw) || typeof raw !== "object" - || Object.keys(raw).some((key) => !["platform", "target_name", "runtime_target_sha256", "confirm"].includes(key)) - || raw.platform !== "binance" || raw.confirm !== "RESUME_EXISTING" - || typeof raw.runtime_target_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(raw.runtime_target_sha256)) { - throw new HttpError("resume accepts only the existing Binance target and RESUME_EXISTING confirmation", 400); - } - const config = await loadAccountOptionsConfig(env); - const matches = (config.options?.binance || []).filter((item) => item.target_name === raw.target_name); - const repository = platformRepositories(env).binance; - if (matches.length !== 1 || repository !== "QuantStrategyLab/BinancePlatform" - || resolvedVariableScope(matches[0].variable_scope, { ...matches[0], platform: "binance" }) !== "repository") { - throw new HttpError("resume requires one configured Binance repository target", 400); - } - let target; - try { - const variables = await fetchGithubVariables(env.RUNTIME_SETTINGS_DISPATCH_TOKEN, repository, "repository", ""); - target = await binanceResumeTarget(Object.fromEntries(variables), matches[0]); - } catch { - return json({ ok: false, error: "resume source unavailable; no request submitted" }, 502); - } - if (!target || target.runtime_target_sha256 !== raw.runtime_target_sha256) { - return json({ ok: false, error: "existing recovery configuration changed or unavailable; refresh before continuing" }, 409); - } - // Only the external switch is requested. The platform's private ACTIVE_LKG - // validator remains mandatory; a console confirmation cannot activate it. - const resumeRequest = { target_id: `binance/${matches[0].target_name}`, - github: { repository, variable_scope: "repository" }, ...target }; - const settingsRepository = env.RUNTIME_SETTINGS_REPO || DEFAULT_REPOSITORY; - const workflow = "manual-binance-resume.yml"; - try { - const response = await fetchWithTimeout(`https://api.github.com/repos/${settingsRepository}/actions/workflows/${workflow}/dispatches`, { - method: "POST", headers: githubHeaders(env.RUNTIME_SETTINGS_DISPATCH_TOKEN), - body: JSON.stringify({ ref: "main", inputs: { resume_request: JSON.stringify(resumeRequest), apply: "true", confirm: "RESUME_EXISTING" } }), - }); - if (!response.ok) throw new Error("dispatch failed"); - } catch { - return json({ ok: false, error: "resume dispatch unverified; check the existing run before retrying" }, 502); - } - return json({ ok: true, configured: false, platform_applied: false, - actions_url: `https://github.com/${settingsRepository}/actions/workflows/${workflow}` }); -} - -async function dispatchSwitch(request, env) { - requireEnv(env, "RUNTIME_SETTINGS_DISPATCH_TOKEN"); - requireSameOrigin(request, { requireOrigin: true }); - const session = await readSession(request, env); - if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); - - 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)); - if (!SWITCH_ACTIVATION_CONNECTED) { - return json({ - ok: false, - error: "账户应用尚未接通,系统未提交变更", - workflow_dispatched: false, - }, 409); - } - const repository = env.RUNTIME_SETTINGS_REPO || DEFAULT_REPOSITORY; - const workflow = env.RUNTIME_SETTINGS_WORKFLOW || DEFAULT_WORKFLOW; - const apiUrl = `https://api.github.com/repos/${repository}/actions/workflows/${workflow}/dispatches`; - const response = await fetchWithTimeout(apiUrl, { - method: "POST", - headers: githubHeaders(env.RUNTIME_SETTINGS_DISPATCH_TOKEN), - body: JSON.stringify({ - ref: env.RUNTIME_SETTINGS_REF || "main", - inputs, - }), - }); - - if (!response.ok) { - const text = await response.text(); - return json({ ok: false, error: `GitHub dispatch failed: ${text.slice(0, 600)}` }, 502); - } - - return json({ - ok: true, - repository, - workflow, - actions_url: `https://github.com/${repository}/actions/workflows/${workflow}`, - account_options_sync: { - synced: false, - deferred: true, - reason: "workflow_success_required", - }, - inputs, - }); -} - -async function syncDefaultStrategyForAccount(env, accountOptions, inputs, session) { - if (hasRuntimeInstanceStore(env)) { - try { - await runtimeInstanceCommand(env, { action: "sync_defaults", actor: session?.login || "github-actions", inputs }); - return { synced: true, audit_logged: true }; - } catch (error) { - return { synced: false, reason: error instanceof HttpError ? error.message : "runtime_instances_unavailable" }; - } - } - if (!hasConfigStore(env)) return { synced: false, reason: "kv_not_bound" }; - try { - const { options, changed } = updateAccountOptionsDefaultStrategy(accountOptions, inputs); - let auditLogged = false; - if (changed) { - await writeConfigJson(env, ACCOUNT_OPTIONS_KEY, validateAccountOptionsSchemaOrThrow(options)); - try { - await appendAuditLog(env, { - ts: new Date().toISOString(), - login: session?.login || "", - action: "sync_default_strategy", - platform: inputs.platform, - target_name: inputs.target_name, - strategy_profile: inputs.strategy_profile, - }); - auditLogged = true; - } catch { - auditLogged = false; - } - } - return { synced: true, changed, audit_logged: auditLogged }; - } catch (error) { - return { synced: false, error: error.message || "account option sync failed" }; - } -} - -async function syncAccountDefaultResponse(request, env) { - requireInternalSyncToken(request, env); - let rawInput; - try { - rawInput = await request.json(); - } catch { - return json({ ok: false, error: "request body must be valid JSON" }, 400); - } - let inputs; - try { - inputs = normalizeSwitchInputs(rawInput); - } catch (error) { - if (isFrozenStrategyProfile({ profile: rawInput?.strategy_profile })) { - throw new HttpError("frozen strategy requires valid continuity input", 409); - } - throw error; - } - const accountConfig = await loadAccountOptionsConfig(env); - const strategyProfiles = await loadStrategyProfilesConfig(env); - const strategy = strategyProfiles.find((item) => sameStrategyProfile(item.profile, inputs.strategy_profile)); - if (!strategy) throw new Error(`strategy ${inputs.strategy_profile} is not configured`); - - let accountOptions = accountConfig.options; - let accountOption = configuredAccountForInputs(inputs, accountOptions); - let registeredLegacyContinuityAccount = false; - if (!accountOption) { - if (isFrozenStrategyProfile(strategy)) throw new HttpError(`strategy ${inputs.strategy_profile} is frozen for new selection`, 409); - if (hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instance_registration_requires_management", 409); - const registration = registerLegacyContinuityAccount(env, accountOptions, inputs, strategy); - accountOptions = registration.options; - accountOption = registration.account; - registeredLegacyContinuityAccount = registration.registered; - if (registeredLegacyContinuityAccount) { - await writeConfigJson(env, ACCOUNT_OPTIONS_KEY, validateAccountOptionsSchemaOrThrow(accountOptions)); - try { - await appendAuditLog(env, { - ts: new Date().toISOString(), - login: "github-actions", - action: "register_legacy_continuity_account", - platform: inputs.platform, - target_name: inputs.target_name, - strategy_profile: inputs.strategy_profile, - live_continuity_state: inputs.live_continuity_state, - }); - } catch { - // The routing registration is still valid if its non-critical audit - // append fails; callers receive the registration result below. - } - } - } - if (!accountOption) throw new Error("switch inputs do not match configured account options"); - if (isFrozenStrategyProfile(strategy) && !sameStrategyProfile(accountOption.default_strategy_profile, inputs.strategy_profile)) { - throw new HttpError(`strategy ${inputs.strategy_profile} requires an existing matching account default`, 409); - } - assertStrategyAllowedForAccount(inputs, accountOption, strategyProfiles); - const result = await syncDefaultStrategyForAccount(env, accountOptions, inputs, { - login: "github-actions", - }); - const kvSyncSkipped = result.reason === "kv_not_bound"; - const accountOptionsSync = kvSyncSkipped ? { ...result, skipped: true } : result; - return json( - { - ok: result.synced || kvSyncSkipped, - account_options_sync: accountOptionsSync, - legacy_continuity_account_registered: registeredLegacyContinuityAccount, - }, - result.synced || kvSyncSkipped ? 200 : 500, - ); -} - -function requireInternalSyncToken(request, env) { - const expected = env.STRATEGY_SWITCH_SYNC_TOKEN || env.RUNTIME_SETTINGS_DISPATCH_TOKEN; - if (!expected) throw new HttpError("internal 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("internal sync token is invalid", 401); -} - -async function syncStrategyProfilesResponse(request, env) { - requireInternalSyncToken(request, env); - const result = await syncStrategyProfilesConfig(env, { login: "github-actions" }); - const kvSyncSkipped = result.reason === "kv_not_bound"; - const strategyProfilesSync = kvSyncSkipped ? { ...result, skipped: true } : result; - return json( - { - ok: result.synced || kvSyncSkipped, - strategy_profiles_sync: strategyProfilesSync, - strategy_profiles_count: result.count, - }, - result.synced || kvSyncSkipped ? 200 : 500, - ); -} - -async function syncStrategyHealthResponse(request, env) { - requireDedicatedStrategyHealthSyncToken(request, env); - if (!hasConfigStore(env)) { - return json({ ok: false, error: "strategy health KV is not configured" }, 503); - } - - let raw; - try { - raw = await readBoundedJson(request, STRATEGY_HEALTH_MAX_BODY_BYTES); - } catch (error) { - return json({ ok: false, error: error.message || "invalid strategy health payload" }, error.status || 400); - } - - let snapshot; - try { - snapshot = normalizeStrategyHealthSnapshot(raw, "strategy health snapshot"); - } catch (error) { - return json({ ok: false, error: error.message || "invalid strategy health payload" }, 400); - } - - await writeConfigJson(env, STRATEGY_HEALTH_SNAPSHOT_KEY, snapshot); - try { - await appendAuditLog(env, { - ts: new Date().toISOString(), - login: "strategy-health-sync", - action: "sync_strategy_health", - schema_version: snapshot.schema_version, - strategy_count: snapshot.summary.strategy_count, - data_status: snapshot.data_status, - }); - } catch { - // Snapshot delivery remains successful; audit data must never expose the payload. - } - return json({ - ok: true, - schema_version: snapshot.schema_version, - strategy_count: snapshot.summary.strategy_count, - generated_at: snapshot.generated_at, - }); -} - -async function strategyHealthResponse(request, env) { - const session = await readSession(request, env); - if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); - if (!hasConfigStore(env)) return json(emptyStrategyHealthPayload("snapshot_unavailable")); - - let snapshot; - try { - const stored = await readConfigJson(env, STRATEGY_HEALTH_SNAPSHOT_KEY); - if (!stored) return json(emptyStrategyHealthPayload("snapshot_unavailable")); - snapshot = normalizeStrategyHealthSnapshot(stored, STRATEGY_HEALTH_SNAPSHOT_KEY); - } catch { - return json(emptyStrategyHealthPayload("snapshot_invalid")); - } - - const ttlSeconds = strategyHealthStaleTtlSeconds(env); - const freshnessTimestamps = [snapshot.generated_at, snapshot.computed_at] - .filter(Boolean) - .map((value) => Date.parse(value)); - const freshnessAt = freshnessTimestamps.length ? Math.min(...freshnessTimestamps) : Number.NaN; - const now = Date.now(); - const futureBeyondClockSkew = Number.isFinite(freshnessAt) && freshnessAt > now + 5 * 60 * 1000; - const ageSeconds = futureBeyondClockSkew - ? Number.POSITIVE_INFINITY - : Number.isFinite(freshnessAt) - ? Math.max(0, (now - freshnessAt) / 1000) - : Number.POSITIVE_INFINITY; - if (["ready", "partial"].includes(snapshot.data_status) && ageSeconds > ttlSeconds) { - snapshot.data_status = "stale"; - } - return json(snapshot); -} - -async function humanDecisionRequestKey({ requestId, kind, actor, subjectId, materialKey, action, target }) { - if (requestId !== undefined && requestId !== null && requestId !== "") { - const text = String(requestId).trim(); - if (!/^[A-Za-z0-9._:-]{1,128}$/.test(text)) throw new HttpError("invalid request_id", 400); - return `id:${kind}:${text}`; - } - const digest = await calculateOwnerDecisionSha256({ - actor, kind, subject_id: subjectId, material_key: materialKey, action, target: target || null, - }); - return `fp:${digest}`; -} - -async function researchPromotionMaterialKey(ticket) { - return calculateOwnerDecisionSha256({ - ticket_id: ticket.ticket_id, - strategy_profile: ticket.strategy_profile, - domain: ticket.domain, - proposed_params: ticket.proposed_params, - shadow_passed: ticket.shadow_passed, - shadow_evidence_kind: ticket.shadow_evidence_kind, - }); -} - -// The legacy six-field material key remains the durable terminal index. This -// independent versioned snapshot binds all saved producer review content without -// rekeying historical records or hashing post-decision/observation-only fields. -function researchPromotionReviewMaterial(ticket) { - return { - schema_version: "qsl_promotion_review_material.v2", - ticket_id: ticket.ticket_id, strategy_profile: ticket.strategy_profile, domain: ticket.domain, - proposed_params: ticket.proposed_params, shadow_passed: ticket.shadow_passed, - shadow_evidence_kind: ticket.shadow_evidence_kind, - drift_status: ticket.drift_status, drift_score: ticket.drift_score, - budget: ticket.budget, search_iterations: ticket.search_iterations, - suggested_risk_profile: ticket.suggested_risk_profile, - notification_subject: ticket.notification_subject, notification_body: ticket.notification_body, - notes: ticket.notes, research_summary: ticket.research_summary || null, - }; -} -const RESEARCH_PROMOTION_REVIEW_INVALID = "research_promotion_review_material_invalid"; -class ResearchPromotionReviewMaterialError extends HttpError { - constructor() { super(RESEARCH_PROMOTION_REVIEW_INVALID, 400); } -} -async function researchPromotionReviewBinding(ticket) { - let canonical; - try { - canonical = canonicalResearchTaskJson(researchPromotionReviewMaterial(ticket)); - } catch (error) { - // Only canonical data validation is recoverable per ticket. Hash/storage - // failures must not be relabelled as invalid material or a healthy queue. - if (error?.message === "research task must use finite JSON values" - || error?.message === "research task must use JSON values") throw new ResearchPromotionReviewMaterialError(); - throw error; - } - return { schema_version: "qsl_promotion_review_binding.v2", review_sha256: await sha256Hex(canonical) }; -} -function promotionReviewBindingsMatch(left, right) { - return left?.schema_version === "qsl_promotion_review_binding.v2" && right?.schema_version === left.schema_version - && /^[0-9a-f]{64}$/.test(left.review_sha256) && left.review_sha256 === right.review_sha256; -} -function promotionMaterialReviewBinding(row) { - try { - const binding = JSON.parse(row?.summary_json || "{}").review_binding; - return promotionReviewBindingsMatch(binding, binding) ? { schema_version: "qsl_promotion_review_binding.v2", review_sha256: binding.review_sha256 } : null; - } catch { return null; } -} - -async function recoveryDecisionMaterialKey(recovery) { - if (recovery.mode === "initial_enrollment") return calculateOwnerDecisionSha256({ - mode: recovery.mode, recovery_id: recovery.recovery_id, - candidate_sha256: recovery.candidate?.candidate_sha256 || recovery.candidate_sha256, - facts_sha256: recovery.candidate?.facts_sha256 || recovery.facts_sha256, - }); - return calculateOwnerDecisionSha256({ - recovery_id: recovery.recovery_id, - candidate_sha256: recovery.candidate_sha256, - dual_review_binding_sha256: recovery.dual_review.evidence_binding_sha256, - }); -} - -async function mirrorConfigJson(env, key, value) { - try { - await writeConfigJson(env, key, value); - return true; - } catch { - return false; - } -} - -async function readLegacyConfig(env, key) { - try { - return { value: await readConfigJson(env, key), blocked: false, unreadable: false }; - } catch (error) { - if (String(error?.message || "").includes("must be valid JSON")) return { value: null, blocked: true, unreadable: false }; - return { value: null, blocked: false, unreadable: true }; - } -} - -function humanDecisionTimestamp(value) { - return String(value || new Date().toISOString()).replace(/\.\d{3}Z$/, "Z"); -} - -// Actionable material expires when the evidence that produced it would itself be -// stale. A missing or future-dated anchor yields null, which callers treat as -// "not actionable" rather than inventing authority. -function controlPlaneDecisionExpiryAt(snapshot, ttlSeconds, now = Date.now()) { - const timestamps = [snapshot?.generated_at, snapshot?.computed_at] - .filter(Boolean) - .map((value) => Date.parse(value)) - .filter((value) => Number.isFinite(value)); - if (!timestamps.length) return null; - const observedAt = Math.min(...timestamps); - if (observedAt > now + 5 * 60 * 1000) return null; - return observedAt + ttlSeconds * 1000; -} - -function reconciliationRecoveryDecisionExpiryAt(recovery, source, ttlSeconds, now = Date.now()) { - const sourceExpiry = controlPlaneDecisionExpiryAt(source, ttlSeconds, now); - if (sourceExpiry == null) return null; - const observedAt = Date.parse(recovery.mode === "initial_enrollment" - ? recovery.candidate?.last_observed_at - : recovery.last_observed_at); - if (!Number.isFinite(observedAt) || observedAt > now + 5 * 60 * 1000) return null; - return Math.min(sourceExpiry, observedAt + ttlSeconds * 1000); -} - -async function publishHumanOwnerMaterials(env, snapshot, sourceId) { - const ownerCandidates = (snapshot.candidates || []).filter(isOwnerDecisionCandidate); - if (!ownerCandidates.length && !hasRuntimeInstanceStore(env)) return { ok: true, skipped: true }; - if (!hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instances_not_bound", 503); - const expiresAtMs = controlPlaneDecisionExpiryAt(snapshot, controlPlaneStaleTtlSeconds(env)); - const grouped = new Map(); - for (const candidate of ownerCandidates) { - const list = grouped.get(candidate.candidate_id) || []; - list.push(candidate); - grouped.set(candidate.candidate_id, list); - } - const materials = []; - const duplicateSubjectIds = []; - for (const [subjectId, list] of grouped) { - if (list.length > 1) duplicateSubjectIds.push(subjectId); - const candidate = list[0]; - const materialKey = await ownerDecisionCandidateEvidenceSha256(candidate); - const legacyRead = await readLegacyConfig(env, ownerDecisionCurrentKey(subjectId)); - if (legacyRead.unreadable) throw new HttpError("human_decision_legacy_unreadable", 503); - let legacy = null; - if (legacyRead.blocked) legacy = { blocked: true }; - else if (legacyRead.value) { - try { - const intent = await normalizeOwnerDecisionIntent(legacyRead.value, ownerDecisionCurrentKey(subjectId)); - if (intent.candidate_id !== subjectId || intent.candidate_evidence_sha256 !== materialKey) legacy = { blocked: true }; - else { - legacy = { - record: { - action: intent.decision, - actor: intent.decided_by, - decided_at: intent.decided_at, - material_key: materialKey, - target_json: canonicalResearchTaskJson({ decision: intent.decision }), - payload: intent, - }, - }; - } - } catch { - legacy = { blocked: true }; - } - } - materials.push({ - subject_id: subjectId, - material_key: materialKey, - summary_json: JSON.stringify({ candidate_id: subjectId }), - eligible: list.length === 1 && candidate.freshness?.status === "fresh", - expires_at_ms: expiresAtMs, - legacy, - }); - } - return runtimeInstanceCommand(env, { - action: "owner_sync_materials", - actor: "control-plane-sync", - source_id: sourceId, - updated_at: humanDecisionTimestamp(), - now_ms: Date.now(), - replace_source: true, - materials, - duplicate_subject_ids: duplicateSubjectIds, - }); -} - -function recoveryMaterialEligible(recovery, sourceFreshness, evidenceFreshness) { - if (recovery.mode === "initial_enrollment") return sourceFreshness?.data_status === "ready" && evidenceFreshness?.data_status === "ready" - && Object.values(recovery.candidate.coverage).every(status => status === "complete"); - return sourceFreshness?.data_status === "ready" - && evidenceFreshness?.data_status === "ready" - && recovery.readiness === "awaiting_human_confirmation" - && recovery.reconciliation_state === "RECONCILE_ONLY" - && !recovery.blocker_codes.length - && recovery.dual_review.evidence_binding_sha256 === recovery.candidate_sha256 - && recovery.evidence_sample_count >= 1; -} - -async function publishHumanRecoveryMaterials(env, source) { - if (!hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instances_not_bound", 503); - const ttlSeconds = reconciliationRecoveryStaleTtlSeconds(env); - const now = Date.now(); - const sourceFreshness = controlPlaneSnapshotFreshness(source, ttlSeconds, now); - const grouped = new Map(); - for (const recovery of source.recoveries || []) { - const list = grouped.get(recovery.recovery_id) || []; - list.push(recovery); - grouped.set(recovery.recovery_id, list); - } - const materials = []; - const duplicateSubjectIds = []; - for (const [subjectId, list] of grouped) { - if (list.length > 1) duplicateSubjectIds.push(subjectId); - const recovery = list[0]; - const evidenceFreshness = reconciliationRecoveryEvidenceFreshness(recovery, sourceFreshness, ttlSeconds, now); - const materialKey = await recoveryDecisionMaterialKey(recovery); - const legacyRead = recovery.mode === "initial_enrollment" ? { unreadable: false, blocked: false, value: null } - : await readLegacyConfig(env, reconciliationRecoveryConfirmationCurrentKey(subjectId)); - if (legacyRead.unreadable) throw new HttpError("human_decision_legacy_unreadable", 503); - let legacy = null; - if (legacyRead.blocked) legacy = { blocked: true }; - else if (legacyRead.value) { - try { - const confirmation = await normalizeReconciliationRecoveryConfirmation(legacyRead.value, reconciliationRecoveryConfirmationCurrentKey(subjectId)); - if (confirmation.recovery_id !== subjectId || confirmation.candidate_sha256 !== recovery.candidate_sha256 || confirmation.dual_review_binding_sha256 !== recovery.dual_review.evidence_binding_sha256) { - legacy = { blocked: true }; - } else { - legacy = { - record: { - action: "approve", - actor: confirmation.confirmed_by, - decided_at: confirmation.confirmed_at, - material_key: materialKey, - target_json: canonicalResearchTaskJson({ decision: "approve" }), - payload: confirmation, - }, - }; - } - } catch { - legacy = { blocked: true }; - } - } - materials.push({ - subject_id: subjectId, - material_key: materialKey, - summary_json: JSON.stringify({ recovery_id: subjectId }), - eligible: list.length === 1 && recoveryMaterialEligible(recovery, sourceFreshness, evidenceFreshness), - expires_at_ms: reconciliationRecoveryDecisionExpiryAt(recovery, source, ttlSeconds, now), - legacy, - }); - } - return runtimeInstanceCommand(env, { - action: "recovery_sync_materials", - actor: "reconciliation-recovery-source-sync", - source_id: source.source_id, - updated_at: humanDecisionTimestamp(), - now_ms: Date.now(), - replace_source: true, - materials, - duplicate_subject_ids: duplicateSubjectIds, - }); -} - -async function publishHumanPromotionMaterial(env, ticket, existing) { - if (!hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instances_not_bound", 503); - const materialKey = await researchPromotionMaterialKey(ticket); - let legacy = null; - if (existing && (existing.state === "human_accepted" || existing.state === "human_rejected")) { - const existingKey = await researchPromotionMaterialKey(existing); - const actor = String(existing.human_decision_actor || "").trim(); - if (!actor || !existing.human_decided_at || existingKey !== materialKey) legacy = { blocked: true }; - else { - legacy = { - record: { - action: existing.state === "human_accepted" ? "accept" : "reject", - actor, - decided_at: existing.human_decided_at, - material_key: materialKey, - target_json: canonicalResearchTaskJson({ - selected_account: existing.selected_account || null, - confirmation: { - target_platform: existing.confirmation_target_platform, - execution_mode: existing.confirmation_execution_mode, - risk_profile: existing.confirmation_risk_profile, - }, - }), - payload: { ticket: existing, selected_account: existing.selected_account || null, proposed_params: existing.proposed_params }, - }, - }; - } - } - return runtimeInstanceCommand(env, { - action: "promotion_sync_materials", - actor: "research-promotion-ticket-sync", - source_id: "research-promotion", - updated_at: humanDecisionTimestamp(), - now_ms: Date.now(), - replace_source: false, - materials: [{ - subject_id: ticket.ticket_id, - material_key: materialKey, - summary_json: JSON.stringify({ - ticket_id: ticket.ticket_id, - strategy_profile: ticket.strategy_profile, - domain: ticket.domain, - review_binding: await researchPromotionReviewBinding(ticket), - }), - eligible: true, - expires_at_ms: Date.now() + controlPlaneStaleTtlSeconds(env) * 1000, - legacy, - }], - }); -} - -// A fixed projection of the durable terminal record, never a request echo or KV fallback. -async function humanDecisionReceipt(record, expected) { - const invalid = () => { throw new HttpError("human_decision_receipt_unavailable", 503); }; - const text = (value, max) => typeof value === "string" && value.length > 0 && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value); - const actions = { owner: ["approve_limited_live_canary", "keep_parked"], recovery: ["approve", "reject"], promotion: ["accept", "reject"] }; - if (!record || !actions[record.kind]?.includes(record.action) || !text(record.subject_id, 256) - || !/^[0-9a-f]{64}$/.test(record.material_key) || !text(record.actor, 100) - || !text(record.decided_at, 40) || !Number.isFinite(Date.parse(record.decided_at)) - || record.kind !== expected.kind || record.subject_id !== expected.subject_id || record.material_key !== expected.material_sha256) invalid(); - let target; - try { target = JSON.parse(record.target_json); } catch { invalid(); } - let projection; - if (record.kind === "promotion") { - const account = target?.selected_account; - const confirmation = target?.confirmation; - if (account != null && (!SUPPORTED_PLATFORMS.includes(account.platform) || !text(account.key, 128))) invalid(); - if (confirmation != null && (!SUPPORTED_PLATFORMS.includes(confirmation.target_platform) - || !["live", "paper", "dry_run"].includes(confirmation.execution_mode) - || !RESEARCH_PROMOTION_RISK_PROFILES.includes(confirmation.risk_profile))) invalid(); - if (record.action === "accept" && (!account || !confirmation || account.platform !== confirmation.target_platform)) invalid(); - if (record.payload?.ticket?.live_authority_granted !== false || record.payload?.ticket?.human_decision !== record.action - || record.payload?.ticket?.ticket_id !== record.subject_id || record.payload.ticket.human_decided_at !== record.decided_at - || await researchPromotionMaterialKey(record.payload.ticket) !== record.material_key - || canonicalResearchTaskJson(record.payload.selected_account ?? null) !== canonicalResearchTaskJson(account ?? null) - || canonicalResearchTaskJson(record.payload.confirmation ?? null) !== canonicalResearchTaskJson(confirmation ?? null)) invalid(); - projection = { - selected_account: account ? { platform: account.platform, key: account.key } : null, - confirmation: confirmation ? { target_platform: confirmation.target_platform, execution_mode: confirmation.execution_mode, risk_profile: confirmation.risk_profile } : null, - }; - } else { - if (target?.decision !== record.action || record.payload?.no_order !== true || record.payload?.execution_authority_granted !== false) invalid(); - const payload = record.payload; - if (record.kind === "owner") { - if (payload.schema_version !== "qsl_owner_decision_intent.v1" || payload.candidate_id !== record.subject_id - || payload.candidate_evidence_sha256 !== record.material_key || payload.decision !== record.action - || payload.decided_by !== record.actor || payload.decided_at !== record.decided_at - || payload.decision_sha256 !== await calculateOwnerDecisionSha256(payload)) invalid(); - } else if (payload.mode === "initial_enrollment") { - const approval = record.action === "approve"; - if (payload.schema_version !== (approval ? "qsl_initial_enrollment_confirmation.v1" : "qsl_initial_enrollment_rejection.v1") - || payload.recovery_id !== record.subject_id || (approval ? payload.confirmed_by : payload.decided_by) !== record.actor - || (approval ? payload.confirmed_at : payload.decided_at) !== record.decided_at || (!approval && payload.decision !== "reject") - || (approval && payload.confirmation_sha256 !== await calculateReconciliationRecoveryConfirmationSha256(payload)) - || await recoveryDecisionMaterialKey(payload) !== record.material_key) invalid(); - } else { - const approval = record.action === "approve"; - if (payload.schema_version !== (approval ? "qsl_reconciliation_recovery_confirmation.v1" : "qsl_reconciliation_recovery_rejection.v1") - || payload.recovery_id !== record.subject_id || (approval ? payload.confirmed_by : payload.decided_by) !== record.actor - || (approval ? payload.confirmed_at : payload.decided_at) !== record.decided_at || (!approval && payload.decision !== "reject") - || await recoveryDecisionMaterialKey({ recovery_id: payload.recovery_id, candidate_sha256: payload.candidate_sha256, dual_review: { evidence_binding_sha256: payload.dual_review_binding_sha256 } }) !== record.material_key) invalid(); - } - projection = { decision: target.decision }; - } - // Extra stored target fields must not be silently dropped into an apparently matching receipt. - if (canonicalResearchTaskJson(target) !== canonicalResearchTaskJson(projection)) invalid(); - let reviewBinding = null; - if (record.kind === "promotion" && record.payload?.review_binding !== undefined) { - const material = record.payload.review_material; - if (material?.schema_version !== "qsl_promotion_review_material.v2" || material.ticket_id !== record.subject_id - || await researchPromotionMaterialKey(material) !== record.material_key - || !promotionReviewBindingsMatch(record.payload.review_binding, { schema_version: "qsl_promotion_review_binding.v2", review_sha256: await calculateOwnerDecisionSha256(material) })) invalid(); - reviewBinding = { schema_version: "qsl_promotion_review_binding.v2", review_sha256: record.payload.review_binding.review_sha256 }; - } - const receipt = { - schema_version: reviewBinding ? "qsl_human_decision_receipt.v2" : "qsl_human_decision_receipt.v1", - ...(reviewBinding ? { review_binding: reviewBinding } : {}), - kind: record.kind, subject_id: record.subject_id, material_sha256: record.material_key, - action: record.action, target: projection, decided_at: record.decided_at, decided_by: record.actor, - no_order: true, execution_authority_granted: false, - }; - return { ...receipt, receipt_sha256: await calculateOwnerDecisionSha256(receipt) }; -} - -async function exactHumanDecisionResponse(request, env, session, kind) { - const params = new URL(request.url).searchParams; - if (!params.has("decision_subject_id") && !params.has("decision_material_sha256")) return null; - requireSameOrigin(request); - if (!session.admin) return json({ ok: false, error: "admin required" }, 403); - const subjectId = params.get("decision_subject_id"); - const material = params.get("decision_material_sha256"); - if (params.getAll("decision_subject_id").length !== 1 || params.getAll("decision_material_sha256").length !== 1 - || !subjectId || subjectId.length > 256 || /[\u0000-\u001f\u007f]/.test(subjectId) || !/^[0-9a-f]{64}$/.test(material || "")) { - return json({ ok: false, error: "human_decision_lookup_invalid" }, 400); - } - try { - if (!hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instances_not_bound", 503); - const result = await runtimeInstanceCommand(env, { - action: `${kind}_read`, actor: session.login, subject_id: subjectId, exact_material_key: material, now_ms: Date.now(), - }); - if (result.ok !== true || result.exact_material_key !== material || !Object.prototype.hasOwnProperty.call(result, "decision")) { - throw new HttpError("human_decision_receipt_unavailable", 503); - } - const receipt = result.decision === null ? null : await humanDecisionReceipt(result.decision, { kind, subject_id: subjectId, material_sha256: material }); - return json({ ok: true, decision_readback: { - schema_version: "qsl_human_decision_readback.v1", source: "durable_human_decision_record", - kind, subject_id: subjectId, requested_material_sha256: material, observed_at: new Date().toISOString(), - lookup_status: receipt ? "found" : "not_found", receipt, current_material: result.current_material, - } }); - } catch { - // Unreadable authority is never reported as a completed empty lookup. - return json({ ok: false, error: "human_decision_receipt_unavailable" }, 503); - } -} - -async function readHumanDecisionAuthority(env, kind, subjectId) { - if (!hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instances_not_bound", 503); - return runtimeInstanceCommand(env, { action: `${kind}_read`, actor: `${kind}-decision-read`, subject_id: subjectId }); -} - -// A safe projection of durable material authority. It carries only digests and -// server-side expiry; raw provider payloads and credentials never enter a read model. -function humanDecisionAuthorityView(authority) { - if (!authority || typeof authority !== "object") return null; - const materialKey = /^[0-9a-f]{64}$/.test(authority.material_key || "") ? authority.material_key : null; - const expiresAt = typeof authority.expires_at === "string" && Number.isFinite(Date.parse(authority.expires_at)) - ? authority.expires_at - : null; - return { - material_sha256: materialKey, - expires_at: expiresAt, - eligible: typeof authority.eligible === "boolean" ? authority.eligible : null, - blocked: authority.blocked === true, - unavailable: authority.migrated === false, - source: "durable_human_decision_material", - }; -} - -function overlayPromotionDecision(ticket, authority) { - if (!authority || authority.migrated === false) return ticket; - const current = authority.eligible === true && authority.blocked !== true ? authority.decision : null; - if (current?.payload?.ticket && (current.action === "accept" || current.action === "reject")) { - return normalizeResearchPromotionTicket(current.payload.ticket, "human decision ticket"); - } - if (ticket.state !== "human_accepted" && ticket.state !== "human_rejected") return ticket; - return normalizeResearchPromotionTicket({ - ...ticket, - state: "awaiting_human", - human_decision: "", - human_decided_at: "", - confirmation_target_platform: "", - confirmation_execution_mode: "", - confirmation_risk_profile: "", - }, "human decision ticket"); -} - -async function promotionTicketWithAuthority(env, ticket) { - const authority = await readHumanDecisionAuthority(env, "promotion", ticket.ticket_id); - return overlayPromotionDecision(ticket, authority); -} - -async function syncControlPlaneResponse(request, env) { - requireDedicatedControlPlaneSyncToken(request, env); - if (!hasConfigStore(env)) { - return json({ ok: false, error: "control plane KV is not configured" }, 503); - } - - let raw; - try { - raw = await readBoundedJson(request, CONTROL_PLANE_MAX_BODY_BYTES); - } catch (error) { - return json({ ok: false, error: error.message || "invalid control plane payload" }, error.status || 400); - } - - let snapshot; - try { - snapshot = normalizeControlPlaneSnapshot(raw, "control plane snapshot"); - } catch (error) { - return json({ ok: false, error: error.message || "invalid control plane payload" }, 400); - } - try { - await publishHumanOwnerMaterials(env, snapshot, "control-plane-snapshot"); - } catch (error) { - return json({ ok: false, error: error.message || "owner decision material unavailable" }, error.status || 503); - } - - await writeConfigJson(env, CONTROL_PLANE_SNAPSHOT_KEY, snapshot); - try { - await appendAuditLog(env, { - ts: new Date().toISOString(), - login: "control-plane-sync", - action: "sync_control_plane", - schema_version: snapshot.schema_version, - candidate_count: snapshot.summary.candidate_count, - data_status: snapshot.data_status, - }); - } catch { - // A successful snapshot must not expose its payload through audit failures. - } - return json({ - ok: true, - schema_version: snapshot.schema_version, - candidate_count: snapshot.summary.candidate_count, - generated_at: snapshot.generated_at, - }); -} - -async function syncControlPlaneSourceResponse(request, env) { - const validationOnly = requireControlPlaneSourceSyncToken(request, env); - if (!hasConfigStore(env)) { - return json({ ok: false, error: "control plane KV is not configured" }, 503); - } - - let raw; - try { - raw = await readBoundedJson(request, CONTROL_PLANE_MAX_BODY_BYTES); - } catch (error) { - return json({ ok: false, error: error.message || "invalid control plane source payload" }, error.status || 400); - } - - let source; - try { - source = normalizeControlPlaneSourceSnapshot(raw, "control plane source snapshot"); - } catch (error) { - return json({ ok: false, error: error.message || "invalid control plane source payload" }, 400); - } - - if (validationOnly) { - const candidate = source.candidates[0]; - const runId = candidate?.candidate_id?.match(/^soxl_three_asset_mid_weight_validation_([1-9][0-9]*)$/)?.[1]; - if ( - source.source_id !== "aiaudit.soxl_manual_validation" || - source.candidates.length !== 1 || !runId || - candidate.candidate_kind !== "individual" || candidate.domain !== "us_equity" || - candidate.lifecycle.stage !== "P3" || candidate.lifecycle.status !== "parked" || - candidate.recommendation.code !== "park" || candidate.evidence.p3_evidence_id !== runId - ) { - return json({ ok: false, error: "validation publisher scope mismatch" }, 403); - } - } - try { - await publishHumanOwnerMaterials(env, source, source.source_id); - } catch (error) { - return json({ ok: false, error: error.message || "owner decision material unavailable" }, error.status || 503); - } - - await writeConfigJson(env, controlPlaneSourceKey(source.source_id), source); - try { - await appendAuditLog(env, { - ts: new Date().toISOString(), - login: "control-plane-source-sync", - action: "sync_control_plane_source", - source_id: source.source_id, - schema_version: source.schema_version, - candidate_count: source.candidates.length, - data_status: source.data_status, - }); - } catch { - // The source snapshot is accepted independently of an optional audit write. - } - return json({ - ok: true, - source_id: source.source_id, - schema_version: source.schema_version, - candidate_count: source.candidates.length, - generated_at: source.generated_at, - }); -} - -async function controlPlaneResponse(request, env) { - const session = await readSession(request, env); - if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); - return json(await currentControlPlanePayload(env)); -} - -async function currentControlPlanePayload(env) { - if (!hasConfigStore(env)) return emptyControlPlanePayload("snapshot_unavailable"); - - const sourceSnapshot = await aggregateControlPlaneSources(env); - if (sourceSnapshot) return sourceSnapshot; - - let snapshot; - try { - const stored = await readConfigJson(env, CONTROL_PLANE_SNAPSHOT_KEY); - if (!stored) return emptyControlPlanePayload("snapshot_unavailable"); - snapshot = normalizeControlPlaneSnapshot(stored, CONTROL_PLANE_SNAPSHOT_KEY); - } catch { - return emptyControlPlanePayload("snapshot_invalid"); - } - - const freshness = controlPlaneResearchSnapshotFreshness( - snapshot, - controlPlaneStaleTtlSeconds(env), - Date.now(), - ); - snapshot.data_status = freshness.data_status; - return snapshot; -} - -function isOwnerDecisionCandidate(candidate) { - return candidate?.lifecycle?.stage === "P6" - && candidate.lifecycle.status === "owner_decision_required" - && candidate.recommendation?.code === "owner_live_decision"; -} - -function currentOwnerDecisionCandidate(controlPlane, candidateId) { - if (controlPlane?.data_status !== "ready") { - throw new HttpError("current control-plane evidence is not ready", 409); - } - const candidate = controlPlane.candidates.find((item) => item.candidate_id === candidateId); - if (!candidate || !isOwnerDecisionCandidate(candidate)) { - throw new HttpError("candidate is not awaiting an owner decision", 409); - } - if (candidate.freshness?.status !== "fresh") { - throw new HttpError("candidate evidence is not fresh", 409); - } - return candidate; -} - -function normalizeOwnerDecisionRequest(payload) { - const value = assertRequiredAndOptionalFields(payload, [ - "candidate_id", "decision", "candidate_evidence_sha256", - ], ["request_id"], "owner decision request"); - return { - candidate_id: normalizeControlPlaneIdentifier(value.candidate_id, "owner decision request.candidate_id", false), - decision: cleanChoice(value.decision, OWNER_DECISION_CHOICES, "owner decision request.decision"), - candidate_evidence_sha256: normalizeResearchTaskDigest( - value.candidate_evidence_sha256, - "owner decision request.candidate_evidence_sha256", - ), - request_id: value.request_id, - }; -} - -async function ownerDecisionCandidateEvidenceSha256(candidate) { - return await calculateOwnerDecisionSha256({ - candidate_id: candidate.candidate_id, - candidate_kind: candidate.candidate_kind, - domain: candidate.domain, - lifecycle: candidate.lifecycle, - evidence: candidate.evidence, - recommendation: { code: candidate.recommendation?.code || "none" }, - }); -} - -async function buildOwnerDecisionIntent({ candidate, decision, decidedBy, candidateEvidenceSha256, decidedAt }) { - const intent = { - schema_version: OWNER_DECISION_INTENT_SCHEMA_VERSION, - candidate_id: candidate.candidate_id, - candidate_kind: candidate.candidate_kind, - domain: candidate.domain, - decision, - decided_at: decidedAt, - decided_by: decidedBy, - candidate_evidence_sha256: candidateEvidenceSha256, - no_order: true, - execution_authority_granted: false, - decision_sha256: "", - }; - intent.decision_sha256 = await calculateOwnerDecisionSha256(intent); - return await normalizeOwnerDecisionIntent(intent, "owner decision intent"); -} - -async function normalizeOwnerDecisionIntent(payload, fieldName) { - const value = assertExactFields(payload, [ - "schema_version", "candidate_id", "candidate_kind", "domain", "decision", "decided_at", "decided_by", - "candidate_evidence_sha256", "no_order", "execution_authority_granted", "decision_sha256", - ], fieldName); - if (value.schema_version !== OWNER_DECISION_INTENT_SCHEMA_VERSION) { - throw new Error(`${fieldName}.schema_version is unsupported`); - } - const intent = { - schema_version: OWNER_DECISION_INTENT_SCHEMA_VERSION, - candidate_id: normalizeControlPlaneIdentifier(value.candidate_id, `${fieldName}.candidate_id`, false), - candidate_kind: cleanChoice(value.candidate_kind, CONTROL_PLANE_CANDIDATE_KINDS, `${fieldName}.candidate_kind`), - domain: cleanChoice(value.domain, STRATEGY_HEALTH_DOMAINS, `${fieldName}.domain`), - decision: cleanChoice(value.decision, OWNER_DECISION_CHOICES, `${fieldName}.decision`), - decided_at: normalizeResearchTaskTimestamp(value.decided_at, `${fieldName}.decided_at`), - decided_by: cleanGithubLogin(value.decided_by, `${fieldName}.decided_by`), - candidate_evidence_sha256: normalizeResearchTaskDigest( - value.candidate_evidence_sha256, - `${fieldName}.candidate_evidence_sha256`, - ), - no_order: value.no_order, - execution_authority_granted: value.execution_authority_granted, - decision_sha256: normalizeResearchTaskDigest(value.decision_sha256, `${fieldName}.decision_sha256`), - }; - if (intent.no_order !== true || intent.execution_authority_granted !== false) { - throw new Error(`${fieldName} must remain no-order and non-executable`); - } - if (intent.decision_sha256 !== await calculateOwnerDecisionSha256(intent)) { - throw new Error(`${fieldName}.decision_sha256 mismatch`); - } - return intent; -} - -async function calculateOwnerDecisionSha256(payload) { - const material = { ...payload }; - delete material.decision_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 readOwnerDecisionIntent(env, candidateId, materialKey) { - let authority; - try { - authority = await readHumanDecisionAuthority(env, "owner", candidateId); - } catch (error) { - return { intent: null, authority: null, error: error.message || "runtime_instances_unavailable" }; - } - const authorityView = humanDecisionAuthorityView(authority); - if (authority.blocked) return { intent: null, authority: authorityView, error: "owner_decision_legacy_blocked" }; - if (authority.decision && authority.material_key === materialKey && authority.decision.payload?.no_order === true && authority.decision.payload?.execution_authority_granted === false) { - return { intent: authority.decision.payload, authority: authorityView, error: null }; - } - if (authority.migrated === false) { - try { - const stored = await readConfigJson(env, ownerDecisionCurrentKey(candidateId)); - if (!stored) return { intent: null, authority: authorityView, error: null }; - const intent = await normalizeOwnerDecisionIntent(stored, ownerDecisionCurrentKey(candidateId)); - if (materialKey && intent.candidate_evidence_sha256 !== materialKey) return { intent: null, authority: authorityView, error: null }; - return { intent, authority: authorityView, error: null, read_only: true }; - } catch { - return { intent: null, authority: authorityView, error: "owner_decision_intent_invalid" }; - } - } - return { intent: null, authority: authorityView, error: null }; -} - -function ownerDecisionArchiveKey(candidateId, decisionSha256) { - return `${OWNER_DECISION_INTENT_PREFIX}${candidateId}:${decisionSha256}`; -} - -function ownerDecisionCurrentKey(candidateId) { - return `${OWNER_DECISION_CURRENT_PREFIX}${candidateId}`; -} - -function emptyOwnerDecisionQueue(errorCode, controlPlane = null) { - return { - schema_version: OWNER_DECISION_QUEUE_SCHEMA_VERSION, - data_status: controlPlane?.data_status === "stale" ? "stale" : "unavailable", - computed_at: controlPlane?.computed_at || null, - candidates: [], - policy: { - admin_required: true, - current_evidence_required: true, - execution_authority_granted: false, - no_order: true, - notice: "网页只记录所有者决定意图;不会下单、变更资金或启用实盘。", - }, - errors: uniqueStrings([...(controlPlane?.errors || []), errorCode]), - }; -} - -async function ownerDecisionQueueResponse(request, env) { - return ownerDecisionQueueResponseImpl(request, env, { - json, - readSession, - exactHumanDecisionResponse, - hasConfigStore, - currentControlPlanePayload, - isOwnerDecisionCandidate, - ownerDecisionCandidateEvidenceSha256, - readOwnerDecisionIntent, - emptyOwnerDecisionQueue, - uniqueStrings, - OWNER_DECISION_MAX_CANDIDATES, - OWNER_DECISION_QUEUE_SCHEMA_VERSION, - }); -} - -async function recordOwnerDecisionResponse(request, env) { - return recordOwnerDecisionResponseImpl(request, env, { - json, - requireSameOrigin, - readSession, - hasConfigStore, - readBoundedJson, - normalizeOwnerDecisionRequest, - currentControlPlanePayload, - currentOwnerDecisionCandidate, - ownerDecisionCandidateEvidenceSha256, - humanDecisionTimestamp, - buildOwnerDecisionIntent, - canonicalResearchTaskJson, - humanDecisionRequestKey, - runtimeInstanceCommand, - humanDecisionReceipt, - mirrorConfigJson, - ownerDecisionArchiveKey, - ownerDecisionCurrentKey, - appendAuditLog, - }); -} - -// Recovery snapshots contain digest-bound review metadata. Initial enrollment -// additionally carries bounded broker facts for the authenticated review; -// raw receipts remain in the platform's protected source, and confirmation -// records intent without granting execution authority. -async function initialEnrollmentAccountOption(env, item) { - if (!await initialEnrollmentBindingsMatch(item.candidate, item.facts) - || item.candidate.platform_id.toLowerCase() !== "ibkr" - || item.facts.observed_at !== item.candidate.first_observed_at - || item.facts.observed_at !== item.candidate.last_observed_at) throw new HttpError("initial_enrollment_invalid", 409); - const config = await loadAccountOptionsConfig(env); - const configured = await loadAccountFactsBindings(env, { propagateReadErrors: true }); - const bindings = (configured?.bindings || []).filter(binding => binding.platform === "ibkr" && binding.target_id === item.target_id); - const options = (config.options?.ibkr || []).filter(option => bindings.length === 1 && option.key === bindings[0].account_key); - if (options.length !== 1 || bindings.length !== 1 || !accountFactsOptionMatchesBinding(options[0], bindings[0]) - || await initialEnrollmentDigest({ account_ids: [bindings[0].account_selector] }, true) !== item.candidate.account_scope_sha256) { - throw new HttpError("initial_enrollment_identity_mismatch", 409); - } - return options[0]; -} - -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"); - for (const recovery of source.recoveries) if (recovery.mode === "initial_enrollment") await initialEnrollmentAccountOption(env, recovery); - } catch (error) { - return json({ ok: false, error: error.message || "invalid reconciliation recovery payload" }, 400); - } - try { - await publishHumanRecoveryMaterials(env, source); - } catch (error) { - return json({ ok: false, error: error.message || "reconciliation recovery material unavailable" }, error.status || 503); - } - 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); - const exact = await exactHumanDecisionResponse(request, env, session, "recovery"); - if (exact) return exact; - if (!hasConfigStore(env)) return json(emptyReconciliationRecoveryPayload("snapshot_unavailable")); - return json(decorateQueue(await aggregateReconciliationRecoverySources(env), "recoveries", "reconciliation_recovery")); -} - -async function syncBinancePrivateScopeResponse(request, env) { - requireDedicatedReconciliationRecoverySyncToken(request, env); - const store = configStore(env); - if (!store) { - return json({ ok: false, error: "binance_private_scope_storage_unavailable" }, 503); - } - let raw; - try { - raw = await readBoundedJson(request, BINANCE_PRIVATE_SCOPE_MAX_BODY_BYTES); - } catch (error) { - return json({ ok: false, error: "invalid_binance_private_scope_payload" }, error.status === 413 ? 413 : 400); - } - let report; - try { - report = normalizeBinancePrivateScopeReport(raw, { requireIngestFreshness: true }); - } catch { - return json({ ok: false, error: "invalid_binance_private_scope_payload" }, 400); - } - try { - await store.put(BINANCE_PRIVATE_SCOPE_KEY, JSON.stringify(report), { - expirationTtl: BINANCE_PRIVATE_SCOPE_WRITE_TTL_SECONDS, - }); - } catch { - return json({ ok: false, error: "binance_private_scope_storage_unavailable" }, 503); - } - return json({ - ok: true, - observed_at: report.observed_at, - source_run_id: report.source_run_id, - asset_count: report.assets.length, - }); -} - -async function trustedBinanceFactsBinding(env, readiness = null) { - const text = String(env.BINANCE_ACCOUNT_FACTS_BINDING_JSON || ""); - if (!text) return null; - let binding; - try { binding = normalizeBinanceFactsBinding(JSON.parse(text)); } - catch { throw new BinanceFactsError("binance_account_facts_binding_invalid", 503); } - await assertBinanceFactsSourceBinding(binding); - if (readiness) readiness.binding_valid = true; - let config; - try { config = await loadAccountOptionsConfig(env); } - catch { - throw new BinanceFactsError("binance_account_facts_account_options_unavailable", 503); - } - if (readiness) readiness.account_options_readable = true; - const options = config.options?.binance || []; - const matchCount = options.filter(option => binanceFactsOptionMatches(option, binding)).length; - if (matchCount !== 1) { - throw new BinanceFactsError("binance_account_facts_binding_unmatched", 409); - } - if (readiness) readiness.unique_match = true; - return binding; -} - -function binanceFactsErrorResponse(error) { - return json({ ok: false, error: error instanceof BinanceFactsError - ? error.code : "binance_account_facts_unavailable" }, error instanceof BinanceFactsError ? error.status : 503); -} - -function binanceFactsSyncAuthorizationResponse(request, env) { - const token = String(env.BINANCE_ACCOUNT_FACTS_SYNC_TOKEN || ""); - const otherTokens = ["ACCOUNT_FACTS_SYNC_TOKEN", "IBKR_ACCOUNT_FACTS_SYNC_TOKEN", "SCHWAB_ACCOUNT_FACTS_SYNC_TOKEN", "FIRSTRADE_ACCOUNT_FACTS_SYNC_TOKEN", - "RECONCILIATION_RECOVERY_SYNC_TOKEN", "RECONCILIATION_RECOVERY_CONTROLLER_TOKEN", "STRATEGY_SWITCH_SYNC_TOKEN"]; - if (!token || otherTokens.some(name => env[name] && env[name] === token)) { - return json({ ok: false, error: "binance_account_facts_token_unavailable" }, 503); - } - if ((request.headers.get("Authorization") || "").match(/^Bearer\s+(.+)$/i)?.[1] !== token) { - return json({ ok: false, error: "binance_account_facts_token_invalid" }, 401); - } - return null; -} - -async function binanceDigestSnapshotResponse(request, env, url) { - const token = env.BINANCE_DIGEST_READ_TOKEN; - const otherTokens = ["BINANCE_ACCOUNT_FACTS_SYNC_TOKEN", "ACCOUNT_FACTS_SYNC_TOKEN", "IBKR_ACCOUNT_FACTS_SYNC_TOKEN", - "SCHWAB_ACCOUNT_FACTS_SYNC_TOKEN", "FIRSTRADE_ACCOUNT_FACTS_SYNC_TOKEN", "RECONCILIATION_RECOVERY_SYNC_TOKEN", - "RECONCILIATION_RECOVERY_CONTROLLER_TOKEN", "STRATEGY_SWITCH_SYNC_TOKEN", "SESSION_SECRET"]; - if (typeof token !== "string" || !/^[A-Za-z0-9_-]{32,256}$/.test(token) - || otherTokens.some(name => env[name] === token)) { - return json({ ok: false, error: "digest_snapshot_read_unavailable" }, 503); - } - if (request.headers.get("Authorization") !== `Bearer ${token}`) { - return json({ ok: false, error: "digest_snapshot_read_unauthorized" }, 401); - } - const day = url.searchParams.get("business_day"); - const start = Date.parse(`${day}T06:00:00+08:00`); - const end = start + 86400000; - if (!/^\d{4}-\d{2}-\d{2}$/.test(day || "") || !Number.isFinite(start) - || new Date(start + 8 * 3600000).toISOString().slice(0, 10) !== day || end > Date.now()) { - return json({ ok: false, error: "digest_snapshot_window_invalid" }, 400); - } - try { - const binding = await trustedBinanceFactsBinding(env); - if (!binding || !configStore(env)) throw new Error("digest_snapshot_read_unavailable"); - const retained = await runtimeInstanceCommand(env, { - action: "binance_detail_read", binding, date: day, business_day: true, - }); - if (retained?.ok !== true) throw new Error("digest_snapshot_read_unavailable"); - // The existing latest cache can supply a snapshot from before this - // retention path was released, provided it still matches the window. - const raw = retained.report ?? await readConfigJson(env, `${BINANCE_FACTS_KEY}:${binding.source_binding.id}`); - if (!raw) return json({ ok: false, error: "digest_snapshot_missing" }, 404); - const report = normalizeBinanceAccountFacts(raw, binding); - const observedFrom = Date.parse(report.observed_started_at); - const observedUntil = Date.parse(report.observed_finished_at); - if (observedFrom < start || observedUntil >= end) { - return json({ ok: false, error: "digest_snapshot_missing" }, 404); - } - const valuation = report.wallet_valuation; - const wallet = url.searchParams.get("include_valuation") === "1" && valuation?.status === "available" - ? { wallet_valuation: { - amount: valuation.amount, currency: valuation.currency, as_of: valuation.observed_at, - } } - : {}; - return json({ ok: true, business_day: day, account_key: binding.account_key, - target_id: binding.target_id, as_of: report.observed_finished_at, - holdings: report.assets.map(({ asset, quantity }) => ({ asset, quantity })), ...wallet }); - } catch { - return json({ ok: false, error: "digest_snapshot_read_unavailable" }, 503); - } -} - -async function binanceAccountFactsReadinessResponse(request, env) { - const authError = binanceFactsSyncAuthorizationResponse(request, env); - if (authError) return authError; - const readiness = { binding_valid: false, account_options_readable: false, unique_match: false }; - try { - const binding = await trustedBinanceFactsBinding(env, readiness); - if (!binding) throw new BinanceFactsError("binance_account_facts_binding_missing", 503); - return json({ ok: true, ready: true, ...readiness }); - } catch (error) { - return json({ ok: false, ready: false, ...readiness, - error: error instanceof BinanceFactsError ? error.code : "binance_account_facts_unavailable" }, - error instanceof BinanceFactsError ? error.status : 503); - } -} - -async function syncBinanceAccountFactsResponse(request, env) { - const authError = binanceFactsSyncAuthorizationResponse(request, env); - if (authError) return authError; - try { - const binding = await trustedBinanceFactsBinding(env); - if (!binding) throw new BinanceFactsError("binance_account_facts_binding_missing", 503); - const raw = await readBoundedJson(request, BINANCE_FACTS_MAX_BYTES); - const report = normalizeBinanceAccountFacts(raw, binding, { ingest: true }); - const store = configStore(env); - if (!store) throw new BinanceFactsError("binance_account_facts_storage_unavailable", 503); - if (!hasRuntimeInstanceStore(env)) throw new BinanceFactsError("binance_account_facts_history_storage_unavailable", 503); - const storageKey = `${BINANCE_FACTS_KEY}:${binding.source_binding.id}`; - const previous = await readConfigJson(env, storageKey); - let unchanged = false; - if (previous) { - const validPrevious = normalizeBinanceAccountFacts(previous, binding); - const incomingTime = Date.parse(report.observed_finished_at); - const previousTime = Date.parse(validPrevious.observed_finished_at); - if (incomingTime < previousTime || (incomingTime === previousTime - && canonicalJson(report) !== canonicalJson(validPrevious))) { - throw new BinanceFactsError("binance_account_facts_observation_conflict", 409); - } - unchanged = incomingTime === previousTime; - } - try { - const detail = await runtimeInstanceCommand(env, { - action: "binance_detail_put", binding, report, date: report.observed_finished_at.slice(0, 10), - }); - if (detail.readback_verified !== true) throw new Error("binance_detail_readback_failed"); - const history = binanceWalletHistoryEntry(report, binding); - await runtimeInstanceCommand(env, { - action: "binance_wallet_history_put", - account_key: binding.account_key, - storage_account_key: binanceWalletHistoryStorageKey(binding), - target_id: binding.target_id, - account_scope: binding.account_scope, - source_binding_id: binding.source_binding.id, - history, - }); - } catch { - throw new BinanceFactsError("binance_account_facts_history_storage_unavailable", 503); - } - if (unchanged) return json({ ok: true, status: "unchanged" }); - await store.put(storageKey, JSON.stringify(report), { expirationTtl: 2 * 24 * 60 * 60 }); - return json({ ok: true, status: "published", observed_finished_at: report.observed_finished_at, - asset_count: report.assets.length }); - } catch (error) { - if (error?.status === 413) return json({ ok: false, error: "binance_account_facts_payload_too_large" }, 413); - return binanceFactsErrorResponse(error); - } -} - -async function binanceDetailArchiveResponse(request, env, url) { - const authError = binanceFactsSyncAuthorizationResponse(request, env); - if (authError) return authError; - try { - const binding = await trustedBinanceFactsBinding(env); - if (!binding) throw new Error("binding_missing"); - const result = await runtimeInstanceCommand(env, { - action: "binance_detail_read", binding, date: url.searchParams.get("date"), - }); - return json(result, result.report ? 200 : 404); - } catch { - return json({ ok: false, error: "binance_detail_archive_unavailable" }, 503); - } -} - -async function binanceWalletHistoryResponse(request, env, url) { - if (!accountFactsReadModelEnabled(env)) return accountFactsDisabledResponse(); - const session = await readSession(request, env); - if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); - if (!hasRuntimeInstanceStore(env)) return json({ ok: false, error: "account_facts_store_unavailable" }, 503); - try { - const binding = await trustedBinanceFactsBinding(env); - if (!binding) throw new BinanceFactsError("binance_account_facts_binding_missing", 503); - if (url.searchParams.get("account_key") !== binding.account_key) { - throw new BinanceFactsError("binance_account_facts_binding_unmatched", 409); - } - let historicalBindings; - try { - const encoded = String(env.BINANCE_ACCOUNT_FACTS_HISTORY_BINDINGS_JSON || "[]"); - historicalBindings = await assertBinanceFactsHistoryBindings(JSON.parse(encoded), binding); - } catch { - throw new BinanceFactsError("binance_account_facts_history_bindings_invalid", 503); - } - const buckets = []; - for (const sourceBinding of [binding, ...historicalBindings]) { - const stored = await runtimeInstanceCommand(env, { - action: "binance_wallet_history_read", - account_key: binding.account_key, - storage_account_key: binanceWalletHistoryStorageKey(sourceBinding), - target_id: binding.target_id, - account_scope: binding.account_scope, - source_binding_id: sourceBinding.source_binding.id, - }); - buckets.push({ binding: sourceBinding, days: Array.isArray(stored.days) ? stored.days : [] }); - } - return json(projectBinanceWalletHistoryFromSources(buckets, binding, historicalBindings)); - } catch (error) { - const allowedErrors = new Set([ - "binance_account_facts_binding_invalid", "binance_account_facts_account_options_unavailable", - "binance_account_facts_binding_unmatched", "binance_account_facts_binding_missing", - "binance_wallet_history_identity_mismatch", "binance_wallet_history_stored_invalid", - "binance_wallet_history_conflict", "binance_account_facts_history_bindings_invalid", - ]); - const code = allowedErrors.has(error?.code || error?.message) - ? (error.code || error.message) : "binance_wallet_history_unavailable"; - return json({ ok: false, error: code }, error?.status || 503); - } -} - -async function binanceAccountFactsResponse(request, env) { - const session = await readSession(request, env); - if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); - try { - const binding = await trustedBinanceFactsBinding(env); - if (!binding) return json({ ok: true, report_status: "unavailable", report: null }); - const raw = await readConfigJson(env, `${BINANCE_FACTS_KEY}:${binding.source_binding.id}`); - if (!raw) return json({ ok: true, report_status: "missing", report: null }); - const report = projectBinanceAccountFacts(raw, binding); - if (!report) return json({ ok: true, report_status: "expired", report: null }); - return json({ ok: true, report_status: "available", report }); - } catch { return json({ ok: true, report_status: "unavailable", report: null }); } -} - -async function binancePrivateScopeResponse(request, env) { - const session = await readSession(request, env); - if (!session) return json({ ok: false, error: "login required" }, 401); - if (!session.allowed || !session.admin) return json({ ok: false, error: "admin required" }, 403); - if (!hasConfigStore(env)) return json({ ok: true, report: null }); - try { - const raw = await readConfigJson(env, BINANCE_PRIVATE_SCOPE_KEY); - if (!raw) return json({ ok: true, report: null }); - const report = normalizeBinancePrivateScopeReport(raw); - const ageMs = Date.now() - Date.parse(report.observed_at); - if (ageMs > BINANCE_PRIVATE_SCOPE_READ_MAX_AGE_MS || ageMs < -BINANCE_PRIVATE_SCOPE_FUTURE_SKEW_MS) { - return json({ ok: true, report: null }); - } - return json({ ok: true, report }); - } catch { - return json({ ok: true, report: null }); - } -} - -// This is a least-privilege service-to-service read for a platform-owned -// controller. It deliberately returns only the current, redacted digest -// binding and immutable human receipt; broker state remains in the platform. -async function reconciliationRecoveryControllerReadResponse(request, env, url) { - requireDedicatedReconciliationRecoveryControllerToken(request, env); - if (!hasConfigStore(env)) { - return json({ ok: false, error: "reconciliation recovery KV is not configured" }, 503); - } - let recoveryId; - try { - recoveryId = normalizeControlPlaneIdentifier( - url.searchParams.get("recovery_id") || "", - "reconciliation recovery controller recovery_id", - false, - ); - } catch (error) { - return json({ ok: false, error: error.message || "invalid recovery_id" }, 400); - } - const dashboard = await aggregateReconciliationRecoverySources(env); - let recovery; - try { - recovery = currentReconciliationRecoveryRequest(dashboard, recoveryId); - } catch (error) { - return json({ ok: false, error: error.message || "reconciliation recovery is unavailable" }, error.status || 409); - } - const entry = dashboard.recoveries.find((item) => item.recovery?.recovery_id === recoveryId); - if (!entry?.confirmation) { - return json({ ok: false, error: "reconciliation recovery has no current confirmation" }, 404); - } - if (recovery.mode === "initial_enrollment") return json({ ok: true, schema_version: "qsl_initial_enrollment_controller_read.v1", - recovery: { mode: recovery.mode, recovery_id: recovery.recovery_id, candidate_sha256: recovery.candidate_sha256, facts_sha256: recovery.facts_sha256 }, - confirmation: entry.confirmation, policy: { no_order: true, execution_authority_granted: false, controller_must_reverify: true } }); - return json({ - ok: true, - schema_version: RECONCILIATION_RECOVERY_CONTROLLER_READ_SCHEMA_VERSION, - recovery: { - recovery_id: recovery.recovery_id, - platform: recovery.platform, - strategy_profile: recovery.strategy_profile, - environment: recovery.environment, - reconciliation_state: recovery.reconciliation_state, - candidate_sha256: recovery.candidate_sha256, - dual_review_binding_sha256: recovery.dual_review.evidence_binding_sha256, - evidence_sample_count: recovery.evidence_sample_count, - first_observed_at: recovery.first_observed_at, - last_observed_at: recovery.last_observed_at, - }, - confirmation: entry.confirmation, - policy: { - no_order: true, - execution_authority_granted: false, - controller_must_reverify: true, - }, - }); -} - -function currentReconciliationRecoveryRequest(dashboard, recoveryId) { - const entry = dashboard?.recoveries?.find((item) => item.recovery?.recovery_id === recoveryId); - if (entry?.recovery?.mode !== "initial_enrollment" && dashboard?.data_status !== "ready") { - throw new HttpError("current reconciliation recovery evidence is not ready", 409); - } - if (!entry || entry.freshness?.data_status !== "ready") { - throw new HttpError("reconciliation recovery request is not fresh", 409); - } - const recovery = entry.recovery; - if (recovery.mode === "initial_enrollment") { - if (recovery.readiness !== "awaiting_human_confirmation" || recovery.blocker_codes.length) throw new HttpError("initial_enrollment_incomplete", 409); - return recovery; - } - if ( - recovery.readiness !== "awaiting_human_confirmation" || - recovery.reconciliation_state !== "RECONCILE_ONLY" || - recovery.blocker_codes.length || - recovery.dual_review.evidence_binding_sha256 !== recovery.candidate_sha256 || - recovery.evidence_sample_count < 1 - ) { - throw new HttpError("reconciliation recovery request is not eligible for confirmation", 409); - } - return recovery; -} - -function normalizeReconciliationRecoveryConfirmationRequest(payload) { - if (payload?.mode === "initial_enrollment") { - const value = assertRequiredAndOptionalFields(payload, ["mode", "recovery_id", "candidate_sha256", "facts_sha256"], ["decision", "request_id"], "initial enrollment confirmation request"); - const decision = value.decision ?? "approve"; - if (!["approve", "reject"].includes(decision)) throw new Error("initial enrollment decision unsupported"); - return { mode: "initial_enrollment", recovery_id: normalizeControlPlaneIdentifier(value.recovery_id, "recovery_id", false), - candidate_sha256: normalizeResearchTaskDigest(value.candidate_sha256, "candidate_sha256"), - facts_sha256: normalizeResearchTaskDigest(value.facts_sha256, "facts_sha256"), decision, request_id: value.request_id }; - } - const value = assertRequiredAndOptionalFields(payload, [ - "recovery_id", "candidate_sha256", "dual_review_binding_sha256", - ], ["decision", "request_id"], "reconciliation recovery confirmation request"); - const decision = Object.prototype.hasOwnProperty.call(value, "decision") ? value.decision : "approve"; - if (decision !== "approve" && decision !== "reject") { - throw new Error("reconciliation recovery confirmation request.decision is unsupported"); - } - 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", - ), - decision, - request_id: value.request_id, - }; -} - -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) { - let authority; - try { - authority = await readHumanDecisionAuthority(env, "recovery", recoveryId); - } catch (error) { - return { confirmation: null, rejection: null, authority: null, error: error.message || "runtime_instances_unavailable" }; - } - const authorityView = humanDecisionAuthorityView(authority); - if (authority.migrated === false) { - try { - const stored = await readConfigJson(env, reconciliationRecoveryConfirmationCurrentKey(recoveryId)); - if (!stored) return { confirmation: null, rejection: null, authority: authorityView, error: null }; - return { - confirmation: await normalizeReconciliationRecoveryConfirmation( - stored, - reconciliationRecoveryConfirmationCurrentKey(recoveryId), - ), - rejection: null, - authority: authorityView, - error: null, - read_only: true, - }; - } catch { - return { confirmation: null, rejection: null, authority: authorityView, error: "reconciliation_recovery_confirmation_invalid" }; - } - } - if (authority.blocked) return { confirmation: null, rejection: null, authority: authorityView, error: "reconciliation_recovery_legacy_blocked" }; - if (authority.eligible !== true) return { confirmation: null, rejection: null, authority: authorityView, error: null }; - if (authority.decision?.action === "reject") { - const payload = authority.decision.payload; - if (payload?.decision === "reject" && payload?.no_order === true && payload?.execution_authority_granted === false) { - return { confirmation: null, rejection: payload, authority: authorityView, error: null }; - } - return { confirmation: null, rejection: null, authority: authorityView, error: null }; - } - if (authority.decision?.action === "approve" && authority.decision.payload?.no_order === true && authority.decision.payload?.execution_authority_granted === false) { - return { confirmation: authority.decision.payload, rejection: null, authority: authorityView, error: null }; - } - return { confirmation: null, rejection: null, authority: authorityView, error: null }; -} - -async function recordInitialEnrollmentDecision(env, session, requested, recovery) { - if (recovery.mode !== "initial_enrollment" || requested.candidate_sha256 !== recovery.candidate_sha256 - || requested.facts_sha256 !== recovery.facts_sha256) return json({ ok: false, error: "initial_enrollment_changed" }, 409); - const decidedAt = humanDecisionTimestamp(); - const materialKey = await recoveryDecisionMaterialKey(recovery); - const base = { mode: "initial_enrollment", recovery_id: recovery.recovery_id, - candidate_sha256: recovery.candidate_sha256, facts_sha256: recovery.facts_sha256, - no_order: true, execution_authority_granted: false }; - const payload = requested.decision === "approve" - ? { schema_version: "qsl_initial_enrollment_confirmation.v1", ...base, confirmed_at: decidedAt, confirmed_by: session.login } - : { schema_version: "qsl_initial_enrollment_rejection.v1", ...base, decision: "reject", decided_at: decidedAt, decided_by: session.login }; - if (requested.decision === "approve") payload.confirmation_sha256 = await calculateReconciliationRecoveryConfirmationSha256(payload); - const target = { decision: requested.decision }; - try { - const requestKey = await humanDecisionRequestKey({ requestId: requested.request_id, kind: "recovery", actor: session.login, - subjectId: recovery.recovery_id, materialKey, action: requested.decision, target }); - const recorded = await runtimeInstanceCommand(env, { action: "recovery_decide", actor: session.login, - subject_id: recovery.recovery_id, material_key: materialKey, action_name: requested.decision, - request_key: requestKey, target_json: canonicalResearchTaskJson(target), decided_at: decidedAt, now_ms: Date.now(), payload }); - const decisionReceipt = await humanDecisionReceipt(recorded.record, { kind: "recovery", subject_id: recovery.recovery_id, material_sha256: materialKey }); - return json({ ok: true, confirmation: recorded.record?.action === "approve" ? recorded.record.payload : null, - rejection: recorded.record?.action === "reject" ? recorded.record.payload : null, decision_receipt: decisionReceipt, replayed: recorded.replayed === true }); - } catch (error) { return json({ ok: false, error: error.message || "initial_enrollment_unavailable" }, error.status || 503); } -} - -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.mode === "initial_enrollment") return recordInitialEnrollmentDecision(env, session, requested, recovery); - if (recovery.mode === "initial_enrollment" || - 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 decidedAt = humanDecisionTimestamp(); - const materialKey = await recoveryDecisionMaterialKey(recovery); - const confirmation = requested.decision === "approve" - ? await buildReconciliationRecoveryConfirmation({ recovery, confirmedBy: session.login, confirmedAt: decidedAt }) - : null; - const rejection = requested.decision === "reject" ? { - schema_version: "qsl_reconciliation_recovery_rejection.v1", - recovery_id: recovery.recovery_id, - candidate_sha256: recovery.candidate_sha256, - dual_review_binding_sha256: recovery.dual_review.evidence_binding_sha256, - decision: "reject", - decided_at: decidedAt, - decided_by: session.login, - no_order: true, - execution_authority_granted: false, - } : null; - const target = { decision: requested.decision }; - let requestKey; - try { - requestKey = await humanDecisionRequestKey({ - requestId: requested.request_id, - kind: "recovery", - actor: session.login, - subjectId: recovery.recovery_id, - materialKey, - action: requested.decision, - target, - }); - } catch (error) { - return json({ ok: false, error: error.message || "invalid reconciliation recovery confirmation" }, error.status || 400); - } - let recorded; - try { - recorded = await runtimeInstanceCommand(env, { - action: "recovery_decide", - actor: session.login, - subject_id: recovery.recovery_id, - material_key: materialKey, - action_name: requested.decision, - request_key: requestKey, - target_json: canonicalResearchTaskJson(target), - decided_at: decidedAt, - now_ms: Date.now(), - payload: confirmation || rejection, - }); - } catch (error) { - return json({ ok: false, error: error.message || "reconciliation recovery confirmation is unavailable" }, error.status || 503); - } - const decisionReceipt = await humanDecisionReceipt(recorded.record, { kind: "recovery", subject_id: recovery.recovery_id, material_sha256: materialKey }); - const recordedPayload = recorded.record.payload; - if (recorded.record?.action === "approve") { - await mirrorConfigJson(env, reconciliationRecoveryConfirmationArchiveKey(recovery.recovery_id, recordedPayload.confirmation_sha256), recordedPayload); - await mirrorConfigJson(env, reconciliationRecoveryConfirmationCurrentKey(recovery.recovery_id), recordedPayload); - } - let auditLogged = false; - try { - await appendAuditLog(env, { - ts: recorded.record?.decided_at || decidedAt, - login: session.login, - action: "record_reconciliation_recovery_confirmation", - recovery_id: recovery.recovery_id, - candidate_sha256: recovery.candidate_sha256, - dual_review_binding_sha256: recovery.dual_review.evidence_binding_sha256, - confirmation_sha256: recordedPayload.confirmation_sha256 || "", - decision: recorded.record?.action || requested.decision, - 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: recorded.record?.action === "approve" ? recordedPayload : null, - rejection: recorded.record?.action === "reject" ? recordedPayload : null, - decision_receipt: decisionReceipt, - audit_logged: auditLogged, - replayed: recorded.replayed === true, - }); -} - -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); - if (recovery.mode === "initial_enrollment") { - const option = await initialEnrollmentAccountOption(env, recovery); - const incomplete = Object.entries(recovery.candidate.coverage).filter(([, status]) => status !== "complete").map(([name]) => `initial_enrollment_${name}_incomplete`); - const view = { ...recovery, platform: "ibkr", strategy_profile: recovery.candidate.strategy_profile, - target_name: option.target_name, account_key: option.key, candidate_sha256: recovery.candidate.candidate_sha256, - facts_sha256: recovery.candidate.facts_sha256, first_observed_at: recovery.candidate.first_observed_at, - last_observed_at: recovery.candidate.last_observed_at, - readiness: incomplete.length ? "blocked" : "awaiting_human_confirmation", blocker_codes: incomplete }; - const matches = decision => decision?.mode === "initial_enrollment" - && decision.candidate_sha256 === view.candidate_sha256 && decision.facts_sha256 === view.facts_sha256; - const confirmation = matches(result.confirmation) ? result.confirmation : null; - const rejection = matches(result.rejection) ? result.rejection : null; - const presentation = recoveryMaterialPresentation({ source, recovery: view, confirmation, rejection, decisionError: result.error }); - presentation.label = { zh: "首次登记确认", en: "Initial enrollment" }; - presentation.material_kind = "initial_enrollment"; - recoveries.push({ source_id: source.source_id, freshness, recovery: view, confirmation, rejection, presentation, - decision_authority: result.authority || null, - decision_binding: { kind: "recovery", subject_id: view.recovery_id, material_sha256: await recoveryDecisionMaterialKey(recovery) } }); - continue; - } - 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; - const rejection = result.rejection - && result.rejection.candidate_sha256 === recovery.candidate_sha256 - && result.rejection.dual_review_binding_sha256 === recovery.dual_review.evidence_binding_sha256 - ? result.rejection - : null; - recoveries.push({ source_id: source.source_id, freshness, recovery, confirmation, rejection, - presentation: recoveryMaterialPresentation({ source, recovery, confirmation, rejection, decisionError: result.error }), - decision_authority: result.authority || null, - decision_binding: { kind: "recovery", subject_id: recovery.recovery_id, material_sha256: await recoveryDecisionMaterialKey(recovery) }, - }); - } - 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 && - !entry.rejection, - ).length, - blocked: uniqueRecoveries.filter((entry) => entry.recovery.readiness === "blocked").length, - confirmed: uniqueRecoveries.filter((entry) => Boolean(entry.confirmation)).length, - }, - recoveries: uniqueRecoveries, - current_reconciliation_observation: currentReconciliationObservationPresentation(), - 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) return []; - let listing; - try { - listing = await directoryList(env, RECONCILIATION_RECOVERY_SOURCE_PREFIX, RECONCILIATION_RECOVERY_MAX_SOURCES); - } catch (error) { - return [emptyReconciliationRecoverySourceSnapshot(sourceListFailure(error, "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) throw new Error("indexed_record_unavailable"); - const source = normalizeReconciliationRecoverySourceSnapshot(stored, key); - for (const recovery of source.recoveries) if (recovery.mode === "initial_enrollment") await initialEnrollmentAccountOption(env, recovery); - sources.push(source); - } catch { - sources.push(emptyReconciliationRecoverySourceSnapshot("reconciliation_recovery_source_invalid")); - } - } - return sources; -} - -function reconciliationRecoverySourceKey(sourceId) { - return `${RECONCILIATION_RECOVERY_SOURCE_PREFIX}${sourceId}`; -} - -function runtimeDailyReadModelEnabled(env) { - return String(env.RUNTIME_DAILY_READ_MODEL_ENABLED || "") === "true"; -} - -function runtimeDailyDisabledResponse() { - return json({ ok: false, error: "runtime_daily_disabled" }, 404); -} - -function runtimeDailyKey(businessDate, target = RUNTIME_DAILY_TARGET) { - return `${RUNTIME_DAILY_KEY_PREFIX}${target.target_key}:${businessDate}`; -} - -function runtimeDailyToday(now = Date.now()) { - return runtimeDailyBusinessDate(now); -} - -function runtimeDailyCalendarDate(value) { - if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return null; - const [year, month, day] = value.split("-").map(Number); - const utc = new Date(Date.UTC(year, month - 1, day)); - if (utc.getUTCFullYear() !== year || utc.getUTCMonth() !== month - 1 || utc.getUTCDate() !== day) return null; - return value; -} - -function runtimeDailyInstant(value) { - if (typeof value !== "string") return null; - const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,9})?(Z|[+-](\d{2}):(\d{2}))$/.exec(value); - if (!match) return null; - const year = Number(match[1]); - const month = Number(match[2]); - const day = Number(match[3]); - const hour = Number(match[4]); - const minute = Number(match[5]); - const second = Number(match[6]); - if (hour > 23 || minute > 59 || second > 59) return null; - if (match[7] !== "Z" && (Number(match[8]) > 23 || Number(match[9]) > 59)) return null; - const utc = new Date(Date.UTC(year, month - 1, day)); - if (utc.getUTCFullYear() !== year || utc.getUTCMonth() !== month - 1 || utc.getUTCDate() !== day) return null; - const parsed = Date.parse(value); - return Number.isFinite(parsed) ? parsed : null; -} - -function runtimeDailyExactKeys(value, keys) { - if (!value || typeof value !== "object" || Array.isArray(value)) return false; - const present = Object.keys(value); - return present.length === keys.length && keys.every((key) => Object.prototype.hasOwnProperty.call(value, key)); -} - -function runtimeDailyBoundedText(value, max) { - if (typeof value !== "string" || !value || value.length > max) return false; - if (/[\u0000-\u001f\u007f]/.test(value)) return false; - const lowered = value.toLowerCase(); - return !["secret", "token", "password", "bearer", "traceback", "/users/", "/home/"].some((marker) => lowered.includes(marker)); -} - -function runtimeDailyChoice(value, allowed) { - return typeof value === "string" && allowed.includes(value); -} - -function runtimeDailyReject(code) { - throw new HttpError(code, 400); -} - -function runtimeDailyOptionalInstant(value, observedMs) { - if (value === null) return null; - const parsed = runtimeDailyInstant(value); - if (parsed === null) runtimeDailyReject("invalid_runtime_daily_time"); - if (parsed > observedMs + RUNTIME_DAILY_FUTURE_SKEW_MS) runtimeDailyReject("runtime_daily_future_observation"); - return parsed; -} - -function runtimeDailyScheduleInstants(schedule, observedMs) { - const latest = schedule.latest_due_at === null ? null : runtimeDailyInstant(schedule.latest_due_at); - const next = schedule.next_due_at === null ? null : runtimeDailyInstant(schedule.next_due_at); - const grace = schedule.grace_ends_at === null ? null : runtimeDailyInstant(schedule.grace_ends_at); - if (schedule.latest_due_at !== null && latest === null) runtimeDailyReject("invalid_runtime_daily_time"); - if (schedule.next_due_at !== null && next === null) runtimeDailyReject("invalid_runtime_daily_time"); - if (schedule.grace_ends_at !== null && grace === null) runtimeDailyReject("invalid_runtime_daily_time"); - // latest_due is the last cron time already reached. next_due and grace_ends are plans. - if (latest !== null && latest > observedMs + RUNTIME_DAILY_FUTURE_SKEW_MS) runtimeDailyReject("invalid_runtime_daily_time"); - if (next !== null && next <= observedMs) runtimeDailyReject("invalid_runtime_daily_time"); - if (latest !== null && next !== null && next <= latest) runtimeDailyReject("invalid_runtime_daily_time"); - if (latest !== null && grace !== null && grace < latest) runtimeDailyReject("invalid_runtime_daily_time"); -} - -// These additional constraints describe only the merged Schwab projector. The -// original LongBridge key sets, time tolerance and receipt protocol stay intact. -function validateSchwabRuntimeDailyRun(run, observedMs, started, finished) { - const evidence = run.evidence; - const confirmations = { - not_due: ["not_applicable"], no_action: ["not_applicable"], no_signal: ["not_applicable"], no_rebalance: ["not_applicable"], - risk_blocked: ["not_applicable"], submitted: ["not_observed"], broker_acknowledged: ["acknowledged"], - partially_filled: ["partially_filled"], filled: ["filled"], reconciliation_required: ["reconciliation_required"], - failed: ["not_applicable", "not_observed", "reconciliation_required"], - }; - if ((run.run_id !== null && !/^run\.[0-9a-f]{32}$/.test(run.run_id)) - || run.source_object !== null || run.source_objects.length !== 0 || run.object_updated_at !== null - || run.execution_lane === "paper" || run.run_time_known !== (started !== null || finished !== null)) runtimeDailyReject("invalid_runtime_daily_run"); - if ((started !== null && started > observedMs) || (finished !== null && finished > observedMs)) runtimeDailyReject("invalid_runtime_daily_time"); - if (run.report_status !== null && !["ok", "skipped", "success", "completed", "no_action", "error", "failed", "failure", "cancelled", "canceled", "timed_out"].includes(run.report_status)) runtimeDailyReject("invalid_runtime_daily_run"); - if (evidence.execution_status !== null && !["no_op", "no_action", "no_signal", "no_rebalance", "skipped", "outside_market_hours", "submitted", "pending_reconciliation", "reconciliation_required", "unknown", "unknown_order", "blocked", "risk_blocked", "error", "failed", "failure", "dry_run", "previewed", "partial", "partially_filled", "filled", "completed"].includes(evidence.execution_status)) runtimeDailyReject("invalid_runtime_daily_run"); - if (!runtimeDailyChoice(evidence.receipt_state, ["valid", "missing", "invalid"])) runtimeDailyReject("invalid_runtime_daily_run"); - if (evidence.receipt_state === "valid") { - if (typeof evidence.receipt_id !== "string" || !/^execution-receipt\.[0-9a-f]{32}$/.test(evidence.receipt_id) - || started === null || finished === null - || !Object.prototype.hasOwnProperty.call(confirmations, evidence.receipt_outcome) - || !confirmations[evidence.receipt_outcome].includes(evidence.receipt_broker_confirmation)) runtimeDailyReject("invalid_runtime_daily_run"); - } else if (evidence.receipt_id !== null || evidence.receipt_outcome !== null || evidence.receipt_broker_confirmation !== null) runtimeDailyReject("invalid_runtime_daily_run"); - const completedReceipt = { no_submission: "no_action", no_signal: "no_signal", no_rebalance: "no_rebalance", filled: "filled" }; - if (Object.prototype.hasOwnProperty.call(completedReceipt, run.activity) - && (evidence.receipt_state !== "valid" || evidence.receipt_outcome !== completedReceipt[run.activity] - || !["ok", "skipped", "success", "completed", "no_action"].includes(run.report_status) - || run.execution_lane !== "live" || runtimeDailyRunIssue(run) !== null)) runtimeDailyReject("runtime_daily_summary_conflict"); - if (["no_submission", "no_signal", "no_rebalance"].includes(run.activity) - && (evidence.broker_submission_done !== false || evidence.action_done === true || evidence.orders_pending_count !== 0 - || (evidence.execution_status !== null && !["no_op", "no_action", "no_signal", "no_rebalance", "skipped", "outside_market_hours", "completed"].includes(evidence.execution_status)))) runtimeDailyReject("runtime_daily_summary_conflict"); -} - -function validateSchwabRuntimeDailySummary(raw, record, runs, observedMs) { - if (raw.completeness !== (record.completeness === "complete" ? "complete" : "incomplete")) runtimeDailyReject("runtime_daily_summary_conflict"); - const runSignatures = new Map(); - for (const run of runs) { - if (run.run_id === null) continue; - const signature = canonicalResearchTaskJson(run); - const previous = runSignatures.get(run.run_id); - if (previous !== undefined && previous !== signature && !record.conflicts.includes(run.run_id)) runtimeDailyReject("runtime_daily_summary_conflict"); - runSignatures.set(run.run_id, signature); - } - const incomplete = raw.read_errors.length > 0 || record.conflicts.length > 0 || record.schedule.state === "unevaluable" - || record.excluded_reports.some(item => item.reason !== "other_business_date") - || runs.some(run => run.run_id === null || run.evidence.receipt_state !== "valid" || !run.run_time_known); - if (record.completeness === "complete" && incomplete) runtimeDailyReject("runtime_daily_summary_conflict"); - // A run that only finishes after the due time cannot prove that due cycle. - // This is a receiver invariant of the existing producer, not a new scheduler. - const latest = runtimeDailyInstant(record.schedule.latest_due_at); - const covering = latest === null ? [] : runs.filter(run => { - const started = runtimeDailyInstant(run.started_at), finished = runtimeDailyInstant(run.finished_at); - return started !== null && finished !== null && started >= latest && finished <= observedMs - && runtimeDailyToday(started) === record.business_date; - }); - const quiet = ["no_submission", "no_signal", "no_rebalance"]; - if ([...quiet, "filled", ...RUNTIME_DAILY_SCHEDULE_ONLY].includes(record.status) - && record.completeness !== "complete") runtimeDailyReject("runtime_daily_summary_conflict"); - if ([...quiet, "filled"].includes(record.status)) { - if (record.kind !== "run" || covering.length === 0 || covering.some(run => run.execution_lane !== "live")) runtimeDailyReject("runtime_daily_summary_conflict"); - if (quiet.includes(record.status) && (!['due', 'within_grace'].includes(record.schedule.state) - || covering.some(run => run.activity !== record.status))) runtimeDailyReject("runtime_daily_summary_conflict"); - if (record.status === "filled" && !covering.some(run => run.activity === "filled")) runtimeDailyReject("runtime_daily_summary_conflict"); - } - if (RUNTIME_DAILY_SCHEDULE_ONLY.includes(record.status) - && (record.kind !== "schedule" || record.schedule.state !== record.status || covering.length > 0)) runtimeDailyReject("runtime_daily_summary_conflict"); -} - -function validateSchwabRuntimeDailySchedule(schedule, observedMs) { - const { state, reason, publication_grace_ended: ended, expected_window: window } = schedule; - const latest = schedule.latest_due_at === null ? null : runtimeDailyInstant(schedule.latest_due_at); - const next = schedule.next_due_at === null ? null : runtimeDailyInstant(schedule.next_due_at); - const grace = schedule.grace_ends_at === null ? null : runtimeDailyInstant(schedule.grace_ends_at); - const reasons = { due: ["publication_grace_ended"], within_grace: ["publication_grace_open"], - not_due: ["before_schedule", "no_cron_on_business_date"], market_closed: ["market_closed"], - outside_window: ["outside_expected_window"], unevaluable: ["schedule_missing", "schedule_invalid"] }; - if (!reasons[state]?.includes(reason) - || (latest !== null && (latest > observedMs || runtimeDailyToday(latest) !== schedule.business_date)) - || (next !== null && (next <= observedMs || runtimeDailyToday(next) !== schedule.business_date))) runtimeDailyReject("invalid_runtime_daily_projection"); - let valid; - if (state === "unevaluable") valid = latest === null && next === null && grace === null && ended === null && window === "unspecified"; - else if (state === "due" || state === "within_grace") valid = latest !== null && grace !== null && next === null && window !== "outside" - && (state === "due" ? ended === true && grace <= observedMs : ended === false && grace > observedMs); - else if (state === "not_due") valid = latest === null && grace === null && ended === null && window !== "outside" - && ((reason === "before_schedule") === (next !== null)); - else if (state === "market_closed") valid = latest !== null && grace === null && ended === null; - else valid = window === "outside" && ended === null && ((latest === null) === (grace === null)) && !(grace !== null && next !== null); - if (!valid) runtimeDailyReject("invalid_runtime_daily_projection"); -} - -function normalizeRuntimeDailyRun(run, observedMs, target) { - if (!runtimeDailyExactKeys(run, [ - "run_id", "source_object", "source_objects", "started_at", "finished_at", "object_updated_at", - "report_status", "execution_lane", "activity", "run_time_known", "evidence", - ])) runtimeDailyReject("invalid_runtime_daily_run"); - if (run.run_id !== null && !runtimeDailyBoundedText(run.run_id, 120)) runtimeDailyReject("invalid_runtime_daily_run"); - if (run.source_object !== null && !runtimeDailyBoundedText(run.source_object, 512)) runtimeDailyReject("invalid_runtime_daily_run"); - if (!Array.isArray(run.source_objects) || run.source_objects.length > 8 || run.source_objects.some((item) => !runtimeDailyBoundedText(item, 512))) { - runtimeDailyReject("invalid_runtime_daily_run"); - } - const started = runtimeDailyOptionalInstant(run.started_at, observedMs); - const finished = runtimeDailyOptionalInstant(run.finished_at, observedMs); - runtimeDailyOptionalInstant(run.object_updated_at, observedMs); - if (started !== null && finished !== null && finished < started) runtimeDailyReject("invalid_runtime_daily_time"); - if (run.report_status !== null && !runtimeDailyBoundedText(run.report_status, 64)) runtimeDailyReject("invalid_runtime_daily_run"); - if (!runtimeDailyChoice(run.execution_lane, RUNTIME_DAILY_LANES) || !runtimeDailyChoice(run.activity, RUNTIME_DAILY_ACTIVITIES)) { - runtimeDailyReject("invalid_runtime_daily_run"); - } - if (typeof run.run_time_known !== "boolean") runtimeDailyReject("invalid_runtime_daily_run"); - const evidenceKeys = [ - "execution_status", "broker_submission_done", "action_done", "orders_pending_count", "errors_present", - "receipt_outcome", "receipt_broker_confirmation", - ]; - if (target.platform === "schwab") evidenceKeys.push("receipt_state", "receipt_id"); - if (!runtimeDailyExactKeys(run.evidence, evidenceKeys)) runtimeDailyReject("invalid_runtime_daily_run"); - const evidence = run.evidence; - if (evidence.execution_status !== null && !runtimeDailyBoundedText(evidence.execution_status, 64)) runtimeDailyReject("invalid_runtime_daily_run"); - for (const field of ["broker_submission_done", "action_done"]) { - if (evidence[field] !== null && typeof evidence[field] !== "boolean") runtimeDailyReject("invalid_runtime_daily_run"); - } - if (evidence.orders_pending_count !== null && (!Number.isInteger(evidence.orders_pending_count) || evidence.orders_pending_count < 0 || evidence.orders_pending_count > 100000)) { - runtimeDailyReject("invalid_runtime_daily_run"); - } - if (typeof evidence.errors_present !== "boolean") runtimeDailyReject("invalid_runtime_daily_run"); - for (const field of ["receipt_outcome", "receipt_broker_confirmation"]) { - if (evidence[field] !== null && !runtimeDailyBoundedText(evidence[field], 64)) runtimeDailyReject("invalid_runtime_daily_run"); - } - if (target.platform === "schwab") validateSchwabRuntimeDailyRun(run, observedMs, started, finished); - return { - run_id: run.run_id, - source_object: run.source_object, - source_objects: [...run.source_objects], - started_at: run.started_at, - finished_at: run.finished_at, - object_updated_at: run.object_updated_at, - report_status: run.report_status, - execution_lane: run.execution_lane, - activity: run.activity, - run_time_known: run.run_time_known, - evidence: { ...evidence }, - }; -} - -function normalizeRuntimeDailyProjection(raw, now = Date.now(), registeredTarget = null) { - if (!runtimeDailyExactKeys(raw, ["platform", "observed_at", "completeness", "read_errors", "records", "unmatched_reports"])) { - runtimeDailyReject("invalid_runtime_daily_projection"); - } - const target = registeredTarget || runtimeDailyTarget(raw.platform); - if (target && raw.platform !== target.platform) runtimeDailyReject("invalid_runtime_daily_target"); - if (!target || !runtimeDailyChoice(raw.completeness, ["complete", "incomplete"])) { - runtimeDailyReject("invalid_runtime_daily_projection"); - } - const observedMs = runtimeDailyInstant(raw.observed_at); - if (observedMs === null) runtimeDailyReject("invalid_runtime_daily_time"); - if (observedMs > now + RUNTIME_DAILY_FUTURE_SKEW_MS) runtimeDailyReject("runtime_daily_future_observation"); - if (!Array.isArray(raw.read_errors) || raw.read_errors.length > RUNTIME_DAILY_MAX_LIST || raw.read_errors.some((item) => !runtimeDailyBoundedText(item, 120))) { - runtimeDailyReject("invalid_runtime_daily_projection"); - } - if (!Array.isArray(raw.records) || raw.records.length !== 1) runtimeDailyReject("invalid_runtime_daily_target"); - if (!Array.isArray(raw.unmatched_reports) || raw.unmatched_reports.length > RUNTIME_DAILY_MAX_LIST) { - runtimeDailyReject("invalid_runtime_daily_projection"); - } - const record = raw.records[0]; - if (!runtimeDailyExactKeys(record, [ - "platform", "target_key", "target", "business_date", "timezone", "observed_at", "status", "kind", - "completeness", "execution_lane", "schedule", "runs", "excluded_reports", "conflicts", "fills", - ])) runtimeDailyReject("invalid_runtime_daily_projection"); - if (record.platform !== target.platform || record.target_key !== target.target_key || record.timezone !== RUNTIME_DAILY_TIMEZONE) { - runtimeDailyReject(record.timezone === RUNTIME_DAILY_TIMEZONE ? "invalid_runtime_daily_target" : "invalid_runtime_daily_timezone"); - } - if (!runtimeDailyExactKeys(record.target, ["service", "strategy_profile", "account_scope"])) runtimeDailyReject("invalid_runtime_daily_target"); - if (record.target.service !== target.service || record.target.strategy_profile !== target.strategy_profile || record.target.account_scope !== target.account_scope) { - runtimeDailyReject("invalid_runtime_daily_target"); - } - const businessDate = runtimeDailyCalendarDate(record.business_date); - if (!businessDate || businessDate > runtimeDailyToday(now)) runtimeDailyReject("invalid_runtime_daily_date"); - if (target.platform === "schwab" && businessDate > runtimeDailyToday(observedMs)) runtimeDailyReject("invalid_runtime_daily_date"); - if (record.observed_at !== raw.observed_at) runtimeDailyReject("invalid_runtime_daily_time"); - if (!runtimeDailyChoice(record.status, RUNTIME_DAILY_STATUSES) || !runtimeDailyChoice(record.kind, RUNTIME_DAILY_KINDS)) { - runtimeDailyReject("invalid_runtime_daily_projection"); - } - if (!runtimeDailyChoice(record.completeness, ["complete", "incomplete", "insufficient"]) || !runtimeDailyChoice(record.execution_lane, RUNTIME_DAILY_LANES)) { - runtimeDailyReject("invalid_runtime_daily_projection"); - } - if (!runtimeDailyExactKeys(record.schedule, [ - "state", "business_date", "timezone", "latest_due_at", "next_due_at", "grace_ends_at", - "publication_grace_ended", "expected_window", "reason", - ])) runtimeDailyReject("invalid_runtime_daily_projection"); - const schedule = record.schedule; - if (!runtimeDailyChoice(schedule.state, RUNTIME_DAILY_SCHEDULE_STATES) || schedule.business_date !== businessDate || schedule.timezone !== RUNTIME_DAILY_TIMEZONE) { - runtimeDailyReject("invalid_runtime_daily_projection"); - } - runtimeDailyScheduleInstants(schedule, observedMs); - if (schedule.publication_grace_ended !== null && typeof schedule.publication_grace_ended !== "boolean") { - runtimeDailyReject("invalid_runtime_daily_projection"); - } - if (!runtimeDailyChoice(schedule.expected_window, ["unspecified", "inside", "outside"])) { - runtimeDailyReject("invalid_runtime_daily_projection"); - } - if (schedule.reason !== null && !runtimeDailyBoundedText(schedule.reason, 120)) runtimeDailyReject("invalid_runtime_daily_projection"); - if (target.platform === "schwab") validateSchwabRuntimeDailySchedule(schedule, observedMs); - if (!Array.isArray(record.runs) || record.runs.length > RUNTIME_DAILY_MAX_RUNS) runtimeDailyReject("runtime_daily_run_limit"); - const runs = record.runs.map((run) => normalizeRuntimeDailyRun(run, observedMs, target)); - if (!Array.isArray(record.excluded_reports) || record.excluded_reports.length > RUNTIME_DAILY_MAX_LIST) runtimeDailyReject("invalid_runtime_daily_projection"); - const excluded = record.excluded_reports.map((item) => { - const keys = Object.keys(item || {}); - const allowed = keys.length === 3 ? ["run_id", "reason", "business_date"] : ["run_id", "reason"]; - if (!runtimeDailyExactKeys(item, allowed) || !runtimeDailyBoundedText(item.reason, 64)) runtimeDailyReject("invalid_runtime_daily_projection"); - if (item.run_id !== null && !runtimeDailyBoundedText(item.run_id, 120)) runtimeDailyReject("invalid_runtime_daily_projection"); - if (allowed.length === 3 && runtimeDailyCalendarDate(item.business_date) === null) runtimeDailyReject("invalid_runtime_daily_date"); - if (target.platform === "schwab" && (allowed.length !== 2 - || !runtimeDailyChoice(item.reason, ["missing_run_time", "invalid_run_time", "inverted_run_time", "future_run_time", "other_business_date"]) - || (item.run_id !== null && !/^run\.[0-9a-f]{32}$/.test(item.run_id)))) runtimeDailyReject("invalid_runtime_daily_projection"); - return allowed.length === 3 - ? { run_id: item.run_id, reason: item.reason, business_date: item.business_date } - : { run_id: item.run_id, reason: item.reason }; - }); - if (!Array.isArray(record.conflicts) || record.conflicts.length > RUNTIME_DAILY_MAX_LIST || record.conflicts.some((item) => !runtimeDailyBoundedText(item, 160))) { - runtimeDailyReject("invalid_runtime_daily_projection"); - } - if (["no_submission", "no_signal", "no_rebalance", "filled", ...RUNTIME_DAILY_SCHEDULE_ONLY].includes(record.status) - && (record.conflicts.length || runs.some(run => runtimeDailyRunIssue(run) !== null))) { - runtimeDailyReject("runtime_daily_summary_conflict"); - } - if (!runtimeDailyExactKeys(record.fills, ["source", "records", "count"]) || record.fills.source !== "not_connected" || record.fills.count !== null || !Array.isArray(record.fills.records) || record.fills.records.length !== 0) { - runtimeDailyReject("invalid_runtime_daily_fills"); - } - const unmatched = raw.unmatched_reports.map((item) => { - if (target.platform === "schwab") { - if (!runtimeDailyExactKeys(item, ["reason"]) || !runtimeDailyChoice(item.reason, ["invalid_report", "wrong_platform", "wrong_target", "invalid_source_object"])) runtimeDailyReject("invalid_runtime_daily_projection"); - return { reason: item.reason }; - } - if (!runtimeDailyExactKeys(item, ["run_id", "platform", "service", "strategy_profile", "account_scope", "reason"])) { - runtimeDailyReject("invalid_runtime_daily_projection"); - } - for (const field of ["platform", "service", "strategy_profile", "account_scope", "reason"]) { - if (item[field] !== null && !runtimeDailyBoundedText(item[field], 120)) runtimeDailyReject("invalid_runtime_daily_projection"); - } - if (item.run_id !== null && !runtimeDailyBoundedText(item.run_id, 120)) runtimeDailyReject("invalid_runtime_daily_projection"); - return { ...item }; - }); - if (target.platform === "schwab") { - if (raw.read_errors.some(item => !["report_read_error", "coverage_unconfirmed", "report_rejected"].includes(item)) - || record.conflicts.some(item => !/^run\.[0-9a-f]{32}$/.test(item)) - || record.execution_lane === "paper") runtimeDailyReject("invalid_runtime_daily_projection"); - const lanes = new Set(runs.map(run => run.execution_lane)); - if (record.execution_lane !== (lanes.size === 1 ? runs[0].execution_lane : "insufficient")) runtimeDailyReject("invalid_runtime_daily_projection"); - validateSchwabRuntimeDailySummary(raw, record, runs, observedMs); - } - return { - platform: target.platform, - observed_at: raw.observed_at, - completeness: raw.completeness, - read_errors: [...raw.read_errors], - records: [{ - platform: target.platform, - target_key: target.target_key, - target: { ...record.target }, - business_date: businessDate, - timezone: RUNTIME_DAILY_TIMEZONE, - observed_at: raw.observed_at, - status: record.status, - kind: record.kind, - completeness: record.completeness, - execution_lane: record.execution_lane, - schedule: { ...schedule }, - runs, - excluded_reports: excluded, - conflicts: [...record.conflicts], - fills: { source: "not_connected", records: [], count: null }, - }], - unmatched_reports: unmatched, - }; -} - -async function runtimeDailyTrustedBinding(env, target, accountOptions = null) { - const config = accountOptions ? { options: accountOptions } : await loadAccountOptionsConfig(env); - const accountKey = runtimeDailyBoundAccountKey(config?.options, target.platform); - if (!accountKey) return null; - if (target.platform === "longbridge") return { accountKey, fingerprint: null }; - // The payload never supplies this identity. Reuse the protected account-facts - // contract rather than inferring a physical account from service/live labels. - const bindings = await loadAccountFactsBindings(env, { propagateReadErrors: true }); - const matches = (bindings?.bindings || []).filter(item => item.platform === target.platform - && item.service_name === target.service && item.account_scope === target.account_scope); - const option = config.options.schwab.find(item => item.key === accountKey); - if (matches.length !== 1 || !accountFactsOptionMatchesBinding(option, matches[0])) return null; - return { accountKey, sourceBindingId: matches[0].source_binding.id, - fingerprint: await sha256Hex(canonicalResearchTaskJson({ target, binding: matches[0] })) }; -} - -// A protected registration opts an account into this read model; account-facts -// bindings alone do not register a daily source or authorize a publisher. -async function runtimeDailyRegisteredBindings(env, accountOptions) { - if (!hasConfigStore(env)) throw Error("runtime_daily_bindings_store_unavailable"); - const registry = await readConfigJson(env, "runtime_daily_bindings"); - if (registry === null) return []; - if (!runtimeDailyExactKeys(registry, ["schema_version", "bindings"]) - || registry.schema_version !== "qsl_runtime_daily_bindings.v1" || !Array.isArray(registry.bindings) || registry.bindings.length > 64) - throw Error("runtime_daily_bindings_invalid"); - if (!registry.bindings.length) return []; - const facts = await loadAccountFactsBindings(env, { propagateReadErrors: true }); - const entries = registry.bindings.map(item => { - const platform = item?.platform || "ibkr"; - const keys = ["account_key", "target_id", "source_binding_id", "strategy_profile", "target_key", ...(Object.hasOwn(item || {}, "platform") ? ["platform"] : [])]; - if (!runtimeDailyExactKeys(item, keys) || !["ibkr", "longbridge", "firstrade", "binance"].includes(platform) - || !runtimeDailyBoundedText(item.account_key, 120) || !runtimeDailyBoundedText(item.target_id, 120) - || typeof item.source_binding_id !== "string" || !/^[a-f0-9]{64}$/.test(item.source_binding_id) - || !runtimeDailyBoundedText(item.strategy_profile, 120)) throw Error("runtime_daily_bindings_invalid"); - const target = parseRuntimeDailyTargetKey(item.target_key, platform); - if (!target || (platform === "ibkr" && item.target_key.split("|").length !== 4) - || target.strategy_profile !== item.strategy_profile - || ![target.service, target.strategy_profile, target.account_scope].every(value => /^[A-Za-z0-9][A-Za-z0-9._-]{0,119}$/.test(value))) - throw Error("runtime_daily_bindings_invalid"); - return { item, target, platform }; - }); - let binance = null, binanceReadFailed = false; - if (entries.some(entry => entry.platform === "binance")) { - try { binance = await trustedBinanceFactsBinding(env); } - catch { binanceReadFailed = true; } - } - return Promise.all(entries.map(async ({ item, target, platform }) => { - if (platform === "binance" && binanceReadFailed) return { platform, target_key: item.target_key, account_key: item.account_key, status: "read_failed" }; - const collision = entries.some(other => other.item !== item && (other.item.account_key === item.account_key && other.platform === platform - || other.item.target_key === item.target_key || other.item.target_id === item.target_id - || other.item.source_binding_id === item.source_binding_id)); - const resolved = platform === "binance" ? {ok: Boolean(binance), binding: binance} - : resolveTrustedAccountFactsBinding(facts, item.target_id, item.source_binding_id); - const options = (accountOptions?.[platform] || []).filter(option => option.key === item.account_key); - const b = resolved.binding; - const optionMatches = Boolean(b) && (platform === "binance" ? options.length === 1 && binanceFactsOptionMatches(options[0], b) - : options.length === 1 && accountFactsOptionMatchesBinding(options[0], b)); - const binding = resolved.ok && b.platform === platform && b.account_key === item.account_key - && b.target_id === item.target_id && b.source_binding.id === item.source_binding_id && optionMatches - && (target.service === b.service_name || (platform === "binance" && b.service_name === "")) - && (target.account_scope === b.account_scope || (platform === "longbridge" && target.account_scope === "hk" && b.account_scope === "HK")) ? b : null; - const sharedPhysicalAccount = platform === "ibkr" && binding && entries.some(other => other.platform === "ibkr" && other.item !== item && other.item.account_key !== item.account_key - && facts?.bindings.some(otherBinding => otherBinding.platform === "ibkr" && otherBinding.account_key === other.item.account_key - && otherBinding.account_selector === binding.account_selector)); - if (collision || !binding || sharedPhysicalAccount) return { platform, target_key: item.target_key, account_key: item.account_key, status: "conflict" }; - return { platform, target_key: item.target_key, account_key: item.account_key, status: "bound", target, - sourceBindingId: item.source_binding_id, - fingerprint: await sha256Hex(canonicalResearchTaskJson({ target, registration: item, binding })) }; - })); -} - -async function runtimeDailyPublicBindings(env, accountOptions, readRegistered = () => runtimeDailyRegisteredBindings(env, accountOptions)) { - const legacy = await Promise.all(Object.values(RUNTIME_DAILY_TARGETS).map(async target => { - try { - const binding = await runtimeDailyTrustedBinding(env, target, accountOptions); - return { platform: target.platform, target_key: target.target_key, account_key: binding?.accountKey || null, - status: binding ? "bound" : "unresolved" }; - } catch { - return { platform: target.platform, target_key: target.target_key, account_key: null, status: "read_failed" }; - } - })); - try { - const registered = await readRegistered(); - return [...legacy, ...registered.map(({ platform, target_key, account_key, status }) => ({ platform, target_key, account_key, status }))]; - } catch { - return [...legacy, { platform: "ibkr", target_key: "", account_key: null, status: "read_failed" }]; - } -} - -function runtimeDailyStoredProjection(raw, target, binding) { - if (target.platform === "longbridge" && !binding.fingerprint) return raw; - if (!runtimeDailyExactKeys(raw, ["binding_fingerprint", "projection"])) throw new HttpError("runtime_daily_record_invalid", 503); - if (raw.binding_fingerprint !== binding.fingerprint) throw new HttpError("runtime_daily_binding_changed", 409); - return raw.projection; -} - -function runtimeDailyUnattributedResponse() { - return json({ ok: false, error: "runtime_daily_account_unattributed" }, 409); -} - -function runtimeDailyPublicRecord(record) { - return { - target_key: record.target_key, - service: record.target.service, - strategy_profile: record.target.strategy_profile, - account_scope: record.target.account_scope, - business_date: record.business_date, - timezone: record.timezone, - observed_at: record.observed_at, - status: record.status, - kind: record.kind, - completeness: record.completeness, - execution_lane: record.execution_lane, - schedule: { - state: record.schedule.state, - business_date: record.schedule.business_date, - timezone: record.schedule.timezone, - latest_due_at: record.schedule.latest_due_at, - next_due_at: record.schedule.next_due_at, - grace_ends_at: record.schedule.grace_ends_at, - publication_grace_ended: record.schedule.publication_grace_ended, - expected_window: record.schedule.expected_window, - // Only Schwab's validated, fixed closed-session facts may reach health UI. - // Legacy LongBridge reasons remain private bounded diagnostic text. - ...(record.platform === "schwab" && ["no_cron_on_business_date", "market_closed"].includes(record.schedule.reason) - ? { reason: record.schedule.reason } : {}), - }, - runs: record.runs.map((run) => ({ - run_id: run.run_id, - started_at: run.started_at, - finished_at: run.finished_at, - activity: run.activity, - execution_lane: run.execution_lane, - errors_present: run.evidence.errors_present, - ...(record.platform === "schwab" ? { issue: runtimeDailyRunIssue(run) } : {}), - })), - conflict_count: record.conflicts.length, - excluded_count: record.excluded_reports.length, - }; -} - -// The directory contains only successfully discovered or registered record keys. -// Unknown coverage remains null; it must never look like an empty history. -async function runtimeDailyHistory(env, target) { - const prefix = `${RUNTIME_DAILY_KEY_PREFIX}${target.target_key}:`; - try { - const listing = await directoryList(env, prefix, RUNTIME_DAILY_HISTORY_PAGE * RUNTIME_DAILY_HISTORY_MAX_PAGES); - const dates = listing.keys.map(entry => runtimeDailyCalendarDate(entry.name.slice(prefix.length))).filter(Boolean).sort(); - return { available_from: dates[0] || null, available_through: dates.at(-1) || null, - stored_days: dates.length, truncated: false }; - } catch { - return null; - } -} - -function runtimeDailyReadSummary(status, reason, observedRunCount = null) { - return { - source: "stored_runtime_daily_projection", status, reason, - observed_run_count: observedRunCount, - total_run_count: status === "complete" ? observedRunCount : null, - }; -} - -function runtimeDailyReadStatus(stored, now) { - const record = stored.records[0]; - // Preserve partial observations without turning their length into a day total. - // Read completeness and execution outcome/freshness are separate facts. - const complete = stored.completeness === "complete" && record.completeness === "complete" - && !stored.read_errors.length && !stored.unmatched_reports.length && !record.conflicts.length - && record.kind !== "incomplete" && !["read_incomplete", "missing_report", "insufficient", "conflict"].includes(record.status); - const observedMs = runtimeDailyInstant(stored.observed_at); - const today = record.business_date === runtimeDailyToday(now); - let dataStatus = "historical"; - if (today) { - const fresh = observedMs !== null && observedMs <= now + RUNTIME_DAILY_FUTURE_SKEW_MS && now - observedMs <= RUNTIME_DAILY_STALE_MS; - dataStatus = fresh ? "fresh" : "stale"; - } - return { - data_status: dataStatus, - read_summary: runtimeDailyReadSummary(complete ? "complete" : "partial", - complete ? null : "runtime_daily_incomplete", record.runs.length), - read_error_count: stored.read_errors.length, - // These producer codes contain no object paths, identities or exception text. - // Unknown legacy diagnostics remain private and retain an explicit error. - read_error_codes: stored.read_errors.map(error => { - if (["report_read_error", "coverage_unconfirmed", "report_rejected", "read_failed", "read_truncated", "list_failed"].includes(error)) return error; - if (error === "listing truncated") return "listing_truncated"; - if (error === "runtime_target_enabled_schedule_unverified") return "schedule_unverified"; - if (error === "runtime_target_disabled") return "target_disabled"; - return "source_read_error"; - }), - unmatched_count: stored.unmatched_reports.length, - record: runtimeDailyPublicRecord(record), - fills: { source: "not_connected", records: [], count: null }, - }; -} - -async function syncRuntimeDailyResponse(request, env) { - if (!runtimeDailyReadModelEnabled(env)) return runtimeDailyDisabledResponse(); - const ibkrToken = String(env.IBKR_RUNTIME_DAILY_SYNC_TOKEN || ""); - const supplied = (request.headers.get("Authorization") || "").match(/^Bearer\s+(.+)$/i)?.[1] || ""; - const isIbkr = !!ibkrToken && supplied === ibkrToken && supplied !== String(env.EXECUTION_EVIDENCE_SYNC_TOKEN || ""); - if (isIbkr && [env.ACCOUNT_FACTS_SYNC_TOKEN, - env.IBKR_ACCOUNT_FACTS_SYNC_TOKEN, env.SCHWAB_ACCOUNT_FACTS_SYNC_TOKEN, - env.FIRSTRADE_ACCOUNT_FACTS_SYNC_TOKEN, env.BINANCE_ACCOUNT_FACTS_SYNC_TOKEN, - env.STRATEGY_SWITCH_SYNC_TOKEN, env.RECONCILIATION_RECOVERY_SYNC_TOKEN, - env.RECONCILIATION_RECOVERY_CONTROLLER_TOKEN, env.CYCLE_HEALTH_PROVISIONER_TOKEN] - .some(value => value && String(value) === ibkrToken)) { - throw new HttpError("runtime_daily_token_ambiguous", 503); - } - if (!isIbkr) requireDedicatedExecutionEvidenceSyncToken(request, env); - if (!hasConfigStore(env)) return json({ ok: false, error: "runtime daily KV is not configured" }, 503); - let raw; - try { - raw = await readBoundedJson(request, RUNTIME_DAILY_MAX_BODY_BYTES); - } catch (error) { - return json({ ok: false, error: error.message || "invalid_runtime_daily_projection" }, error.status || 400); - } - let target, binding, projection; - try { - const configured = String(env.IBKR_RUNTIME_DAILY_TARGET_KEY || ""); - let authorized; - try { authorized = configured.startsWith("[") || configured.startsWith("{") ? JSON.parse(configured) : [configured]; } - catch { throw new HttpError("runtime_daily_target_unauthorized", 403); } - const isGroups = authorized && !Array.isArray(authorized) && typeof authorized === "object"; - if (isGroups && !runtimeDailyExactKeys(authorized, ["ibkr", "execution"])) throw new HttpError("runtime_daily_target_unauthorized", 403); - const allowed = isGroups ? authorized[isIbkr ? "ibkr" : "execution"] : isIbkr ? authorized : []; - if (!Array.isArray(allowed) || allowed.length > 64 || new Set(allowed).size !== allowed.length - || allowed.some(key => typeof key !== "string" || !parseRuntimeDailyTargetKey(key, isIbkr ? "ibkr" : "longbridge") - || (isIbkr && key.split("|").length !== 4))) throw new HttpError("runtime_daily_target_unauthorized", 403); - const registeredRequest = raw?.records?.length === 1 && allowed.includes(raw.records[0]?.target_key); - if (isIbkr || registeredRequest) { - if (!registeredRequest || (isIbkr ? raw?.platform !== "ibkr" : raw?.platform === "ibkr")) throw new HttpError("runtime_daily_target_unauthorized", 403); - const options = (await loadAccountOptionsConfig(env)).options; - const matches = (await runtimeDailyRegisteredBindings(env, options)).filter(item => item.target_key === raw.records[0].target_key && item.platform === raw.platform); - if (matches.length !== 1 || matches[0].status !== "bound") return runtimeDailyUnattributedResponse(); - target = matches[0].target; - binding = { accountKey: matches[0].account_key, fingerprint: matches[0].fingerprint, sourceBindingId: matches[0].sourceBindingId }; - } else { - projection = normalizeRuntimeDailyProjection(raw); - target = runtimeDailyTarget(projection.platform); - binding = await runtimeDailyTrustedBinding(env, target); - } - } - catch (error) { - if (error instanceof HttpError) throw error; - return json({ ok: false, error: "runtime_daily_read_failed" }, 503); - } - if (!binding) return runtimeDailyUnattributedResponse(); - if (!projection) projection = normalizeRuntimeDailyProjection(raw, Date.now(), target); - if (target.platform === "schwab" || binding.sourceBindingId) { - // Reuse the existing protected source_binding.id, not a new identity or a - // body-supplied account. This also guards a new day with no cached fingerprint. - const sourceBindingId = request.headers.get("X-QSL-Source-Binding-ID"); - if (typeof sourceBindingId !== "string" || !/^[0-9a-f]{64}$/.test(sourceBindingId)) { - return json({ ok: false, error: "runtime_daily_source_binding_invalid" }, 400); - } - if (sourceBindingId !== binding.sourceBindingId) { - return json({ ok: false, error: "runtime_daily_source_binding_mismatch" }, 409); - } - } - const accountKey = binding.accountKey; - const businessDate = projection.records[0].business_date; - const store = configStore(env); - const key = runtimeDailyKey(businessDate, target); - const storedText = await store.get(key); - if (storedText !== null && storedText !== undefined) { - let storedRaw; - let storedProjection; - try { - storedRaw = JSON.parse(storedText); - storedProjection = normalizeRuntimeDailyProjection(runtimeDailyStoredProjection(storedRaw, target, binding), Date.now(), target); - } catch (error) { - if (error.message === "runtime_daily_binding_changed") return json({ ok: false, error: error.message }, 409); - return json({ ok: false, error: "runtime_daily_existing_record_invalid" }, 409); - } - if (storedProjection.records[0].business_date !== businessDate || storedProjection.platform !== target.platform - || storedProjection.records[0].target_key !== target.target_key) { - return json({ ok: false, error: "runtime_daily_existing_record_invalid" }, 409); - } - const storedObservedAt = runtimeDailyInstant(storedProjection.observed_at); - const incomingObservedAt = runtimeDailyInstant(projection.observed_at); - if (storedObservedAt === null || incomingObservedAt === null) { - return json({ ok: false, error: "runtime_daily_existing_record_invalid" }, 409); - } - if (incomingObservedAt < storedObservedAt) { - return json({ ok: false, error: "runtime_daily_stale_observation" }, 409); - } - if (incomingObservedAt === storedObservedAt) { - if (canonicalResearchTaskJson(projection) === canonicalResearchTaskJson(storedProjection)) { - return json({ - ok: true, - stored: true, - business_date: businessDate, - platform: target.platform, - target_key: target.target_key, - account_key: accountKey, - ...(binding.fingerprint && target.platform !== "schwab" ? { observed_at: projection.observed_at } : {}), - }); - } - return json({ ok: false, error: "runtime_daily_observation_conflict" }, 409); - } - } - await writeConfigJson(env, key, binding.fingerprint - ? { binding_fingerprint: binding.fingerprint, projection } : projection); - return json({ - ok: true, - stored: true, - business_date: businessDate, - platform: target.platform, - target_key: target.target_key, - account_key: accountKey, - ...(binding.fingerprint && target.platform !== "schwab" ? { observed_at: projection.observed_at } : {}), - }); -} - -async function runtimeDailyResponse(request, env, url, context = null) { - if (!runtimeDailyReadModelEnabled(env)) return runtimeDailyDisabledResponse(); - const session = await (context ? context.readSession() : readSession(request, env)); - if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); - if (!hasConfigStore(env)) return json({ ok: false, error: "runtime daily KV is not configured" }, 503); - const platforms = url.searchParams.getAll("platform"); - const accounts = url.searchParams.getAll("account_key"); - if (platforms.length > 1 || accounts.length > 1 || url.searchParams.getAll("date").length !== 1 - || (platforms.length !== accounts.length)) return json({ ok: false, error: "invalid_runtime_daily_selection" }, 400); - // Legacy date-only reads always mean PAPER, never the first configured target. - let target = platforms.length ? runtimeDailyTarget(platforms[0]) : RUNTIME_DAILY_TARGET; - if ((!target && !["ibkr", "firstrade", "binance"].includes(platforms[0])) || (accounts.length && !runtimeDailyBoundedText(accounts[0], 120))) return json({ ok: false, error: "invalid_runtime_daily_selection" }, 400); - const historyOptions = url.searchParams.getAll("include_history"); - if (historyOptions.length > 1 || historyOptions.some(value => !["true", "false"].includes(value))) - return json({ ok: false, error: "invalid_runtime_daily_selection" }, 400); - const includeHistory = historyOptions[0] !== "false"; - const businessDate = runtimeDailyCalendarDate(url.searchParams.get("date")); - if (!businessDate || businessDate > runtimeDailyToday()) return json({ ok: false, error: "invalid_runtime_daily_date" }, 400); - let binding; - try { - const config = await (context ? context.readOptions() : loadAccountOptionsConfig(env)); - const matches = accounts.length ? (await (context ? context.readRegistered() : runtimeDailyRegisteredBindings(env, config.options))) - .filter(item => item.platform === platforms[0] && item.account_key === accounts[0]) : []; - if (matches.length) { - if (matches.length !== 1 || matches[0].status !== "bound") return runtimeDailyUnattributedResponse(); - binding = { accountKey: matches[0].account_key, fingerprint: matches[0].fingerprint }; - target = matches[0].target; - } else if (platforms[0] === "ibkr" || !target) return runtimeDailyUnattributedResponse(); - else binding = await runtimeDailyTrustedBinding(env, target, config.options); - } - catch { return json({ ok: false, error: "runtime_daily_read_failed" }, 503); } - if (!binding || (accounts.length && accounts[0] !== binding.accountKey)) return runtimeDailyUnattributedResponse(); - const accountKey = binding.accountKey; - let stored; - try { - stored = await readConfigJson(env, runtimeDailyKey(businessDate, target)); - } catch { - return json({ ok: false, error: "runtime_daily_read_failed", - read_summary: runtimeDailyReadSummary("read_failed", "runtime_daily_read_failed") }, 503); - } - const base = { - ok: true, - date: businessDate, - timezone: RUNTIME_DAILY_TIMEZONE, - account_key: accountKey, - platform: target.platform, - target_key: target.target_key, - history: includeHistory ? await runtimeDailyHistory(env, target) : null, - }; - if (stored === null || stored === undefined) { - return json({ ...base, data_status: "unavailable", record: null, fills: null, read_error_count: 0, unmatched_count: 0, - read_summary: runtimeDailyReadSummary("no_data", "runtime_daily_record_missing") }); - } - let projection; - try { - projection = normalizeRuntimeDailyProjection(runtimeDailyStoredProjection(stored, target, binding), Date.now(), target); - } catch (error) { - if (error.message === "runtime_daily_binding_changed") return json({ ok: false, error: error.message }, 409); - return json({ ok: false, error: "runtime_daily_record_invalid", - read_summary: runtimeDailyReadSummary("read_failed", "runtime_daily_record_invalid") }, 503); - } - if (projection.records[0].business_date !== businessDate || projection.platform !== target.platform - || projection.records[0].target_key !== target.target_key) { - return json({ ok: false, error: "runtime_daily_record_invalid", - read_summary: runtimeDailyReadSummary("read_failed", "runtime_daily_record_invalid") }, 503); - } - return json({ ...base, ...runtimeDailyReadStatus(projection, Date.now()) }); -} - -async function syncExecutionEvidenceSourceResponse(request, env) { - requireDedicatedExecutionEvidenceSyncToken(request, env); - if (!hasConfigStore(env)) { - return json({ ok: false, error: "execution evidence KV is not configured" }, 503); - } - - const receiptId = request.headers.get("X-QSL-Execution-Evidence-Receipt"); - if (receiptId !== null && !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(receiptId)) { - return json({ ok: false, error: "invalid execution evidence receipt identifier" }, 400); - } - - let raw; - try { - raw = await readBoundedJson(request, EXECUTION_EVIDENCE_MAX_BODY_BYTES); - } catch (error) { - return json({ ok: false, error: error.message || "invalid execution evidence payload" }, error.status || 400); - } - - let source; - try { - source = normalizeExecutionEvidenceSourceSnapshot(raw, "execution evidence source snapshot"); - } catch (error) { - return json({ ok: false, error: error.message || "invalid execution evidence payload" }, 400); - } - - await writeConfigJson(env, executionEvidenceSourceKey(source.source_id), source); - try { - await appendAuditLog(env, { - ts: new Date().toISOString(), - login: "execution-evidence-source-sync", - action: "sync_execution_evidence_source", - source_id: source.source_id, - schema_version: source.schema_version, - deployment_count: source.deployments.length, - data_status: source.data_status, - ...(receiptId === null ? {} : { receipt_id: receiptId }), - }); - } catch { - // The source snapshot is accepted independently of optional audit retention. - } - return json({ - ok: true, - source_id: source.source_id, - schema_version: source.schema_version, - deployment_count: source.deployments.length, - generated_at: source.generated_at, - ...(receiptId === null ? {} : { receipt_id: receiptId }), - }); -} - -async function executionEvidenceResponse(request, env) { - const session = await readSession(request, env); - if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); - if (!hasConfigStore(env)) return json(emptyExecutionEvidencePayload("snapshot_unavailable")); - return json(await aggregateExecutionEvidenceSources(env)); -} - -async function aggregateExecutionEvidenceSources(env) { - const sources = await readExecutionEvidenceSources(env); - if (!sources.length) return emptyExecutionEvidencePayload("snapshot_unavailable"); - - const ttlSeconds = executionEvidenceStaleTtlSeconds(env); - const now = Date.now(); - const deployments = []; - const deploymentIds = new Set(); - const duplicateDeploymentIds = new Set(); - const errors = []; - const timestamps = []; - let hasReadySource = false; - let hasStaleSource = false; - - for (const source of sources) { - const freshness = controlPlaneSnapshotFreshness(source, ttlSeconds, now); - if (source.generated_at) timestamps.push(source.generated_at); - if (source.computed_at) timestamps.push(source.computed_at); - if (freshness.data_status === "ready") hasReadySource = true; - if (freshness.data_status === "stale") { - hasStaleSource = true; - errors.push("execution_evidence_source_stale"); - } - for (const deployment of source.deployments) { - if (deploymentIds.has(deployment.deployment_id)) { - duplicateDeploymentIds.add(deployment.deployment_id); - errors.push("execution_evidence_duplicate_deployment"); - continue; - } - deploymentIds.add(deployment.deployment_id); - deployments.push({ source_id: source.source_id, freshness, deployment }); - } - errors.push(...source.errors); - } - - const uniqueDeployments = deployments.filter((entry) => !duplicateDeploymentIds.has(entry.deployment.deployment_id)); - const dataStatus = hasStaleSource ? "stale" : (hasReadySource ? "ready" : "unavailable"); - return { - schema_version: EXECUTION_EVIDENCE_DASHBOARD_SCHEMA_VERSION, - generated_at: earliestControlPlaneTimestamp(timestamps), - computed_at: earliestControlPlaneTimestamp(timestamps), - data_status: dataStatus, - summary: normalizeExecutionEvidenceSummary(uniqueDeployments.map((entry) => entry.deployment)), - deployments: uniqueDeployments, - policy: { - execution_evidence_read_only: true, - p6_owner_decision_required: true, - limited_live_canary_active: false, - notice: "执行证据按策略、平台和执行通道分别记录;它不授权订单或实盘。", - }, - errors: uniqueStrings(errors), - }; -} - -async function readExecutionEvidenceSources(env) { - const store = configStore(env); - if (!store) return []; - let listing; - try { - listing = await directoryList(env, EXECUTION_EVIDENCE_SOURCE_PREFIX, EXECUTION_EVIDENCE_MAX_SOURCES); - } catch { - return [emptyExecutionEvidenceSourceSnapshot("execution_evidence_source_list_unavailable")]; - } - const keys = Array.isArray(listing?.keys) ? listing.keys : []; - const sources = []; - for (const entry of keys.slice(0, EXECUTION_EVIDENCE_MAX_SOURCES)) { - const key = typeof entry?.name === "string" ? entry.name : ""; - if (!key.startsWith(EXECUTION_EVIDENCE_SOURCE_PREFIX)) continue; - try { - const stored = await readConfigJson(env, key); - if (!stored) throw new Error("indexed_record_unavailable"); - sources.push(normalizeExecutionEvidenceSourceSnapshot(stored, key)); - } catch { - sources.push(emptyExecutionEvidenceSourceSnapshot("execution_evidence_source_invalid")); - } - } - return sources; -} - -function executionEvidenceSourceKey(sourceId) { - return `${EXECUTION_EVIDENCE_SOURCE_PREFIX}${sourceId}`; -} - -function runtimeTargetLifecycleAuditState(source, ttlSeconds, now) { - if (!source) return null; - const state = structuredClone(source); - state.freshness = controlPlaneSnapshotFreshness(source, ttlSeconds, now).data_status; - delete state.generated_at; - delete state.computed_at; - for (const target of state.targets) { - if (target.deployment?.observed_at) { - target.deployment.observed_at = controlPlaneSnapshotFreshness({ - data_status: "ready", computed_at: target.deployment.observed_at, - }, ttlSeconds, now).data_status; - } - } - return JSON.stringify(state); -} - -async function syncRuntimeTargetLifecycleSourceResponse(request, env) { - // This publisher has the same narrow scope as execution evidence: sanitized - // platform status only, never credentials, accounts, orders, or commands. - requireDedicatedExecutionEvidenceSyncToken(request, env); - if (!hasConfigStore(env)) { - return json({ ok: false, error: "runtime target lifecycle KV is not configured" }, 503); - } - let raw; - try { - raw = await readBoundedJson(request, RUNTIME_TARGET_LIFECYCLE_MAX_BODY_BYTES); - } catch (error) { - return json({ ok: false, error: error.message || "invalid runtime target lifecycle payload" }, error.status || 400); - } - let source; - try { - source = normalizeRuntimeTargetLifecycleSourceSnapshot(raw, "runtime target lifecycle source snapshot"); - } catch (error) { - return json({ ok: false, error: error.message || "invalid runtime target lifecycle payload" }, 400); - } - const sourceKey = runtimeTargetLifecycleSourceKey(source.source_id); - let previous = null; - try { - previous = normalizeRuntimeTargetLifecycleSourceSnapshot(await readConfigJson(env, sourceKey)); - } catch { - // Missing/unreadable history must not prevent a fresh, valid observation. - } - // Always retain the new evidence timestamps. Only the optional rolling audit - // skips unchanged polling records; operator actions are audited separately. - await writeConfigJson(env, sourceKey, source); - try { - const ttlSeconds = executionEvidenceStaleTtlSeconds(env); - const now = Date.now(); - if (runtimeTargetLifecycleAuditState(previous, ttlSeconds, now) !== runtimeTargetLifecycleAuditState(source, ttlSeconds, now)) { - await appendAuditLog(env, { - ts: new Date().toISOString(), - login: "runtime-target-lifecycle-source-sync", - action: "sync_runtime_target_lifecycle_source", - source_id: source.source_id, - schema_version: source.schema_version, - target_count: source.targets.length, - data_status: source.data_status, - }); - } - } catch { - // A valid no-order snapshot remains useful when convenience audit retention fails. - } - await maybeAutoDispatchAccountDiagnosis(env, source); - return json({ - ok: true, - source_id: source.source_id, - schema_version: source.schema_version, - target_count: source.targets.length, - generated_at: source.generated_at, - }); -} - -async function runtimeReadbackResponse(request, env, url) { - const session = await readSession(request, env); - if (!session?.allowed) return json({ ok: false, error: "login required", error_state: "unauthorized" }, 401); - if ([...url.searchParams.keys()].some(key => key !== "scope") || url.searchParams.getAll("scope").length !== 1 - || !/^[a-z0-9_-]+:[a-zA-Z0-9_.-]+$/.test(url.searchParams.get("scope") || "")) - return json({ ok: false, error: "invalid_runtime_scope" }, 400); - const scope = url.searchParams.get("scope"); - try { - const config = await loadAccountOptionsConfig(env); - const accounts = Object.entries(config.options || {}).flatMap(([platform, rows]) => rows.map(row => ({ ...row, platform }))); - const matches = accounts.filter(row => `${row.platform}:${row.key}` === scope); - if (!matches.length) return json({ ok: false, error: "runtime_scope_not_found" }, 404); - if (matches.length !== 1) return json({ ok: false, error: "runtime_binding_conflict" }, 409); - if (!hasConfigStore(env)) return json({ ok: false, error: "runtime_readback_unavailable" }, 503); - const snapshot = await aggregateRuntimeTargetLifecycleSources(env); - if (snapshot.errors?.some(code => /source_list_unavailable|source_invalid/.test(code))) - return json({ ok: false, error: "runtime_readback_failed" }, 503); - return json(runtimeReadbackProjection(matches[0], accounts, snapshot)); - } catch { - return json({ ok: false, error: "runtime_readback_failed" }, 503); - } -} - -async function runtimeTargetLifecycleResponse(request, env) { - const session = await readSession(request, env); - if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); - if (!hasConfigStore(env)) return json(emptyRuntimeTargetLifecyclePayload("snapshot_unavailable")); - return json(await aggregateRuntimeTargetLifecycleSources(env)); -} - -function requireAccountDiagnosisToken(request, env) { - const expected = String(env.ACCOUNT_DIAGNOSIS_SYNC_TOKEN || ""); - if (!expected) throw new HttpError("account diagnosis 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("account diagnosis sync token is invalid", 401); -} - -function accountDiagnosisRequestId(value) { - const text = String(value || "").trim().toLowerCase(); - if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(text)) { - throw new HttpError("invalid_account_diagnosis_request_id", 400); - } - return text; -} - -function accountDiagnosisChecks(entry, dataStatus) { - const target = entry?.target?.target || {}; - const monitoring = entry?.target?.monitoring || {}; - const deployment = entry?.target?.deployment || {}; - return { - configured_state: ["enabled", "disabled"].includes(target.configured_state) ? target.configured_state : "unknown", - runtime_enabled: typeof deployment.runtime_enabled === "boolean" ? deployment.runtime_enabled : null, - scheduler_state: ["enabled", "paused"].includes(deployment.scheduler_state) ? deployment.scheduler_state : "unknown", - runtime_guard: ["pass", "attention", "unavailable"].includes(monitoring.runtime_guard) ? monitoring.runtime_guard : "unavailable", - execution_heartbeat: ["pass", "attention", "unavailable", "not_due", "not_applicable"].includes(monitoring.execution_heartbeat) - ? monitoring.execution_heartbeat : "unavailable", - freshness: ["ready", "stale", "unavailable"].includes(entry?.freshness?.data_status) - ? entry.freshness.data_status : (dataStatus === "ready" ? "unavailable" : dataStatus), - }; -} - -function accountDiagnosisHasConfirmedAttention(checks) { - return ["ready", "stale"].includes(checks.freshness) - && (checks.freshness === "stale" || (checks.configured_state !== "disabled" && checks.runtime_enabled === false) - || (checks.configured_state === "enabled" && checks.scheduler_state === "paused") - || checks.runtime_guard === "attention" || checks.execution_heartbeat === "attention"); -} - -function accountDiagnosisStateFingerprint(targetId, checks) { - return `${targetId}:${JSON.stringify(checks)}`; -} - -function accountDiagnosisTargetSpec(platform, targetId) { - const spec = ACCOUNT_DIAGNOSIS_TARGETS[String(targetId || "").trim()]; - return spec && (platform == null || spec.platform === platform) ? spec : null; -} - -async function accountDiagnosisAccount(env, platform, key) { - let config; - try { - config = await loadAccountOptionsConfig(env); - } catch { - throw accountDiagnosisConflict("account_diagnosis_config_unavailable"); - } - const options = Array.isArray(config.options?.[platform]) - ? config.options[platform].filter(item => item?.key === key) : []; - if (options.length === 0) throw accountDiagnosisConflict("account_diagnosis_account_not_found"); - if (options.length !== 1) throw accountDiagnosisConflict("account_diagnosis_account_not_unique"); - const account = options[0]; - const targetId = String(account.runtime_status_target_id || "").trim(); - if (!accountDiagnosisTargetSpec(platform, targetId)) throw accountDiagnosisConflict("account_diagnosis_target_not_admitted"); - const allIdentityMatches = Object.entries(config.options || {}).flatMap(([itemPlatform, items]) => - (Array.isArray(items) ? items : []).filter(item => String(item?.runtime_status_target_id || "").trim() === targetId) - .map(item => ({ platform: itemPlatform, key: item.key }))); - if (allIdentityMatches.length !== 1 || allIdentityMatches[0].platform !== platform || allIdentityMatches[0].key !== key) { - throw accountDiagnosisConflict("account_diagnosis_identity_conflict"); - } - return { platform, key, account, targetId, config }; -} - -async function accountDiagnosisContext(env, platform, key) { - const identity = await accountDiagnosisAccount(env, platform, key); - if (!hasConfigStore(env)) throw accountDiagnosisConflict("account_diagnosis_lifecycle_store_unavailable"); - const sources = await readRuntimeTargetLifecycleSources(env); - const ttlSeconds = executionEvidenceStaleTtlSeconds(env); - const matches = []; - for (const source of sources) { - const freshness = controlPlaneSnapshotFreshness(source, ttlSeconds, Date.now()); - for (const target of source.targets) { - if (target.target_id === identity.targetId && target.target.platform === identity.platform) { - matches.push({ target, freshness, observedAt: source.computed_at || source.generated_at }); - } - } - } - if (matches.length === 0) { - throw accountDiagnosisConflict("account_diagnosis_lifecycle_match_missing"); - } - if (matches.length !== 1) { - throw accountDiagnosisConflict("account_diagnosis_lifecycle_match_ambiguous"); - } - const entry = matches[0]; - let checks; - try { - checks = accountDiagnosisChecks(entry, entry.freshness.data_status); - } catch { - throw accountDiagnosisConflict("account_diagnosis_schema_incompatible"); - } - if (!checks || typeof checks !== "object") { - throw accountDiagnosisConflict("account_diagnosis_schema_incompatible"); - } - if (!["ready", "stale"].includes(checks.freshness)) throw accountDiagnosisConflict("account_diagnosis_source_unavailable"); - const observedAt = entry.observedAt; - if (!observedAt) throw accountDiagnosisConflict("account_diagnosis_observation_missing"); - return { - platform, key, targetId: identity.targetId, checks, observedAt, - stateFingerprint: accountDiagnosisStateFingerprint(identity.targetId, checks), - attention: accountDiagnosisHasConfirmedAttention(checks), - }; -} - -function accountDiagnosisPublicTask(task, recheckStatus = task?.recheck_dispatch_state) { - if (!task) return null; - return { - request_id: task.request_id, - platform: task.platform, - key: task.key, - target_id: task.target_id, - trigger: task.trigger, - status: task.status, - summary: task.summary || "", - reason_code: task.reason_code || "", - dispatch_state: task.dispatch_state, - recheck_status: recheckStatus, - created_at: task.created_at, - updated_at: task.updated_at, - }; -} - -async function accountDiagnosisRecheckStatus(env, task) { - if (!task || task.status !== "succeeded" || task.recheck_dispatch_state !== "sent") { - return task?.recheck_dispatch_state || "not_requested"; - } - const resultAt = Date.parse(task.updated_at); - if (!Number.isFinite(resultAt)) return "sent"; - try { - const sources = await readRuntimeTargetLifecycleSources(env); - const ttlSeconds = executionEvidenceStaleTtlSeconds(env); - const matches = []; - for (const source of sources) { - const freshness = controlPlaneSnapshotFreshness(source, ttlSeconds, Date.now()); - const observedAt = source.computed_at || source.generated_at; - if (freshness.data_status !== "ready" || !observedAt || Date.parse(observedAt) <= resultAt) continue; - for (const target of source.targets) { - if (target.target_id === task.target_id && target.target.platform === task.platform) { - matches.push({ target, freshness, observedAt }); - } - } - } - if (matches.length !== 1) return "sent"; - const checks = accountDiagnosisChecks(matches[0], matches[0].freshness.data_status); - if (checks.freshness !== "ready" || checks.runtime_guard === "unavailable" || checks.execution_heartbeat === "unavailable" - || checks.runtime_enabled === null || checks.scheduler_state === "unknown") return "unavailable"; - if (checks.configured_state === "enabled") { - return checks.runtime_enabled === true && checks.scheduler_state === "enabled" - && checks.runtime_guard === "pass" && ["pass", "not_due"].includes(checks.execution_heartbeat) ? "passed" : "attention"; - } - if (checks.configured_state === "disabled") { - return checks.runtime_enabled === false && checks.scheduler_state === "paused" - && checks.runtime_guard === "pass" && checks.execution_heartbeat === "not_applicable" ? "passed" : "attention"; - } - return "unavailable"; - } catch { - return "sent"; - } -} - -function accountDiagnosisInternalTask(task) { - return { - request_id: task.request_id, - platform: task.platform, - key: task.key, - target_id: task.target_id, - trigger: task.trigger, - observed_at: task.observed_at, - checks: task.checks, - status: task.status, - }; -} - -function accountDiagnosisNativeContext(env) { - try { - const binding = JSON.parse(env.ACCOUNT_DIAGNOSIS_NATIVE_BINDING_JSON || "null"); - const expected = ["schema_version", "project", "resource", "audience", "provider", "profile", "model", "worker"]; - if (!binding || Object.keys(binding).sort().join() !== expected.sort().join() - || binding.schema_version !== "qrs.account-diagnosis-native.v1" - || binding.project !== "QuantStrategyLab/QuantRuntimeSettings" - || expected.filter(key => key !== "schema_version").some(key => typeof binding[key] !== "string" - || !binding[key] || binding[key].length > 200 || /\s/.test(binding[key])) - || binding.profile.length > 100 || binding.profile.includes(":")) throw Error(); - const url = new URL(binding.resource); - if (url.protocol !== "https:" || !url.hostname || url.username || url.password || url.search || url.hash - || binding.resource.endsWith("/")) throw Error(); - const transport = env.ACCOUNT_DIAGNOSIS_NATIVE_TRANSPORT; - // This seam carries existing approved submit/read authentication. Neither - // an old GitHub dispatch token nor the QRS callback token can substitute it. - if (!transport || typeof transport.fetch !== "function") throw Error(); - return { binding, transport }; - } catch { - throw new HttpError("account_diagnosis_native_not_configured", 503); - } -} - -function accountDiagnosisNativeRequest(task, binding) { - const input = accountDiagnosisInternalTask(task); - const properties = { status: { enum: ["succeeded", "failed", "unknown"] }, - summary: { type: "string", minLength: 1, maxLength: ACCOUNT_DIAGNOSIS_SUMMARY_MAX_CHARS }, - reason_code: { enum: ACCOUNT_DIAGNOSIS_REASON_CODES }, advisory_only: { const: true }, - execution_authority_granted: { const: false } }; - return { mode: "agent", profile: binding.profile, model: binding.model, - objective: "Diagnose only the supplied account/runtime availability checks. Return an advisory report, not a health or execution approval. Do not access accounts, change configuration, repair, trade, deploy, publish, or notify.", - materials: [input], timeout_seconds: 600, - output_schema: { type: "object", additionalProperties: false, required: Object.keys(properties), properties } }; -} - -async function accountDiagnosisNativeIO(context, method, path, body, headers = {}) { - const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), 10000); - try { - const response = await context.transport.fetch(new Request(context.binding.resource + path, { - method, redirect: "manual", signal: controller.signal, - headers: { "Content-Type": "application/json", ...headers }, - ...(body === undefined ? {} : { body: JSON.stringify(body) }), - })); - if (!response.ok || !response.body) throw Error(); - const reader = response.body.getReader(); - const chunks = []; - let length = 0; - while (true) { - const { done, value } = await reader.read(); - if (done) break; - length += value.length; - if (length > 64 * 1024) { await reader.cancel(); throw Error(); } - chunks.push(value); - } - const bytes = new Uint8Array(length); - let offset = 0; - for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.length; } - return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes)); - } catch { - throw new HttpError("account_diagnosis_native_outcome_unknown", 502); - } finally { - clearTimeout(timer); - } -} - -async function accountDiagnosisNativeStop(env, context, task) { - const reserved = await runtimeInstanceCommand(env, { action: "diagnosis_native_cancel", actor: "native-diagnosis-caller", - request_id: task.request_id, updated_at: new Date().toISOString() }); - if (reserved.send_cancel) { - try { await accountDiagnosisNativeIO(context, "POST", `/v1/tasks/${encodeURIComponent(reserved.task.native.task_id)}/cancel`, {}); } catch { /* Unknown cancellation is never retried. */ } - } - return reserved; -} - -async function accountDiagnosisNativeReadback(env, context, task, record, cancelRequested = false) { - const native = task.native; - if (!native || canonicalResearchTaskJson(native.binding) !== canonicalResearchTaskJson(context.binding)) { - throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); - } - const taskId = record?.id; - const validId = typeof taskId === "string" && /^[A-Za-z0-9_-]{1,200}$/.test(taskId); - if (!validId || (native.task_id && taskId !== native.task_id) - || record.owner !== native.binding.project || record.provider !== native.binding.provider - || record.result_kind !== "advisory" || record.revision !== 1 - || !Number.isFinite(record.deadline) || (native.task_deadline !== null && native.task_deadline !== record.deadline) - || await sha256Hex(canonicalResearchTaskJson(record.request ?? null)) !== native.request_sha256) { - throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); - } - const next = structuredClone(native); - next.task_id = taskId; - next.task_deadline = record.deadline; - if (record.worker !== null && record.worker !== undefined || record.attempt !== null && record.attempt !== undefined) { - if (record.worker !== native.binding.worker || typeof record.attempt !== "string" || !record.attempt || record.attempt.length > 200) { - throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); - } - const claim = [record.worker, record.attempt, record.revision]; - if (native.claim && canonicalResearchTaskJson(native.claim) !== canonicalResearchTaskJson(claim)) { - throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); - } - next.claim = claim; - } else if (native.claim) { - throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); - } - const expired = Date.now() / 1000 >= Math.min(native.deadline, record.deadline); - let status = "queued", summary = "", reason = "", terminal = false; - const negativeTerminal = ["failed", "invalid_output", "cancelled"].includes(record.status); - if (((cancelRequested || expired || native.cancel_requested || native.cancel_sent) && !negativeTerminal) - || record.status === "cancel_requested" || record.status === "outcome_unknown") { - status = "unknown"; - next.phase = "hold"; - } else if (["queued", "submitting", "running"].includes(record.status)) { - status = record.status === "running" ? "running" : "queued"; - if (status === "running" && !next.claim) throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); - next.phase = "pending"; - } else if (record.status === "completed") { - const output = record.output; - if (!next.claim || !output || Object.keys(output).sort().join() !== "advisory_only,execution_authority_granted,reason_code,status,summary" - || !["succeeded", "failed", "unknown"].includes(output.status) - || typeof output.summary !== "string" || !output.summary.trim() || output.summary.length > ACCOUNT_DIAGNOSIS_SUMMARY_MAX_CHARS - || !ACCOUNT_DIAGNOSIS_REASON_CODES.includes(output.reason_code) - || output.advisory_only !== true || output.execution_authority_granted !== false) { - throw new HttpError("invalid_account_diagnosis_result", 400); - } - next.output_sha256 = await sha256Hex(canonicalResearchTaskJson(output)); - next.phase = "terminal"; - status = output.status; summary = output.summary.trim(); reason = output.reason_code; - terminal = true; - } else if (["failed", "invalid_output", "cancelled"].includes(record.status)) { - next.phase = "terminal"; status = "failed"; terminal = true; - summary = "Native diagnosis did not produce an accepted advisory result."; reason = "invalid_response"; - } else { - throw new HttpError("invalid_account_diagnosis_result", 400); - } - // All terminal adoption is still bounded by the original operation deadline. - if (terminal && status === "succeeded" && Date.now() / 1000 >= Math.min(native.deadline, record.deadline)) { - return accountDiagnosisNativeStop(env, context, { ...task, native: next }); - } - if (terminal) next.receipt = await sha256Hex(canonicalResearchTaskJson({ task_id: taskId, claim: next.claim, - revision: record.revision, request_sha256: next.request_sha256, output_sha256: next.output_sha256, - native_status: record.status, status, summary, reason })); - const saved = await runtimeInstanceCommand(env, { action: "diagnosis_native_save", actor: "native-diagnosis-caller", - request_id: task.request_id, native: next, status, summary, reason_code: reason, updated_at: new Date().toISOString() }); - if ((cancelRequested || native.cancel_requested || expired) && !terminal) return accountDiagnosisNativeStop(env, context, saved.task); - if (terminal && status === "succeeded" && saved.changed) { - // Durable result persistence may itself cross expiry; no recheck then. - if (Date.now() / 1000 >= Math.min(native.deadline, record.deadline)) { - return accountDiagnosisNativeStop(env, context, saved.task); - } - try { - await dispatchAccountDiagnosisRecheck(env, saved.task); - await runtimeInstanceCommand(env, { action: "diagnosis_mark_recheck", actor: "native-diagnosis-caller", - request_id: task.request_id, recheck_dispatch_state: "sent", updated_at: new Date().toISOString() }); - } catch { - await runtimeInstanceCommand(env, { action: "diagnosis_mark_recheck", actor: "native-diagnosis-caller", - request_id: task.request_id, recheck_dispatch_state: "unknown", updated_at: new Date().toISOString() }); - } - } - return saved; -} - -async function reconcileAccountDiagnosis(env, requestId, cancel = false) { - const context = accountDiagnosisNativeContext(env); - const current = await runtimeInstanceCommand(env, { action: "diagnosis_read", actor: "native-diagnosis-caller", request_id: requestId }); - let task = current.task; - if (!task?.native) throw new HttpError("account_diagnosis_legacy_retired", 409); - if (canonicalResearchTaskJson(task.native.binding) !== canonicalResearchTaskJson(context.binding)) { - throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); - } - if (task.native.phase === "terminal") return { ...current, changed: false }; - if (cancel) { - const stopped = await accountDiagnosisNativeStop(env, context, task); - task = stopped.task; - if (task.native.receipt || task.native.task_id) return stopped; - } - if (task.native.task_id && (Date.now() / 1000 >= Math.min(task.native.deadline, task.native.task_deadline ?? Infinity) - || (task.native.cancel_requested && !task.native.cancel_sent))) return accountDiagnosisNativeStop(env, context, task); - try { - let taskId = task.native.task_id; - if (!taskId) { - const found = await accountDiagnosisNativeIO(context, "GET", "/v1/tasks/lookup", undefined, - { "Idempotency-Key": task.native.operation_key, "X-Request-SHA256": task.native.request_sha256 }); - if (!found || Object.keys(found).join() !== "id" || typeof found.id !== "string" || !/^[A-Za-z0-9_-]{1,200}$/.test(found.id)) { - throw new HttpError("account_diagnosis_native_outcome_unknown", 502); - } - taskId = found.id; - } - const record = await accountDiagnosisNativeIO(context, "GET", `/v1/tasks/${encodeURIComponent(taskId)}`); - if (record.id !== taskId) throw accountDiagnosisConflict("account_diagnosis_native_binding_conflict"); - return accountDiagnosisNativeReadback(env, context, task, record, cancel); - } catch (error) { - // A read/validation failure retains the original obligation beyond24h. - try { - await runtimeInstanceCommand(env, { action: "diagnosis_native_save", actor: "native-diagnosis-caller", - request_id: requestId, native: { ...task.native, phase: "hold" }, status: "unknown", summary: "", reason_code: "", - updated_at: new Date().toISOString() }); - } catch { /* Never overwrite a concurrently accepted immutable terminal receipt. */ } - throw error; - } -} - -async function dispatchAccountDiagnosis(env, requestId) { - const context = accountDiagnosisNativeContext(env); - const current = await runtimeInstanceCommand(env, { action: "diagnosis_read", actor: "native-diagnosis-caller", request_id: requestId }); - const request = accountDiagnosisNativeRequest(current.task, context.binding); - const native = { version: "qrs.native-diagnosis.v1", binding: context.binding, request, - input_sha256: await sha256Hex(canonicalResearchTaskJson(request.materials)), - request_sha256: await sha256Hex(canonicalResearchTaskJson(request)), operation_key: "qrs-account-diagnosis:" + requestId, - deadline: Date.now() / 1000 + 600, task_id: null, task_deadline: null, claim: null, - output_sha256: null, receipt: null, cancel_requested: false, cancel_sent: false, phase: "intent" }; - const intent = await runtimeInstanceCommand(env, { action: "diagnosis_native_intent", actor: "native-diagnosis-caller", - request_id: requestId, native, updated_at: new Date().toISOString() }); - if (!intent.created) return intent; // Never replay POST, even after a lost reply. - if (Date.now() / 1000 >= intent.task.native.deadline) return accountDiagnosisNativeStop(env, context, intent.task); - const record = await accountDiagnosisNativeIO(context, "POST", "/v1/tasks", request, - { "Idempotency-Key": native.operation_key }); - return accountDiagnosisNativeReadback(env, context, intent.task, record); -} - -function accountDiagnosisAutoEnabled(env) { - return String(env[ACCOUNT_DIAGNOSIS_AUTO_ENABLED_ENV] || "").toLowerCase() === "true"; -} - -async function accountDiagnosisIdentityForTarget(env, targetId) { - const spec = accountDiagnosisTargetSpec(null, targetId); - if (!spec) return null; - let config; - try { - config = await loadAccountOptionsConfig(env); - } catch { - return null; - } - const matches = Object.entries(config.options || {}).flatMap(([platform, items]) => - (Array.isArray(items) ? items : []) - .filter(item => String(item?.runtime_status_target_id || "").trim() === targetId) - .map(item => ({ platform, key: item.key }))); - if (matches.length !== 1 || matches[0].platform !== spec.platform) return null; - try { - return await accountDiagnosisAccount(env, matches[0].platform, matches[0].key); - } catch { - return null; - } -} - -async function maybeAutoDispatchAccountDiagnosis(env, source) { - if (!accountDiagnosisAutoEnabled(env)) return; - try { accountDiagnosisNativeContext(env); } catch { return; } - const now = Date.now(); - const freshness = controlPlaneSnapshotFreshness(source, executionEvidenceStaleTtlSeconds(env), now); - if (freshness.data_status !== "ready") return; - const observedAt = source.computed_at || source.generated_at; - if (!observedAt) return; - for (const [targetId, spec] of Object.entries(ACCOUNT_DIAGNOSIS_TARGETS)) { - try { - const matchingTargets = source.targets.filter(item => - item.target_id === targetId && item.target?.platform === spec.platform); - if (matchingTargets.length !== 1) continue; - const target = matchingTargets[0]; - const checks = accountDiagnosisChecks({ target, freshness }, freshness.data_status); - if (checks.configured_state !== "enabled" || !accountDiagnosisHasConfirmedAttention(checks)) continue; - const identity = await accountDiagnosisIdentityForTarget(env, targetId); - if (!identity) continue; - const createdAt = new Date().toISOString(); - const requestId = crypto.randomUUID(); - const created = await runtimeInstanceCommand(env, { - action: "diagnosis_create", actor: "runtime-target-lifecycle-source-sync", request_id: requestId, - platform: identity.platform, key: identity.key, target_id: identity.targetId, trigger: "incident", - observed_at: observedAt, checks, state_fingerprint: accountDiagnosisStateFingerprint(identity.targetId, checks), - created_at: createdAt, now, auto_source: "source_sync", - }); - if (!created.created) continue; - try { - await dispatchAccountDiagnosis(env, requestId); - await runtimeInstanceCommand(env, { - action: "diagnosis_mark_dispatch", actor: "runtime-target-lifecycle-source-sync", - request_id: requestId, dispatch_state: "sent", updated_at: new Date().toISOString(), - }); - } catch { - await runtimeInstanceCommand(env, { - action: "diagnosis_mark_dispatch", actor: "runtime-target-lifecycle-source-sync", - request_id: requestId, dispatch_state: "unknown", updated_at: new Date().toISOString(), - }); - } - } catch { - // One target's unavailable identity or dispatch must not affect the other targets. - } - } -} - -async function dispatchAccountDiagnosisRecheck(env, task) { - const token = String(env.RUNTIME_SETTINGS_DISPATCH_TOKEN || ""); - if (!token) throw new HttpError("account_diagnosis_recheck_unavailable", 503); - const spec = accountDiagnosisTargetSpec(task?.platform, task?.target_id); - if (!spec) throw accountDiagnosisConflict("account_diagnosis_target_not_admitted"); - const url = `https://api.github.com/repos/${spec.recheckRepository}/actions/workflows/${spec.recheckWorkflow}/dispatches`; - const body = task.platform === "longbridge" - ? { ref: "main", inputs: { target: task.target_id.slice("longbridge.".length) } } - : { ref: "main" }; - const response = await fetchWithTimeout(url, { - method: "POST", - headers: githubHeaders(token), - body: JSON.stringify(body), - }); - if (!response.ok) throw new HttpError("account_diagnosis_recheck_unverified", 502); -} - -async function accountDiagnosisResponse(request, env, url) { - const session = await readSession(request, env); - if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); - if (!hasRuntimeInstanceStore(env)) return json({ ok: false, error: "account_diagnosis_storage_unavailable" }, 503); - if (request.method === "GET") { - const params = [...url.searchParams.keys()].sort(); - if (params.join(",") !== "key,platform") return json({ ok: false, error: "account_diagnosis_query_required" }, 400); - try { - const identity = await accountDiagnosisAccount(env, url.searchParams.get("platform"), url.searchParams.get("key")); - const result = await runtimeInstanceCommand(env, { action: "diagnosis_latest", actor: session.login, platform: identity.platform, key: identity.key }); - const recheckStatus = await accountDiagnosisRecheckStatus(env, result.task); - return json({ ok: true, account: { platform: identity.platform, key: identity.key, target_id: identity.targetId }, task: accountDiagnosisPublicTask(result.task, recheckStatus) }); - } catch (error) { - if (error instanceof HttpError) return json(accountDiagnosisErrorPayload(error), error.status); - return json({ ok: false, error: "account_diagnosis_unavailable", reason_code: "account_diagnosis_unavailable" }, 503); - } - } - if (request.method !== "POST") return json({ ok: false, error: "method_not_allowed" }, 405); - try { requireSameOrigin(request, { requireOrigin: true }); } catch (error) { return json({ ok: false, error: error.message }, error.status || 403); } - let raw; - try { raw = await readBoundedJson(request, ACCOUNT_DIAGNOSIS_MAX_BODY_BYTES); } catch (error) { - return json({ ok: false, error: error.message || "invalid_account_diagnosis_request" }, error.status || 400); - } - if (!raw || typeof raw !== "object" || Array.isArray(raw) || Object.keys(raw).sort().join(",") !== "key,platform,trigger") { - return json({ ok: false, error: "invalid_account_diagnosis_request" }, 400); - } - if (!ACCOUNT_DIAGNOSIS_TRIGGERS.includes(raw.trigger)) return json({ ok: false, error: "invalid_account_diagnosis_trigger" }, 400); - try { accountDiagnosisNativeContext(env); } catch { return json({ ok: false, error: "account_diagnosis_native_not_configured" }, 503); } - try { - const context = await accountDiagnosisContext(env, raw.platform, raw.key); - if (raw.trigger === "incident" && !context.attention) { - return json(accountDiagnosisErrorPayload(accountDiagnosisConflict("account_diagnosis_incident_requires_attention")), 409); - } - const requestId = crypto.randomUUID(); - const createdAt = new Date().toISOString(); - const created = await runtimeInstanceCommand(env, { - action: "diagnosis_create", actor: session.login, request_id: requestId, - platform: context.platform, key: context.key, target_id: context.targetId, trigger: raw.trigger, - observed_at: context.observedAt, checks: context.checks, state_fingerprint: context.stateFingerprint, created_at: createdAt, now: Date.now(), - }); - if (!created.created) return json({ ok: true, deduplicated: true, request_id: created.task.request_id, status: created.task.status, task: accountDiagnosisPublicTask(created.task) }, 200); - try { - await dispatchAccountDiagnosis(env, requestId); - await runtimeInstanceCommand(env, { action: "diagnosis_mark_dispatch", actor: session.login, request_id: requestId, dispatch_state: "sent", updated_at: new Date().toISOString() }); - } catch (error) { - await runtimeInstanceCommand(env, { action: "diagnosis_mark_dispatch", actor: session.login, request_id: requestId, dispatch_state: "unknown", updated_at: new Date().toISOString() }); - const latest = await runtimeInstanceCommand(env, { action: "diagnosis_read", actor: session.login, request_id: requestId }); - return json({ ok: false, error: error.message || "account_diagnosis_dispatch_unverified", request_id: latest.task.request_id, status: latest.task.status, task: accountDiagnosisPublicTask(latest.task) }, error.status || 502); - } - const latest = await runtimeInstanceCommand(env, { action: "diagnosis_read", actor: session.login, request_id: requestId }); - return json({ ok: true, status: "queued", request_id: latest.task.request_id, task: accountDiagnosisPublicTask(latest.task) }, 202); - } catch (error) { - if (error instanceof HttpError) return json(accountDiagnosisErrorPayload(error), error.status); - return json({ ok: false, error: "account_diagnosis_unavailable", reason_code: "account_diagnosis_unavailable" }, 503); - } -} - -async function accountDiagnosisInternalResponse(request, env, url) { - try { requireAccountDiagnosisToken(request, env); } catch (error) { return json({ ok: false, error: error.message }, error.status || 401); } - if (!hasRuntimeInstanceStore(env)) return json({ ok: false, error: "account_diagnosis_storage_unavailable" }, 503); - if (url.search) return json({ ok: false, error: "invalid_account_diagnosis_request" }, 400); - let requestId; - try { requestId = accountDiagnosisRequestId(url.pathname.slice(ACCOUNT_DIAGNOSIS_INTERNAL_PREFIX.length)); } catch (error) { - return json({ ok: false, error: error.message }, error.status || 400); - } - if (request.method === "GET") { - try { - const result = await runtimeInstanceCommand(env, { action: "diagnosis_read", actor: "aab-account-diagnosis", request_id: requestId }); - return json(accountDiagnosisInternalTask(result.task)); - } catch (error) { - return json({ ok: false, error: error.message || "account_diagnosis_request_not_found" }, error.status || 404); - } - } - if (request.method !== "POST") return json({ ok: false, error: "method_not_allowed" }, 405); - let raw; - try { raw = await readBoundedJson(request, ACCOUNT_DIAGNOSIS_MAX_BODY_BYTES); } catch (error) { - return json({ ok: false, error: error.message || "invalid_account_diagnosis_request" }, error.status || 400); - } - if (!raw || typeof raw !== "object" || Array.isArray(raw) || raw.request_id !== requestId) { - return json({ ok: false, error: "invalid_account_diagnosis_request" }, 400); - } - try { - if (Object.keys(raw).sort().join() !== "action,request_id" || !["readback", "cancel"].includes(raw.action)) { - throw new HttpError("invalid_account_diagnosis_result", 400); - } - const result = await reconcileAccountDiagnosis(env, requestId, raw.action === "cancel"); - const latest = await runtimeInstanceCommand(env, { action: "diagnosis_read", actor: "native-diagnosis-caller", request_id: requestId }); - return json({ ok: true, replayed: !result.changed, recheck_status: latest.task.recheck_dispatch_state, - task: accountDiagnosisPublicTask(latest.task) }); - } catch (error) { - if (error instanceof HttpError) return json(accountDiagnosisErrorPayload(error), error.status); - return json({ ok: false, error: "account_diagnosis_native_outcome_unknown" }, 502); - } -} - -function projectRuntimeAccountState(entry) { - const base = { scope: "monitoring_only", limit: "not_trading_or_books" }; - const target = entry?.target || {}; - const monitoring = target.monitoring || {}; - const disposition = target.disposition || {}; - const deployment = target.deployment; - const sourceFresh = entry?.freshness?.data_status === "ready"; - const deploymentFresh = Boolean(deployment?.observed_at) && entry?.deployment_freshness?.data_status === "ready"; - let activation = "unknown"; - if (deploymentFresh && deployment.runtime_enabled === true && deployment.scheduler_state === "enabled") activation = "enabled"; - else if (deploymentFresh && deployment.runtime_enabled === false && deployment.scheduler_state === "paused") activation = "disabled"; - const configured = target.target?.configured_state; - const retainedFault = monitoring.runtime_guard === "attention" - || monitoring.execution_heartbeat === "attention" - || disposition.code === "parked"; - if (retainedFault) return { ...base, health: "abnormal", activation, reason: "retained_attention" }; - if ((configured === "enabled" && activation === "disabled") || (configured === "disabled" && activation === "enabled")) { - return { ...base, health: "abnormal", activation, reason: "config_inconsistent" }; - } - if (monitoring.execution_heartbeat === "not_due") { - return { ...base, health: "unknown", activation, reason: "check_not_due" }; - } - const enabledAgrees = configured === "enabled" - && activation === "enabled" - && monitoring.runtime_guard === "pass" - && monitoring.execution_heartbeat === "pass" - && disposition.code === "continue_enabled_monitoring"; - const disabledAgrees = configured === "disabled" - && activation === "disabled" - && monitoring.runtime_guard === "pass" - && monitoring.execution_heartbeat === "not_applicable" - && disposition.code === "continue_disabled_validation"; - if (sourceFresh && (enabledAgrees || disabledAgrees)) { - return { ...base, health: "normal", activation, reason: "monitoring_agrees" }; - } - let reason = "evidence_insufficient"; - if (!sourceFresh) reason = "source_not_fresh"; - else if (!deployment) reason = "deployment_missing"; - else if (!deploymentFresh) reason = "deployment_not_fresh"; - else if (activation === "unknown") reason = "activation_unconfirmed"; - return { ...base, health: "unknown", activation, reason }; -} - -async function aggregateRuntimeTargetLifecycleSources(env) { - const sources = await readRuntimeTargetLifecycleSources(env); - if (!sources.length) return emptyRuntimeTargetLifecyclePayload("snapshot_unavailable"); - const ttlSeconds = executionEvidenceStaleTtlSeconds(env); - const now = Date.now(); - const targets = []; - const targetIds = new Set(); - const duplicateTargetIds = new Set(); - const errors = []; - const timestamps = []; - let hasReadySource = false; - let hasStaleSource = false; - for (const source of sources) { - const freshness = controlPlaneSnapshotFreshness(source, ttlSeconds, now); - if (source.generated_at) timestamps.push(source.generated_at); - if (source.computed_at) timestamps.push(source.computed_at); - if (freshness.data_status === "ready") hasReadySource = true; - if (freshness.data_status === "stale") { - hasStaleSource = true; - errors.push("runtime_target_lifecycle_source_stale"); - } - for (const target of source.targets) { - if (targetIds.has(target.target_id)) { - duplicateTargetIds.add(target.target_id); - errors.push("runtime_target_lifecycle_duplicate_target"); - continue; - } - targetIds.add(target.target_id); - const entry = { - source_id: source.source_id, - observed_at: earliestControlPlaneTimestamp([source.generated_at, source.computed_at].filter(Boolean)), - evidence_valid_for_seconds: ttlSeconds, - freshness, - target, - // A lifecycle source is deliberately no-order. Keep this derived - // observation separate from the monitoring checks so a green check - // cannot be mistaken for a broker submission or fill receipt. - execution_observation: runtimeTargetLifecycleExecutionObservation(target), - ...(target.deployment ? {deployment_freshness: target.deployment.observed_at - ? controlPlaneSnapshotFreshness({data_status:"ready", computed_at:target.deployment.observed_at}, ttlSeconds, now) - : freshness} : {}), - }; - entry.account_state = projectRuntimeAccountState(entry); - targets.push(entry); - } - errors.push(...source.errors); - } - const uniqueTargets = targets.filter((entry) => !duplicateTargetIds.has(entry.target.target_id)); - const dataStatus = hasStaleSource ? "stale" : (hasReadySource ? "ready" : "unavailable"); - return { - schema_version: RUNTIME_TARGET_LIFECYCLE_DASHBOARD_SCHEMA_VERSION, - generated_at: earliestControlPlaneTimestamp(timestamps), - computed_at: earliestControlPlaneTimestamp(timestamps), - data_status: dataStatus, - summary: { - target_count: uniqueTargets.length, - enabled: uniqueTargets.filter((entry) => entry.target.target.configured_state === "enabled").length, - disabled: uniqueTargets.filter((entry) => entry.target.target.configured_state === "disabled").length, - attention: uniqueTargets.filter((entry) => entry.target.disposition.code === "parked").length, - }, - targets: uniqueTargets, - policy: { - lifecycle_status_read_only: true, - execution_observation_read_only: true, - no_order: true, - order_or_fill_evidence: "not_collected", - notice: "已启用目标持续监控;已停用目标持续进行无执行验证。运行或心跳通过只说明监测正常,不代表已提交订单或已成交。", - }, - errors: uniqueStrings(errors), - }; -} - -function runtimeTargetLifecycleExecutionObservation(target) { - const monitoring = target?.monitoring || {}; - const configuredState = target?.target?.configured_state; - let code = "unavailable"; - if (monitoring.runtime_guard === "attention" || monitoring.execution_heartbeat === "attention") { - code = "attention"; - } else if (monitoring.runtime_guard === "unavailable" || monitoring.execution_heartbeat === "unavailable") { - code = "unavailable"; - } else if (configuredState === "disabled") { - code = "not_applicable"; - } else if (monitoring.execution_heartbeat === "not_due") { - code = "not_due"; - } else if (monitoring.execution_heartbeat === "pass") { - code = "monitoring_only"; - } - return { - code: cleanChoice(code, RUNTIME_TARGET_LIFECYCLE_EXECUTION_OBSERVATIONS, "runtime target lifecycle execution observation"), - // The source contract always declares no_order=true. Only the separate - // execution-receipt chain may later provide an order/fill attestation. - order_or_fill_evidence: "not_collected", - }; -} - -async function readRuntimeTargetLifecycleSources(env) { - const store = configStore(env); - if (!store) return []; - let listing; - try { - listing = await directoryList(env, RUNTIME_TARGET_LIFECYCLE_SOURCE_PREFIX, RUNTIME_TARGET_LIFECYCLE_MAX_SOURCES); - } catch { - return [emptyRuntimeTargetLifecycleSourceSnapshot("runtime_target_lifecycle_source_list_unavailable")]; - } - const keys = Array.isArray(listing?.keys) ? listing.keys : []; - const sources = []; - for (const entry of keys.slice(0, RUNTIME_TARGET_LIFECYCLE_MAX_SOURCES)) { - const key = typeof entry?.name === "string" ? entry.name : ""; - if (!key.startsWith(RUNTIME_TARGET_LIFECYCLE_SOURCE_PREFIX)) continue; - try { - const stored = await readConfigJson(env, key); - if (!stored) throw new Error("indexed_record_unavailable"); - sources.push(normalizeRuntimeTargetLifecycleSourceSnapshot(stored, key)); - } catch { - sources.push(emptyRuntimeTargetLifecycleSourceSnapshot("runtime_target_lifecycle_source_invalid")); - } - } - return sources; -} - -function runtimeTargetLifecycleSourceKey(sourceId) { - return `${RUNTIME_TARGET_LIFECYCLE_SOURCE_PREFIX}${sourceId}`; -} - -async function syncResearchTaskSourceResponse(request, env) { - requireDedicatedResearchTaskSyncToken(request, env); - if (!hasConfigStore(env)) { - return json({ ok: false, error: "research task KV is not configured" }, 503); - } - - let raw; - try { - raw = await readBoundedJson(request, RESEARCH_TASK_MAX_BODY_BYTES); - } catch (error) { - return json({ ok: false, error: error.message || "invalid research task source payload" }, error.status || 400); - } - - let source; - try { - source = await normalizeResearchTaskSourceSnapshot(raw, "research task source snapshot"); - } catch (error) { - return json({ ok: false, error: error.message || "invalid research task source payload" }, 400); - } - - await writeConfigJson(env, researchTaskSourceKey(source.source_id), source); - try { - await appendAuditLog(env, { - ts: new Date().toISOString(), - login: "research-task-source-sync", - action: "sync_research_task_source", - source_id: source.source_id, - schema_version: source.schema_version, - task_count: source.tasks.length, - data_status: source.data_status, - }); - } catch { - // A valid no-order task index does not depend on optional audit retention. - } - return json({ - ok: true, - source_id: source.source_id, - schema_version: source.schema_version, - task_count: source.tasks.length, - generated_at: source.generated_at, - }); -} - -async function researchTaskResponse(request, env) { - const session = await readSession(request, env); - if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); - if (!hasConfigStore(env)) return json(emptyResearchTaskPayload("snapshot_unavailable")); - return json(await aggregateResearchTaskSources(env)); -} - -async function aggregateResearchTaskSources(env) { - const sources = await readResearchTaskSources(env); - if (!sources.length) return emptyResearchTaskPayload("snapshot_unavailable"); - - const ttlSeconds = researchTaskStaleTtlSeconds(env); - const now = Date.now(); - const taskIds = new Set(); - const duplicateTaskIds = new Set(); - const tasks = []; - const errors = []; - const timestamps = []; - let hasReadySource = false; - let hasStaleSource = false; - - for (const source of sources) { - const freshness = controlPlaneSnapshotFreshness(source, ttlSeconds, now); - if (source.generated_at) timestamps.push(source.generated_at); - if (source.computed_at) timestamps.push(source.computed_at); - if (freshness.data_status === "ready") hasReadySource = true; - if (freshness.data_status === "stale") { - hasStaleSource = true; - errors.push("research_task_source_stale"); - } - for (const task of source.tasks) { - if (taskIds.has(task.task_id)) { - duplicateTaskIds.add(task.task_id); - errors.push("research_task_duplicate_task"); - continue; - } - taskIds.add(task.task_id); - tasks.push({ source_id: source.source_id, freshness, task }); - } - errors.push(...source.errors); - } - - const uniqueTasks = tasks.filter((entry) => !duplicateTaskIds.has(entry.task.task_id)); - const dataStatus = hasStaleSource ? "stale" : (hasReadySource ? "ready" : "unavailable"); - return { - schema_version: RESEARCH_TASK_DASHBOARD_SCHEMA_VERSION, - generated_at: earliestControlPlaneTimestamp(timestamps), - computed_at: earliestControlPlaneTimestamp(timestamps), - data_status: dataStatus, - summary: { task_count: uniqueTasks.length }, - tasks: uniqueTasks, - policy: { - research_only: true, - no_order: true, - size_zero_required: true, - p4_p5_p6_authorized: false, - notice: "研究任务只用于离线、零仓位实验;不会调参、改代码、部署或下单。", - }, - errors: uniqueStrings(errors), - }; -} - -async function readResearchTaskSources(env) { - const store = configStore(env); - if (!store) return []; - let listing; - try { - listing = await directoryList(env, RESEARCH_TASK_SOURCE_PREFIX, RESEARCH_TASK_MAX_SOURCES); - } catch { - return [emptyResearchTaskSourceSnapshot("research_task_source_list_unavailable")]; - } - const keys = Array.isArray(listing?.keys) ? listing.keys : []; - const sources = []; - for (const entry of keys.slice(0, RESEARCH_TASK_MAX_SOURCES)) { - const key = typeof entry?.name === "string" ? entry.name : ""; - if (!key.startsWith(RESEARCH_TASK_SOURCE_PREFIX)) continue; - try { - const stored = await readConfigJson(env, key); - if (!stored) throw new Error("indexed_record_unavailable"); - sources.push(await normalizeResearchTaskSourceSnapshot(stored, key)); - } catch { - sources.push(emptyResearchTaskSourceSnapshot("research_task_source_invalid")); - } - } - return sources; -} - -function researchTaskSourceKey(sourceId) { - return `${RESEARCH_TASK_SOURCE_PREFIX}${sourceId}`; -} - -async function aggregateControlPlaneSources(env) { - const now = Date.now(); - let retirements; - try { - const stored = await configStore(env).get(CONTROL_PLANE_SOURCE_RETIREMENTS_KEY); - retirements = stored === null || stored === undefined - ? new Map() - : normalizeControlPlaneSourceRetirements(JSON.parse(stored), now); - } catch { - return emptyControlPlanePayload("control_plane_source_retirements_invalid"); - } - const sources = await readControlPlaneSources(env); - // A legacy aggregate has no source identity and cannot honor a retirement. - if (!sources?.length && !retirements.size) return null; - - const ttlSeconds = controlPlaneStaleTtlSeconds(env); - const candidates = []; - const historicalSources = []; - const candidateIds = new Set(); - const duplicateCandidateIds = new Set(); - const errors = []; - const timestamps = []; - let hasReadySource = false; - let hasStaleSource = false; - - for (const source of sources || []) { - const sourceFreshness = controlPlaneResearchSnapshotFreshness(source, ttlSeconds, now); - const retirement = retirements.get(source.source_id); - if (retirement) { - historicalSources.push({ retirement, snapshot: source, freshness: sourceFreshness }); - continue; - } - 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("control_plane_source_stale"); - } - for (const candidate of source.candidates) { - if (candidateIds.has(candidate.candidate_id)) { - duplicateCandidateIds.add(candidate.candidate_id); - errors.push("control_plane_duplicate_candidate"); - continue; - } - candidateIds.add(candidate.candidate_id); - candidates.push({ - ...candidate, - source_id: source.source_id, - freshness: mergeControlPlaneFreshness(candidate.freshness, sourceFreshness), - }); - } - errors.push(...source.errors); - } - - const dataStatus = hasStaleSource ? "stale" : (hasReadySource ? "ready" : "unavailable"); - const uniqueCandidates = candidates.filter((candidate) => !duplicateCandidateIds.has(candidate.candidate_id)); - const uniqueErrors = uniqueStrings(errors); - return { - schema_version: "qsl_control_plane_dashboard.v1", - generated_at: earliestControlPlaneTimestamp(timestamps), - computed_at: earliestControlPlaneTimestamp(timestamps), - data_status: dataStatus, - summary: normalizeControlPlaneSummary(uniqueCandidates), - attention: normalizeControlPlaneAttention({ - dataStatus, - candidates: uniqueCandidates, - errors: uniqueErrors, - }), - candidates: uniqueCandidates, - ...(retirements.size ? { historical_sources: historicalSources } : {}), - policy: normalizeControlPlanePolicy({ - p4_p5_automation: "not_configured", - p6_owner_decision_required: true, - notice: "P1–P3 自动研究已接入;P4/P5 尚未配置,live 仍需所有者明确决定。", - }, "aggregated control plane policy"), - errors: uniqueErrors, - }; -} - -function normalizeControlPlaneSourceRetirements(payload, now) { - const value = assertExactFields(payload, ["schema_version", "retirements"], CONTROL_PLANE_SOURCE_RETIREMENTS_KEY); - if (value.schema_version !== "qsl_control_plane_source_retirements.v1" - || !Array.isArray(value.retirements) || value.retirements.length > CONTROL_PLANE_MAX_SOURCES) { - throw new Error("invalid control plane source retirements"); - } - const retirements = new Map(); - for (const entry of value.retirements) { - const item = assertExactFields(entry, ["source_id", "retired_at", "reason"], "source retirement"); - const sourceId = normalizeControlPlaneIdentifier(item.source_id, "source retirement.source_id", false); - const retiredAt = normalizeStrategyHealthTimestamp(item.retired_at, "source retirement.retired_at"); - const reason = typeof item.reason === "string" - ? sanitizeStrategyHealthText(item.reason, "source retirement.reason", 240, false, null) : null; - // "unavailable" is the diagnostic identity for unreadable source records. - if (sourceId === "unavailable" || retirements.has(sourceId) || Date.parse(retiredAt) > now || !reason) { - throw new Error("invalid control plane source retirement"); - } - retirements.set(sourceId, { source_id: sourceId, retired_at: retiredAt, reason }); - } - return retirements; -} - -async function readControlPlaneSources(env) { - const store = configStore(env); - if (!store) return null; - let listing; - try { - listing = await directoryList(env, CONTROL_PLANE_SOURCE_PREFIX, CONTROL_PLANE_MAX_SOURCES); - } catch (error) { - return [emptyControlPlaneSourceSnapshot(sourceListFailure(error, "control_plane_source_list_unavailable"))]; - } - const keys = Array.isArray(listing?.keys) ? listing.keys : []; - const sources = []; - for (const entry of keys.slice(0, CONTROL_PLANE_MAX_SOURCES)) { - const key = typeof entry?.name === "string" ? entry.name : ""; - if (!key.startsWith(CONTROL_PLANE_SOURCE_PREFIX)) continue; - try { - const stored = await readConfigJson(env, key); - if (!stored) throw new Error("indexed_record_unavailable"); - const source = normalizeControlPlaneSourceSnapshot(stored, key); - if (key !== controlPlaneSourceKey(source.source_id)) throw new Error("control plane source identity mismatch"); - sources.push(source); - } catch { - sources.push(emptyControlPlaneSourceSnapshot("control_plane_source_invalid")); - } - } - return sources; -} - -function controlPlaneSourceKey(sourceId) { - return `${CONTROL_PLANE_SOURCE_PREFIX}${sourceId}`; -} - -async function syncAdaptiveSelectionSourceResponse(request, env) { - requireDedicatedAdaptiveSelectionSyncToken(request, env); - if (!hasConfigStore(env)) { - return json({ ok: false, error: "adaptive selection KV is not configured" }, 503); - } - - let raw; - try { - raw = await readBoundedJson(request, ADAPTIVE_SELECTION_MAX_BODY_BYTES); - } catch (error) { - return json({ ok: false, error: error.message || "invalid adaptive selection payload" }, error.status || 400); - } - - let source; - try { - source = await normalizeAdaptiveSelectionSourceSnapshot(raw, "adaptive selection source snapshot"); - } catch (error) { - return json({ ok: false, error: error.message || "invalid adaptive selection payload" }, 400); - } - - await writeConfigJson(env, adaptiveSelectionSourceKey(source.source_id), source); - try { - await appendAuditLog(env, { - ts: new Date().toISOString(), - login: "adaptive-selection-source-sync", - action: "sync_adaptive_selection_source", - source_id: source.source_id, - schema_version: source.schema_version, - candidate_count: source.decision?.candidates.length || 0, - data_status: source.data_status, - no_order: true, - }); - } catch { - // This source remains safe and usable if the rolling convenience log fails. - } - return json({ - ok: true, - source_id: source.source_id, - schema_version: source.schema_version, - candidate_count: source.decision?.candidates.length || 0, - generated_at: source.generated_at, - no_order: true, - }); -} - -async function syncM0ResearchLedgerResponse(request, env) { - requireDedicatedM0ResearchSyncToken(request, env); - if (!hasConfigStore(env)) { - return json({ ok: false, error: "M0 research ledger KV is not configured" }, 503); - } - - let raw; - try { - raw = await readBoundedJson(request, M0_RESEARCH_MAX_BODY_BYTES); - } catch (error) { - return json({ ok: false, error: error.message || "invalid M0 research ledger payload" }, error.status || 400); - } - - let envelope; - try { - envelope = await normalizeM0ResearchLedgerTransport(raw, "M0 research ledger transport"); - } catch (error) { - // Validation deliberately completes before any KV write. A malformed, - // over-scoped, or digest-mismatched payload can therefore never replace - // the last known-good current ledger. - return json({ ok: false, error: error.message || "invalid M0 research ledger payload" }, 400); - } - - const current = await readCurrentM0ResearchLedgerRecord(env); - if (current) { - // A retry for the exact immutable source is not a new decision or a - // second write. Acknowledge it so a lost response cannot make a valid - // no-order publication look failed, while retaining the guards below. - if (envelope.source_artifact.sha256 === current.envelope.source_artifact.sha256) { - return json({ - ok: true, - schema_version: current.envelope.schema_version, - source_repository: current.envelope.source_artifact.repository, - source_revision: current.envelope.source_artifact.revision, - source_run_id: current.envelope.source_artifact.run_id, - artifact_id: current.envelope.source_artifact.artifact_id, - ledger_sha256: current.envelope.ledger_sha256, - replayed: true, - no_order: true, - expires_at: current.expires_at, - }); - } - const replayError = m0ResearchLedgerReplayError(current.envelope, envelope); - if (replayError) return json({ ok: false, error: replayError }, 409); - } - - // Capture once so the persisted interval is exactly the requested physical - // retention even when the request crosses a wall-clock second boundary. - const storedNow = new Date(); - const storedAt = utcTimestampSeconds(storedNow); - const expiresAt = utcTimestampSeconds(new Date(storedNow.getTime() + M0_RESEARCH_RETENTION_SECONDS * 1000)); - const record = { - schema_version: M0_RESEARCH_STORAGE_SCHEMA_VERSION, - stored_at: storedAt, - expires_at: expiresAt, - envelope, - }; - try { - // Archive first: an archive failure must not advance the current pointer. - // The archive identity is derived exclusively from the verified digest; - // callers never choose a KV key. - await writeM0ResearchLedgerRecord(env, m0ResearchLedgerArchiveKey(envelope.ledger_sha256), record); - await writeM0ResearchLedgerRecord(env, M0_RESEARCH_CURRENT_KEY, record); - } catch { - return json({ ok: false, error: "M0 research ledger persistence failed" }, 503); - } - - try { - await appendAuditLog(env, { - ts: storedAt, - login: "m0-research-ledger-sync", - action: "sync_m0_research_ledger", - source_repository: envelope.source_artifact.repository, - source_revision: envelope.source_artifact.revision, - source_run_id: envelope.source_artifact.run_id, - artifact_id: envelope.source_artifact.artifact_id, - ledger_sha256: envelope.ledger_sha256, - no_order: true, - }); - } catch { - // Retention of an optional convenience log must not change a valid, - // already persisted no-order research ledger. - } - return json({ - ok: true, - schema_version: envelope.schema_version, - source_repository: envelope.source_artifact.repository, - source_revision: envelope.source_artifact.revision, - source_run_id: envelope.source_artifact.run_id, - artifact_id: envelope.source_artifact.artifact_id, - ledger_sha256: envelope.ledger_sha256, - replayed: false, - no_order: true, - expires_at: expiresAt, - }); -} - -async function m0ResearchLedgerResponse(request, env) { - const session = await readSession(request, env); - if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); - if (!hasConfigStore(env)) return json(emptyM0ResearchDashboardPayload("m0_research_ledger_unavailable")); - const record = await readCurrentM0ResearchLedgerRecord(env); - return json(record - ? projectM0ResearchDashboardForRead(record.envelope.ledger, record.envelope.ledger_sha256) - : emptyM0ResearchDashboardPayload("m0_research_ledger_unavailable")); -} - -function m0ResearchLedgerReplayError(current, incoming) { - if (!current || !incoming) return null; - if (incoming.ledger_sha256 === current.ledger_sha256 - || incoming.source_artifact.sha256 === current.source_artifact.sha256) { - return "M0 research ledger current replay rejected"; - } - // Run IDs are closed identifiers rather than an assumed numeric sequence. - // An equal source/run identity is a replay; ledger time is the portable - // ordering guard for a different run identifier. - if (incoming.source_artifact.repository === current.source_artifact.repository - && incoming.source_artifact.run_id === current.source_artifact.run_id) { - return "M0 research ledger current replay rejected"; - } - const incomingTime = Date.parse(incoming.ledger.computed_at); - const currentTime = Date.parse(current.ledger.computed_at); - if (Number.isFinite(incomingTime) && Number.isFinite(currentTime) && incomingTime <= currentTime) { - return "M0 research ledger time rollback rejected"; - } - return null; -} - -function m0ResearchLedgerArchiveKey(ledgerSha256) { - return `${M0_RESEARCH_ARCHIVE_PREFIX}${ledgerSha256}`; -} - -async function writeM0ResearchLedgerRecord(env, key, record) { - const store = configStore(env); - if (!store) throw new Error("M0 research ledger KV is not configured"); - await store.put(key, JSON.stringify(record), { expirationTtl: M0_RESEARCH_RETENTION_SECONDS }); -} - -async function readCurrentM0ResearchLedgerRecord(env) { - if (!hasConfigStore(env)) return null; - let stored; - try { - stored = await readConfigJson(env, M0_RESEARCH_CURRENT_KEY); - } catch { - return null; - } - if (!stored) return null; - try { - return await normalizeM0ResearchLedgerStorageRecord(stored, "M0 research ledger current record"); - } catch { - return null; - } -} - -async function adaptiveSelectionResponse(request, env) { - const session = await readSession(request, env); - if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); - if (!hasConfigStore(env)) return json(emptyAdaptiveSelectionPayload("adaptive_selection_source_unavailable")); - return json(await aggregateAdaptiveSelectionSources(env)); -} - -async function aggregateAdaptiveSelectionSources(env) { - const sources = await readAdaptiveSelectionSources(env); - if (!sources.length) return emptyAdaptiveSelectionPayload("adaptive_selection_source_unavailable"); - - const ttlSeconds = adaptiveSelectionStaleTtlSeconds(env); - const now = Date.now(); - const selections = []; - const errors = []; - const timestamps = []; - let hasReadySource = false; - let hasStaleSource = false; - - for (const source of sources) { - const freshness = controlPlaneSnapshotFreshness(source, ttlSeconds, now); - if (source.generated_at) timestamps.push(source.generated_at); - if (source.computed_at) timestamps.push(source.computed_at); - if (freshness.data_status === "ready") hasReadySource = true; - if (freshness.data_status === "stale") { - hasStaleSource = true; - errors.push("adaptive_selection_source_stale"); - } - if (source.decision) selections.push({ source_id: source.source_id, freshness, decision: source.decision }); - errors.push(...source.errors); - } - - return { - schema_version: ADAPTIVE_SELECTION_DASHBOARD_SCHEMA_VERSION, - generated_at: earliestControlPlaneTimestamp(timestamps), - computed_at: earliestControlPlaneTimestamp(timestamps), - data_status: hasStaleSource ? "stale" : (hasReadySource ? "ready" : "unavailable"), - summary: normalizeAdaptiveSelectionSummary(selections), - selections: selections.sort((left, right) => left.source_id.localeCompare(right.source_id)), - policy: { - authority: ADAPTIVE_SELECTION_AUTHORITY, - no_order: true, - execution_authority_granted: false, - notice: "M1 仅展示 Shadow 建议和拒绝原因;不会修改策略、平台、资金、运行状态或订单。", - }, - errors: uniqueStrings(errors), - }; -} - -async function readAdaptiveSelectionSources(env) { - const store = configStore(env); - if (!store) return []; - let listing; - try { - listing = await directoryList(env, ADAPTIVE_SELECTION_SOURCE_PREFIX, ADAPTIVE_SELECTION_MAX_SOURCES); - } catch { - return [emptyAdaptiveSelectionSourceSnapshot("adaptive_selection_source_list_unavailable")]; - } - const keys = Array.isArray(listing?.keys) ? listing.keys : []; - const sources = []; - for (const entry of keys.slice(0, ADAPTIVE_SELECTION_MAX_SOURCES)) { - const key = typeof entry?.name === "string" ? entry.name : ""; - if (!key.startsWith(ADAPTIVE_SELECTION_SOURCE_PREFIX)) continue; - try { - const stored = await readConfigJson(env, key); - if (!stored) throw new Error("indexed_record_unavailable"); - sources.push(await normalizeAdaptiveSelectionSourceSnapshot(stored, key)); - } catch { - sources.push(emptyAdaptiveSelectionSourceSnapshot("adaptive_selection_source_invalid")); - } - } - return sources; -} - -function adaptiveSelectionSourceKey(sourceId) { - return `${ADAPTIVE_SELECTION_SOURCE_PREFIX}${sourceId}`; -} - -function controlPlaneSnapshotFreshness(snapshot, ttlSeconds, now) { - const timestamps = [snapshot.generated_at, snapshot.computed_at] - .filter(Boolean) - .map((value) => Date.parse(value)); - const freshnessAt = timestamps.length ? Math.min(...timestamps) : Number.NaN; - const futureBeyondClockSkew = Number.isFinite(freshnessAt) && freshnessAt > now + 5 * 60 * 1000; - const ageSeconds = futureBeyondClockSkew - ? Number.POSITIVE_INFINITY - : Number.isFinite(freshnessAt) - ? Math.max(0, Math.round((now - freshnessAt) / 1000)) - : Number.POSITIVE_INFINITY; - const dataStatus = snapshot.data_status === "ready" && ageSeconds > ttlSeconds - ? "stale" - : snapshot.data_status; - return { data_status: dataStatus, age_seconds: Number.isFinite(ageSeconds) ? ageSeconds : null }; -} - -function controlPlaneResearchSnapshotFreshness(snapshot, ttlSeconds, now) { - if (snapshot.data_status !== "ready") { - return controlPlaneSnapshotFreshness(snapshot, ttlSeconds, now); - } - const timestamps = [snapshot.generated_at, snapshot.computed_at] - .filter(Boolean) - .map((value) => Date.parse(value)); - const freshnessAt = timestamps.length ? Math.min(...timestamps) : Number.NaN; - const futureBeyondClockSkew = Number.isFinite(freshnessAt) && freshnessAt > now + 5 * 60 * 1000; - const ageSeconds = futureBeyondClockSkew - ? Number.POSITIVE_INFINITY - : Number.isFinite(freshnessAt) - ? Math.max(0, Math.round((now - freshnessAt) / 1000)) - : Number.POSITIVE_INFINITY; - if (!Number.isFinite(freshnessAt) || futureBeyondClockSkew) { - return { data_status: "stale", age_seconds: null }; - } - - const ttlDeadline = freshnessAt + ttlSeconds * 1000; - const nextScheduledRefresh = nextControlPlaneResearchScheduleAfter(freshnessAt); - const scheduledDeadline = nextScheduledRefresh + CONTROL_PLANE_RESEARCH_DELIVERY_GRACE_MS; - const staleDeadline = Math.max(ttlDeadline, scheduledDeadline); - return { - data_status: now > staleDeadline ? "stale" : "ready", - age_seconds: ageSeconds, - }; -} - -function nextControlPlaneResearchScheduleAfter(freshnessAt) { - const scheduled = new Date(freshnessAt); - scheduled.setUTCMinutes(0, 0, 0); - scheduled.setUTCHours(CONTROL_PLANE_RESEARCH_SCHEDULE_UTC_HOUR); - if (scheduled.getTime() <= freshnessAt) scheduled.setUTCDate(scheduled.getUTCDate() + 1); - while (!CONTROL_PLANE_RESEARCH_SCHEDULE_UTC_DAYS.has(scheduled.getUTCDay())) { - scheduled.setUTCDate(scheduled.getUTCDate() + 1); - } - return scheduled.getTime(); -} - -// 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.mode === "initial_enrollment" ? recovery.candidate.last_observed_at : 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; - const ageSeconds = sourceAgeSeconds === null - ? candidateAge - : (candidateAge === null ? sourceAgeSeconds : Math.max(candidateAge, sourceAgeSeconds)); - const status = candidateFreshness.status === "stale" || sourceFreshness.data_status !== "ready" - ? "stale" - : candidateFreshness.status; - return { status, age_seconds: ageSeconds }; -} - -function earliestControlPlaneTimestamp(values) { - const timestamps = values - .filter(Boolean) - .map((value) => ({ value, timestamp: Date.parse(value) })) - .filter((item) => Number.isFinite(item.timestamp)) - .sort((left, right) => left.timestamp - right.timestamp); - return timestamps[0]?.value || null; -} - -async function readBoundedJson(request, maxBytes) { - const declaredLength = Number(request.headers.get("Content-Length") || 0); - if (declaredLength > maxBytes) throw new HttpError("request body is too large", 413); - const bytes = await request.arrayBuffer(); - if (bytes.byteLength > maxBytes) throw new HttpError("request body is too large", 413); - try { - return JSON.parse(new TextDecoder().decode(bytes)); - } catch { - throw new HttpError("request body must be valid JSON", 400); - } -} - -function requireDedicatedStrategyHealthSyncToken(request, env) { - const expected = String(env.STRATEGY_HEALTH_SYNC_TOKEN || ""); - if (!expected) throw new HttpError("strategy health 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("strategy health sync token is invalid", 401); -} - -function requireDedicatedControlPlaneSyncToken(request, env) { - const expected = String(env.CONTROL_PLANE_SYNC_TOKEN || ""); - if (!expected) throw new HttpError("control plane 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("control plane sync token is invalid", 401); -} - -function requireControlPlaneSourceSyncToken(request, env) { - const validationToken = String(env.AAB_VALIDATION_SYNC_TOKEN || ""); - const token = (request.headers.get("Authorization") || "").match(/^Bearer\s+(.+)$/i)?.[1] || ""; - // Check the narrower credential first, including accidental equal bindings. - if (validationToken && token === validationToken) return true; - if (validationToken && !env.CONTROL_PLANE_SYNC_TOKEN) { - throw new HttpError("control plane source sync token is invalid", 401); - } - requireDedicatedControlPlaneSyncToken(request, env); - return false; -} - -function requireDedicatedAdaptiveSelectionSyncToken(request, env) { - const expected = String(env.ADAPTIVE_SELECTION_SYNC_TOKEN || ""); - if (!expected) throw new HttpError("adaptive selection 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("adaptive selection sync token is invalid", 401); -} - -function requireDedicatedExecutionEvidenceSyncToken(request, env) { - const expected = String(env.EXECUTION_EVIDENCE_SYNC_TOKEN || ""); - if (!expected) throw new HttpError("execution evidence 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("execution evidence sync token is invalid", 401); -} - -function requireDedicatedAccountFactsSyncToken(request, env) { - const configured = [ - ["longbridge", String(env.ACCOUNT_FACTS_SYNC_TOKEN || "")], - [IBKR_ACCOUNT_FACTS_PLATFORM, String(env.IBKR_ACCOUNT_FACTS_SYNC_TOKEN || "")], - [SCHWAB_ACCOUNT_FACTS_PLATFORM, String(env.SCHWAB_ACCOUNT_FACTS_SYNC_TOKEN || "")], - [FIRSTRADE_ACCOUNT_FACTS_PLATFORM, String(env.FIRSTRADE_ACCOUNT_FACTS_SYNC_TOKEN || "")], - ].filter(([, token]) => token); - const firstradeToken = String(env.FIRSTRADE_ACCOUNT_FACTS_SYNC_TOKEN || ""); - if (firstradeToken && [env.BINANCE_ACCOUNT_FACTS_SYNC_TOKEN, env.EXECUTION_EVIDENCE_SYNC_TOKEN, - env.STRATEGY_SWITCH_SYNC_TOKEN, env.RECONCILIATION_RECOVERY_SYNC_TOKEN, - env.RECONCILIATION_RECOVERY_CONTROLLER_TOKEN, env.CYCLE_HEALTH_PROVISIONER_TOKEN] - .some(other => other && String(other) === firstradeToken)) { - throw new HttpError("account_facts_sync_token_ambiguous", 503); - } - if (new Set(configured.map(([, token]) => token)).size !== configured.length) { - throw new HttpError("account_facts_sync_token_ambiguous", 503); - } - if (!configured.length) throw new HttpError("account_facts_sync_token_not_configured", 500); - const header = request.headers.get("Authorization") || ""; - const token = header.match(/^Bearer\s+(.+)$/i)?.[1] || ""; - const match = configured.find(([, expected]) => token === expected); - if (match) return match[0]; - throw new HttpError("account_facts_sync_token_invalid", 401); -} - -function accountFactsDisabledResponse() { - return json({ ok: false, error: "account_facts_disabled" }, 404); -} - -// Sync producers keep only the HTTP status, so a refused push is undiagnosable -// from their logs. Publish the refusal code, tagged with a fixed endpoint literal -// from the caller; free-form exception text is classified to "unknown" so payload -// content, identities and credentials cannot enter the log stream. The response -// body and status are exactly what this helper already returned. -function accountFactsErrorResponse(error, endpoint = "read") { - const code = error instanceof AccountFactsError ? error.code - : error instanceof HttpError ? error.message - : error?.message || "invalid_account_facts_history"; - const status = error?.status || 400; - console.warn("account_facts_refused", { - endpoint, - http_status: status, - error_category: typeof code === "string" && /^[a-z][a-z0-9_]{2,63}$/.test(code) ? code : "unknown", - }); - return json({ ok: false, error: code }, status); -} - -function requireCycleHealthProvisionToken(request, env) { - const expected = String(env.STRATEGY_SWITCH_SYNC_TOKEN || env.RUNTIME_SETTINGS_DISPATCH_TOKEN || ""); - if (!expected) throw new HttpError("cycle_health_provisioner_unavailable", 503); - const sourceTokens = [ - env.ACCOUNT_FACTS_SYNC_TOKEN, env.IBKR_ACCOUNT_FACTS_SYNC_TOKEN, - env.SCHWAB_ACCOUNT_FACTS_SYNC_TOKEN, env.FIRSTRADE_ACCOUNT_FACTS_SYNC_TOKEN, env.BINANCE_ACCOUNT_FACTS_SYNC_TOKEN, - env.EXECUTION_EVIDENCE_SYNC_TOKEN, - ].filter(Boolean).map(String); - if (sourceTokens.includes(expected)) throw new HttpError("cycle_health_provisioner_token_alias", 503); - const token = (request.headers.get("Authorization") || "").match(/^Bearer\s+(.+)$/i)?.[1] || ""; - if (token !== expected) throw new HttpError("cycle_health_provisioner_token_invalid", 401); -} - -async function resolveCycleHealthBindingFingerprint(env, targetId, bindingId) { - const config = await loadAccountOptionsConfig(env); - const options = Array.isArray(config?.options?.longbridge) ? config.options.longbridge : []; - const matches = options.filter(o => o.runtime_status_target_id === targetId); - const allMatches = Object.entries(config.options || {}).flatMap(([platform, items]) => - (Array.isArray(items) ? items : []).filter(o => o.runtime_status_target_id === targetId).map(o => ({ platform, key: o.key }))); - if (matches.length !== 1 || allMatches.length !== 1 || allMatches[0].platform !== "longbridge" - || options.filter(o => o.key === matches[0]?.key).length !== 1) throw new HttpError("cycle_health_option_not_unique", 409); - const bindings = await loadAccountFactsBindings(env); - const bound = bindings?.bindings?.filter(b => b.platform === "longbridge" && b.account_key === matches[0].key) || []; - if (bound.length !== 1 || bound[0].account_scope !== "paper" || bound[0].source_binding.id !== bindingId - || !accountFactsOptionMatchesBinding(matches[0], bound[0])) throw new HttpError("cycle_health_binding_mismatch", 409); - return cycleHealthSha256({ binding: bound[0], runtime_status_target_id: targetId }); -} - -async function runtimeCycleHealthAdmissionResponse(request, env) { - try { - requireCycleHealthProvisionToken(request, env); - if (!hasRuntimeInstanceStore(env)) throw new HttpError("cycle_health_store_unavailable", 503); - const raw = await readBoundedJson(request, 8192); - const fields = ["source_id", "target_id", "configuration_sha256", "required_from", "expected_authority_revision"]; - if (!raw || typeof raw !== "object" || Array.isArray(raw) - || Object.keys(raw).length !== fields.length || fields.some(field => !Object.hasOwn(raw, field))) { - throw new HttpError("cycle_health_admission_request_invalid", 400); - } - const sourceId = normalizeControlPlaneIdentifier(raw.source_id, "source_id", false); - const targetId = raw.target_id; - if (targetId !== "longbridge.paper" || !/^[a-f0-9]{64}$/.test(raw.configuration_sha256 || "") - || !Number.isSafeInteger(raw.expected_authority_revision) || raw.expected_authority_revision < 0) { - throw new HttpError("cycle_health_admission_request_invalid", 400); - } - const requiredFrom = normalizeCycleInstant(raw.required_from); - const bindingId = request.headers.get("X-QSL-Source-Binding-ID") || ""; - if (!/^[a-f0-9]{64}$/.test(bindingId)) throw new HttpError("cycle_health_binding_required", 409); - const bindingFingerprint = await resolveCycleHealthBindingFingerprint(env, targetId, bindingId); - return json(await runtimeInstanceCommand(env, { - action: "cycle_health_provision", source_id: sourceId, target_id: targetId, - binding_fingerprint: bindingFingerprint, configuration_sha256: raw.configuration_sha256, - required_from: requiredFrom, expected_authority_revision: raw.expected_authority_revision, - })); - } catch (error) { - return json({ ok: false, error: error instanceof HttpError || error instanceof CycleHealthError - ? error.message : "cycle_health_admission_unavailable" }, error.status || 400); - } -} - -async function runtimeCycleHealthSourceResponse(request, env) { - try { - const platform = requireDedicatedAccountFactsSyncToken(request, env); - if (platform !== "longbridge") throw new HttpError("cycle_health_token_platform_mismatch", 403); - const dedicated = String(env.ACCOUNT_FACTS_SYNC_TOKEN || ""); - if ([env.EXECUTION_EVIDENCE_SYNC_TOKEN, env.BINANCE_ACCOUNT_FACTS_SYNC_TOKEN].some(t => t && String(t) === dedicated)) { - throw new HttpError("cycle_health_token_alias", 503); - } - if (!hasRuntimeInstanceStore(env)) throw new HttpError("cycle_health_store_unavailable", 503); - const url = new URL(request.url); - let source, targetId, sourceId, historyCursor = null; - const bindingId = request.headers.get("X-QSL-Source-Binding-ID") || ""; - if (!/^[a-f0-9]{64}$/.test(bindingId)) throw new HttpError("cycle_health_binding_required", 409); - if (request.method === "GET") { - if ([...url.searchParams.keys()].some(k => !["source_id", "target_id", "history_cursor"].includes(k)) - || url.searchParams.getAll("source_id").length !== 1 || url.searchParams.getAll("target_id").length !== 1 - || url.searchParams.getAll("history_cursor").length > 1) throw new HttpError("cycle_health_invalid_query", 400); - sourceId = normalizeControlPlaneIdentifier(url.searchParams.get("source_id"), "source_id", false); - targetId = url.searchParams.get("target_id"); - historyCursor = url.searchParams.get("history_cursor"); - if (historyCursor !== null) { - if (historyCursor.length > 512) throw new HttpError("cycle_health_history_cursor_invalid", 400); - } - } else { - const raw = await readBoundedJson(request, ACCOUNT_FACTS_MAX_BODY_BYTES); - if (!Array.isArray(raw?.targets) || raw.targets.length !== 1) throw new HttpError("cycle_health_one_target_required", 400); - const { cycle_health: health, ...legacyTarget } = raw.targets[0]; - if (!health) throw new HttpError("cycle_health_extension_required", 400); - // Reuse the lifecycle shape with the companion member removed. The legacy - // normalizer and legacy POST still reject this extension as before. - source = normalizeRuntimeTargetLifecycleSourceSnapshot({ ...raw, targets: [legacyTarget] }); - normalizeCycleInstant(raw.generated_at); normalizeCycleInstant(raw.computed_at); - if (raw.generated_at !== raw.computed_at || source.data_status !== "ready") throw new HttpError("cycle_health_observation_time_mismatch", 400); - source.generated_at = raw.generated_at; source.computed_at = raw.computed_at; - if (legacyTarget.deployment?.observed_at !== undefined) { - source.targets[0].deployment.observed_at = normalizeCycleInstant(legacyTarget.deployment.observed_at); - if (compareCycleInstants(legacyTarget.deployment.observed_at, raw.computed_at) > 0) throw new HttpError("cycle_health_deployment_chronology", 400); - } - const age = Date.now() - Date.parse(source.computed_at); - if (age > ACCOUNT_FACTS_STALE_MS || age < -ACCOUNT_FACTS_FUTURE_SKEW_MS) throw new HttpError("cycle_health_observation_age", 409); - targetId = source.targets[0].target_id; sourceId = source.source_id; - source.targets[0].cycle_health = await normalizeCycleHealth(health, { - target_id: targetId, source_binding_id: bindingId, observed_at: source.computed_at, - }); - } - if (targetId !== "longbridge.paper" || (source && (source.targets[0].target.platform !== "longbridge" || source.targets[0].target.execution_mode !== "paper"))) throw new HttpError("cycle_health_paper_target_required", 403); - const bindingFingerprint = await resolveCycleHealthBindingFingerprint(env, targetId, bindingId); - const identity = { source_id: sourceId, target_id: targetId, binding_fingerprint: bindingFingerprint, source_binding_id: bindingId }; - const admission = await runtimeInstanceCommand(env, { ...identity, action: "cycle_health_admission" }); - if (source && source.targets[0].cycle_health.configuration_sha256 !== admission.configuration_sha256) throw new HttpError("cycle_health_configuration_not_admitted", 409); - const command = { ...identity, ...admission, action: source ? "cycle_health_put" : "cycle_health_read", - ...(historyCursor ? { history_cursor: historyCursor } : {}) }; - if (source) { - command.observation = source; - command.observation_json = canonicalCycleJson(source); - command.observation_sha256 = await cycleHealthSha256(source); - command.received_at = new Date().toISOString(); - } - return json(await runtimeInstanceCommand(env, command)); - } catch (error) { - return json({ ok: false, error: error instanceof HttpError || error instanceof CycleHealthError ? error.message : "cycle_health_invalid_source" }, error.status || 400); - } -} - -async function loadAccountFactsBindings(env, { propagateReadErrors = false } = {}) { - if (!hasConfigStore(env)) { - if (propagateReadErrors) throw new Error("account_facts_bindings_store_unavailable"); - return null; - } - let raw; - try { - raw = await readConfigJson(env, ACCOUNT_FACTS_BINDINGS_KEY); - } catch (error) { - if (propagateReadErrors) throw error; - return null; - } - if (!raw) return null; - return normalizeAccountFactsBindings(raw); -} - -async function loadStoredAccountFactsMap(env, accountOptions, bindings) { - if (!hasRuntimeInstanceStore(env)) throw new HttpError("account_facts_store_unavailable", 503); - const accounts = []; - const bindingByAccount = new Map( - (bindings?.bindings || []).map((item) => [`${item.platform}:${item.account_key}`, item]), - ); - for (const [platform, options] of Object.entries(accountOptions || {})) { - if (!Array.isArray(options)) continue; - for (const option of options) { - if (!option || typeof option.key !== "string" || !option.key) continue; - const binding = bindingByAccount.get(`${platform}:${option.key}`); - if (!binding || !accountFactsOptionMatchesBinding(option, binding)) continue; - accounts.push({ - platform, - account_key: option.key, - account_scope: binding.account_scope, - account_selector: binding.account_selector, - ...(platform === SCHWAB_ACCOUNT_FACTS_PLATFORM - ? { broker_account_hash: binding.broker_account_hash } - : {}), - ...(platform === FIRSTRADE_ACCOUNT_FACTS_PLATFORM ? { broker_account_id: binding.broker_account_id } : {}), - target_id: binding.target_id, - source_binding_id: binding.source_binding.id, - }); - } - } - const listed = await runtimeInstanceCommand(env, { - action: "account_facts_list", - accounts, - }); - const storedByAccount = new Map(); - for (const row of listed.observations || []) { - if (!row?.platform || !row?.account_key || !row?.payload) continue; - storedByAccount.set(`${row.platform}:${row.account_key}`, row.payload); - } - return storedByAccount; -} - -function periodReturnCommand(binding, action) { - return { action, platform: "ibkr", account_key: binding.account_key, account_scope: binding.account_scope, - account_selector: binding.account_selector, target_id: binding.target_id, source_binding_id: binding.source_binding.id, - ...(binding.history_source_binding_ids ? { history_source_binding_ids: binding.history_source_binding_ids } : {}) }; -} - -async function readBoundAccountExecutions(option, env) { - if (option.platform !== "ibkr" || !accountFactsReadModelEnabled(env)) return null; - const bindings = await loadAccountFactsBindings(env); - const matches = (bindings?.bindings || []).filter(binding => binding.platform === option.platform - && binding.account_key === option.key); - if (!matches.length) return null; - if (matches.length !== 1 || !accountFactsOptionMatchesBinding(option, matches[0])) { - throw new HttpError("execution_account_unattributed", 409); - } - const stored = await runtimeInstanceCommand(env, periodReturnCommand(matches[0], "account_executions_read")); - if (!stored.ok) throw new HttpError("execution_store_unavailable", 503); - return stored.executions; -} - -async function syncAccountExternalFlowResponse(request, env) { - if (!accountFactsReadModelEnabled(env)) return accountFactsDisabledResponse(); - try { - const platform = requireDedicatedAccountFactsSyncToken(request, env); - if (!hasConfigStore(env) || !hasRuntimeInstanceStore(env)) throw new HttpError("account_facts_store_unavailable", 503); - const raw = await readBoundedJson(request, ACCOUNT_FACTS_MAX_BODY_BYTES); - const fields = ["schema_version", "target_id", "source_binding_id", "account_scope", "account_selector", "externalFlow"]; - if (!raw || typeof raw !== "object" || Array.isArray(raw) || Object.keys(raw).length !== fields.length - || fields.some(key => !Object.hasOwn(raw, key)) || raw.schema_version !== "account_external_flow.v1") { - throw new HttpError("invalid_external_flow", 400); - } - const bindings = await loadAccountFactsBindings(env); - const resolved = resolveTrustedAccountFactsBinding(bindings, raw.target_id, raw.source_binding_id); - if (!resolved.ok || resolved.binding.platform !== platform) throw new HttpError("account_facts_identity_mismatch", 409); - const binding = resolved.binding; - if (raw.account_scope !== binding.account_scope || raw.account_selector !== binding.account_selector) { - throw new HttpError("account_facts_identity_mismatch", 409); - } - const config = await loadAccountOptionsConfig(env); - const options = (config.options?.[platform] || []).filter(item => item.key === binding.account_key); - if (options.length !== 1 || !accountFactsOptionMatchesBinding(options[0], binding)) throw new HttpError("account_facts_account_unattributed", 409); - const flow = normalizeExternalFlow(raw.externalFlow); - if (!externalFlowEnabled(env) && !dateOnlyExternalFlow(flow)) throw new HttpError("external_flow_disabled", 409); - const source = { ibkr: "ibkr_flex", schwab: "schwab_transactions", longbridge: "longbridge_cashflow" }[platform]; - if (!source || flow.windows.some(w => w.source !== source)) throw new HttpError("external_flow_source_mismatch", 409); - const stored = await runtimeInstanceCommand(env, { action: "account_external_flow_put", platform, - account_key: binding.account_key, account_scope: binding.account_scope, account_selector: binding.account_selector, - target_id: binding.target_id, source_binding_id: binding.source_binding.id, - ...(platform === SCHWAB_ACCOUNT_FACTS_PLATFORM ? { broker_account_hash: binding.broker_account_hash } : {}), - ...(platform === FIRSTRADE_ACCOUNT_FACTS_PLATFORM ? { broker_account_id: binding.broker_account_id } : {}), - externalFlow: flow }); - return json({ ok: true, stored: true, unchanged: stored.unchanged === true, readback_verified: stored.readback_verified === true, - account_key: binding.account_key, window_count: flow.windows.length, - record_count: new Set(flow.windows.flatMap(w => w.records.map(r => r.source_ref))).size }); - } catch (error) { - if (error instanceof AccountFactsError || error instanceof HttpError || error?.code?.startsWith("external_flow") - || error?.code === "invalid_external_flow") return accountFactsErrorResponse(error, "external_flow_sync"); - return json({ ok: false, error: "account_external_flow_unavailable" }, 503); - } -} - -async function syncAccountExecutionsResponse(request, env) { - if (!accountFactsReadModelEnabled(env)) return accountFactsDisabledResponse(); - try { - if (requireDedicatedAccountFactsSyncToken(request, env) !== "ibkr") throw new HttpError("account_facts_sync_token_platform_mismatch", 403); - if (!hasConfigStore(env) || !hasRuntimeInstanceStore(env)) throw new HttpError("account_facts_store_unavailable", 503); - const raw = await readBoundedJson(request, ACCOUNT_FACTS_MAX_BODY_BYTES); - const bindings = await loadAccountFactsBindings(env); - const resolved = resolveTrustedAccountFactsBinding(bindings, raw?.target_id, raw?.source_binding_id); - if (!resolved.ok || resolved.binding.platform !== "ibkr") throw new HttpError("account_facts_identity_mismatch", 409); - const binding = resolved.binding; - const config = await loadAccountOptionsConfig(env); - const options = (config.options?.ibkr || []).filter(item => item.key === binding.account_key); - if (options.length !== 1 || !accountFactsOptionMatchesBinding(options[0], binding)) throw new HttpError("account_facts_account_unattributed", 409); - const payload = normalizeExecutions(raw, { binding }); - if (payload.records.some(row => row.report_ref !== payload.source_ref)) throw new HttpError("invalid_broker_executions", 400); - const stored = await runtimeInstanceCommand(env, { ...periodReturnCommand(binding, "account_executions_put"), payload }); - return json({ ok: true, stored: true, unchanged: stored.unchanged === true, - account_key: binding.account_key, period: payload.period, record_count: payload.records.length }); - } catch (error) { - if (error instanceof AccountFactsError || error instanceof HttpError || error?.code?.startsWith("execution_") - || error?.code === "invalid_broker_executions") return accountFactsErrorResponse(error, "executions_sync"); - return json({ ok: false, error: "account_executions_unavailable" }, 503); - } -} - -async function syncAccountPeriodReturnResponse(request, env) { - if (!accountFactsReadModelEnabled(env)) return accountFactsDisabledResponse(); - try { - if (requireDedicatedAccountFactsSyncToken(request, env) !== "ibkr") throw new HttpError("account_facts_sync_token_platform_mismatch", 403); - if (!hasConfigStore(env) || !hasRuntimeInstanceStore(env)) throw new HttpError("account_facts_store_unavailable", 503); - const raw = await readBoundedJson(request, ACCOUNT_FACTS_MAX_BODY_BYTES); - const bindings = await loadAccountFactsBindings(env); - const resolved = resolveTrustedAccountFactsBinding(bindings, raw?.target_id, raw?.source_binding_id); - if (!resolved.ok || resolved.binding.platform !== "ibkr") throw new HttpError("account_facts_identity_mismatch", 409); - const binding = resolved.binding; - const config = await loadAccountOptionsConfig(env); - const options = (config.options?.ibkr || []).filter(item => item.key === binding.account_key); - if (options.length !== 1 || !accountFactsOptionMatchesBinding(options[0], binding)) throw new HttpError("account_facts_account_unattributed", 409); - const payload = normalizeIbkrPeriodReturn(raw, { binding }); - const stored = await runtimeInstanceCommand(env, { ...periodReturnCommand(binding, "account_period_return_put"), payload, - historical: new URL(request.url).searchParams.get("historical") === "true" }); - return json({ ok: true, stored: true, unchanged: stored.unchanged === true, account_key: binding.account_key, - period: payload.period, currency: payload.currency, method: payload.method }); - } catch (error) { - if (error instanceof AccountFactsError || error instanceof HttpError) return accountFactsErrorResponse(error, "period_return_sync"); - return json({ ok: false, error: "account_period_return_unavailable" }, 503); - } -} - -async function syncAccountFactsResponse(request, env, options) { - return syncAccountFactsResponseImpl(request, env, { - json, - hasConfigStore, - hasRuntimeInstanceStore, - loadAccountOptionsConfig, - loadAccountFactsBindings, - runtimeInstanceCommand, - readBoundedJson, - requireDedicatedAccountFactsSyncToken, - accountFactsErrorResponse: (error) => accountFactsErrorResponse( - error, options?.historical ? "history_sync" : "current_sync"), - }, options); -} - -async function accountFactsResponse(request, env) { - return accountFactsResponseImpl(request, env, { - json, - readSession, - hasConfigStore, - hasRuntimeInstanceStore, - loadAccountOptionsConfig, - loadAccountFactsBindings: (env) => loadAccountFactsBindings(env, { propagateReadErrors: true }), - loadStoredAccountFactsMap, - runtimeInstanceCommand, - periodReturnCommand, - accountFactsErrorResponse, - }); -} - -async function accountFactsHistoryResponse(request, env, url) { - return accountFactsHistoryResponseImpl(request, env, url, { - json, - readSession, - hasConfigStore, - hasRuntimeInstanceStore, - loadAccountOptionsConfig, - loadAccountFactsBindings: (env) => loadAccountFactsBindings(env, { propagateReadErrors: true }), - runtimeInstanceCommand, - accountFactsErrorResponse, - sha256Hex, - }); -} - -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 requireDedicatedReconciliationRecoveryControllerToken(request, env) { - const expected = String(env.RECONCILIATION_RECOVERY_CONTROLLER_TOKEN || ""); - if (!expected) throw new HttpError("reconciliation recovery controller token is not configured", 500); - if (expected === String(env.RECONCILIATION_RECOVERY_SYNC_TOKEN || "")) { - throw new HttpError("reconciliation recovery controller token must differ from the source sync token", 500); - } - const header = request.headers.get("Authorization") || ""; - const token = header.match(/^Bearer\s+(.+)$/i)?.[1] || ""; - if (token !== expected) throw new HttpError("reconciliation recovery controller token is invalid", 401); -} - - - -function requireDedicatedResearchPromotionSyncToken(request, env) { - const expected = String(env.RESEARCH_PROMOTION_SYNC_TOKEN || ""); - if (!expected) throw new HttpError("research promotion 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("research promotion sync token is invalid", 401); -} - -function researchPromotionTicketKey(ticketId) { - return `${RESEARCH_PROMOTION_TICKET_PREFIX}${ticketId}`; -} - -function platformSupportsBrokerPaperMode(platform) { - // Broker paper/sim is distinct from the local dry_run adapter. LongBridge's - // paper account is currently the only configured broker simulation route. - return platform === "longbridge"; -} - -function supportedBrokerEnvironmentsForPlatform(platform) { - return platformSupportsBrokerPaperMode(platform) ? [...BROKER_ENVIRONMENTS] : ["live"]; -} - -function normalizeBrokerEnvironment(value, fieldName = "broker_environment") { - const environment = String(value || "").trim(); - if (!BROKER_ENVIRONMENTS.includes(environment)) { - throw new Error(`${fieldName} must be live or paper`); - } - return environment; -} - -function normalizeResearchPromotionRiskProfile(value, fieldName) { - const profile = String(value || "").trim().toUpperCase(); - if (!RESEARCH_PROMOTION_RISK_PROFILES.includes(profile)) { - throw new Error( - `${fieldName} must be CAPITAL_PRESERVATION, BALANCED_COMPOUNDING, or GROWTH_COMPOUNDING`, - ); - } - return profile; -} - -function normalizePromotionConfirmation(value, fieldName, { paperSupported }) { - if (!value || Array.isArray(value) || typeof value !== "object") { - throw new Error(`${fieldName} must be an object`); - } - // Keep exact QPK PromotionConfirmation.to_dict() fields. - assertExactFields(value, ["target_platform", "execution_mode", "risk_profile"], fieldName); - const targetPlatform = String(value.target_platform || "").trim(); - const executionMode = String(value.execution_mode || "").trim().toLowerCase(); - const riskProfile = normalizeResearchPromotionRiskProfile( - value.risk_profile, - `${fieldName}.risk_profile`, - ); - if (!targetPlatform) throw new Error(`${fieldName}.target_platform is required`); - if (!RESEARCH_PROMOTION_EXECUTION_MODES.includes(executionMode)) { - throw new Error(`${fieldName}.execution_mode must be live or paper`); - } - if (executionMode === "paper" && !paperSupported) { - throw new Error("paper is unavailable for this platform; synthetic matching is not supported"); - } - return { - target_platform: targetPlatform, - execution_mode: executionMode, - risk_profile: riskProfile, - }; -} - -function validateResearchPromotionSelectedAccount({ - ticket, - selectedAccount, - confirmation, - accountOptions, - strategyProfiles, -}) { - assertExactFields(selectedAccount, ["platform", "key"], "selected_account"); - const platform = cleanChoice(selectedAccount.platform, SUPPORTED_PLATFORMS, "selected_account.platform"); - const key = cleanSlug(selectedAccount.key, "selected_account.key"); - const targetPlatform = String(confirmation?.target_platform || "").trim(); - if (targetPlatform !== platform) { - throw new HttpError("selected_account platform does not match confirmation target_platform", 409); - } - const option = (accountOptions?.[platform] || []).find((item) => item.key === key); - if (!option) throw new HttpError("selected_account does not match configured account", 409); - const brokerEnvironment = String(option.broker_environment || "").trim(); - if (!BROKER_ENVIRONMENTS.includes(brokerEnvironment)) { - throw new HttpError("selected account broker environment is unknown", 409); - } - if (!supportedBrokerEnvironmentsForPlatform(platform).includes(brokerEnvironment)) { - throw new HttpError(`${platform} does not support broker environment ${brokerEnvironment}`, 400); - } - if (String(confirmation?.execution_mode || "").trim().toLowerCase() !== brokerEnvironment) { - throw new HttpError("selected account broker environment does not match confirmation execution_mode", 409); - } - const strategy = (Array.isArray(strategyProfiles) ? strategyProfiles : []) - .find((item) => item.profile === ticket?.strategy_profile); - if (!strategy) throw new HttpError("selected candidate strategy profile is not configured", 409); - if (strategy.domain !== ticket?.domain) { - throw new HttpError("selected candidate strategy domain does not match ticket domain", 409); - } - if (!supportedDomainsForAccount(platform, option).includes(ticket.domain)) { - throw new HttpError("selected account does not support candidate domain", 409); - } - if (PLATFORM_CONFIG[platform]?.dry_run_only) { - throw new HttpError(`${platform} does not support broker execution`, 400); - } - assertDcaPlatform(platform, ticket.strategy_profile); - if (!(strategy.allowed_execution_modes || []).includes(brokerEnvironment)) { - throw new HttpError(`strategy ${strategy.profile} does not support broker environment ${brokerEnvironment}`, 400); - } - // A broker paper/live selection still needs the real adapter path. The - // account's dry_run default is not an implicit paper capability. - const adapterMode = cleanExecutionMode( - option.default_execution_mode || PLATFORM_CONFIG[platform]?.default_execution_mode || "live", - ); - if (adapterMode !== "live") { - throw new HttpError("selected account adapter is not configured for broker execution", 400); - } - return { platform, key, broker_environment: brokerEnvironment }; -} - -function researchPromotionCandidateIdentity(ticket) { - return { - strategy_profile: String(ticket?.strategy_profile || ""), - domain: String(ticket?.domain || ""), - proposed_params: - ticket?.proposed_params && typeof ticket.proposed_params === "object" && !Array.isArray(ticket.proposed_params) - ? ticket.proposed_params - : {}, - search_iterations: Number(ticket?.search_iterations || 0), - shadow_evidence_kind: String(ticket?.shadow_evidence_kind || ""), - }; -} - -function researchPromotionCandidatesMatch(left, right) { - return ( - canonicalResearchTaskJson(researchPromotionCandidateIdentity(left)) - === canonicalResearchTaskJson(researchPromotionCandidateIdentity(right)) - ); -} - -function assertResearchPromotionExpectedCandidate(ticket, raw, { requireExpectedParams = false } = {}) { - if (requireExpectedParams && !Object.prototype.hasOwnProperty.call(raw || {}, "expected_proposed_params")) { - throw new HttpError("accept requires expected_proposed_params", 400); - } - if (!Object.prototype.hasOwnProperty.call(raw || {}, "expected_proposed_params")) return; - const expected = raw.expected_proposed_params; - if (!expected || typeof expected !== "object" || Array.isArray(expected)) { - throw new HttpError("expected_proposed_params must be an object", 400); - } - const stored = ticket.proposed_params || {}; - if (canonicalResearchTaskJson(expected) !== canonicalResearchTaskJson(stored)) { - throw new HttpError("expected_proposed_params does not match stored ticket candidate", 409); - } - if (raw.expected_strategy_profile != null) { - if (String(raw.expected_strategy_profile).trim() !== ticket.strategy_profile) { - throw new HttpError("expected_strategy_profile does not match stored ticket", 409); - } - } - if (raw.expected_domain != null) { - if (String(raw.expected_domain).trim() !== ticket.domain) { - throw new HttpError("expected_domain does not match stored ticket", 409); - } - } -} - -const RESEARCH_SUMMARY_HAN = /[\u4e00-\u9fff]/; -const RESEARCH_SUMMARY_LATIN = /[A-Za-z]/; -const RESEARCH_SUMMARY_DIGIT = /[0-9\uff10-\uff19]/; -const RESEARCH_SUMMARY_LOCALE_FIELDS = ["question", "basis", "limits", "suggestion"]; -const RESEARCH_SUMMARY_BINDING_FIELDS = [ - "ticket_id", "strategy_profile", "domain", "proposed_params", "comparison", - "shadow_evidence_kind", "shadow_passed", "notes", -]; -const RESEARCH_SUMMARY_NEW_KEYS = ["status", "provider", "model", "scope", "locales", "binding"]; -const RESEARCH_SUMMARY_OLD_KEYS = ["status", "provider", "model", "text"]; -const RESEARCH_SUMMARY_HIDDEN = { status: "unavailable", text: "", provider: "", model: "" }; - -function researchSummaryExactKeys(value, keys) { - if (!value || Array.isArray(value) || typeof value !== "object") return false; - const present = Object.keys(value); - return present.length === keys.length && keys.every((key) => Object.prototype.hasOwnProperty.call(value, key)); -} - -function researchSummaryCodePointLength(text) { - return Array.from(text).length; -} - -function researchSummarySegment(text, chinese) { - if (typeof text !== "string" || !text.trim() || researchSummaryCodePointLength(text) > 240) return null; - if (RESEARCH_SUMMARY_DIGIT.test(text)) return null; - const hasHan = RESEARCH_SUMMARY_HAN.test(text); - if (chinese) return hasHan ? text : null; - if (hasHan || !RESEARCH_SUMMARY_LATIN.test(text)) return null; - return text; -} - -function sameResearchSummaryMaterial(left, right) { - return canonicalResearchTaskJson(left) === canonicalResearchTaskJson(right); -} - -function readBilingualResearchPromotionExplanation(ai, material) { - if (ai.status !== "available" || (ai.provider !== "codex" && ai.provider !== "cursor")) return null; - if (typeof ai.model !== "string" || !ai.model.trim() || ai.scope !== "candidate") return null; - const locales = ai.locales; - if (!researchSummaryExactKeys(locales, ["zh-CN", "en"])) return null; - const savedLocales = {}; - for (const [name, chinese] of [["zh-CN", true], ["en", false]]) { - const section = locales[name]; - if (!researchSummaryExactKeys(section, RESEARCH_SUMMARY_LOCALE_FIELDS)) return null; - const saved = {}; - for (const field of RESEARCH_SUMMARY_LOCALE_FIELDS) { - const segment = researchSummarySegment(section[field], chinese); - if (segment == null) return null; - saved[field] = segment; - } - savedLocales[name] = saved; - } - if (!researchSummaryExactKeys(ai.binding, RESEARCH_SUMMARY_BINDING_FIELDS)) return null; - if (!sameResearchSummaryMaterial(ai.binding, material)) return null; - return { - status: "available", - provider: ai.provider, - model: ai.model, - scope: "candidate", - locales: savedLocales, - binding: JSON.parse(canonicalResearchTaskJson(material)), - }; -} - -function readLegacyResearchPromotionExplanation(ai) { - const status = String(ai.status || "").trim(); - if (status !== "available" && status !== "unavailable") return null; - if (typeof ai.text !== "string" || typeof ai.provider !== "string" || typeof ai.model !== "string") return null; - if (ai.text.length > 2000 || ai.model.length > 120) return null; - if (status === "available" && (ai.provider !== "codex" || !ai.text.trim())) return null; - if (status === "unavailable" && ai.provider !== "") return null; - return { - status, - text: ai.text.trim(), - provider: ai.provider, - model: ai.model.trim(), - }; -} - -function normalizeSavedResearchPromotionExplanation(ai, material) { - try { - if (ai == null) return { ...RESEARCH_SUMMARY_HIDDEN }; - if (researchSummaryExactKeys(ai, RESEARCH_SUMMARY_NEW_KEYS)) { - return readBilingualResearchPromotionExplanation(ai, material) || { ...RESEARCH_SUMMARY_HIDDEN }; - } - if (researchSummaryExactKeys(ai, RESEARCH_SUMMARY_OLD_KEYS)) { - return readLegacyResearchPromotionExplanation(ai) || { ...RESEARCH_SUMMARY_HIDDEN }; - } - return { ...RESEARCH_SUMMARY_HIDDEN }; - } catch { - return { ...RESEARCH_SUMMARY_HIDDEN }; - } -} - -function normalizeResearchPromotionSummary(raw, ticket, fieldName = "research_summary") { - if (raw == null) return null; - if (!raw || Array.isArray(raw) || typeof raw !== "object") return null; - try { - const comparisonForBinding = raw.comparison; - const identity = raw.identity; - if (!identity || Array.isArray(identity) || typeof identity !== "object") return null; - const identityParams = identity.proposed_params; - if ( - String(identity.strategy_profile || "").trim() !== ticket.strategy_profile - || String(identity.domain || "").trim() !== ticket.domain - || !identityParams || Array.isArray(identityParams) - || canonicalResearchTaskJson(identityParams) !== canonicalResearchTaskJson(ticket.proposed_params || {}) - ) return null; - - if (raw.strategy_description != null && typeof raw.strategy_description !== "string") return null; - const strategyDescription = String(raw.strategy_description || "").trim(); - if (strategyDescription.length > 500) return null; - - let plugins = null; - if (raw.plugins === null) { - plugins = null; - } else if (Array.isArray(raw.plugins)) { - plugins = raw.plugins.map((plugin, index) => { - if (!plugin || Array.isArray(plugin) || typeof plugin !== "object") { - throw new Error(`${fieldName}.plugins[${index}] must be an object`); - } - if (typeof plugin.id !== "string" || typeof plugin.description !== "string") { - throw new Error(`${fieldName}.plugins[${index}] must include text fields`); - } - const id = plugin.id.trim(); - const description = plugin.description.trim(); - if (id.length > 120 || description.length > 300) throw new Error(`${fieldName}.plugins[${index}] is too long`); - return { id, description }; - }); - } else if (raw.plugins == null) { - plugins = null; - } else { - return null; - } - - const comparison = raw.comparison == null ? { status: "unavailable" } : raw.comparison; - if (!comparison || Array.isArray(comparison) || typeof comparison !== "object") return null; - const comparisonStatus = String(comparison.status || "").trim(); - if (comparisonStatus !== "comparable" && comparisonStatus !== "unavailable") return null; - const optionalText = (value, name) => { - if (value == null) return null; - if (typeof value !== "string") throw new Error(`${fieldName}.${name} must be text or null`); - const text = value.trim(); - if (text.length > 120) throw new Error(`${fieldName}.${name} is too long`); - return text; - }; - const exactCostModel = (value, name) => { - if (value == null) return null; - if (typeof value !== "string") throw new Error(`${fieldName}.${name} must be text or null`); - if (value.length > 120) throw new Error(`${fieldName}.${name} is too long`); - return value; - }; - const normalizeMetrics = (value, name) => { - if (!value || Array.isArray(value) || typeof value !== "object") { - throw new Error(`${fieldName}.${name} must be an object`); - } - const cagr = value.cagr; - const maxDrawdown = value.max_drawdown; - if (typeof cagr !== "number" || typeof maxDrawdown !== "number" - || !Number.isFinite(cagr) || !Number.isFinite(maxDrawdown) - || cagr < -1 || maxDrawdown < -1 || maxDrawdown > 0) { - throw new Error(`${fieldName}.${name} must use finite numbers`); - } - return { cagr, max_drawdown: maxDrawdown }; - }; - const normalizedComparison = { - status: comparisonStatus, - start_date: optionalText(comparison.start_date, "comparison.start_date"), - end_date: optionalText(comparison.end_date, "comparison.end_date"), - cost_model: exactCostModel(comparison.cost_model, "comparison.cost_model"), - baseline: null, - candidate: null, - }; - if (comparisonStatus === "comparable") { - if (!normalizedComparison.start_date || !normalizedComparison.end_date || !normalizedComparison.cost_model?.trim()) return null; - if (!/^\d{4}-\d{2}-\d{2}$/.test(normalizedComparison.start_date) - || !/^\d{4}-\d{2}-\d{2}$/.test(normalizedComparison.end_date)) return null; - const startMillis = Date.parse(`${normalizedComparison.start_date}T00:00:00Z`); - const endMillis = Date.parse(`${normalizedComparison.end_date}T00:00:00Z`); - if (!Number.isFinite(startMillis) || !Number.isFinite(endMillis) || startMillis > endMillis) return null; - normalizedComparison.baseline = normalizeMetrics(comparison.baseline, "comparison.baseline"); - normalizedComparison.candidate = normalizeMetrics(comparison.candidate, "comparison.candidate"); - } else { - if (comparison.baseline != null) normalizeMetrics(comparison.baseline, "comparison.baseline"); - if (comparison.candidate != null) normalizeMetrics(comparison.candidate, "comparison.candidate"); - } - - if (!Array.isArray(raw.limitations) || raw.limitations.length > 8 || raw.limitations.some((item) => typeof item !== "string" || item.length > 300)) return null; - const aiExplanation = normalizeSavedResearchPromotionExplanation(raw.ai_explanation, { - ticket_id: ticket.ticket_id, - strategy_profile: ticket.strategy_profile, - domain: ticket.domain, - proposed_params: ticket.proposed_params || {}, - comparison: comparisonForBinding, - shadow_evidence_kind: ticket.shadow_evidence_kind, - shadow_passed: ticket.shadow_passed, - notes: Array.isArray(ticket.notes) ? ticket.notes : [], - }); - - return { - identity: { - strategy_profile: ticket.strategy_profile, - domain: ticket.domain, - proposed_params: ticket.proposed_params || {}, - }, - strategy_description: strategyDescription, - plugins, - comparison: normalizedComparison, - limitations: raw.limitations.map((item) => item.trim()), - ai_explanation: aiExplanation, - }; - } catch { - return null; - } -} - -function normalizeResearchPromotionTicket(raw, fieldName = "research promotion ticket") { - if (!raw || Array.isArray(raw) || typeof raw !== "object") { - throw new Error(`${fieldName} must be an object`); - } - const ticketId = String(raw.ticket_id || "").trim(); - const strategyProfile = String(raw.strategy_profile || "").trim(); - const domain = String(raw.domain || "").trim(); - const state = String(raw.state || "").trim(); - if (!ticketId) throw new Error(`${fieldName}.ticket_id is required`); - if (!strategyProfile) throw new Error(`${fieldName}.strategy_profile is required`); - if (!domain) throw new Error(`${fieldName}.domain is required`); - if (!RESEARCH_PROMOTION_STATES.includes(state)) { - throw new Error(`${fieldName}.state is unsupported`); - } - if (raw.live_authority_granted === true) { - throw new Error(`${fieldName}.live_authority_granted must remain false`); - } - const suggested = normalizeResearchPromotionRiskProfile( - raw.suggested_risk_profile || DEFAULT_RESEARCH_PROMOTION_RISK_PROFILE, - `${fieldName}.suggested_risk_profile`, - ); - const driftStatus = String(raw.drift_status || ""); - const normalizedTicket = { - schema: RESEARCH_PROMOTION_TICKET_SCHEMA, - ticket_id: ticketId, - strategy_profile: strategyProfile, - domain, - state, - drift_status: driftStatus, - drift_score: driftStatus === "new_research" && raw.drift_score === null - ? null - : Number(raw.drift_score || 0), - created_at: String(raw.created_at || ""), - updated_at: String(raw.updated_at || ""), - budget: raw.budget && typeof raw.budget === "object" && !Array.isArray(raw.budget) ? raw.budget : {}, - proposed_params: - raw.proposed_params && typeof raw.proposed_params === "object" && !Array.isArray(raw.proposed_params) - ? raw.proposed_params - : {}, - search_iterations: Number(raw.search_iterations || 0), - shadow_evidence_kind: String(raw.shadow_evidence_kind || ""), - // Qualification must never treat string values such as "false" as a - // passed observation. Keep malformed legacy values unknown and let the - // application gate reject them. - shadow_passed: raw.shadow_passed === true ? true : raw.shadow_passed === false ? false : null, - notification_subject: String(raw.notification_subject || ""), - notification_body: String(raw.notification_body || ""), - human_decision: String(raw.human_decision || ""), - human_decided_at: String(raw.human_decided_at || ""), - live_authority_granted: false, - suggested_risk_profile: suggested, - confirmation_target_platform: String(raw.confirmation_target_platform || ""), - confirmation_execution_mode: String(raw.confirmation_execution_mode || ""), - confirmation_risk_profile: String(raw.confirmation_risk_profile || ""), - notes: Array.isArray(raw.notes) ? raw.notes.map((item) => String(item)) : [], - }; - const researchSummary = normalizeResearchPromotionSummary(raw.research_summary, normalizedTicket, `${fieldName}.research_summary`); - if (researchSummary) normalizedTicket.research_summary = researchSummary; - return normalizedTicket; -} - -function applyResearchPromotionDecision( - ticket, - { - decision, - confirmation = null, - paperSupported = false, - selectedAccount = null, - accountOptions = null, - strategyProfiles = null, - decidedAt = null, - expectedRaw = null, - }, -) { - if (ticket.state !== "awaiting_human") { - throw new HttpError(`ticket ${ticket.ticket_id} is not awaiting human (state=${ticket.state})`, 409); - } - const normalized = String(decision || "").trim().toLowerCase(); - if (normalized !== "accept" && normalized !== "reject") { - throw new HttpError("decision must be accept or reject", 400); - } - assertResearchPromotionExpectedCandidate(ticket, expectedRaw || {}, { - requireExpectedParams: normalized === "accept", - }); - const stamp = decidedAt || new Date().toISOString(); - const next = { - ...ticket, - human_decision: normalized, - human_decided_at: stamp, - live_authority_granted: false, - updated_at: stamp, - notes: [...(ticket.notes || [])], - }; - if (normalized === "accept") { - if (!confirmation) { - throw new HttpError( - "accept requires confirmation (target_platform, execution_mode, risk_profile)", - 400, - ); - } - const confirmed = normalizePromotionConfirmation(confirmation, "confirmation", { - paperSupported: Boolean(paperSupported), - }); - if (!selectedAccount) { - throw new HttpError("accept requires selected_account", 400); - } - validateResearchPromotionSelectedAccount({ - ticket, - selectedAccount, - confirmation: confirmed, - accountOptions, - strategyProfiles, - }); - next.confirmation_target_platform = confirmed.target_platform; - next.confirmation_execution_mode = confirmed.execution_mode; - next.confirmation_risk_profile = confirmed.risk_profile; - next.state = "human_accepted"; - next.notes.push( - "human_accepted_intent_only_no_live_authority", - `confirmation_platform=${confirmed.target_platform}`, - `confirmation_mode=${confirmed.execution_mode}`, - `confirmation_risk_profile=${confirmed.risk_profile}`, - ); - } else { - next.state = "human_rejected"; - next.notes.push("human_rejected"); - } - return next; -} - -async function syncResearchPromotionTicketResponse(request, env) { - requireDedicatedResearchPromotionSyncToken(request, env); - if (!hasConfigStore(env)) { - return json({ ok: false, error: "research promotion KV is not configured" }, 503); - } - let raw; - try { - raw = await readBoundedJson(request, RESEARCH_PROMOTION_MAX_BODY_BYTES); - } catch (error) { - return json({ ok: false, error: error.message || "invalid research promotion ticket" }, error.status || 400); - } - let ticket; - try { - ticket = normalizeResearchPromotionTicket(raw); - } catch (error) { - return json({ ok: false, error: error.message || "invalid research promotion ticket" }, 400); - } - if (ticket.state !== "awaiting_human") { - return json( - { ok: false, error: "research promotion sync only accepts awaiting_human tickets" }, - 400, - ); - } - const existingRaw = await readConfigJson(env, researchPromotionTicketKey(ticket.ticket_id)); - let existing = null; - if (existingRaw) { - try { - existing = normalizeResearchPromotionTicket(existingRaw, "stored research promotion ticket"); - } catch (error) { - return json( - { ok: false, error: error.message || "invalid stored research promotion ticket" }, - 400, - ); - } - } - let material; - try { - material = await publishHumanPromotionMaterial(env, ticket, existing); - } catch (error) { - return json({ ok: false, error: error.message || "research promotion material unavailable" }, error.status || 503); - } - if (material.identity_conflict) { - return json( - { ok: false, error: "refusing to overwrite awaiting_human research promotion ticket with mismatched candidate identity" }, - 409, - ); - } - if (material.terminal || material.legacy_blocked) { - return json( - { ok: false, error: `refusing to overwrite terminal research promotion ticket state=${material.terminal_state || existing?.state || "human_accepted"}` }, - 409, - ); - } - await writeConfigJson(env, researchPromotionTicketKey(ticket.ticket_id), ticket); - try { - await appendAuditLog(env, { - ts: new Date().toISOString(), - login: "research-promotion-ticket-sync", - action: "sync_research_promotion_ticket", - ticket_id: ticket.ticket_id, - state: ticket.state, - suggested_risk_profile: ticket.suggested_risk_profile, - live_authority_granted: false, - }); - } catch { - // Intent sync must not depend on optional audit retention. - } - return json({ - ok: true, - ticket_id: ticket.ticket_id, - state: ticket.state, - suggested_risk_profile: ticket.suggested_risk_profile, - live_authority_granted: false, - }); -} - -async function fetchResearchPromotionTicketResponse(request, env) { - requireDedicatedResearchPromotionSyncToken(request, env); - if (!hasConfigStore(env)) { - return json({ ok: false, error: "research promotion KV is not configured" }, 503); - } - const ticketId = String(new URL(request.url).searchParams.get("ticket_id") || "").trim(); - if (!ticketId) return json({ ok: false, error: "ticket_id is required" }, 400); - const stored = await readConfigJson(env, researchPromotionTicketKey(ticketId)); - if (!stored) return json({ ok: false, error: "research promotion ticket not found" }, 404); - let ticket; - try { - ticket = normalizeResearchPromotionTicket(stored); - ticket = await promotionTicketWithAuthority(env, ticket); - } catch (error) { - return json({ ok: false, error: error.message || "invalid stored research promotion ticket" }, error.status || 400); - } - if (ticket.live_authority_granted) { - return json({ ok: false, error: "refusing to expose live_authority_granted=true" }, 500); - } - return json({ - ok: true, - ticket: attachRiskEnvelopeView(ticket), - live_authority_granted: false, - }); -} - -async function listResearchPromotionTickets(env) { - if (!hasConfigStore(env)) return []; - const store = configStore(env); - let listing; - try { - listing = await directoryList(env, RESEARCH_PROMOTION_TICKET_PREFIX, RESEARCH_PROMOTION_MAX_TICKETS); - } catch (error) { - throw new HttpError(sourceListFailure(error, "research_promotion_source_list_unavailable"), 503); - } - const keys = listing?.keys || []; - const tickets = []; - for (const entry of keys) { - const key = entry.name || entry; - if (!String(key).startsWith(RESEARCH_PROMOTION_TICKET_PREFIX)) continue; - const stored = await readConfigJson(env, key); - if (!stored) throw new HttpError("kv_directory_record_unavailable", 503); - try { - const record = normalizeResearchPromotionTicket(stored, key); - const authority = await readHumanDecisionAuthority(env, "promotion", record.ticket_id); - const ticket = attachPromotionQueueReadMetadata(record, overlayPromotionDecision(record, authority), authority); - if (ticket.live_authority_granted) continue; - if (ticket.state === "awaiting_human") { - // Project the same durable observation used by the decision metadata. - ticket.decision_authority = humanDecisionAuthorityView(authority); - } - tickets.push(ticket); - } catch (error) { - if (error instanceof HttpError && error.status === 503) throw error; - // Skip corrupt tickets rather than failing the whole queue. - } - } - tickets.sort((left, right) => String(right.updated_at).localeCompare(String(left.updated_at))); - return tickets.slice(0, RESEARCH_PROMOTION_MAX_TICKETS); -} - -function attachPromotionQueueReadMetadata(record, ticket, authority) { - // Reuse the queue's existing material observation. Unknown is not permission; - // these fixed reasons describe stored intent material flags, not full decision - // permission, ticket-hash validation, or account application gates. - const migrated = typeof authority?.migrated === "boolean" ? authority.migrated : null; - const eligible = migrated === true && typeof authority?.eligible === "boolean" ? authority.eligible : null; - const blocked = migrated === true && typeof authority?.blocked === "boolean" ? authority.blocked : null; - const blockerCodes = migrated !== true ? null - : blocked === true ? ["human_decision_legacy_blocked"] - : eligible === false ? ["human_decision_material_conflict"] - : eligible === true && blocked === false ? [] : null; - return { - ...ticket, - decision_material: { migrated, eligible, blocked, blocker_codes: blockerCodes }, - decision_review_binding: authority?.review_binding || null, - // Timestamps of the already-read KV ticket record, not producer-observed - // time, freshness, a validity window, or evidence of actual execution. - ticket_record_timestamps: { - created_at: typeof record?.created_at === "string" && record.created_at ? record.created_at : null, - updated_at: typeof record?.updated_at === "string" && record.updated_at ? record.updated_at : null, - }, - }; -} - -async function researchPromotionTicketsResponse(request, env) { - const session = await readSession(request, env); - if (!session?.allowed) return json({ ok: false, error: "login required" }, 401); - const exact = await exactHumanDecisionResponse(request, env, session, "promotion"); - if (exact) return exact; - const tickets = []; - const invalidTickets = []; - for (const ticket of await listResearchPromotionTickets(env)) { - let reviewBinding; - try { - reviewBinding = await researchPromotionReviewBinding(ticket); - } catch (error) { - if (!(error instanceof ResearchPromotionReviewMaterialError)) throw error; - // Keep a bounded, explicit non-actionable diagnosis; never serialize the - // invalid numbers as fake zero/null or certify this ticket with a digest. - invalidTickets.push({ - ticket_id: ticket.ticket_id, strategy_profile: ticket.strategy_profile, state: ticket.state, - decision_material: { eligible: false, blocked: true, blocker_codes: [RESEARCH_PROMOTION_REVIEW_INVALID] }, - decision_binding: null, live_authority_granted: false, no_order: true, - }); - continue; - } - tickets.push({ ...ticket, decision_binding: { - kind: "promotion", subject_id: ticket.ticket_id, material_sha256: await researchPromotionMaterialKey(ticket), - review_binding: promotionReviewBindingsMatch(reviewBinding, ticket.decision_review_binding) ? reviewBinding : null, - } }); - } - const awaiting = tickets.filter((ticket) => ticket.state === "awaiting_human"); - const applicationTickets = tickets.filter((ticket) => - ticket.state === "awaiting_human" || ticket.state === "human_accepted"); - const accountConfig = await loadAccountOptionsConfig(env); - const strategyProfiles = await loadStrategyProfilesConfig(env); - const applications = []; - for (const ticket of applicationTickets) { - const preparation = attachResearchPromotionApplicationPreparation(ticket, accountConfig.options, strategyProfiles); - let application = null; - if (hasRuntimeInstanceStore(env)) { - try { - const stored = await runtimeInstanceCommand(env, { action: "application_latest", actor: session.login, ticket_id: ticket.ticket_id }); - application = publicResearchPromotionApplication(stored.application); - } catch { - // Application storage is optional for the research-only queue view. - } - } - applications.push({ ...preparation, application }); - } - return json({ - schema_version: RESEARCH_PROMOTION_QUEUE_SCHEMA, - data_status: hasConfigStore(env) ? (invalidTickets.length ? "partial" : "ready") : "unavailable", - computed_at: new Date().toISOString(), - tickets: decorateQueue({ tickets: awaiting.map(attachRiskEnvelopeView) }, "tickets", "strategy_promotion").tickets, - applications, - ...(invalidTickets.length ? { invalid_tickets: invalidTickets } : {}), - summary: { - ticket_count: awaiting.length, - awaiting_human: awaiting.length, - application_count: applications.length, - ...(invalidTickets.length ? { invalid_review_count: invalidTickets.length } : {}), - }, - policy: { - admin_required_to_decide: true, - live_authority_granted: false, - no_order: true, - application_dispatch_allowed: false, - activation_status: "not_connected", - notice: "网页只记录晋级人工意图;确认不授予实盘权限,也不下单。", - }, - errors: invalidTickets.length ? [RESEARCH_PROMOTION_REVIEW_INVALID] : [], - }); -} - -const DECISION_POLICY = Object.freeze({ - no_order: true, - execution_authority_granted: false, - current_evidence_required: true, - human_confirmation_required: true, - notice: "只记录人工决定意图;不会下单、变更资金或启用实盘。", -}); - -function decisionInboxSources() { - return { - owner: ownerDecisionQueueResponse, - promotion: researchPromotionTicketsResponse, - recovery: reconciliationRecoveryResponse, - credential: (request, env) => credentialMaintenanceResponse( - request, env, new URL("/api/credential-maintenance", request.url), - ), - }; -} - -function decodeDecisionApiId(raw) { - let id; - try { - id = decodeURIComponent(String(raw || "")); - } catch { - return null; - } - const parts = decisionCategoryPart(id); - if (parts) return { id, category: parts.category, subject_id: parts.subject_id }; - const legacyCredential = /^credential:(.+)$/.exec(id); - if (!legacyCredential || legacyCredential[1].length > 256 || /[\u0000-\u001f\u007f]/.test(legacyCredential[1])) return null; - return { id, category: "credential_maintenance", subject_id: legacyCredential[1] }; -} - -function decodeOperationApiId(raw) { - let id; - try { - id = decodeURIComponent(String(raw || "")); - } catch { - return null; - } - const match = /^operation:(owner_decision|strategy_promotion|paper_account_application|reconciliation_recovery|credential_maintenance):(.+)$/.exec(id); - if (!match || match[2].length > 256 || /[\u0000-\u001f\u007f]/.test(match[2])) return null; - return { id, category: match[1], subject_id: match[2] }; -} - -const DECISION_SOURCE_BY_CATEGORY = Object.freeze({ - owner_decision: "owner", - strategy_promotion: "promotion", - paper_account_application: "promotion", - reconciliation_recovery: "recovery", - credential_maintenance: "credential", -}); - -/** - * Absence is only proven when the owning source was read. A failed or - * unavailable source stays a 503 so a missing id never masks a broken read. - */ -function decisionSourceUnreadable(queue, category) { - const name = DECISION_SOURCE_BY_CATEGORY[category]; - const source = (queue?.sources || []).find((entry) => entry.source === name); - if (!source) return true; - return ["read_failed", "unavailable"].includes(source.status); -} - -// The durable record keeps the operation id in its request key. The id names the -// recorded intent; a legacy fingerprint key carries no client operation id. -function recordedDecisionOperationId(record) { - const match = /^id:(?:owner|promotion|recovery):(.+)$/.exec( - typeof record?.request_key === "string" ? record.request_key : "", - ); - return match ? match[1] : null; -} - -// The recorded intent's canonical target: accepted selection for promotion, action elsewhere. -function decisionIntentTargetJson(category, action, selection) { - if (category === "strategy_promotion" || category === "paper_account_application") { - const accepted = action === "accept" ? selection : null; - return canonicalResearchTaskJson({ - selected_account: accepted?.selected_account ?? null, - confirmation: accepted?.confirmation ?? null, - }); - } - return canonicalResearchTaskJson({ decision: action }); -} - -// A terminal item synthesized from the durable record. It keeps the original -// decision id resolvable after the pending projection drops or recategorizes it. -function durableDecisionItem(record, parsed, kind, aliasItem = null) { - const material = /^[0-9a-f]{64}$/.test(record.material_key || "") ? record.material_key : null; - const reviewSha = /^[0-9a-f]{64}$/.test(record.payload?.review_binding?.review_sha256 || "") - ? record.payload.review_binding.review_sha256 : null; - const binding = { - kind, - subject_id: record.subject_id, - material_sha256: material, - ...(reviewSha ? { review_binding: { schema_version: "qsl_promotion_review_binding.v2", review_sha256: reviewSha } } : {}), - }; - return { - id: parsed.id, - short_id: shortId(parsed.id), - decision_id: parsed.id, - category: parsed.category, - title_key: `decisions.${parsed.category}`, - revision: decisionRevision(binding, parsed.id), - actionable: false, - blocking_reason_codes: ["decision_already_decided"], - allowed_actions: [], - request_id_required: false, - action_endpoint: null, - decision_state: { - status: "decided", - action: record.action, - decided_at: typeof record.decided_at === "string" ? record.decided_at : null, - }, - decision_binding: binding, - operation_reference: aliasItem?.operation_reference - || decisionOperationReference({}, parsed.category, parsed.subject_id), - source: "durable_human_decision_record", - }; -} - -async function loadDecisionById(request, env, parsed) { - // Detail and action routes address one decision by id. Reading the unpaged - // projection keeps them independent of the presentation page a client last saw. - const queue = await inbox(request, env, decisionInboxSources(), { paginate: false }); - const item = queue.items.find((entry) => entry.id === parsed.id) || null; - if (item) return { queue, item, authorityRaw: undefined }; - // A terminal record outlives the pending projection: accepted paper tickets are - // recategorized and rejected tickets leave the queue, so resolve them durably. - const kind = decisionKind(parsed.category); - if (!kind || !hasRuntimeInstanceStore(env)) return { queue, item: null, authorityRaw: undefined }; - let authorityRaw; - try { - authorityRaw = await readHumanDecisionAuthority(env, kind, parsed.subject_id); - } catch { - throw new HttpError("decision_authority_unavailable", 503); - } - const record = authorityRaw?.migrated === false ? null : authorityRaw?.decision; - if (!record || record.kind !== kind || record.subject_id !== parsed.subject_id) - return { queue, item: null, authorityRaw }; - const alias = queue.items.find((entry) => entry.decision_binding?.subject_id === parsed.subject_id - && entry.decision_binding?.kind === kind); - return { queue, item: durableDecisionItem(record, parsed, kind, alias || null), authorityRaw }; -} - -async function readDecisionAuthorityOrNull(env, kind, subjectId) { - if (!kind) return null; - if (!hasRuntimeInstanceStore(env)) throw new HttpError("runtime_instances_not_bound", 503); - return readHumanDecisionAuthority(env, kind, subjectId); -} - -async function decisionReviewResponse(request, env, rawId) { - if (request.method !== "GET") return json({ ok: false, error: "method_not_allowed" }, 405); - const session = await readSession(request, env); - if (!session?.allowed) return json({ ok: false, error: "login required", error_state: "unauthorized" }, 401); - const parsed = decodeDecisionApiId(rawId); - if (!parsed) return json({ ok: false, error: "decision_id_invalid" }, 400); - const { queue, item, authorityRaw: loadedAuthority } = await loadDecisionById(request, env, parsed); - if (!item) { - if (decisionSourceUnreadable(queue, parsed.category)) - return json({ ok: false, error: "decision_source_unavailable", error_state: queue.error_state || null }, 503); - return json({ ok: false, error: "decision_not_found" }, 404); - } - const kind = decisionKind(parsed.category); - let authorityRaw; - try { - authorityRaw = loadedAuthority !== undefined - ? loadedAuthority - : await readDecisionAuthorityOrNull(env, kind, parsed.subject_id); - } catch { - return json({ ok: false, error: "decision_authority_unavailable" }, 503); - } - const authority = authorityRaw ? humanDecisionAuthorityView(authorityRaw) : null; - const now = Date.now(); - let receipt = null; - const errors = []; - if (authorityRaw?.decision) { - try { - receipt = await humanDecisionReceipt(authorityRaw.decision, { - kind, - subject_id: parsed.subject_id, - material_sha256: authorityRaw.decision.material_key, - }); - } catch { - errors.push("decision_receipt_unavailable"); - } - } - if (authorityRaw?.migrated === false) errors.push("decision_authority_unavailable"); - if (queue.status !== "ok") errors.push("decision_source_partial"); - return json({ - schema_version: DECISION_REVIEW_SCHEMA_VERSION, - server_time: new Date(now).toISOString(), - data_status: errors.length ? "partial" : "ready", - decision_id: item.id, - decision: item, - current_eligibility: decisionCurrentEligibility(item, authority, now), - decision_authority: authority, - decision_receipt: receipt, - operation: item.operation_reference, - policy: DECISION_POLICY, - tier_policies: riskTierPolicyCatalog(), - errors: [...new Set(errors)], - }); -} - -function decisionSelection(raw) { - const selection = raw?.selection; - if (!selection || Array.isArray(selection) || typeof selection !== "object") return null; - return { - confirmation: selection.confirmation && typeof selection.confirmation === "object" && !Array.isArray(selection.confirmation) - ? selection.confirmation : null, - selected_account: selection.selected_account && typeof selection.selected_account === "object" && !Array.isArray(selection.selected_account) - ? selection.selected_account : null, - }; -} - -async function decisionActionResponse(request, env, rawId) { - requireSameOrigin(request, { requireOrigin: true }); - const session = await readSession(request, env); - if (!session?.allowed) return json({ ok: false, error: "login required", error_state: "unauthorized" }, 401); - if (!session.admin) return json({ ok: false, error: "admin required" }, 403); - const parsed = decodeDecisionApiId(rawId); - if (!parsed) return json({ ok: false, error: "decision_id_invalid" }, 400); - const supported = DECISION_ACTIONS[parsed.category] || []; - const kind = decisionKind(parsed.category); - if (!kind || !supported.length) return json({ ok: false, error: "decision_not_actionable" }, 409); - - let raw; - try { - raw = await readBoundedJson(request, DECISION_ACTION_MAX_BODY_BYTES); - } catch (error) { - return json({ ok: false, error: error.message || "invalid decision action" }, error.status || 400); - } - if (!raw || Array.isArray(raw) || typeof raw !== "object") - return json({ ok: false, error: "decision_action_request_invalid" }, 400); - if (Object.keys(raw).some((key) => !["action", "expected_revision", "expected_expires_at", "request_id", "operation_id", "selection"].includes(key))) - return json({ ok: false, error: "decision_action_request_invalid" }, 400); - const action = typeof raw.action === "string" ? raw.action.trim() : ""; - if (!supported.includes(action)) - return json({ ok: false, error: "decision_action_invalid" }, 400); - if (typeof raw.expected_revision !== "string" || !/^rev[0-9]-[0-9a-z-]{1,64}$/.test(raw.expected_revision)) - return json({ ok: false, error: "decision_revision_required" }, 400); - const requestId = typeof raw.request_id === "string" ? raw.request_id.trim() : ""; - // The operation id names the intent. It is the durable idempotency key, so a - // retry with the same id replays and a new intent needs a new id. - const operationId = decisionOperationId(raw); - if (!operationId) { - if (requestId && !/^[A-Za-z0-9._:-]{1,128}$/.test(requestId)) - return json({ ok: false, error: "invalid request_id" }, 400); - return json({ ok: false, error: "decision_operation_id_invalid" }, 400); - } - const operationSha256 = await decisionOperationSha256(parsed.id, raw); - const selection = decisionSelection(raw); - if ((parsed.category === "strategy_promotion" || parsed.category === "paper_account_application") - && action === "accept" && (!selection?.confirmation || !selection?.selected_account)) - return json({ ok: false, error: "decision_selection_required" }, 400); - - const { queue, item } = await loadDecisionById(request, env, parsed); - if (!item) { - if (decisionSourceUnreadable(queue, parsed.category)) - return json({ ok: false, error: "decision_source_unavailable", error_state: queue.error_state || null }, 503); - return json({ ok: false, error: "decision_not_found" }, 404); - } - const now = Date.now(); - const serverTime = new Date(now).toISOString(); - let authorityRaw; - try { - authorityRaw = await readHumanDecisionAuthority(env, kind, parsed.subject_id); - } catch { - return json({ ok: false, error: "decision_authority_unavailable", server_time: serverTime }, 503); - } - const authority = humanDecisionAuthorityView(authorityRaw); - const materialKey = item.decision_binding?.material_sha256 || null; - const existing = authorityRaw?.decision; - if (existing && materialKey && existing.material_key === materialKey) { - // The durable request key keeps the operation id that produced the record. - const recordedOperationId = recordedDecisionOperationId(existing); - // A retry replays only when the recorded operation id and the complete - // canonical intent match. Any other intent on the same material conflicts, - // whether it reuses the id or presents a new one with the same action. - let recordedTarget = null; - try { - recordedTarget = canonicalResearchTaskJson(JSON.parse(existing.target_json || "{}")); - } catch { - recordedTarget = null; - } - const intentMatches = recordedOperationId !== null && recordedOperationId === operationId - && recordedTarget !== null - && recordedTarget === decisionIntentTargetJson(parsed.category, action, selection); - if (existing.kind !== kind || existing.subject_id !== parsed.subject_id || existing.action !== action - || !intentMatches) { - return json({ - ok: false, - error: "decision_operation_conflict", - decision_id: item.id, - operation_id: operationId, - recorded_operation_id: recordedOperationId, - server_time: serverTime, - }, 409); - } - const receipt = await humanDecisionReceipt(existing, { - kind, subject_id: parsed.subject_id, material_sha256: materialKey, - }); - return json({ - schema_version: DECISION_ACTION_SCHEMA_VERSION, - ok: true, - replayed: true, - decision_id: item.id, - revision: item.revision, - action, - operation_id: operationId, - operation_sha256: operationSha256, - decision_receipt: receipt, - decided_at: existing.decided_at, - decision_state: { status: "decided", action, decided_at: existing.decided_at }, - operation: item.operation_reference, - policy: DECISION_POLICY, - server_time: serverTime, - }); - } - if (raw.expected_revision !== item.revision) - return json({ ok: false, error: "decision_revision_conflict", decision_id: item.id, server_time: serverTime }, 409); - if (raw.expected_expires_at !== undefined && raw.expected_expires_at !== item.expires_at) - return json({ ok: false, error: "decision_expiry_changed", decision_id: item.id, server_time: serverTime }, 409); - if (decisionExpiryState(item.expires_at, now) !== "active") - return json({ ok: false, error: "decision_expired", decision_id: item.id, server_time: serverTime }, 409); - if (decisionExpiryState(authority?.expires_at, now) !== "active") - return json({ ok: false, error: "decision_expired", decision_id: item.id, server_time: serverTime }, 409); - if (authority?.blocked === true) - return json({ ok: false, error: "decision_legacy_blocked", decision_id: item.id, server_time: serverTime }, 409); - if (authority?.eligible === false) - return json({ ok: false, error: "decision_material_ineligible", decision_id: item.id, server_time: serverTime }, 409); - if (item.actionable !== true) - return json({ - ok: false, error: "decision_not_actionable", decision_id: item.id, - blocking_reason_codes: item.blocking_reason_codes || [], server_time: serverTime, - }, 409); - if (!(item.allowed_actions || []).some((entry) => entry.action === action)) - return json({ ok: false, error: "decision_action_not_allowed", decision_id: item.id, server_time: serverTime }, 409); - if (!materialKey) - return json({ ok: false, error: "decision_evidence_unavailable", decision_id: item.id, server_time: serverTime }, 409); - - let targetPath; - let innerBody; - if (parsed.category === "owner_decision") { - targetPath = "/api/owner-decisions"; - innerBody = { - candidate_id: parsed.subject_id, - decision: action, - candidate_evidence_sha256: materialKey, - request_id: operationId, - }; - } else if (parsed.category === "reconciliation_recovery") { - targetPath = "/api/reconciliation-recovery-confirmations"; - if (item.initial_enrollment) { - innerBody = { - mode: "initial_enrollment", - recovery_id: parsed.subject_id, - candidate_sha256: item.initial_enrollment.candidate_sha256, - facts_sha256: item.initial_enrollment.facts_sha256, - decision: action, - request_id: operationId, - }; - } else { - innerBody = { - recovery_id: parsed.subject_id, - candidate_sha256: item.evidence_summary?.candidate_sha256, - dual_review_binding_sha256: item.evidence_summary?.dual_review_binding_sha256, - decision: action, - request_id: operationId, - }; - } - } else { - targetPath = "/api/research-promotion-decisions"; - let ticket; - try { - ticket = await loadStoredResearchPromotionTicket(env, parsed.subject_id); - } catch (error) { - return json({ - ok: false, error: error.message || "research promotion ticket unavailable", - decision_id: item.id, server_time: serverTime, - }, error.status || 503); - } - innerBody = { - ticket_id: parsed.subject_id, - decision: action, - expected_proposed_params: ticket.proposed_params, - expected_material_sha256: materialKey, - ...(item.decision_binding?.review_binding?.review_sha256 - ? { expected_review_sha256: item.decision_binding.review_binding.review_sha256 } : {}), - ...(action === "accept" && selection ? { - confirmation: selection.confirmation, - selected_account: selection.selected_account, - } : {}), - request_id: operationId, - }; - } - - const innerResponse = await ({ - "/api/owner-decisions": recordOwnerDecisionResponse, - "/api/reconciliation-recovery-confirmations": recordReconciliationRecoveryConfirmationResponse, - "/api/research-promotion-decisions": recordResearchPromotionDecisionResponse, - })[targetPath](new Request(new URL(targetPath, request.url), { - method: "POST", - headers: new Headers(request.headers), - body: JSON.stringify(innerBody), - }), env); - let payload = null; - try { - payload = await innerResponse.json(); - } catch { - payload = null; - } - if (!innerResponse.ok || payload?.ok !== true) { - return json({ - ...(payload && typeof payload === "object" ? payload : { ok: false, error: "decision_action_unavailable" }), - schema_version: DECISION_ACTION_SCHEMA_VERSION, - decision_id: item.id, - action, - server_time: serverTime, - }, innerResponse.status || 503); - } - const decidedAt = payload.intent?.decided_at - || payload.confirmation?.confirmed_at - || payload.ticket?.human_decided_at - || null; - return json({ - schema_version: DECISION_ACTION_SCHEMA_VERSION, - ok: true, - replayed: payload.replayed === true, - decision_id: item.id, - revision: item.revision, - action, - operation_id: operationId, - operation_sha256: operationSha256, - decision_receipt: payload.decision_receipt || null, - decided_at: decidedAt, - decision_state: { status: "decided", action, decided_at: decidedAt }, - operation: item.operation_reference, - policy: DECISION_POLICY, - server_time: serverTime, - }); -} - -function operationDecisionStatus(category, decision, application) { - const promoted = category === "strategy_promotion" || category === "paper_account_application"; - if (promoted && application) { - return { status: operationStatusFromApplication(application), reason_code: application.reason_code || null }; - } - if (!decision) return { status: "unaccepted", reason_code: "decision_pending" }; - // The operation records the human decision; it never implies activation. - if (promoted) return decision.action === "accept" - ? { status: "accepted", reason_code: "application_not_created" } - : { status: "failed", reason_code: "decision_rejected" }; - return { status: "accepted", reason_code: "operation_not_connected" }; -} - -async function operationResponse(request, env, rawId) { - const session = await readSession(request, env); - if (!session?.allowed) return json({ ok: false, error: "login required", error_state: "unauthorized" }, 401); - const parsed = decodeOperationApiId(rawId); - if (!parsed) return json({ ok: false, error: "operation_not_found" }, 404); - const now = Date.now(); - const serverTime = new Date(now).toISOString(); - const kind = decisionKind(parsed.category); - if (kind && !hasRuntimeInstanceStore(env)) - return json({ ok: false, error: "runtime_instances_not_bound", server_time: serverTime }, 503); - - let authorityRaw = null; - let ticket = null; - if (kind) { - try { - if (parsed.category === "strategy_promotion" || parsed.category === "paper_account_application") { - ticket = await loadStoredResearchPromotionTicket(env, parsed.subject_id); - } - authorityRaw = await readHumanDecisionAuthority(env, kind, parsed.subject_id); - } catch (error) { - return json({ - ok: false, error: error.message || "operation_unavailable", - operation_id: parsed.id, server_time: serverTime, - }, error.status || 503); - } - } - let application = null; - if (kind === "promotion") { - try { - const stored = await runtimeInstanceCommand(env, { - action: "application_latest", actor: session.login, ticket_id: parsed.subject_id, - }); - application = stored.application || null; - } catch { - return json({ ok: false, error: "operation_unavailable", operation_id: parsed.id, server_time: serverTime }, 503); - } - } - const decision = authorityRaw?.decision || null; - const status = operationDecisionStatus(parsed.category, decision, application); - const publicApplication = application ? publicResearchPromotionApplication(application) : null; - return json({ - schema_version: OPERATION_SCHEMA_VERSION, - server_time: serverTime, - data_status: "ready", - operation: { - id: parsed.id, - decision_id: `${parsed.category}:${parsed.subject_id}`, - kind: kind || parsed.category, - subject_id: parsed.subject_id, - status: status.status, - reason_code: status.reason_code, - activation_status: "not_connected", - updated_at: publicApplication?.updated_at || decision?.decided_at || ticket?.updated_at || null, - dispatch: publicApplication ? { - status: publicApplication.dispatch_state || "unknown", - workflow_run_id: publicApplication.workflow_run_id || null, - updated_at: publicApplication.updated_at || null, - } : null, - application: publicApplication, - }, - decision: decision ? { - material_sha256: /^[0-9a-f]{64}$/.test(decision.material_key || "") ? decision.material_key : null, - action: decision.action, - decided_at: decision.decided_at, - } : null, - policy: DECISION_POLICY, - errors: [], - }); -} - -// Event type by normalized projection source. Comparing the normalized source -// keeps a promotion application from being reported as an account-settings approval. -function auditEventAction(source, kind) { - if (source === "durable_human_decision") return `record_${kind}_decision`; - if (source === "research_promotion_application") return "research_promotion_application"; - if (source === "account_settings_application") return "account_settings_approval"; - return null; -} - -function auditEventSubjectId(row) { - if (typeof row.ticket_id === "string" && row.ticket_id) return { kind: "strategy_promotion", id: row.ticket_id }; - if (typeof row.recovery_id === "string" && row.recovery_id) return { kind: "reconciliation_recovery", id: row.recovery_id }; - if (typeof row.candidate_id === "string" && row.candidate_id) return { kind: "owner_decision", id: row.candidate_id }; - if (typeof row.platform === "string" && typeof row.key === "string") return { kind: "account_settings", id: `${row.platform}:${row.key}` }; - return null; -} - -async function auditEventsResponse(request, env, url) { - const session = await readSession(request, env); - if (!session?.allowed) return json({ ok: false, error: "login required", error_state: "unauthorized" }, 401); - let page; - try { - page = auditPageQuery(url); - } catch { - return json({ ok: false, error: "invalid_pagination" }, 400); - } - const now = Date.now(); - const serverTime = new Date(now).toISOString(); - if (!hasRuntimeInstanceStore(env)) - return json({ ok: false, error: "runtime_instances_not_bound", server_time: serverTime }, 503); - let rows; - try { - const result = await runtimeInstanceCommand(env, { - action: "console_audit_events", actor: session.login, limit: page.limit + 1, snapshot: page.snapshot, before: page.before, - }); - if (!Array.isArray(result.rows)) throw Error("invalid_audit_rows"); - rows = result.rows; - } catch { - return json({ ok: false, error: "audit_projection_unavailable", server_time: serverTime }, 503); - } - const events = []; - for (const row of rows) { - const occurredAt = typeof row.occurred_at === "string" && Number.isFinite(Date.parse(row.occurred_at)) - ? row.occurred_at : null; - if (!occurredAt) return json({ ok: false, error: "audit_projection_invalid" }, 503); - if (row.source === "credential_rotation") { - events.push({ id: row.event_id, event_id: row.event_id, occurred_at: occurredAt, - actor_alias: await actorAlias(row.actor || null), action: row.action, - subject: { kind: "credential", id: "ibkr_flex" }, operation_id: row.ref ?? null, outcome: row.status }); - continue; - } - const source = row.source === "decision" ? "durable_human_decision" - : row.source === "promotion_application" ? "research_promotion_application" - : row.source === "account_settings_application" ? "account_settings_application" - : null; - if (!source) return json({ ok: false, error: "audit_projection_invalid" }, 503); - const subject = source === "durable_human_decision" - ? { kind: `${row.kind}_decision`, id: row.subject_id } - : source === "research_promotion_application" - ? { kind: "strategy_promotion", id: row.subject_id } - : { kind: "account_settings", id: row.subject_id }; - if (typeof subject.id !== "string" || !subject.id || subject.id.length > 256) return json({ ok: false, error: "audit_projection_invalid" }, 503); - const revision = /^[0-9a-f]{64}$/.test(row.revision || "") ? row.revision : null; - events.push({ - id: `${source}:${row.ref}`, - source, - occurred_at: occurredAt, - action: auditEventAction(source, row.kind), - actor_alias: await actorAlias(row.actor || null), - subject, - revision, - outcome: typeof row.status === "string" && row.status.length <= 40 ? row.status : null, - ref: source === "durable_human_decision" - ? `/api/decisions/${encodeURIComponent(`${row.kind === "promotion" ? "strategy_promotion" : row.kind === "owner" ? "owner_decision" : "reconciliation_recovery"}:${row.subject_id}`)}` - : source === "research_promotion_application" - ? `/api/operations/${encodeURIComponent(`operation:strategy_promotion:${row.subject_id}`)}` - : null, - }); - } - try { - if (!hasConfigStore(env)) throw Error("audit_log_unavailable"); - const storedLog = await readConfigJson(env, AUDIT_LOG_KEY); - if (storedLog !== null && !Array.isArray(storedLog)) throw Error("audit_log_invalid"); - const log = storedLog || []; - const ids = await Promise.all(log.map(row => cycleHealthSha256(row))); - const occurrences = new Map(); - const aliases = await Promise.all(log.map((row) => - actorAlias(typeof row?.login === "string" ? row.login : null))); - log.forEach((row, index) => { - if (!row || typeof row !== "object" || Array.isArray(row)) throw Error("audit_log_invalid"); - const occurredAt = typeof row.ts === "string" && Number.isFinite(Date.parse(row.ts)) ? row.ts : null; - const action = typeof row.action === "string" && row.action.length <= 64 ? row.action : null; - if (!occurredAt || !action) throw Error("audit_log_invalid"); - const subject = auditEventSubjectId(row); - const occurrence = occurrences.get(ids[index]) || 0; - occurrences.set(ids[index], occurrence + 1); - events.push({ - id: `bounded_audit_log:${ids[index]}:${occurrence}`, - source: "bounded_audit_log", - occurred_at: new Date(occurredAt).toISOString(), - action, - actor_alias: aliases[index] || null, - subject, - revision: null, - outcome: "recorded", - ref: null, - }); - }); - } catch { - return json({ ok: false, error: "audit_log_unavailable", server_time: serverTime }, 503); - } - const seen = new Set(); - const unique = events - .filter((event) => { - if (seen.has(event.id)) return false; - seen.add(event.id); - return true; - }) - .filter(event => auditEventInPage(event, page)) - .sort(auditEventOrder); - const pageEvents = unique.slice(0, page.limit); - return json({ - schema_version: AUDIT_EVENTS_SCHEMA_VERSION, - server_time: serverTime, - data_status: "ready", - events: pageEvents.map(event => { if (!event.event_id) return event; const { id, ...safe } = event; return safe; }), - next_cursor: unique.length > page.limit ? auditNextCursor(pageEvents.at(-1), page) : null, - coverage: "durable_human_decisions_and_application_records_and_bounded_audit_log", - policy: DECISION_POLICY, - errors: [], - }); -} - -function attachResearchPromotionApplicationPreparation(ticket, accountOptions, strategyProfiles) { - const blockers = new Set(["activation_not_connected"]); - if (ticket?.shadow_passed !== true || !String(ticket?.shadow_evidence_kind || "").trim()) { - blockers.add("research_evidence_unverified"); - } - const params = ticket?.proposed_params; - const strategy = (Array.isArray(strategyProfiles) ? strategyProfiles : []) - .find((item) => item.profile === ticket.strategy_profile); - const candidateIdentity = researchCandidateIdentityForStrategy(strategy); - const applicationAdapter = researchCandidateApplicationAdapter(strategy); - const candidateParamsBound = candidateIdentity - ? Boolean( - params - && !Array.isArray(params) - && typeof params === "object" - && canonicalResearchTaskJson(params) === canonicalResearchTaskJson(candidateIdentity), - ) - : Boolean(params && !Array.isArray(params) && typeof params === "object" && !Object.keys(params).length); - if (!candidateParamsBound) { - blockers.add("candidate_params_unbound"); - } - if (!strategy) blockers.add("strategy_not_configured"); - if (candidateIdentity && !applicationAdapter) blockers.add("paper_application_adapter_unavailable"); - - const confirmedPlatform = String(ticket.confirmation_target_platform || "").trim(); - const platforms = confirmedPlatform ? [confirmedPlatform] : SUPPORTED_PLATFORMS; - const accounts = []; - for (const platform of strategy ? platforms : []) { - const options = Array.isArray(accountOptions?.[platform]) ? accountOptions[platform] : []; - for (const option of options) { - if (!supportedDomainsForAccount(platform, option).includes(ticket.domain)) continue; - const accountBlockers = []; - const configuredExecutionMode = cleanExecutionMode( - option.default_execution_mode || PLATFORM_CONFIG[platform]?.default_execution_mode || "live", - ); - if (strategy) { - try { - const brokerEnvironment = normalizeBrokerEnvironment( - option.broker_environment, - `account_options.${platform}.${option.key}.broker_environment`, - ); - if (!supportedBrokerEnvironmentsForPlatform(platform).includes(brokerEnvironment)) { - throw new HttpError(`${platform} does not support broker environment ${brokerEnvironment}`, 400); - } - const executionMode = String( - ticket.confirmation_execution_mode || brokerEnvironment, - ).trim().toLowerCase(); - validateResearchPromotionSelectedAccount({ - ticket, - selectedAccount: { platform, key: option.key }, - confirmation: { - target_platform: platform, - execution_mode: executionMode, - risk_profile: ticket.confirmation_risk_profile || ticket.suggested_risk_profile, - }, - accountOptions, - strategyProfiles, - }); - } catch (error) { - accountBlockers.push(researchPromotionApplicationBlocker(error)); - } - } - if (accountBlockers.length) continue; - accounts.push({ - platform, - key: option.key, - label: option.label, - configured_execution_mode: configuredExecutionMode, - broker_environment: option.broker_environment, - preflight_status: accountBlockers.length ? "blocked" : (strategy ? "ready" : "blocked"), - blocker_codes: [...new Set(accountBlockers)], - }); - } - } - if (!accounts.length) blockers.add("configured_account_missing"); - if (strategy && accounts.length && accounts.every((account) => account.preflight_status !== "ready")) { - blockers.add("account_preflight_failed"); - } - const candidateBlockers = [...blockers].filter((code) => code !== "activation_not_connected"); - const previewAccounts = ( - ticket?.state === "human_accepted" - && ticket?.shadow_passed === true - && String(ticket?.shadow_evidence_kind || "").trim() - && candidateIdentity - && applicationAdapter - && strategy?.domain === "us_equity" - && candidateParamsBound - && candidateBlockers.length === 0 - ) - ? accounts.filter((account) => { - const option = (accountOptions?.[account.platform] || []) - .find((item) => item.key === account.key); - return ( - account.platform === "longbridge" - && account.broker_environment === "paper" - && account.configured_execution_mode === "live" - && String(option?.account_selector || "").trim() - ); - }) - : []; - const previewRequest = previewAccounts.length === 1 - ? { - platform_id: previewAccounts[0].platform, - account_scope: String( - (accountOptions?.[previewAccounts[0].platform] || []) - .find((item) => item.key === previewAccounts[0].key)?.account_selector || "", - ).trim(), - strategy_profile: ticket.strategy_profile, - candidate_id: params.candidate_id, - config_sha256: params.config_sha256, - } - : null; - return { - ...attachRiskEnvelopeView(ticket), - application_preparation: { - status: "blocked", - preflight_status: candidateBlockers.length ? "blocked" : "ready", - activation_status: "not_connected", - dispatch_allowed: false, - blocker_codes: [...blockers].sort(), - account_options: accounts, - preview_request: previewRequest, - }, - }; -} - -function researchCandidateIdentityForStrategy(strategy) { - const identity = strategy?.research_candidate_identity; - return identity && typeof identity === "object" && !Array.isArray(identity) ? identity : null; -} - -function researchCandidateApplicationAdapter(strategy) { - if (strategy?.profile !== V7_PAPER_PREVIEW_PROFILE) return null; - return { - platform: "longbridge", - account_key: V7_PAPER_APPLICATION_ACCOUNT_KEY, - workflow_repository: V7_PAPER_APPLICATION_WORKFLOW_REPOSITORY, - workflow: V7_PAPER_APPLICATION_WORKFLOW, - }; -} - -function researchPromotionApplicationBlocker(error) { - const message = String(error?.message || error || "").toLowerCase(); - if (message.includes("broker environment is unknown")) return "broker_environment_unknown"; - if (message.includes("does not support broker environment")) return "broker_environment_unsupported"; - if (message.includes("not configured")) return "strategy_not_configured"; - if (message.includes("domain") && message.includes("not supported")) return "strategy_domain_not_supported"; - if (message.includes("live-enabled") || message.includes("blocked for live") || message.includes("does not allow")) { - return "strategy_execution_not_allowed"; - } - if (message.includes("configured account")) return "configured_account_mismatch"; - return "account_preflight_failed"; -} - -async function recordResearchPromotionDecisionResponse(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, 8 * 1024); - } catch (error) { - return json({ ok: false, error: error.message || "invalid research promotion decision" }, error.status || 400); - } - const ticketId = String(raw?.ticket_id || "").trim(); - const decision = String(raw?.decision || "").trim().toLowerCase(); - if (!ticketId) return json({ ok: false, error: "ticket_id is required" }, 400); - const stored = await readConfigJson(env, researchPromotionTicketKey(ticketId)); - if (!stored) return json({ ok: false, error: "research promotion ticket not found" }, 404); - let ticket; - try { - ticket = normalizeResearchPromotionTicket(stored); - assertResearchPromotionExpectedCandidate(ticket, raw, { requireExpectedParams: decision === "accept" }); - } catch (error) { - return json({ ok: false, error: error.message || "invalid research promotion decision" }, error.status || 400); - } - const materialKey = await researchPromotionMaterialKey(ticket); - // Keep the legacy material index stable; the separate review digest binds the complete producer review. - if (Object.prototype.hasOwnProperty.call(raw, "expected_material_sha256") && raw.expected_material_sha256 !== materialKey) { - return json({ ok: false, error: "human_decision_material_conflict" }, 409); - } - const reviewMaterial = researchPromotionReviewMaterial(ticket); - const reviewBinding = await researchPromotionReviewBinding(ticket); - const selectedAccount = raw?.selected_account || null; - const confirmation = raw?.confirmation || null; - const target = { selected_account: selectedAccount, confirmation }; - const targetJson = canonicalResearchTaskJson(target); - let authority; - try { - authority = await readHumanDecisionAuthority(env, "promotion", ticket.ticket_id); - } catch (error) { - return json({ ok: false, error: error.message || "research promotion decision unavailable" }, error.status || 503); - } - if (authority.migrated !== false) { - if (authority.blocked) return json({ ok: false, error: "human_decision_legacy_blocked" }, 409); - if (authority.eligible !== true || authority.material_key !== materialKey) { - return json({ ok: false, error: "human_decision_material_conflict" }, 409); - } - const existing = authority.decision; - if (existing && existing.action === decision && existing.target_json === targetJson && existing.payload?.ticket) { - if (Object.prototype.hasOwnProperty.call(raw, "expected_review_sha256") - && (existing.payload.review_binding?.schema_version !== "qsl_promotion_review_binding.v2" - || raw.expected_review_sha256 !== existing.payload.review_binding.review_sha256)) { - return json({ ok: false, error: "human_decision_review_binding_unqualified" }, 409); - } - return json({ - ok: true, - ticket: attachRiskEnvelopeView(existing.payload.ticket), - live_authority_granted: false, - replayed: true, - actor: existing.actor, - decision_receipt: await humanDecisionReceipt(existing, { kind: "promotion", subject_id: ticket.ticket_id, material_sha256: materialKey }), - }); - } - if (existing) return json({ ok: false, error: "human_decision_conflict" }, 409); - } - if (Object.prototype.hasOwnProperty.call(raw, "expected_review_sha256") && raw.expected_review_sha256 !== reviewBinding.review_sha256) { - return json({ ok: false, error: "human_decision_review_material_conflict" }, 409); - } - const targetPlatform = String( - raw?.confirmation?.target_platform || ticket.confirmation_target_platform || "", - ).trim(); - const paperSupported = platformSupportsBrokerPaperMode(targetPlatform); - const accountConfig = decision === "accept" ? await loadAccountOptionsConfig(env) : null; - const serverAccountRevision = accountConfig?.source === "durable_object" && Number.isSafeInteger(accountConfig.revision) - ? accountConfig.revision - : null; - const strategyProfiles = decision === "accept" ? await loadStrategyProfilesConfig(env) : null; - let decided; - try { - decided = applyResearchPromotionDecision(ticket, { - decision, - confirmation, - paperSupported, - selectedAccount, - accountOptions: accountConfig?.options, - strategyProfiles, - decidedAt: new Date().toISOString(), - expectedRaw: raw, - }); - } catch (error) { - return json({ ok: false, error: error.message || "invalid research promotion decision" }, error.status || 400); - } - if (decided.live_authority_granted) { - return json({ ok: false, error: "refusing to persist live_authority_granted=true" }, 500); - } - const preflightRevision = decision === "accept" ? serverAccountRevision : null; - if (decision === "accept" && Object.prototype.hasOwnProperty.call(raw, "expected_account_revision") - && Number(raw.expected_account_revision) !== serverAccountRevision) { - return json({ ok: false, error: "runtime_instance_revision_conflict" }, 409); - } - const decidedAt = decided.human_decided_at || humanDecisionTimestamp(); - let requestKey; - try { - requestKey = await humanDecisionRequestKey({ - requestId: raw?.request_id, - kind: "promotion", - actor: session.login, - subjectId: ticket.ticket_id, - materialKey, - action: decision, - target, - }); - } catch (error) { - return json({ ok: false, error: error.message || "invalid research promotion decision" }, error.status || 400); - } - let recorded; - try { - recorded = await runtimeInstanceCommand(env, { - action: "promotion_decide", - ...(Object.prototype.hasOwnProperty.call(raw, "expected_review_sha256") ? { review_binding: reviewBinding } : {}), - actor: session.login, - subject_id: ticket.ticket_id, - material_key: materialKey, - action_name: decision, - request_key: requestKey, - target_json: canonicalResearchTaskJson(target), - decided_at: decidedAt, - now_ms: Date.now(), - preflight_revision: preflightRevision, - payload: { - ticket: decided, - ...(Object.prototype.hasOwnProperty.call(raw, "expected_review_sha256") ? { review_binding: reviewBinding, review_material: reviewMaterial } : {}), - selected_account: selectedAccount, - confirmation, - proposed_params: ticket.proposed_params, - }, - }); - } catch (error) { - return json({ ok: false, error: error.message || "research promotion decision unavailable" }, error.status || 503); - } - const decisionReceipt = await humanDecisionReceipt(recorded.record, { kind: "promotion", subject_id: ticket.ticket_id, material_sha256: materialKey }); - const recordedTicket = recorded.record.payload.ticket; - await mirrorConfigJson(env, researchPromotionTicketKey(recordedTicket.ticket_id), recordedTicket); - try { - await appendAuditLog(env, { - ts: recordedTicket.updated_at, - login: session.login, - action: "research_promotion_decision", - ticket_id: recordedTicket.ticket_id, - decision: recordedTicket.human_decision, - confirmation_target_platform: recordedTicket.confirmation_target_platform, - confirmation_execution_mode: recordedTicket.confirmation_execution_mode, - confirmation_risk_profile: recordedTicket.confirmation_risk_profile, - suggested_risk_profile: recordedTicket.suggested_risk_profile, - live_authority_granted: false, - }); - } catch { - // Decision persistence must not depend on optional audit retention. - } - return json({ - ok: true, - ticket: attachRiskEnvelopeView(recordedTicket), - decision_receipt: decisionReceipt, - live_authority_granted: false, - replayed: recorded.replayed === true, - }); -} - -function v7PaperApplicationCandidate(strategy, ticket) { - if (!researchCandidateApplicationAdapter(strategy)) { - throw new HttpError("paper application adapter is not configured for candidate", 409); - } - if (ticket?.shadow_evidence_kind !== "v7_nonlive_shadow_and_simulated_paper") { - throw new HttpError("v7_shadow_evidence_kind_not_qualified", 409); - } - const notes = Array.isArray(ticket?.notes) ? ticket.notes : []; - const requiredEvidence = [ - "frozen_v7_forward_financial_gates_passed_research_only", - "forward_record_sha256", - "financial_evidence_sha256", - "p1_manifest_sha256", - "p4_policy_sha256", - "baseline_p3_evidence_sha256", - ]; - if (notes[0] !== requiredEvidence[0] || requiredEvidence.slice(1).some((prefix) => - !notes.some((note) => new RegExp(`^${prefix}=[0-9a-f]{64}$`).test(String(note))))) { - throw new HttpError("v7_shadow_evidence_provenance_incomplete", 409); - } - const identity = researchCandidateIdentityForStrategy(strategy); - if (!identity || ticket?.domain !== "us_equity") { - throw new HttpError("research_candidate_not_eligible_for_paper_application", 409); - } - const params = ticket?.proposed_params; - if (!params || Array.isArray(params) || typeof params !== "object" - || canonicalResearchTaskJson(params) !== canonicalResearchTaskJson(identity)) { - throw new HttpError("candidate_params_unbound", 409); - } - return { - candidate_id: String(identity.candidate_id || ""), - config_sha256: String(identity.config_sha256 || ""), - }; -} - -async function loadStoredResearchPromotionTicket(env, ticketId) { - if (!hasConfigStore(env)) throw new HttpError("research promotion KV is not configured", 503); - const stored = await readConfigJson(env, researchPromotionTicketKey(ticketId)); - if (!stored) throw new HttpError("research promotion ticket not found", 404); - let ticket; - try { - ticket = normalizeResearchPromotionTicket(stored, "stored research promotion ticket"); - } catch (error) { - throw new HttpError(error.message || "invalid stored research promotion ticket", 409); - } - return promotionTicketWithAuthority(env, ticket); -} - -async function qualifyV7PaperApplication(env, { ticketId, selectedAccount, expectedRevision }) { - assertExactFields(selectedAccount, ["platform", "key"], "selected_account"); - const platform = cleanChoice(selectedAccount.platform, SUPPORTED_PLATFORMS, "selected_account.platform"); - const key = cleanSlug(selectedAccount.key, "selected_account.key"); - if (platform !== "longbridge" || key !== V7_PAPER_APPLICATION_ACCOUNT_KEY) { - throw new HttpError("only the configured LongBridge paper account is supported", 409); - } - if (!V7_PAPER_APPLICATION_TICKET_RE.test(String(ticketId || ""))) { - throw new HttpError("research promotion ticket is not from the V7 producer", 409); - } - if (!Number.isSafeInteger(expectedRevision) || expectedRevision < 0) { - throw new HttpError("runtime instance revision is required", 400); - } - const ticket = await loadStoredResearchPromotionTicket(env, ticketId); - const promotionMaterialKey = await researchPromotionMaterialKey(ticket); - const promotionAuthority = await readHumanDecisionAuthority(env, "promotion", ticket.ticket_id); - if (promotionAuthority.blocked || promotionAuthority.eligible !== true || promotionAuthority.decision?.action !== "accept" || promotionAuthority.material_key !== promotionMaterialKey) { - throw new HttpError("research candidate is not human_accepted", 409); - } - const boundAccount = promotionAuthority.decision.payload?.selected_account || {}; - if (boundAccount.platform !== platform || boundAccount.key !== key) { - throw new HttpError("promotion_account_binding_conflict", 409); - } - if (ticket.state !== "human_accepted") throw new HttpError("research candidate is not human_accepted", 409); - if (ticket.live_authority_granted !== false || ticket.shadow_passed !== true || !String(ticket.shadow_evidence_kind || "").trim()) { - throw new HttpError("research candidate evidence is not qualified", 409); - } - const strategyProfiles = await loadStrategyProfilesConfig(env); - const strategy = (Array.isArray(strategyProfiles) ? strategyProfiles : []) - .find((item) => item.profile === ticket.strategy_profile); - if (!strategy) throw new HttpError("selected candidate strategy profile is not configured", 409); - const candidate = v7PaperApplicationCandidate(strategy, ticket); - const state = await runtimeInstanceCommand(env); - if (!state.initialized) throw new HttpError("runtime instances are not initialized", 409); - if (state.revision !== expectedRevision) throw new HttpError("runtime instance revision conflict", 409); - const instance = (state.instances || []).find((item) => item.kind === "existing" && item.platform === platform && item.key === key && item.retirement_status === "none"); - const config = instance?.config; - if (!config) throw new HttpError("selected LongBridge paper account is unavailable", 409); - if (String(config.broker_environment || "") !== "paper" - || String(config.account_selector || "") !== V7_PAPER_APPLICATION_ACCOUNT_SCOPE - || String(config.service_name || "") !== V7_PAPER_APPLICATION_SERVICE - || String(config.runtime_status_target_id || "") !== "longbridge.paper" - || String(config.default_execution_mode || "") !== "live") { - throw new HttpError("selected LongBridge paper account binding is incomplete", 409); - } - const expectedStrategyProfile = String(config.default_strategy_profile || "").trim(); - if (!expectedStrategyProfile) throw new HttpError("selected account strategy profile is unknown", 409); - validateResearchPromotionSelectedAccount({ - ticket, - selectedAccount: { platform, key }, - confirmation: { - target_platform: ticket.confirmation_target_platform, - execution_mode: ticket.confirmation_execution_mode, - risk_profile: ticket.confirmation_risk_profile, - }, - accountOptions: { longbridge: [config] }, - strategyProfiles, - }); - return { - ticket, - strategy, - candidate, - promotionMaterialKey, - revision: state.revision, - account: { - platform_id: platform, - account_key: key, - account_scope: V7_PAPER_APPLICATION_ACCOUNT_SCOPE, - account_selector: V7_PAPER_APPLICATION_ACCOUNT_SCOPE, - service_name: V7_PAPER_APPLICATION_SERVICE, - broker_environment: "paper", - expected_strategy_profile: expectedStrategyProfile, - }, - }; -} - -function publicResearchPromotionApplication(application) { - if (!application) return null; - const { claim_token: _claimToken, ...publicApplication } = application; - return publicApplication; -} - -async function dispatchV7PaperApplication(env, applicationId) { - const token = String(env.RUNTIME_SETTINGS_DISPATCH_TOKEN || ""); - if (!token) throw new HttpError("paper application dispatch unavailable", 503); - const url = `https://api.github.com/repos/${V7_PAPER_APPLICATION_WORKFLOW_REPOSITORY}/actions/workflows/${V7_PAPER_APPLICATION_WORKFLOW}/dispatches`; - const response = await fetchWithTimeout(url, { - method: "POST", - headers: githubHeaders(token), - body: JSON.stringify({ ref: "main", inputs: { application_id: applicationId } }), - }); - if (!response.ok) throw new HttpError("paper application dispatch unverified", 502); -} - -function applicationIdFromPath(pathname) { - const value = String(pathname || "").slice("/api/internal/research-promotion-application/".length); - if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)) { - throw new HttpError("invalid application_id", 400); - } - return value; -} - -async function researchPromotionApplicationResponse(request, env, url) { - return researchPromotionApplicationResponseImpl(request, env, url, { - json, - readSession, - requireSameOrigin, - readBoundedJson, - runtimeInstanceCommand, - assertExactFields, - randomToken, - qualifyV7PaperApplication, - dispatchV7PaperApplication, - publicResearchPromotionApplication, - isUnsupportedApplicationActionError: (error) => ( - error instanceof HttpError - && error.message === "unsupported_research_promotion_application_action" - ), - V7_PAPER_APPLICATION_MAX_BODY_BYTES, - V7_PAPER_APPLICATION_SOURCE_COMMIT, - V7_PAPER_APPLICATION_UES_REVISION, - }); -} - -function validateV7PaperApplicationReadback(application, readback) { - if (!readback || typeof readback !== "object" || Array.isArray(readback)) return false; - return readback.application_id === application.application_id - && readback.platform_id === application.platform_id - && readback.account_scope === application.account_scope - && readback.service_name === application.service_name - && readback.strategy_profile === application.strategy_profile - && readback.candidate_id === application.candidate_id - && readback.config_sha256 === application.config_sha256 - && readback.source_commit === application.source_commit - && readback.ues_revision === application.approved_ues_revision - && readback.runtime_target_enabled === false - && typeof readback.revision_name === "string" && readback.revision_name.trim() !== ""; -} - -async function researchPromotionApplicationInternalResponse(request, env, url) { - return researchPromotionApplicationInternalResponseImpl(request, env, url, { - json, - requireDedicatedExecutionEvidenceSyncToken, - hasRuntimeInstanceStore, - applicationIdFromPath, - readBoundedJson, - runtimeInstanceCommand, - assertExactFields, - qualifyV7PaperApplication, - validateV7PaperApplicationReadback, - V7_PAPER_APPLICATION_MAX_BODY_BYTES, - V7_PAPER_APPLICATION_WORKFLOW_REPOSITORY, - V7_PAPER_APPLICATION_WORKFLOW, - }); -} - -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); - const header = request.headers.get("Authorization") || ""; - const token = header.match(/^Bearer\s+(.+)$/i)?.[1] || ""; - if (token !== expected) throw new HttpError("research task sync token is invalid", 401); -} - -function assertExactFields(value, fields, fieldName) { - if (!value || Array.isArray(value) || typeof value !== "object") { - throw new Error(`${fieldName} must be an object`); - } - const actual = Object.keys(value).sort(); - const expected = [...fields].sort(); - if (actual.length !== expected.length || actual.some((item, index) => item !== expected[index])) { - throw new Error(`${fieldName} has invalid fields`); - } - return value; -} - -function assertRequiredAndOptionalFields(value, requiredFields, optionalFields, fieldName) { - if (!value || Array.isArray(value) || typeof value !== "object") { - throw new Error(`${fieldName} must be an object`); - } - const allowed = new Set([...requiredFields, ...optionalFields]); - const actual = Object.keys(value); - if ( - requiredFields.some((field) => !Object.prototype.hasOwnProperty.call(value, field)) - || actual.some((field) => !allowed.has(field)) - ) { - throw new Error(`${fieldName} has invalid fields`); - } - return value; -} - -function normalizeResearchTaskIdentity(value, fieldName) { - const text = String(value || "").trim(); - if (!/^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/.test(text)) { - throw new Error(`${fieldName} must be a stable identity`); - } - return text; -} - -function normalizeResearchTaskDigest(value, fieldName, nullable = false) { - if ((value === null || value === undefined || value === "") && nullable) return null; - const text = String(value || "").trim(); - if (!/^[0-9a-f]{64}$/.test(text)) throw new Error(`${fieldName} must be a lowercase SHA-256`); - return text; -} - -function normalizeResearchTaskRevision(value, fieldName) { - const text = String(value || "").trim(); - if (!/^[0-9a-f]{40}$/.test(text)) throw new Error(`${fieldName} must be a 40-character revision`); - return text; -} - -function normalizeResearchTaskTimestamp(value, fieldName) { - const text = String(value || "").trim(); - if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/.test(text) || Number.isNaN(Date.parse(text))) { - throw new Error(`${fieldName} must be a UTC timestamp`); - } - return text; -} - -function normalizeResearchTaskText(value, fieldName, maxLength) { - const text = String(value || "").trim(); - if (!text || text.length > maxLength || /[<>\\]/.test(text)) throw new Error(`${fieldName} is invalid`); - if (/\b(?:token|secret|password|credential|api[_ -]?key)\s*[:=]/i.test(text)) { - throw new Error(`${fieldName} contains sensitive material`); - } - return text; -} - -function assertResearchTaskHasNoUnsafeMaterial(value, fieldName = "research task") { - if (Array.isArray(value)) { - value.forEach((item, index) => assertResearchTaskHasNoUnsafeMaterial(item, `${fieldName}[${index}]`)); - return; - } - if (!value || typeof value !== "object") return; - for (const [key, nested] of Object.entries(value)) { - if (key !== "no_order" && /(?:secret|token|password|credential|api[_-]?key|order|fill|capital|account|broker)/i.test(key)) { - throw new Error(`${fieldName} contains forbidden execution or secret material`); - } - assertResearchTaskHasNoUnsafeMaterial(nested, `${fieldName}.${key}`); - } -} - -function canonicalResearchTaskJson(value) { - if (value === null) return "null"; - if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value); - if (typeof value === "number") { - if (!Number.isFinite(value)) throw new Error("research task must use finite JSON values"); - return JSON.stringify(value); - } - if (Array.isArray(value)) return `[${value.map(canonicalResearchTaskJson).join(",")}]`; - if (!value || typeof value !== "object") throw new Error("research task must use JSON values"); - return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalResearchTaskJson(value[key])}`).join(",")}}`; -} - -async function sha256Hex(text) { - const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text)); - return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join(""); -} - -async function calculateResearchTaskSha256(payload) { - const material = { ...payload }; - delete material.task_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 normalizeResearchTask(payload, fieldName) { - const task = assertExactFields(payload, [ - "schema", "task_id", "created_at", "digest_algorithm", "task_type", "target", "evidence", "experiment", "authority", "task_sha256", - ], fieldName); - assertResearchTaskHasNoUnsafeMaterial(task, fieldName); - if (task.schema !== RESEARCH_TASK_SCHEMA_VERSION) throw new Error(`${fieldName}.schema is unsupported`); - if (task.digest_algorithm !== "sha256") throw new Error(`${fieldName}.digest_algorithm must be sha256`); - const target = assertExactFields(task.target, ["candidate_id", "candidate_kind", "domain", "repository", "strategy_revision"], `${fieldName}.target`); - const evidence = assertExactFields(task.evidence, ["p1_input_digest", "p2_config_digest", "p3_evidence_id", "producer_revision"], `${fieldName}.evidence`); - const experiment = assertExactFields(task.experiment, ["objective", "hypothesis", "parameter_bounds_sha256", "max_runs", "max_wall_seconds"], `${fieldName}.experiment`); - const authority = assertExactFields(task.authority, ["research_only", "no_order", "size_zero_required", "p4_p5_p6_authorized"], `${fieldName}.authority`); - const normalized = { - schema: RESEARCH_TASK_SCHEMA_VERSION, - task_id: normalizeResearchTaskIdentity(task.task_id, `${fieldName}.task_id`), - created_at: normalizeResearchTaskTimestamp(task.created_at, `${fieldName}.created_at`), - digest_algorithm: "sha256", - task_type: cleanChoice(task.task_type, RESEARCH_TASK_TYPES, `${fieldName}.task_type`), - target: { - candidate_id: normalizeResearchTaskIdentity(target.candidate_id, `${fieldName}.target.candidate_id`), - candidate_kind: cleanChoice(target.candidate_kind, CONTROL_PLANE_CANDIDATE_KINDS, `${fieldName}.target.candidate_kind`), - domain: cleanChoice(target.domain, STRATEGY_HEALTH_DOMAINS, `${fieldName}.target.domain`), - repository: normalizeResearchTaskRepository(target.repository, `${fieldName}.target.repository`), - strategy_revision: normalizeResearchTaskRevision(target.strategy_revision, `${fieldName}.target.strategy_revision`), - }, - evidence: { - p1_input_digest: normalizeResearchTaskDigest(evidence.p1_input_digest, `${fieldName}.evidence.p1_input_digest`, true), - p2_config_digest: normalizeResearchTaskDigest(evidence.p2_config_digest, `${fieldName}.evidence.p2_config_digest`, true), - p3_evidence_id: normalizeResearchTaskDigest(evidence.p3_evidence_id, `${fieldName}.evidence.p3_evidence_id`, true), - producer_revision: normalizeResearchTaskRevision(evidence.producer_revision, `${fieldName}.evidence.producer_revision`), - }, - experiment: { - objective: cleanChoice(experiment.objective, RESEARCH_TASK_OBJECTIVES, `${fieldName}.experiment.objective`), - hypothesis: normalizeResearchTaskText(experiment.hypothesis, `${fieldName}.experiment.hypothesis`, 800), - parameter_bounds_sha256: normalizeResearchTaskDigest(experiment.parameter_bounds_sha256, `${fieldName}.experiment.parameter_bounds_sha256`, true), - max_runs: normalizeResearchTaskBound(experiment.max_runs, `${fieldName}.experiment.max_runs`, 100), - max_wall_seconds: normalizeResearchTaskBound(experiment.max_wall_seconds, `${fieldName}.experiment.max_wall_seconds`, 86400), - }, - authority: { - research_only: authority.research_only, - no_order: authority.no_order, - size_zero_required: authority.size_zero_required, - p4_p5_p6_authorized: authority.p4_p5_p6_authorized, - }, - task_sha256: normalizeResearchTaskDigest(task.task_sha256, `${fieldName}.task_sha256`), - }; - if (JSON.stringify(normalized.authority) !== JSON.stringify({ research_only: true, no_order: true, size_zero_required: true, p4_p5_p6_authorized: false })) { - throw new Error(`${fieldName}.authority must be fixed to offline research only`); - } - if (normalized.task_sha256 !== await calculateResearchTaskSha256(normalized)) { - throw new Error(`${fieldName}.task_sha256 mismatch`); - } - return normalized; -} - -function normalizeResearchTaskRepository(value, fieldName) { - const text = String(value || "").trim(); - if (!/^[A-Za-z0-9][A-Za-z0-9_.-]*\/[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(text)) { - throw new Error(`${fieldName} is invalid`); - } - return text; -} - -function normalizeResearchTaskBound(value, fieldName, maximum) { - if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > maximum) { - throw new Error(`${fieldName} is outside its safe bound`); - } - return value; -} - -async function normalizeResearchTaskSourceSnapshot(payload, fieldName = "research task source snapshot") { - const source = assertExactFields(payload, ["schema_version", "source_id", "generated_at", "computed_at", "data_status", "tasks", "errors"], fieldName); - if (source.schema_version !== RESEARCH_TASK_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.tasks) || source.tasks.length > 100) { - throw new Error(`${fieldName}.tasks must be an array with at most 100 items`); - } - const tasks = []; - const seen = new Set(); - for (const [index, item] of source.tasks.entries()) { - const task = await normalizeResearchTask(item, `${fieldName}.tasks[${index}]`); - if (seen.has(task.task_id)) throw new Error(`${fieldName}.tasks contains duplicate task_id`); - seen.add(task.task_id); - tasks.push(task); - } - if (dataStatus === "unavailable" && tasks.length) throw new Error(`${fieldName}.tasks must be empty when unavailable`); - return { - schema_version: RESEARCH_TASK_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, - tasks, - errors: normalizeStrategyHealthErrors(source.errors), - }; -} - -function emptyResearchTaskSourceSnapshot(errorCode) { - return { - schema_version: RESEARCH_TASK_SOURCE_SCHEMA_VERSION, - source_id: "unavailable", - generated_at: null, - computed_at: null, - data_status: "unavailable", - tasks: [], - errors: [errorCode], - }; -} - -function emptyResearchTaskPayload(errorCode) { - return { - schema_version: RESEARCH_TASK_DASHBOARD_SCHEMA_VERSION, - generated_at: null, - computed_at: null, - data_status: "unavailable", - summary: { task_count: 0 }, - tasks: [], - policy: { - research_only: true, - no_order: true, - size_zero_required: true, - p4_p5_p6_authorized: false, - notice: "研究任务只用于离线、零仓位实验;不会调参、改代码、部署或下单。", - }, - errors: [errorCode], - }; -} - -function researchTaskStaleTtlSeconds(env) { - const configured = Number(env.RESEARCH_TASK_STALE_TTL_SECONDS); - if (!Number.isFinite(configured) || configured < 300 || configured > 604800) { - return RESEARCH_TASK_DEFAULT_STALE_TTL_SECONDS; - } - return Math.floor(configured); -} - -function normalizeStrategyHealthSnapshot(payload, fieldName = "strategy health snapshot") { - if (!payload || Array.isArray(payload) || typeof payload !== "object") { - throw new Error(`${fieldName} must be an object`); - } - if (payload.schema_version !== "strategy_health_dashboard.v1") { - throw new Error(`${fieldName}.schema_version is unsupported`); - } - const status = cleanChoice(payload.data_status || "unavailable", [...STRATEGY_HEALTH_DATA_STATUSES, "partial"], `${fieldName}.data_status`); - const strategies = status === "unavailable" - ? [] - : normalizeStrategyHealthStrategies(payload.strategies, fieldName); - const summary = normalizeStrategyHealthSummary(payload.summary, strategies); - const errors = normalizeStrategyHealthErrors(payload.errors); - return { - schema_version: "strategy_health_dashboard.v1", - generated_at: normalizeStrategyHealthTimestamp(payload.generated_at, `${fieldName}.generated_at`, true), - computed_at: normalizeStrategyHealthTimestamp(payload.computed_at, `${fieldName}.computed_at`, true), - data_status: status, - summary, - strategies, - policy: normalizeStrategyHealthPolicy(payload.policy), - errors, - }; -} - -function normalizeStrategyHealthStrategies(value, fieldName) { - if (!Array.isArray(value) || value.length > 100) { - throw new Error(`${fieldName}.strategies must be an array with at most 100 items`); - } - const seen = new Set(); - return value.map((item, index) => { - if (!item || Array.isArray(item) || typeof item !== "object") { - throw new Error(`${fieldName}.strategies[${index}] must be an object`); - } - const profile = cleanSlug(item.profile, `${fieldName}.strategies[${index}].profile`).toLowerCase(); - if (seen.has(profile)) throw new Error(`${fieldName}.strategies contains duplicate profile`); - seen.add(profile); - return { - profile, - domain: cleanChoice(item.domain, STRATEGY_HEALTH_DOMAINS, `${fieldName}.strategies[${index}].domain`), - as_of: sanitizeStrategyHealthText(item.as_of, `${fieldName}.strategies[${index}].as_of`, 64, true), - status: cleanChoice(item.status, STRATEGY_HEALTH_STATUSES, `${fieldName}.strategies[${index}].status`), - score: normalizeStrategyHealthScore(item.score, `${fieldName}.strategies[${index}].score`), - components: normalizeStrategyHealthComponents(item.components, `${fieldName}.strategies[${index}].components`), - decision: normalizeStrategyHealthDecision(item.decision, `${fieldName}.strategies[${index}].decision`), - review: normalizeStrategyHealthReview(item.review, `${fieldName}.strategies[${index}].review`), - freshness: normalizeStrategyHealthFreshness(item.freshness, `${fieldName}.strategies[${index}].freshness`), - source_revision: sanitizeStrategyHealthText(item.source_revision, `${fieldName}.strategies[${index}].source_revision`, 120, true), - }; - }); -} - -function normalizeStrategyHealthSummary(value, strategies) { - const counts = Object.fromEntries(STRATEGY_HEALTH_STATUSES.map((status) => [status, 0])); - for (const item of strategies) counts[item.status] += 1; - return { strategy_count: strategies.length, ...counts }; -} - -function normalizeStrategyHealthComponents(value, fieldName) { - const source = value && typeof value === "object" && !Array.isArray(value) ? value : {}; - return { - performance: normalizeStrategyHealthScore(source.performance, `${fieldName}.performance`), - risk: normalizeStrategyHealthScore(source.risk, `${fieldName}.risk`), - decay: normalizeStrategyHealthScore(source.decay, `${fieldName}.decay`), - stability: normalizeStrategyHealthScore(source.stability, `${fieldName}.stability`), - operations: normalizeStrategyHealthScore(source.operations, `${fieldName}.operations`), - }; -} - -function normalizeStrategyHealthDecision(value, fieldName) { - const source = value && typeof value === "object" && !Array.isArray(value) ? value : {}; - return { - code: cleanSlug(source.code || "evidence_missing", `${fieldName}.code`).toLowerCase(), - label: sanitizeStrategyHealthText(source.label, `${fieldName}.label`, 120, false, "证据不足,保持研究态"), - reason: sanitizeStrategyHealthText(source.reason, `${fieldName}.reason`, 240, false, "没有可用的机器检查结果。"), - }; -} - -function normalizeStrategyHealthReview(value, fieldName) { - const source = value && typeof value === "object" && !Array.isArray(value) ? value : {}; - return { - requested_stage: sanitizeStrategyHealthText(source.requested_stage, `${fieldName}.requested_stage`, 80, true), - evidence_package_id: sanitizeStrategyHealthText(source.evidence_package_id, `${fieldName}.evidence_package_id`, 120, true), - validation: normalizeStrategyHealthSummaryObject(source.validation), - risk: normalizeStrategyHealthSummaryObject(source.risk), - kelly_readiness: normalizeStrategyHealthSummaryObject(source.kelly_readiness), - }; -} - -function normalizeStrategyHealthSummaryObject(value) { - if (!value || typeof value !== "object" || Array.isArray(value)) return {}; - const result = {}; - for (const [key, raw] of Object.entries(value).slice(0, 12)) { - if (!/^[A-Za-z0-9_.-]{1,48}$/.test(key)) continue; - if (/(token|secret|password|cookie|private|path|key)/i.test(key)) continue; - if (typeof raw === "boolean") result[key] = raw; - else if (typeof raw === "number" && Number.isFinite(raw)) result[key] = raw; - else if (typeof raw === "string") { - const safe = sanitizeStrategyHealthText(raw, "summary.value", 120, true); - if (safe !== null) result[key] = safe; - } - } - return result; -} - -function normalizeStrategyHealthFreshness(value, fieldName) { - const source = value && typeof value === "object" && !Array.isArray(value) ? value : {}; - return { - status: cleanChoice(source.status || "unknown", ["fresh", "stale", "unknown"], `${fieldName}.status`), - age_seconds: normalizeStrategyHealthAge(source.age_seconds, `${fieldName}.age_seconds`), - }; -} - -function normalizeStrategyHealthAge(value, fieldName) { - if (value === null || value === undefined || value === "") return null; - const number = Number(value); - if (!Number.isFinite(number) || number < 0 || number > 315360000) throw new Error(`${fieldName} is invalid`); - return Math.round(number); -} - -function normalizeStrategyHealthScore(value, fieldName) { - if (value === null || value === undefined || value === "") return null; - const number = Number(value); - if (!Number.isFinite(number) || number < 0 || number > 100) throw new Error(`${fieldName} must be between 0 and 100`); - return number; -} - -function normalizeStrategyHealthTimestamp(value, fieldName, nullable = false) { - if ((value === null || value === undefined || value === "") && nullable) return null; - const text = normalizeStrategyHealthText(value, fieldName, 64); - if (!/^\d{4}-\d{2}-\d{2}T.+(?:Z|[+-]\d{2}:\d{2})$/.test(text) || Number.isNaN(Date.parse(text))) { - throw new Error(`${fieldName} must be an ISO timestamp`); - } - return text; -} - -function normalizeStrategyHealthText(value, fieldName, maxLength, nullable = false) { - if ((value === null || value === undefined || value === "") && nullable) return null; - const text = String(value || "").trim(); - if ( - !text || - text.length > maxLength || - /[<>\\]/.test(text) || - text.startsWith("/") || - /^[A-Za-z]:[\\/]/.test(text) - ) throw new Error(`${fieldName} is invalid`); - return text; -} - -function sanitizeStrategyHealthText(value, fieldName, maxLength, nullable = false, fallback = null) { - if (value === null || value === undefined || value === "") return nullable ? null : fallback; - let text; - try { - text = normalizeStrategyHealthText(value, fieldName, maxLength); - } catch { - return nullable ? null : fallback; - } - if ( - /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/i.test(text) || - /\b(?:token|secret|password|api[_ -]?key|private[_ -]?key|cookie)\s*[:=]\s*[^\s,;]{8,}/i.test(text) || - /\b(?:gh[pousr]_[A-Za-z0-9_]{20,}|sk-[A-Za-z0-9_-]{20,}|eyJ[A-Za-z0-9_-]{20,})\b/.test(text) || - /(?:^|[\s(])(?:\/Users\/|\/home\/|[A-Za-z]:[\\/])/.test(text) - ) return nullable ? null : fallback; - return text; -} - -function normalizeStrategyHealthErrors(value) { - if (!Array.isArray(value)) return []; - return uniqueStrings(value.slice(0, 20).filter((item) => /^[a-z][a-z0-9_.-]{0,63}$/.test(String(item || "")))); -} - -function normalizeStrategyHealthPolicy(value) { - const source = value && typeof value === "object" && !Array.isArray(value) ? value : {}; - const list = (raw) => Array.isArray(raw) - ? raw.slice(0, 20).map((item) => sanitizeStrategyHealthText(item, "policy.item", 120, true)).filter(Boolean) - : []; - return { - mode: sanitizeStrategyHealthText(source.mode, "policy.mode", 40, false, "read_only"), - automatic_stages: list(source.automatic_stages), - automatic_modes: list(source.automatic_modes), - human_gate_stages: list(source.human_gate_stages), - canary_requirements: list(source.canary_requirements), - human_actions: list(source.human_actions), - machine_checks: list(source.machine_checks), - notice: sanitizeStrategyHealthText(source.notice, "policy.notice", 240, false, "健康不等于已批准 live。"), - }; -} - -function emptyStrategyHealthPayload(errorCode) { - return { - schema_version: "strategy_health_dashboard.v1", - generated_at: null, - computed_at: null, - data_status: "unavailable", - summary: { strategy_count: 0, healthy: 0, watch: 0, review: 0, critical: 0 }, - strategies: [], - policy: normalizeStrategyHealthPolicy({}), - errors: [errorCode], - }; -} - -function strategyHealthStaleTtlSeconds(env) { - const configured = Number(env.STRATEGY_HEALTH_STALE_TTL_SECONDS); - if (!Number.isFinite(configured) || configured < 300 || configured > 604800) { - return STRATEGY_HEALTH_DEFAULT_STALE_TTL_SECONDS; - } - return Math.floor(configured); -} - -function normalizeControlPlaneSnapshot(payload, fieldName = "control plane snapshot") { - if (!payload || Array.isArray(payload) || typeof payload !== "object") { - throw new Error(`${fieldName} must be an object`); - } - if (payload.schema_version !== "qsl_control_plane_dashboard.v1") { - throw new Error(`${fieldName}.schema_version is unsupported`); - } - for (const required of ["generated_at", "computed_at", "data_status", "summary", "candidates", "policy", "errors"]) { - if (!(required in payload)) throw new Error(`${fieldName}.${required} is required`); - } - if (!payload.summary || Array.isArray(payload.summary) || typeof payload.summary !== "object") { - throw new Error(`${fieldName}.summary must be an object`); - } - const dataStatus = cleanChoice(payload.data_status || "unavailable", STRATEGY_HEALTH_DATA_STATUSES, `${fieldName}.data_status`); - const candidates = dataStatus === "unavailable" ? [] : normalizeControlPlaneCandidates(payload.candidates, fieldName); - const errors = normalizeStrategyHealthErrors(payload.errors); - return { - schema_version: "qsl_control_plane_dashboard.v1", - generated_at: normalizeStrategyHealthTimestamp(payload.generated_at, `${fieldName}.generated_at`, true), - computed_at: normalizeStrategyHealthTimestamp(payload.computed_at, `${fieldName}.computed_at`, true), - data_status: dataStatus, - summary: normalizeControlPlaneSummary(candidates), - attention: normalizeControlPlaneAttention({ dataStatus, candidates, errors }), - candidates, - policy: normalizeControlPlanePolicy(payload.policy, fieldName), - errors, - }; -} - -function normalizeControlPlaneSourceSnapshot(payload, fieldName = "control plane source snapshot") { - if (!payload || Array.isArray(payload) || typeof payload !== "object") { - throw new Error(`${fieldName} must be an object`); - } - if (payload.schema_version !== CONTROL_PLANE_SOURCE_SCHEMA_VERSION) { - throw new Error(`${fieldName}.schema_version is unsupported`); - } - for (const required of ["source_id", "generated_at", "computed_at", "data_status", "candidates", "errors"]) { - if (!(required in payload)) throw new Error(`${fieldName}.${required} is required`); - } - const dataStatus = cleanChoice(payload.data_status || "unavailable", STRATEGY_HEALTH_DATA_STATUSES, `${fieldName}.data_status`); - return { - schema_version: CONTROL_PLANE_SOURCE_SCHEMA_VERSION, - source_id: normalizeControlPlaneIdentifier(payload.source_id, `${fieldName}.source_id`, false), - generated_at: normalizeStrategyHealthTimestamp(payload.generated_at, `${fieldName}.generated_at`, true), - computed_at: normalizeStrategyHealthTimestamp(payload.computed_at, `${fieldName}.computed_at`, true), - data_status: dataStatus, - candidates: dataStatus === "unavailable" ? [] : normalizeControlPlaneCandidates(payload.candidates, fieldName), - errors: normalizeStrategyHealthErrors(payload.errors), - }; -} - -function emptyControlPlaneSourceSnapshot(errorCode) { - return { - schema_version: CONTROL_PLANE_SOURCE_SCHEMA_VERSION, - source_id: "unavailable", - generated_at: null, - computed_at: null, - data_status: "unavailable", - candidates: [], - errors: [errorCode], - }; -} - -function requireDedicatedM0ResearchSyncToken(request, env) { - const expected = String(env.M0_RESEARCH_SYNC_TOKEN || ""); - if (!expected) throw new HttpError("M0 research 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("M0 research sync token is invalid", 401); -} - -async function normalizeM0ResearchLedgerTransport(payload, fieldName = "M0 research ledger transport") { - const source = assertExactFields(payload, [ - "schema_version", "producer", "source_artifact", "ledger_sha256", "ledger", - ], fieldName); - if (source.schema_version !== M0_RESEARCH_TRANSPORT_SCHEMA_VERSION) { - throw new Error(`${fieldName}.schema_version is unsupported`); - } - const normalized = { - schema_version: M0_RESEARCH_TRANSPORT_SCHEMA_VERSION, - producer: normalizeM0ResearchProducer(source.producer, `${fieldName}.producer`), - source_artifact: normalizeM0ResearchSourceArtifact(source.source_artifact, `${fieldName}.source_artifact`), - ledger_sha256: normalizeResearchTaskDigest(source.ledger_sha256, `${fieldName}.ledger_sha256`), - ledger: normalizeM0ResearchLedger(source.ledger, `${fieldName}.ledger`), - }; - // The ledger digest binds the closed embedded ledger. source_artifact.sha256 - // intentionally remains a distinct immutable declaration about the QAR - // source artifact; it is not a hash of this derived ledger. - const recomputed = await calculateM0ResearchLedgerSha256(normalized.ledger); - if (normalized.ledger_sha256 !== recomputed) { - throw new Error(`${fieldName}.ledger_sha256 mismatch`); - } - const computedAt = Date.parse(normalized.ledger.computed_at); - if (!Number.isFinite(computedAt) || computedAt > Date.now() + 5 * 60 * 1000) { - throw new Error(`${fieldName}.ledger.computed_at is in the future`); - } - if (computedAt < Date.now() - M0_RESEARCH_RETENTION_SECONDS * 1000) { - throw new Error(`${fieldName}.ledger is expired`); - } - return normalized; -} - -async function normalizeM0ResearchLedgerStorageRecord(payload, fieldName) { - const record = assertExactFields(payload, ["schema_version", "stored_at", "expires_at", "envelope"], fieldName); - if (record.schema_version !== M0_RESEARCH_STORAGE_SCHEMA_VERSION) { - throw new Error(`${fieldName}.schema_version is unsupported`); - } - const storedAt = normalizeM0ResearchTimestamp(record.stored_at, `${fieldName}.stored_at`); - const expiresAt = normalizeM0ResearchTimestamp(record.expires_at, `${fieldName}.expires_at`); - const storedMillis = Date.parse(storedAt); - const expiresMillis = Date.parse(expiresAt); - if (expiresMillis - storedMillis !== M0_RESEARCH_RETENTION_SECONDS * 1000 || expiresMillis <= Date.now()) { - throw new Error(`${fieldName} is expired`); - } - return { - schema_version: M0_RESEARCH_STORAGE_SCHEMA_VERSION, - stored_at: storedAt, - expires_at: expiresAt, - envelope: await normalizeM0ResearchLedgerTransport(record.envelope, `${fieldName}.envelope`), - }; -} - -function normalizeM0ResearchLedger(payload, fieldName) { - const ledger = assertExactFields(payload, [ - "schema_version", "generated_at", "computed_at", "data_status", "summary", "subjects", "policy", "errors", - ], fieldName); - if (ledger.schema_version !== M0_RESEARCH_LEDGER_SCHEMA_VERSION) { - throw new Error(`${fieldName}.schema_version is unsupported`); - } - const generatedAt = normalizeM0ResearchTimestamp(ledger.generated_at, `${fieldName}.generated_at`); - const computedAt = normalizeM0ResearchTimestamp(ledger.computed_at, `${fieldName}.computed_at`); - if (generatedAt !== computedAt || /\./.test(generatedAt)) { - throw new Error(`${fieldName} timestamps must be the same canonical second`); - } - const dataStatus = cleanChoice(ledger.data_status, ["ready", "unavailable", "stale"], `${fieldName}.data_status`); - if (!Array.isArray(ledger.subjects) || ledger.subjects.length > 50000) { - throw new Error(`${fieldName}.subjects must be a bounded array`); - } - const subjects = ledger.subjects.map((subject, index) => normalizeM0ResearchSubject( - subject, `${fieldName}.subjects[${index}]`, computedAt, - )); - assertM0ResearchSortedUnique(subjects, (subject) => `${subject.subject.kind}\u0000${subject.subject.identifier}`, `${fieldName}.subjects`); - const summary = normalizeM0ResearchLedgerSummary(ledger.summary, `${fieldName}.summary`); - const calculatedSummary = summarizeM0ResearchSubjects(subjects); - if (!m0ResearchSummariesEqual(summary, calculatedSummary)) { - throw new Error(`${fieldName}.summary does not match subjects`); - } - const expectedStatus = summary.fresh_observation_count > 0 - ? "ready" - : (summary.observation_count > 0 ? "stale" : "unavailable"); - if (dataStatus !== expectedStatus) throw new Error(`${fieldName}.data_status does not match subjects`); - if (dataStatus === "unavailable" && subjects.length) throw new Error(`${fieldName}.subjects must be empty when unavailable`); - return { - schema_version: M0_RESEARCH_LEDGER_SCHEMA_VERSION, - generated_at: generatedAt, - computed_at: computedAt, - data_status: dataStatus, - summary, - subjects, - policy: normalizeM0ResearchLedgerPolicy(ledger.policy, `${fieldName}.policy`), - errors: normalizeM0ResearchErrorCodes(ledger.errors, `${fieldName}.errors`), - }; -} - -function normalizeM0ResearchLedgerSummary(value, fieldName) { - const summary = assertExactFields(value, M0_RESEARCH_SUMMARY_FIELDS, fieldName); - const result = {}; - for (const key of M0_RESEARCH_SUMMARY_FIELDS) { - result[key] = normalizeM0ResearchCount(summary[key], `${fieldName}.${key}`); - } - return result; -} - -function m0ResearchSummariesEqual(left, right) { - return M0_RESEARCH_SUMMARY_FIELDS.every((field) => left[field] === right[field]); -} - -function normalizeM0ResearchLedgerPolicy(value, fieldName) { - const policy = assertExactFields(value, ["authority", "no_order", "permitted_next_step", "notice"], fieldName); - if (policy.authority !== "research_only" || policy.no_order !== true || policy.permitted_next_step !== "research_validation_only") { - throw new Error(`${fieldName} must remain research-only and no-order`); - } - return { - authority: "research_only", - no_order: true, - permitted_next_step: "research_validation_only", - notice: normalizeM0ResearchText(policy.notice, `${fieldName}.notice`, 240), - }; -} - -function normalizeM0ResearchSubject(value, fieldName, ledgerComputedAt) { - const item = assertExactFields(value, ["subject", "observations", "horizon_conflict", "historical_stale_horizon_drift"], fieldName); - const subject = assertExactFields(item.subject, ["kind", "identifier"], `${fieldName}.subject`); - const normalizedSubject = { - kind: cleanChoice(subject.kind, M0_RESEARCH_SUBJECT_KINDS, `${fieldName}.subject.kind`), - identifier: normalizeM0ResearchIdentifier(subject.identifier, `${fieldName}.subject.identifier`), - }; - if (!Array.isArray(item.observations) || !item.observations.length || item.observations.length > 100) { - throw new Error(`${fieldName}.observations must be a non-empty bounded array`); - } - const observations = item.observations.map((observation, index) => normalizeM0ResearchObservation( - observation, `${fieldName}.observations[${index}]`, ledgerComputedAt, - )); - assertM0ResearchSortedUnique(observations, (observation) => ( - `${observation.source_report_digest}\u0000${observation.source_entry_digest}` - ), `${fieldName}.observations`); - const expectedHorizonView = calculateM0ResearchHorizonViews(observations); - const horizonConflict = normalizeM0ResearchHorizonView( - item.horizon_conflict, `${fieldName}.horizon_conflict`, ["none", "conflict"], expectedHorizonView.horizon_conflict, - ); - const historicalStaleHorizonDrift = normalizeM0ResearchHorizonView( - item.historical_stale_horizon_drift, `${fieldName}.historical_stale_horizon_drift`, - ["none", "drift", "unavailable"], expectedHorizonView.historical_stale_horizon_drift, - ); - return { - subject: normalizedSubject, - observations, - horizon_conflict: horizonConflict, - historical_stale_horizon_drift: historicalStaleHorizonDrift, - }; -} - -function normalizeM0ResearchObservation(value, fieldName, ledgerComputedAt) { - const item = assertExactFields(value, [ - "source_ids", "source_report_digest", "source_entry_digest", "hypothesis_id", "as_of", "generated_at", "expires_at", - "research_context", "freshness", - ], fieldName); - if (!Array.isArray(item.source_ids) || !item.source_ids.length || item.source_ids.length > 100) { - throw new Error(`${fieldName}.source_ids must be a non-empty bounded array`); - } - const sourceIds = item.source_ids.map((sourceId, index) => normalizeM0ResearchIdentifier(sourceId, `${fieldName}.source_ids[${index}]`)); - assertM0ResearchSortedUnique(sourceIds, (sourceId) => sourceId, `${fieldName}.source_ids`); - const generatedAt = normalizeM0ResearchTimestamp(item.generated_at, `${fieldName}.generated_at`); - const expiresAt = normalizeM0ResearchTimestamp(item.expires_at, `${fieldName}.expires_at`); - const generatedMillis = Date.parse(generatedAt); - const expiresMillis = Date.parse(expiresAt); - if (expiresMillis - generatedMillis !== 7 * 24 * 60 * 60 * 1000) { - throw new Error(`${fieldName} must have the fixed seven-day M0 expiry`); - } - const asOf = normalizeM0ResearchDate(item.as_of, `${fieldName}.as_of`); - if (asOf > generatedAt.slice(0, 10)) throw new Error(`${fieldName}.as_of cannot be after generated_at`); - const context = normalizeM0ResearchContext(item.research_context, `${fieldName}.research_context`); - const freshness = normalizeM0ResearchFreshness( - item.freshness, `${fieldName}.freshness`, generatedMillis, expiresMillis, Date.parse(ledgerComputedAt), - ); - return { - source_ids: sourceIds, - source_report_digest: normalizeResearchTaskDigest(item.source_report_digest, `${fieldName}.source_report_digest`), - source_entry_digest: normalizeResearchTaskDigest(item.source_entry_digest, `${fieldName}.source_entry_digest`), - hypothesis_id: normalizeM0ResearchIdentifier(item.hypothesis_id, `${fieldName}.hypothesis_id`), - as_of: asOf, - generated_at: generatedAt, - expires_at: expiresAt, - research_context: context, - freshness, - }; -} - -function normalizeM0ResearchContext(value, fieldName) { - const context = assertExactFields(value, [ - "state", "primary_horizon", "suitable_horizons", "source_confidence", "source_style", "theme_ids", - ], fieldName); - const primaryHorizon = cleanChoice(context.primary_horizon, M0_RESEARCH_HORIZONS, `${fieldName}.primary_horizon`); - if (!Array.isArray(context.suitable_horizons) || !context.suitable_horizons.length || context.suitable_horizons.length > 4) { - throw new Error(`${fieldName}.suitable_horizons is invalid`); - } - const suitableHorizons = context.suitable_horizons.map((horizon, index) => cleanChoice( - horizon, M0_RESEARCH_HORIZONS, `${fieldName}.suitable_horizons[${index}]`, - )); - if (!suitableHorizons.includes(primaryHorizon)) throw new Error(`${fieldName}.primary_horizon must be suitable`); - assertM0ResearchUnique(suitableHorizons, `${fieldName}.suitable_horizons`); - if (!Array.isArray(context.theme_ids) || context.theme_ids.length > 24) throw new Error(`${fieldName}.theme_ids is invalid`); - const themeIds = context.theme_ids.map((themeId, index) => normalizeM0ResearchIdentifier(themeId, `${fieldName}.theme_ids[${index}]`)); - assertM0ResearchUnique(themeIds, `${fieldName}.theme_ids`); - return { - state: cleanChoice(context.state, M0_RESEARCH_STATES, `${fieldName}.state`), - primary_horizon: primaryHorizon, - suitable_horizons: suitableHorizons, - source_confidence: cleanChoice(context.source_confidence, M0_RESEARCH_CONFIDENCE, `${fieldName}.source_confidence`), - source_style: cleanChoice(context.source_style, M0_RESEARCH_STYLES, `${fieldName}.source_style`), - theme_ids: themeIds, - }; -} - -function normalizeM0ResearchFreshness(value, fieldName, generatedMillis, expiresMillis, ledgerComputedMillis) { - const freshness = assertExactFields(value, ["status", "age_seconds"], fieldName); - const status = cleanChoice(freshness.status, ["fresh", "stale", "unknown"], `${fieldName}.status`); - const expectedAge = generatedMillis > ledgerComputedMillis - ? null - : Math.max(0, Math.floor((ledgerComputedMillis - generatedMillis) / 1000)); - const age = freshness.age_seconds === null ? null : normalizeM0ResearchCount(freshness.age_seconds, `${fieldName}.age_seconds`, 315360000); - if (status === "unknown") { - if (age !== null || generatedMillis <= ledgerComputedMillis) throw new Error(`${fieldName} unknown state is invalid`); - return { status, age_seconds: null }; - } - if (age !== expectedAge || generatedMillis > ledgerComputedMillis) throw new Error(`${fieldName}.age_seconds is invalid`); - if (status === "fresh" && ledgerComputedMillis >= expiresMillis) throw new Error(`${fieldName} cannot be fresh after expiry`); - return { status, age_seconds: age }; -} - -function calculateM0ResearchHorizonViews(observations) { - const freshHorizons = [...new Set(observations - .filter((observation) => observation.freshness.status === "fresh") - .map((observation) => observation.research_context.primary_horizon))].sort(); - const staleHorizons = [...new Set(observations - .filter((observation) => observation.freshness.status === "stale") - .map((observation) => observation.research_context.primary_horizon))].sort(); - return { - horizon_conflict: { status: freshHorizons.length > 1 ? "conflict" : "none", primary_horizons: freshHorizons }, - historical_stale_horizon_drift: { - status: freshHorizons.length - ? (staleHorizons.length && JSON.stringify(staleHorizons) !== JSON.stringify(freshHorizons) ? "drift" : "none") - : (staleHorizons.length ? "unavailable" : "none"), - primary_horizons: staleHorizons, - }, - }; -} - -function projectM0ResearchDashboardForRead(ledger, sourceLedgerSha256, now = new Date()) { - // The stored envelope remains the immutable, digest-bound publication - // record. This is deliberately a different dashboard schema: changing - // freshness at read time must never pretend to be a re-validatable ledger. - const nowMillis = now instanceof Date ? now.getTime() : Date.parse(now); - if (!Number.isFinite(nowMillis)) return emptyM0ResearchDashboardPayload("m0_research_ledger_unavailable"); - const subjects = ledger.subjects.map((entry) => { - const observations = entry.observations.map((observation) => ({ - ...observation, - freshness: projectM0ResearchObservationFreshness(observation, nowMillis), - })); - const horizonViews = calculateM0ResearchHorizonViews(observations); - return { - subject: { ...entry.subject }, - observations, - horizon_conflict: horizonViews.horizon_conflict, - historical_stale_horizon_drift: horizonViews.historical_stale_horizon_drift, - }; - }); - const summary = summarizeM0ResearchSubjects(subjects); - const dataStatus = summary.fresh_observation_count > 0 - ? "ready" - : (summary.observation_count > 0 ? "stale" : "unavailable"); - return { - schema_version: M0_RESEARCH_DASHBOARD_SCHEMA_VERSION, - source_ledger_sha256: sourceLedgerSha256, - source_generated_at: ledger.generated_at, - source_computed_at: ledger.computed_at, - viewed_at: utcTimestampSeconds(new Date(nowMillis)), - data_status: dataStatus, - summary, - subjects, - policy: { ...ledger.policy }, - errors: [...ledger.errors], - }; -} - -function projectM0ResearchObservationFreshness(observation, nowMillis) { - const generatedMillis = Date.parse(observation.generated_at); - const expiresMillis = Date.parse(observation.expires_at); - if (!Number.isFinite(generatedMillis) || !Number.isFinite(expiresMillis) || nowMillis < generatedMillis) { - return { status: "unknown", age_seconds: null }; - } - const ageSeconds = Math.max(0, Math.floor((nowMillis - generatedMillis) / 1000)); - // A stale source is never promoted by this projection. A former fresh - // observation is demoted as soon as its own expiry is reached. - const status = observation.freshness.status === "fresh" && nowMillis < expiresMillis - ? "fresh" - : "stale"; - return { status, age_seconds: ageSeconds }; -} - -function normalizeM0ResearchHorizonView(value, fieldName, allowedStatuses, expected) { - const view = assertExactFields(value, ["status", "primary_horizons"], fieldName); - const status = cleanChoice(view.status, allowedStatuses, `${fieldName}.status`); - if (!Array.isArray(view.primary_horizons) || view.primary_horizons.length > 4) { - throw new Error(`${fieldName}.primary_horizons is invalid`); - } - const horizons = view.primary_horizons.map((horizon, index) => cleanChoice( - horizon, M0_RESEARCH_HORIZONS, `${fieldName}.primary_horizons[${index}]`, - )); - assertM0ResearchSortedUnique(horizons, (horizon) => horizon, `${fieldName}.primary_horizons`); - if (status !== expected.status || JSON.stringify(horizons) !== JSON.stringify(expected.primary_horizons)) { - throw new Error(`${fieldName} does not match observations`); - } - return { status, primary_horizons: horizons }; -} - -function summarizeM0ResearchSubjects(subjects) { - const observations = subjects.flatMap((subject) => subject.observations); - return { - subject_count: subjects.length, - observation_count: observations.length, - fresh_observation_count: observations.filter((observation) => observation.freshness.status === "fresh").length, - stale_observation_count: observations.filter((observation) => observation.freshness.status === "stale").length, - unknown_observation_count: observations.filter((observation) => observation.freshness.status === "unknown").length, - horizon_conflict_count: subjects.filter((subject) => subject.horizon_conflict.status === "conflict").length, - historical_stale_horizon_drift_count: subjects.filter((subject) => subject.historical_stale_horizon_drift.status === "drift").length, - }; -} - -function normalizeM0ResearchErrorCodes(value, fieldName) { - if (!Array.isArray(value) || value.length > 20) throw new Error(`${fieldName} is invalid`); - const errors = value.map((error, index) => { - if (typeof error !== "string" || !/^[a-z][a-z0-9_.-]{0,63}$/.test(error)) { - throw new Error(`${fieldName}[${index}] is invalid`); - } - return error; - }); - assertM0ResearchSortedUnique(errors, (error) => error, fieldName); - return errors; -} - -function normalizeM0ResearchRepository(value, fieldName, allowedRepository) { - const repository = normalizeResearchTaskRepository(value, fieldName); - if (repository !== allowedRepository) throw new Error(`${fieldName} is not an approved M0 repository`); - return repository; -} - -function normalizeM0ResearchRevision(value, fieldName) { - return normalizeResearchTaskRevision(value, fieldName); -} - -function normalizeM0ResearchProducer(value, fieldName) { - const producer = assertExactFields(value, ["repository", "revision"], fieldName); - return { - repository: normalizeM0ResearchRepository( - producer.repository, `${fieldName}.repository`, M0_RESEARCH_ALLOWED_PRODUCER_REPOSITORY, - ), - revision: normalizeM0ResearchRevision(producer.revision, `${fieldName}.revision`), - }; -} - -function normalizeM0ResearchSourceArtifact(value, fieldName) { - const artifact = assertExactFields(value, ["repository", "revision", "run_id", "artifact_id", "sha256"], fieldName); - return { - repository: normalizeM0ResearchRepository( - artifact.repository, `${fieldName}.repository`, M0_RESEARCH_ALLOWED_SOURCE_REPOSITORY, - ), - revision: normalizeM0ResearchRevision(artifact.revision, `${fieldName}.revision`), - run_id: normalizeM0ResearchIdentifier(artifact.run_id, `${fieldName}.run_id`), - artifact_id: normalizeM0ResearchArtifactId(artifact.artifact_id, `${fieldName}.artifact_id`), - sha256: normalizeResearchTaskDigest(artifact.sha256, `${fieldName}.sha256`), - }; -} - -function normalizeM0ResearchArtifactId(value, fieldName) { - // Keep this exactly aligned with #309's generic identifier schema. Artifact - // IDs are provenance labels, not strategy slugs, so uppercase and `:/` are - // valid when they satisfy the closed source-artifact contract. - return normalizeM0ResearchIdentifier(value, fieldName); -} - -function normalizeM0ResearchIdentifier(value, fieldName) { - if (typeof value !== "string" || !/^[A-Za-z0-9._:/-]{1,128}$/.test(value)) { - throw new Error(`${fieldName} is invalid`); - } - return value; -} - -function normalizeM0ResearchTimestamp(value, fieldName) { - if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?Z$/.test(value) || Number.isNaN(Date.parse(value))) { - throw new Error(`${fieldName} must be a UTC timestamp`); - } - if (new Date(value).toISOString().slice(0, 10) !== value.slice(0, 10)) { - throw new Error(`${fieldName} must be a real UTC timestamp`); - } - return value; -} - -function normalizeM0ResearchDate(value, fieldName) { - if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value) || Number.isNaN(Date.parse(`${value}T00:00:00Z`))) { - throw new Error(`${fieldName} must be an ISO date`); - } - if (new Date(`${value}T00:00:00Z`).toISOString().slice(0, 10) !== value) { - throw new Error(`${fieldName} must be a real ISO date`); - } - return value; -} - -function normalizeM0ResearchCount(value, fieldName, maximum = 50000) { - if (!Number.isInteger(value) || value < 0 || value > maximum) throw new Error(`${fieldName} is invalid`); - return value; -} - -function normalizeM0ResearchText(value, fieldName, maximum) { - if (typeof value !== "string" || !value || value.length > maximum || /[<>\\\u0000-\u001f]/.test(value)) { - throw new Error(`${fieldName} is invalid`); - } - return value; -} - -function assertM0ResearchUnique(values, fieldName) { - if (new Set(values).size !== values.length) throw new Error(`${fieldName} contains duplicates`); -} - -function assertM0ResearchSortedUnique(values, key, fieldName) { - let previous = null; - for (const value of values) { - const current = key(value); - if (previous !== null && current <= previous) throw new Error(`${fieldName} must be sorted and unique`); - previous = current; - } -} - -function canonicalM0ResearchLedgerJson(value) { - if (value === null) return "null"; - if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value); - if (typeof value === "number") { - if (!Number.isFinite(value)) throw new Error("M0 research ledger must use finite JSON values"); - return JSON.stringify(value); - } - if (Array.isArray(value)) return `[${value.map(canonicalM0ResearchLedgerJson).join(",")}]`; - if (!value || typeof value !== "object") throw new Error("M0 research ledger must use JSON values"); - return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalM0ResearchLedgerJson(value[key])}`).join(",")}}`; -} - -async function calculateM0ResearchLedgerSha256(ledger) { - const raw = new TextEncoder().encode(canonicalM0ResearchLedgerJson(ledger)); - const digest = await crypto.subtle.digest("SHA-256", raw); - return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join(""); -} - -function emptyM0ResearchDashboardPayload(errorCode, now = new Date()) { - return { - schema_version: M0_RESEARCH_DASHBOARD_SCHEMA_VERSION, - source_ledger_sha256: null, - source_generated_at: null, - source_computed_at: null, - viewed_at: utcTimestampSeconds(now), - data_status: "unavailable", - summary: { - subject_count: 0, - observation_count: 0, - fresh_observation_count: 0, - stale_observation_count: 0, - unknown_observation_count: 0, - horizon_conflict_count: 0, - historical_stale_horizon_drift_count: 0, - }, - subjects: [], - policy: { - authority: "research_only", - no_order: true, - permitted_next_step: "research_validation_only", - notice: "M0 研究台账不可用;不会推断策略、平台、运行状态或订单。", - }, - errors: [errorCode], - }; -} - -async function normalizeAdaptiveSelectionSourceSnapshot(payload, fieldName = "adaptive selection source snapshot") { - const source = assertExactFields(payload, [ - "schema_version", "source_id", "generated_at", "computed_at", "data_status", "decision", "errors", - ], fieldName); - if (source.schema_version !== ADAPTIVE_SELECTION_SOURCE_SCHEMA_VERSION) { - throw new Error(`${fieldName}.schema_version is unsupported`); - } - const dataStatus = cleanChoice(source.data_status, STRATEGY_HEALTH_DATA_STATUSES, `${fieldName}.data_status`); - const generatedAt = normalizeStrategyHealthTimestamp(source.generated_at, `${fieldName}.generated_at`, true); - const computedAt = normalizeStrategyHealthTimestamp(source.computed_at, `${fieldName}.computed_at`, true); - const decision = source.decision === null - ? null - : await normalizeAdaptiveSelectionDecision(source.decision, `${fieldName}.decision`); - if (dataStatus === "unavailable" && decision !== null) { - throw new Error(`${fieldName}.decision must be null when unavailable`); - } - if (dataStatus !== "unavailable" && (!decision || !generatedAt || !computedAt)) { - throw new Error(`${fieldName} requires decision and timestamps when available`); - } - return { - schema_version: ADAPTIVE_SELECTION_SOURCE_SCHEMA_VERSION, - source_id: normalizeControlPlaneIdentifier(source.source_id, `${fieldName}.source_id`, false), - generated_at: generatedAt, - computed_at: computedAt, - data_status: dataStatus, - decision, - errors: normalizeStrategyHealthErrors(source.errors), - }; -} - -async function normalizeAdaptiveSelectionDecision(payload, fieldName) { - const value = assertExactFields(payload, [ - "schema", "decision_id", "created_at", "authority", "no_order", "market_context", "policy_id", - "recommended_strategy_profile", "recommended_platform_id", "candidates", "input_digest", "decision_digest", - ], fieldName); - if (value.schema !== ADAPTIVE_SELECTION_DECISION_SCHEMA_VERSION) { - throw new Error(`${fieldName}.schema is unsupported`); - } - if (value.authority !== ADAPTIVE_SELECTION_AUTHORITY || value.no_order !== true) { - throw new Error(`${fieldName} must remain shadow-only and no-order`); - } - if (!Array.isArray(value.candidates) || value.candidates.length > 1000) { - throw new Error(`${fieldName}.candidates must be a bounded array`); - } - const candidates = value.candidates.map((item, index) => - normalizeAdaptiveSelectionCandidate(item, `${fieldName}.candidates[${index}]`), - ); - const candidateIds = new Set(); - for (const candidate of candidates) { - if (candidateIds.has(candidate.strategy_profile)) { - throw new Error(`${fieldName}.candidates contains duplicate strategy_profile`); - } - candidateIds.add(candidate.strategy_profile); - } - const recommendedStrategy = normalizeControlPlaneIdentifier( - value.recommended_strategy_profile, `${fieldName}.recommended_strategy_profile`, true, - ); - const recommendedPlatform = normalizeControlPlaneIdentifier( - value.recommended_platform_id, `${fieldName}.recommended_platform_id`, true, - ); - const recommendedCandidate = candidates.find((item) => item.strategy_profile === recommendedStrategy && item.accepted); - if ((recommendedStrategy === null) !== (recommendedPlatform === null)) { - throw new Error(`${fieldName}.recommended strategy and platform must be provided together`); - } - if (recommendedStrategy && (!recommendedCandidate || recommendedCandidate.selected_platform_id !== recommendedPlatform)) { - throw new Error(`${fieldName}.recommended candidate is not accepted`); - } - const normalized = { - schema: ADAPTIVE_SELECTION_DECISION_SCHEMA_VERSION, - decision_id: normalizeControlPlaneIdentifier(value.decision_id, `${fieldName}.decision_id`, false), - created_at: normalizeStrategyHealthTimestamp(value.created_at, `${fieldName}.created_at`), - authority: ADAPTIVE_SELECTION_AUTHORITY, - no_order: true, - market_context: normalizeAdaptiveSelectionMarketContext(value.market_context, `${fieldName}.market_context`), - policy_id: normalizeControlPlaneIdentifier(value.policy_id, `${fieldName}.policy_id`, false), - recommended_strategy_profile: recommendedStrategy, - recommended_platform_id: recommendedPlatform, - candidates, - input_digest: normalizeAdaptiveSelectionDigest(value.input_digest, `${fieldName}.input_digest`), - decision_digest: normalizeAdaptiveSelectionDigest(value.decision_digest, `${fieldName}.decision_digest`), - }; - // `input_digest` cannot be reconstructed from the display projection alone: - // QPK calculates it from the private, immutable selection input. The QPK - // decision digest binds that exact input digest to every decision field that - // is displayed here, so a changed input digest cannot be silently accepted. - if (normalized.decision_digest !== await calculateAdaptiveSelectionDecisionDigest(normalized)) { - throw new Error(`${fieldName}.decision_digest mismatch`); - } - return normalized; -} - -function normalizeAdaptiveSelectionMarketContext(value, fieldName) { - const item = assertExactFields(value, [ - "schema", "as_of", "domain", "data_version", "data_freshness_days", "regime", "regime_confidence", "factors", - ], fieldName); - if (item.schema !== "qsl.market_context_snapshot.v1") { - throw new Error(`${fieldName}.schema is unsupported`); - } - const asOf = String(item.as_of || ""); - if (!/^\d{4}-\d{2}-\d{2}$/.test(asOf) || Number.isNaN(Date.parse(`${asOf}T00:00:00Z`))) { - throw new Error(`${fieldName}.as_of must be an ISO date`); - } - if (!Number.isInteger(item.data_freshness_days) || item.data_freshness_days < 0 || item.data_freshness_days > 366) { - throw new Error(`${fieldName}.data_freshness_days is invalid`); - } - const confidence = Number(item.regime_confidence); - if (!Number.isFinite(confidence) || confidence < 0 || confidence > 1) { - throw new Error(`${fieldName}.regime_confidence is invalid`); - } - if (!item.factors || Array.isArray(item.factors) || typeof item.factors !== "object" || Object.keys(item.factors).length > 100) { - throw new Error(`${fieldName}.factors is invalid`); - } - const factors = {}; - for (const [key, raw] of Object.entries(item.factors)) { - if (!/^[a-z][a-z0-9_.-]{0,63}$/.test(key)) throw new Error(`${fieldName}.factors key is invalid`); - const numeric = Number(raw); - if (!Number.isFinite(numeric) || Math.abs(numeric) > 1_000_000) { - throw new Error(`${fieldName}.factors.${key} is invalid`); - } - factors[key] = numeric; - } - return { - schema: "qsl.market_context_snapshot.v1", - as_of: asOf, - domain: cleanChoice(item.domain, STRATEGY_HEALTH_DOMAINS, `${fieldName}.domain`), - data_version: normalizeControlPlaneIdentifier(item.data_version, `${fieldName}.data_version`, false), - data_freshness_days: item.data_freshness_days, - regime: normalizeControlPlaneIdentifier(item.regime, `${fieldName}.regime`, false), - regime_confidence: confidence, - factors, - }; -} - -function normalizeAdaptiveSelectionCandidate(value, fieldName) { - const item = assertExactFields(value, [ - "strategy_profile", "release_digest", "selected_platform_id", "score", "risk_multiplier", "accepted", "reasons", "proposed_weight", - ], fieldName); - const score = item.score === null ? null : Number(item.score); - const riskMultiplier = Number(item.risk_multiplier); - if (score !== null && (!Number.isFinite(score) || Math.abs(score) > 1_000_000)) { - throw new Error(`${fieldName}.score is invalid`); - } - if (!Number.isFinite(riskMultiplier) || riskMultiplier < 0 || riskMultiplier > 1) { - throw new Error(`${fieldName}.risk_multiplier is invalid`); - } - if (typeof item.accepted !== "boolean") throw new Error(`${fieldName}.accepted must be boolean`); - if (item.proposed_weight !== 0) throw new Error(`${fieldName}.proposed_weight must remain zero`); - if (!Array.isArray(item.reasons) || item.reasons.length > 32) throw new Error(`${fieldName}.reasons is invalid`); - const reasons = item.reasons.map((reason) => { - const code = String(reason || ""); - if (!/^[a-z][a-z0-9_.:-]{0,127}$/.test(code)) throw new Error(`${fieldName}.reason is invalid`); - return code; - }); - return { - strategy_profile: normalizeControlPlaneIdentifier(item.strategy_profile, `${fieldName}.strategy_profile`, false), - release_digest: normalizeAdaptiveSelectionReleaseDigest(item.release_digest, `${fieldName}.release_digest`), - selected_platform_id: normalizeControlPlaneIdentifier(item.selected_platform_id, `${fieldName}.selected_platform_id`, true), - score, - risk_multiplier: riskMultiplier, - accepted: item.accepted, - reasons, - proposed_weight: 0, - }; -} - -function normalizeAdaptiveSelectionReleaseDigest(value, fieldName) { - const text = String(value || "").trim(); - if (!/^[A-Za-z0-9._:=+-]{1,160}$/.test(text) || /(token|secret|password|cookie|private|api[_-]?key)/i.test(text)) { - throw new Error(`${fieldName} is invalid`); - } - return text; -} - -function normalizeAdaptiveSelectionDigest(value, fieldName) { - const text = String(value || ""); - if (!/^[a-f0-9]{64}$/.test(text)) throw new Error(`${fieldName} is invalid`); - return text; -} - -function isAdaptiveSelectionFloatPath(path) { - if (path[0] === "market_context") { - return path[1] === "regime_confidence" || path[1] === "factors"; - } - return path[0] === "candidates" - && ["score", "risk_multiplier", "proposed_weight"].includes(path[2]); -} - -function canonicalAdaptiveSelectionNumber(value, forceFloat) { - if (!Number.isFinite(value)) throw new Error("adaptive selection decision must use finite JSON values"); - if (Object.is(value, -0)) return forceFloat ? "-0.0" : "0"; - let text = String(value); - const exponent = text.match(/^(.*)e([+-]?)(\d+)$/i); - if (exponent) { - const [, mantissa, sign, rawExponent] = exponent; - // Python's json.dumps (used by QPK canonical_sha256) pads one-digit - // negative exponents, while V8's Number#toString does not. - text = `${mantissa}e${sign || "+"}${sign === "-" ? rawExponent.padStart(2, "0") : rawExponent}`; - } - if (forceFloat && !/[.eE]/.test(text)) return `${text}.0`; - return text; -} - -function canonicalAdaptiveSelectionDecisionJson(value, path = []) { - if (value === null) return "null"; - if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value); - if (typeof value === "number") return canonicalAdaptiveSelectionNumber(value, isAdaptiveSelectionFloatPath(path)); - if (Array.isArray(value)) { - return `[${value.map((item) => canonicalAdaptiveSelectionDecisionJson(item, [...path, "*"])).join(",")}]`; - } - if (!value || typeof value !== "object") throw new Error("adaptive selection decision must use JSON values"); - return `{${Object.keys(value).sort().map((key) => ( - `${JSON.stringify(key)}:${canonicalAdaptiveSelectionDecisionJson(value[key], [...path, key])}` - )).join(",")}}`; -} - -async function calculateAdaptiveSelectionDecisionDigest(payload) { - const material = { ...payload }; - delete material.decision_digest; - const raw = new TextEncoder().encode(canonicalAdaptiveSelectionDecisionJson(material)); - const digest = await crypto.subtle.digest("SHA-256", raw); - return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join(""); -} - -function normalizeAdaptiveSelectionSummary(selections) { - const entries = Array.isArray(selections) ? selections : []; - const decisions = entries.map((item) => item.decision).filter(Boolean); - const candidates = decisions.flatMap((item) => item.candidates); - return { - source_count: entries.length, - decision_count: decisions.length, - candidate_count: candidates.length, - recommended_count: decisions.filter((item) => item.recommended_strategy_profile).length, - rejected_candidate_count: candidates.filter((item) => !item.accepted).length, - }; -} - -function emptyAdaptiveSelectionSourceSnapshot(errorCode) { - return { - schema_version: ADAPTIVE_SELECTION_SOURCE_SCHEMA_VERSION, - source_id: "unavailable", - generated_at: null, - computed_at: null, - data_status: "unavailable", - decision: null, - errors: [errorCode], - }; -} - -function emptyAdaptiveSelectionPayload(errorCode) { - return { - schema_version: ADAPTIVE_SELECTION_DASHBOARD_SCHEMA_VERSION, - generated_at: null, - computed_at: null, - data_status: "unavailable", - summary: { source_count: 0, decision_count: 0, candidate_count: 0, recommended_count: 0, rejected_candidate_count: 0 }, - selections: [], - policy: { - authority: ADAPTIVE_SELECTION_AUTHORITY, - no_order: true, - execution_authority_granted: false, - notice: "M1 Shadow 建议尚不可用;控制台不会推断策略、平台或订单。", - }, - errors: [errorCode], - }; -} - -function adaptiveSelectionStaleTtlSeconds(env) { - const configured = Number(env.ADAPTIVE_SELECTION_STALE_TTL_SECONDS); - if (!Number.isFinite(configured) || configured < 300 || configured > 604800) { - return ADAPTIVE_SELECTION_DEFAULT_STALE_TTL_SECONDS; - } - return Math.floor(configured); -} - -function normalizeControlPlaneCandidates(value, fieldName) { - if (!Array.isArray(value) || value.length > 1000) { - throw new Error(`${fieldName}.candidates must be an array with at most 1000 items`); - } - const seen = new Set(); - return value.map((item, index) => { - const prefix = `${fieldName}.candidates[${index}]`; - if (!item || Array.isArray(item) || typeof item !== "object") throw new Error(`${prefix} must be an object`); - const candidateId = normalizeControlPlaneIdentifier(item.candidate_id, `${prefix}.candidate_id`, false); - if (seen.has(candidateId)) throw new Error(`${fieldName}.candidates contains duplicate candidate_id`); - seen.add(candidateId); - const lifecycle = normalizeControlPlaneLifecycle(item.lifecycle, prefix); - const recommendation = normalizeControlPlaneRecommendation(item.recommendation, prefix); - const isOwnerDecision = lifecycle.stage === "P6"; - if (isOwnerDecision && (lifecycle.status !== "owner_decision_required" || recommendation.code !== "owner_live_decision")) { - throw new Error(`${prefix} P6 must be an owner_live_decision`); - } - if (!isOwnerDecision && (lifecycle.status === "owner_decision_required" || recommendation.code === "owner_live_decision")) { - throw new Error(`${prefix} only P6 can require an owner_live_decision`); - } - const forwardObservation = item.forward_observation === undefined - ? undefined - : normalizeForwardObservation(item.forward_observation, lifecycle, prefix); - return { - candidate_id: candidateId, - candidate_kind: cleanChoice(item.candidate_kind, CONTROL_PLANE_CANDIDATE_KINDS, `${prefix}.candidate_kind`), - domain: cleanChoice(item.domain, STRATEGY_HEALTH_DOMAINS, `${prefix}.domain`), - lifecycle, - evidence: normalizeControlPlaneEvidence(item.evidence, prefix), - recommendation, - freshness: normalizeStrategyHealthFreshness(item.freshness, `${prefix}.freshness`), - ...(forwardObservation === undefined ? {} : { forward_observation: forwardObservation }), - }; - }); -} - -function normalizeForwardObservation(value, lifecycle, prefix) { - if (!FORWARD_OBSERVATION_RESEARCH_STAGES.has(lifecycle.stage)) { - throw new Error(`${prefix}.forward_observation is only allowed for research stages`); - } - const observation = assertExactFields(value, [ - "state", "observations_completed", "required_trading_sessions", "last_observed_session", "observed_at", "no_order", "live_authority_granted", - ], `${prefix}.forward_observation`); - const completed = observation.observations_completed; - const required = observation.required_trading_sessions; - if (!Number.isSafeInteger(completed) || !Number.isSafeInteger(required) || completed < 0 || required < 1 || completed > required || required > 10000) { - throw new Error(`${prefix}.forward_observation observation counts are invalid`); - } - if (observation.no_order !== true) throw new Error(`${prefix}.forward_observation.no_order must be true`); - if (observation.live_authority_granted !== false) throw new Error(`${prefix}.forward_observation.live_authority_granted must be false`); - return { - state: cleanChoice(observation.state, FORWARD_OBSERVATION_STATES, `${prefix}.forward_observation.state`), - observations_completed: completed, - required_trading_sessions: required, - last_observed_session: normalizeForwardObservationDate(observation.last_observed_session, `${prefix}.forward_observation.last_observed_session`), - observed_at: normalizeStrategyHealthTimestamp(observation.observed_at, `${prefix}.forward_observation.observed_at`), - no_order: true, - live_authority_granted: false, - }; -} - -function normalizeForwardObservationDate(value, fieldName) { - const text = normalizeStrategyHealthText(value, fieldName, 10); - if (!/^\d{4}-\d{2}-\d{2}$/.test(text) || Number.isNaN(Date.parse(`${text}T00:00:00.000Z`)) || new Date(`${text}T00:00:00.000Z`).toISOString().slice(0, 10) !== text) { - throw new Error(`${fieldName} must be an ISO date`); - } - return text; -} - -function normalizeControlPlaneIdentifier(value, fieldName, nullable = true) { - if ((value === null || value === undefined || value === "") && nullable) return null; - const text = String(value || "").trim(); - if (!/^[A-Za-z0-9._=-]{1,128}$/.test(text)) throw new Error(`${fieldName} is invalid`); - if (/(token|secret|password|cookie|private|api[_-]?key)/i.test(text)) throw new Error(`${fieldName} is sensitive`); - return text; -} - -function normalizeControlPlaneLifecycle(value, prefix) { - const source = value && typeof value === "object" && !Array.isArray(value) ? value : {}; - return { - stage: cleanChoice(source.stage, CONTROL_PLANE_STAGES, `${prefix}.lifecycle.stage`), - status: cleanChoice(source.status, CONTROL_PLANE_LIFECYCLE_STATUSES, `${prefix}.lifecycle.status`), - }; -} - -function normalizeControlPlaneEvidence(value, prefix) { - const source = value && typeof value === "object" && !Array.isArray(value) ? value : {}; - return { - p1_input_digest: normalizeControlPlaneIdentifier(source.p1_input_digest, `${prefix}.evidence.p1_input_digest`), - p2_config_digest: normalizeControlPlaneIdentifier(source.p2_config_digest, `${prefix}.evidence.p2_config_digest`), - p3_evidence_id: normalizeControlPlaneIdentifier(source.p3_evidence_id, `${prefix}.evidence.p3_evidence_id`), - source_revision: normalizeControlPlaneIdentifier(source.source_revision, `${prefix}.evidence.source_revision`), - }; -} - -function normalizeControlPlaneRecommendation(value, prefix) { - const source = value && typeof value === "object" && !Array.isArray(value) ? value : {}; - return { - code: cleanChoice(source.code || "none", CONTROL_PLANE_RECOMMENDATIONS, `${prefix}.recommendation.code`), - reason: sanitizeStrategyHealthText(source.reason, `${prefix}.recommendation.reason`, 240, false, "没有可用的机器建议。"), - }; -} - -function normalizeControlPlaneSummary(candidates) { - return { - candidate_count: candidates.length, - deferred: candidates.filter((item) => item.lifecycle.status === "deferred").length, - parked: candidates.filter((item) => item.lifecycle.status === "parked").length, - owner_decision_required: candidates.filter((item) => item.lifecycle.status === "owner_decision_required").length, - }; -} - -function normalizeControlPlaneAttention({ dataStatus, candidates, errors }) { - const normalizedCandidates = Array.isArray(candidates) ? candidates : []; - const normalizedErrors = uniqueStrings(Array.isArray(errors) ? errors : []); - const reasonCodes = new Set(normalizedErrors); - if (dataStatus !== "ready") reasonCodes.add("control_plane_source_unavailable"); - if (!normalizedCandidates.length && dataStatus === "ready") reasonCodes.add("control_plane_candidate_missing"); - if (normalizedCandidates.some((item) => item.lifecycle?.status === "deferred")) { - reasonCodes.add("control_plane_candidate_deferred"); - } - if (normalizedCandidates.some((item) => item.lifecycle?.status === "parked")) { - reasonCodes.add("control_plane_candidate_parked"); - } - const status = dataStatus !== "ready" - ? "unavailable" - : (reasonCodes.size ? "attention_required" : "research_only"); - return { - status: cleanChoice(status, CONTROL_PLANE_ATTENTION_STATUSES, "control plane attention status"), - reason_codes: [...reasonCodes].sort(), - }; -} - -function normalizeControlPlanePolicy(value, fieldName) { - const source = value && typeof value === "object" && !Array.isArray(value) ? value : {}; - if (source.p6_owner_decision_required !== true) { - throw new Error(`${fieldName}.policy.p6_owner_decision_required must be true`); - } - return { - p4_p5_automation: cleanChoice( - source.p4_p5_automation || "not_configured", - CONTROL_PLANE_AUTOMATION_STATES, - `${fieldName}.policy.p4_p5_automation`, - ), - p6_owner_decision_required: true, - notice: sanitizeStrategyHealthText(source.notice, `${fieldName}.policy.notice`, 240, false, "live 仍需所有者明确决定。"), - }; -} - -function normalizeBinancePrivateScopeReport(payload, { requireIngestFreshness = false } = {}) { - const report = assertExactFields(payload, [ - "platform", - "observed_at", - "source_run_id", - "source_sha", - "account_scope_sha256", - "assets", - "historical_difference_unresolved", - "no_order", - "execution_authority_granted", - ], "Binance private scope report"); - if (report.platform !== "binance") throw new Error("Binance private scope report.platform must be binance"); - const observedAt = report.observed_at; - if (typeof observedAt !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(observedAt)) { - throw new Error("Binance private scope report.observed_at must be an ISO timestamp with timezone"); - } - const observedAtMs = Date.parse(observedAt); - if (!Number.isFinite(observedAtMs)) { - throw new Error("Binance private scope report.observed_at must be valid"); - } - if (requireIngestFreshness) { - const ageMs = Date.now() - observedAtMs; - if (ageMs > BINANCE_PRIVATE_SCOPE_INGEST_MAX_AGE_MS || ageMs < -BINANCE_PRIVATE_SCOPE_FUTURE_SKEW_MS) { - throw new Error("Binance private scope report.observed_at is outside the ingestion window"); - } - } - const sourceRunId = report.source_run_id; - if (typeof sourceRunId !== "string" || !/^[1-9]\d{0,19}$/.test(sourceRunId)) { - throw new Error("Binance private scope report.source_run_id must be a decimal string"); - } - const sourceSha = report.source_sha; - if (typeof sourceSha !== "string" || !/^[0-9a-fA-F]{40}$/.test(sourceSha)) { - throw new Error("Binance private scope report.source_sha must be a 40 character hex string"); - } - const accountScopeSha256 = report.account_scope_sha256; - if (typeof accountScopeSha256 !== "string" || !/^[0-9a-fA-F]{64}$/.test(accountScopeSha256)) { - throw new Error("Binance private scope report.account_scope_sha256 must be a 64 character hex string"); - } - if (!Array.isArray(report.assets) || report.assets.length > BINANCE_PRIVATE_SCOPE_MAX_ASSETS) { - throw new Error(`Binance private scope report.assets must contain at most ${BINANCE_PRIVATE_SCOPE_MAX_ASSETS} items`); - } - const seenAssets = new Set(); - const assets = report.assets.map((value, index) => { - const item = assertExactFields(value, ["asset", "free", "locked"], `Binance private scope report.assets[${index}]`); - const asset = item.asset; - if (typeof asset !== "string" || !/^[\p{L}\p{N}]{1,20}$/u.test(asset)) { - throw new Error(`Binance private scope report.assets[${index}].asset is invalid`); - } - if (seenAssets.has(asset)) throw new Error("Binance private scope report.assets contains duplicate assets"); - seenAssets.add(asset); - const free = normalizeBinancePrivateScopeDecimal(item.free, `Binance private scope report.assets[${index}].free`); - const locked = normalizeBinancePrivateScopeDecimal(item.locked, `Binance private scope report.assets[${index}].locked`); - if (binancePrivateScopeDecimalIsZero(free) && binancePrivateScopeDecimalIsZero(locked)) { - throw new Error(`Binance private scope report.assets[${index}] must not be an all-zero row`); - } - return { asset, free, locked }; - }); - if (report.historical_difference_unresolved !== true) { - throw new Error("Binance private scope report.historical_difference_unresolved must be true"); - } - if (report.no_order !== true) throw new Error("Binance private scope report.no_order must be true"); - if (report.execution_authority_granted !== false) { - throw new Error("Binance private scope report.execution_authority_granted must be false"); - } - return { - platform: "binance", - observed_at: observedAt, - source_run_id: sourceRunId, - source_sha: sourceSha.toLowerCase(), - account_scope_sha256: accountScopeSha256.toLowerCase(), - assets, - historical_difference_unresolved: true, - no_order: true, - execution_authority_granted: false, - }; -} - -function normalizeBinancePrivateScopeDecimal(value, fieldName) { - if (typeof value !== "string" || !/^\d+(?:\.\d+)?$/.test(value)) { - throw new Error(`${fieldName} must be a non-negative finite decimal string`); - } - return value; -} - -function binancePrivateScopeDecimalIsZero(value) { - return /^0+(?:\.0+)?$/.test(value); -} - -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) { - if (value?.mode === "initial_enrollment") { - const item = assertExactFields(value, ["mode", "recovery_id", "target_id", "candidate", "facts"], fieldName); - return { mode: "initial_enrollment", recovery_id: normalizeControlPlaneIdentifier(item.recovery_id, `${fieldName}.recovery_id`, false), - target_id: normalizeControlPlaneIdentifier(item.target_id, `${fieldName}.target_id`, false), candidate: item.candidate, facts: item.facts }; - } - 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 < 1 || - observationWindowMs > RECONCILIATION_RECOVERY_MAX_SAMPLE_WINDOW_MS || - recovery.dual_review.evidence_binding_sha256 !== recovery.candidate_sha256 || - recovery.blocker_codes.length - ) { - throw new Error(`${fieldName}.readiness requires at least one observation within a 15-minute window, bound candidate, 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", - ], fieldName); - if (source.schema_version !== EXECUTION_EVIDENCE_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.deployments) || source.deployments.length > 1000) { - throw new Error(`${fieldName}.deployments must be an array with at most 1000 items`); - } - const deployments = []; - const seen = new Set(); - for (const [index, item] of source.deployments.entries()) { - const deployment = normalizeExecutionEvidenceDeployment(item, `${fieldName}.deployments[${index}]`); - if (seen.has(deployment.deployment_id)) { - throw new Error(`${fieldName}.deployments contains duplicate deployment_id`); - } - seen.add(deployment.deployment_id); - deployments.push(deployment); - } - if (dataStatus === "unavailable" && deployments.length) { - throw new Error(`${fieldName}.deployments must be empty when unavailable`); - } - return { - schema_version: EXECUTION_EVIDENCE_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, - deployments, - errors: normalizeStrategyHealthErrors(source.errors), - }; -} - -function normalizeExecutionEvidenceDeployment(value, fieldName) { - const item = assertRequiredAndOptionalFields(value, [ - "deployment_id", "strategy", "target", "capabilities", "evidence", "recommendation", - ], ["execution_receipt"], fieldName); - const strategy = assertExactFields(item.strategy, [ - "candidate_id", "candidate_kind", "domain", "strategy_revision", - ], `${fieldName}.strategy`); - const target = assertExactFields(item.target, ["platform", "environment"], `${fieldName}.target`); - const capabilities = assertExactFields(item.capabilities, ["shadow", "paper"], `${fieldName}.capabilities`); - const evidence = assertExactFields(item.evidence, [ - "strategy", "target_data", "target_execution", - ], `${fieldName}.evidence`); - const recommendation = assertExactFields(item.recommendation, ["code", "reason_code"], `${fieldName}.recommendation`); - const executionReceipt = item.execution_receipt === undefined - ? null - : normalizeExecutionEvidenceReceipt(item.execution_receipt, `${fieldName}.execution_receipt`); - const normalized = { - deployment_id: normalizeControlPlaneIdentifier(item.deployment_id, `${fieldName}.deployment_id`, false), - strategy: { - candidate_id: normalizeControlPlaneIdentifier(strategy.candidate_id, `${fieldName}.strategy.candidate_id`, false), - candidate_kind: cleanChoice(strategy.candidate_kind, CONTROL_PLANE_CANDIDATE_KINDS, `${fieldName}.strategy.candidate_kind`), - domain: cleanChoice(strategy.domain, STRATEGY_HEALTH_DOMAINS, `${fieldName}.strategy.domain`), - strategy_revision: normalizeResearchTaskRevision(strategy.strategy_revision, `${fieldName}.strategy.strategy_revision`), - }, - target: { - platform: cleanChoice(target.platform, EXECUTION_EVIDENCE_PLATFORMS, `${fieldName}.target.platform`), - environment: cleanChoice(target.environment, EXECUTION_EVIDENCE_ENVIRONMENTS, `${fieldName}.target.environment`), - }, - capabilities: { - shadow: cleanChoice(capabilities.shadow, EXECUTION_EVIDENCE_CAPABILITIES, `${fieldName}.capabilities.shadow`), - paper: cleanChoice(capabilities.paper, EXECUTION_EVIDENCE_CAPABILITIES, `${fieldName}.capabilities.paper`), - }, - evidence: { - strategy: cleanChoice(evidence.strategy, EXECUTION_EVIDENCE_STATUSES, `${fieldName}.evidence.strategy`), - target_data: cleanChoice(evidence.target_data, EXECUTION_EVIDENCE_STATUSES, `${fieldName}.evidence.target_data`), - target_execution: cleanChoice(evidence.target_execution, EXECUTION_EVIDENCE_STATUSES, `${fieldName}.evidence.target_execution`), - }, - recommendation: { - code: cleanChoice(recommendation.code, EXECUTION_EVIDENCE_RECOMMENDATIONS, `${fieldName}.recommendation.code`), - reason_code: cleanChoice(recommendation.reason_code, EXECUTION_EVIDENCE_REASON_CODES, `${fieldName}.recommendation.reason_code`), - }, - }; - if (normalized.recommendation.code === "continue_autonomous_shadow" && normalized.capabilities.shadow !== "available") { - throw new Error(`${fieldName}.recommendation requires an available shadow capability`); - } - if (normalized.recommendation.code === "run_autonomous_paper" && normalized.capabilities.paper !== "available") { - throw new Error(`${fieldName}.recommendation requires an available paper capability`); - } - if (normalized.recommendation.code === "owner_limited_live_canary_decision") { - const statuses = Object.values(normalized.evidence); - if (!statuses.every((status) => status === "verified")) { - throw new Error(`${fieldName}.recommendation requires matching verified strategy, data, and execution evidence`); - } - } - if (executionReceipt !== null) { - const expectedExecutionStatus = ["failed", "reconciliation_required"].includes(executionReceipt.outcome) - ? "unavailable" - : (["no_signal", "no_rebalance"].includes(executionReceipt.outcome) ? "not_applicable" : "verified"); - if (normalized.evidence.target_execution !== expectedExecutionStatus) { - throw new Error(`${fieldName}.execution_receipt does not match target_execution evidence`); - } - normalized.execution_receipt = executionReceipt; - } - return normalized; -} - -function normalizeExecutionEvidenceReceipt(value, fieldName) { - const receipt = assertExactFields(value, ["outcome", "broker_confirmation", "observed_at"], fieldName); - const outcome = cleanChoice(receipt.outcome, EXECUTION_RECEIPT_OUTCOMES, `${fieldName}.outcome`); - const confirmation = cleanChoice( - receipt.broker_confirmation, - EXECUTION_RECEIPT_CONFIRMATIONS, - `${fieldName}.broker_confirmation`, - ); - const allowedConfirmations = { - not_due: ["not_applicable"], - no_action: ["not_applicable"], - no_signal: ["not_applicable"], - no_rebalance: ["not_applicable"], - risk_blocked: ["not_applicable"], - submitted: ["not_observed"], - broker_acknowledged: ["acknowledged"], - partially_filled: ["partially_filled"], - filled: ["filled"], - reconciliation_required: ["reconciliation_required"], - failed: ["not_applicable", "not_observed", "reconciliation_required"], - }; - if (!allowedConfirmations[outcome].includes(confirmation)) { - throw new Error(`${fieldName}.broker_confirmation does not match outcome`); - } - return { - outcome, - broker_confirmation: confirmation, - observed_at: normalizeResearchTaskTimestamp(receipt.observed_at, `${fieldName}.observed_at`), - }; -} - -function normalizeRuntimeTargetLifecycleSourceSnapshot(payload, fieldName = "runtime target lifecycle source snapshot") { - const source = assertExactFields(payload, [ - "schema_version", "source_id", "generated_at", "computed_at", "data_status", "targets", "errors", - ], fieldName); - if (source.schema_version !== RUNTIME_TARGET_LIFECYCLE_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.targets) || source.targets.length > 1000) { - throw new Error(`${fieldName}.targets must be an array with at most 1000 items`); - } - const targets = []; - const seen = new Set(); - for (const [index, item] of source.targets.entries()) { - const target = normalizeRuntimeTargetLifecycleTarget(item, `${fieldName}.targets[${index}]`); - if (seen.has(target.target_id)) throw new Error(`${fieldName}.targets contains duplicate target_id`); - seen.add(target.target_id); - targets.push(target); - } - if (dataStatus === "unavailable" && targets.length) { - throw new Error(`${fieldName}.targets must be empty when unavailable`); - } - return { - schema_version: RUNTIME_TARGET_LIFECYCLE_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, - targets, - errors: normalizeStrategyHealthErrors(source.errors), - }; -} - -function normalizeRuntimeTargetLifecycleTarget(value, fieldName) { - const item = assertRequiredAndOptionalFields(value, ["target_id", "target", "monitoring", "disposition", "no_order"], ["deployment"], fieldName); - const target = assertExactFields(item.target, ["platform", "configured_state", "execution_mode"], `${fieldName}.target`); - const monitoring = assertExactFields(item.monitoring, ["runtime_guard", "execution_heartbeat"], `${fieldName}.monitoring`); - const disposition = assertExactFields(item.disposition, ["code", "reason_code"], `${fieldName}.disposition`); - if (item.no_order !== true) throw new Error(`${fieldName}.no_order must be true`); - const normalized = { - target_id: normalizeControlPlaneIdentifier(item.target_id, `${fieldName}.target_id`, false), - target: { - platform: cleanChoice(target.platform, EXECUTION_EVIDENCE_PLATFORMS, `${fieldName}.target.platform`), - configured_state: cleanChoice(target.configured_state, RUNTIME_TARGET_LIFECYCLE_CONFIGURED_STATES, `${fieldName}.target.configured_state`), - execution_mode: cleanChoice(target.execution_mode, RUNTIME_TARGET_LIFECYCLE_EXECUTION_MODES, `${fieldName}.target.execution_mode`), - }, - monitoring: { - runtime_guard: cleanChoice(monitoring.runtime_guard, RUNTIME_TARGET_LIFECYCLE_CHECK_STATUSES, `${fieldName}.monitoring.runtime_guard`), - execution_heartbeat: cleanChoice(monitoring.execution_heartbeat, RUNTIME_TARGET_LIFECYCLE_CHECK_STATUSES, `${fieldName}.monitoring.execution_heartbeat`), - }, - disposition: { - code: cleanChoice(disposition.code, RUNTIME_TARGET_LIFECYCLE_DISPOSITIONS, `${fieldName}.disposition.code`), - reason_code: cleanChoice(disposition.reason_code, RUNTIME_TARGET_LIFECYCLE_REASON_CODES, `${fieldName}.disposition.reason_code`), - }, - no_order: true, - }; - if (item.deployment !== undefined) { - const observed = assertRequiredAndOptionalFields(item.deployment, ["runtime_enabled", "scheduler_state", "strategy_profile", "execution_mode"], ["observed_at"], `${fieldName}.deployment`); - if (observed.runtime_enabled !== null && typeof observed.runtime_enabled !== "boolean") throw new Error("deployment switch must be boolean or null"); - normalized.deployment = { - ...(observed.observed_at === undefined ? {} : {observed_at: normalizeM0ResearchTimestamp(observed.observed_at, "deployment.observed_at")}), - runtime_enabled: observed.runtime_enabled, - scheduler_state: cleanChoice(observed.scheduler_state, ["enabled", "paused", "mixed", "missing", "unknown", "not_applicable"], "deployment.scheduler_state"), - strategy_profile: observed.strategy_profile === null ? null : cleanSlug(observed.strategy_profile, "deployment.strategy_profile"), - execution_mode: observed.execution_mode === null ? null : cleanChoice(observed.execution_mode, RUNTIME_TARGET_LIFECYCLE_EXECUTION_MODES, "deployment.execution_mode"), - }; - } - const guardUnavailable = normalized.monitoring.runtime_guard === "unavailable"; - const heartbeatUnavailable = normalized.monitoring.execution_heartbeat === "unavailable"; - const hasAttention = normalized.monitoring.runtime_guard === "attention" || normalized.monitoring.execution_heartbeat === "attention"; - if (normalized.target.configured_state === "disabled") { - if (normalized.monitoring.execution_heartbeat !== "not_applicable") { - throw new Error(`${fieldName}.disabled target must not claim an execution heartbeat`); - } - if (!hasAttention && !guardUnavailable && normalized.disposition.code !== "continue_disabled_validation") { - throw new Error(`${fieldName}.disabled target requires continue_disabled_validation`); - } - } else if (!hasAttention && !guardUnavailable && !heartbeatUnavailable && normalized.disposition.code !== "continue_enabled_monitoring") { - throw new Error(`${fieldName}.enabled healthy target requires continue_enabled_monitoring`); - } - if (hasAttention || guardUnavailable || heartbeatUnavailable) { - if (normalized.disposition.code !== "parked") throw new Error(`${fieldName}.unhealthy monitoring requires parked`); - } - return normalized; -} - -function normalizeExecutionEvidenceSummary(deployments) { - return { - deployment_count: deployments.length, - autonomous_shadow: deployments.filter((item) => item.recommendation.code === "continue_autonomous_shadow").length, - autonomous_paper: deployments.filter((item) => item.recommendation.code === "run_autonomous_paper").length, - owner_canary_decision: deployments.filter((item) => item.recommendation.code === "owner_limited_live_canary_decision").length, - parked: deployments.filter((item) => item.recommendation.code === "parked").length, - }; -} - -function emptyExecutionEvidenceSourceSnapshot(errorCode) { - return { - schema_version: EXECUTION_EVIDENCE_SOURCE_SCHEMA_VERSION, - source_id: "unavailable", - generated_at: null, - computed_at: null, - data_status: "unavailable", - deployments: [], - errors: [errorCode], - }; -} - -function emptyRuntimeTargetLifecycleSourceSnapshot(errorCode) { - return { - schema_version: RUNTIME_TARGET_LIFECYCLE_SOURCE_SCHEMA_VERSION, - source_id: "unavailable", - generated_at: null, - computed_at: null, - data_status: "unavailable", - targets: [], - errors: [errorCode], - }; -} - -function emptyRuntimeTargetLifecyclePayload(errorCode) { - return { - schema_version: RUNTIME_TARGET_LIFECYCLE_DASHBOARD_SCHEMA_VERSION, - generated_at: null, - computed_at: null, - data_status: "unavailable", - summary: { target_count: 0, enabled: 0, disabled: 0, attention: 0 }, - targets: [], - policy: { - lifecycle_status_read_only: true, - execution_observation_read_only: true, - no_order: true, - order_or_fill_evidence: "not_collected", - notice: "运行目标生命周期快照尚不可用;页面不会推断目标已启用或存在订单/成交。", - }, - errors: [errorCode], - }; -} - -function emptyExecutionEvidencePayload(errorCode) { - return { - schema_version: EXECUTION_EVIDENCE_DASHBOARD_SCHEMA_VERSION, - generated_at: null, - computed_at: null, - data_status: "unavailable", - summary: { deployment_count: 0, autonomous_shadow: 0, autonomous_paper: 0, owner_canary_decision: 0, parked: 0 }, - deployments: [], - policy: { - execution_evidence_read_only: true, - p6_owner_decision_required: true, - limited_live_canary_active: false, - notice: "执行证据尚不可用;控制台不会推断 paper 或 live 状态。", - }, - errors: [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: [], - current_reconciliation_observation: currentReconciliationObservationPresentation(), - 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) { - return EXECUTION_EVIDENCE_DEFAULT_STALE_TTL_SECONDS; - } - return Math.floor(configured); -} - -function emptyControlPlanePayload(errorCode) { - return { - schema_version: "qsl_control_plane_dashboard.v1", - generated_at: null, - computed_at: null, - data_status: "unavailable", - summary: { candidate_count: 0, deferred: 0, parked: 0, owner_decision_required: 0 }, - attention: { status: "unavailable", reason_codes: ["control_plane_source_unavailable", errorCode] }, - candidates: [], - policy: { p4_p5_automation: "not_configured", p6_owner_decision_required: true, notice: "live 仍需所有者明确决定。" }, - errors: [errorCode], - }; -} - -function controlPlaneStaleTtlSeconds(env) { - const configured = Number(env.CONTROL_PLANE_STALE_TTL_SECONDS); - if (!Number.isFinite(configured) || configured < 300 || configured > 604800) { - return CONTROL_PLANE_DEFAULT_STALE_TTL_SECONDS; - } - return Math.floor(configured); -} - -async function syncStrategyProfilesConfig(env, session) { - const profiles = normalizeStrategyProfilesPayload(DEFAULT_STRATEGY_PROFILES, "DEFAULT_STRATEGY_PROFILES"); - if (!hasConfigStore(env)) return { synced: false, reason: "kv_not_bound", count: profiles.length }; - let changed = true; - try { - const current = await readConfigJson(env, STRATEGY_PROFILES_KEY); - if (current) { - const normalizedCurrent = normalizeStrategyProfilesPayload(current, STRATEGY_PROFILES_KEY); - changed = JSON.stringify(normalizedCurrent) !== JSON.stringify(profiles); - } - } catch { - changed = true; - } - let auditLogged = false; - if (changed) { - await writeConfigJson(env, STRATEGY_PROFILES_KEY, profiles); - try { - await appendAuditLog(env, { - ts: new Date().toISOString(), - login: session?.login || "", - action: "sync_strategy_profiles", - count: profiles.length, - }); - auditLogged = true; - } catch { - auditLogged = false; - } - } - return { synced: true, changed, count: profiles.length, audit_logged: auditLogged }; -} - -function updateAccountOptionsDefaultStrategy(accountOptions, inputs) { - const options = normalizeAccountOptionsPayload(accountOptions || {}, ACCOUNT_OPTIONS_KEY); - const platformOptions = options[inputs.platform] || []; - const rawPlatformOptions = Array.isArray(accountOptions?.[inputs.platform]) ? accountOptions[inputs.platform] : []; - let matched = false; - let changed = false; - const updatedPlatformOptions = platformOptions.map((option, index) => { - if (!accountOptionMatchesInputs(option, inputs)) return option; - matched = true; - const rawOption = rawPlatformOptions[index] && typeof rawPlatformOptions[index] === "object" - ? rawPlatformOptions[index] - : {}; - const nextOption = { ...option }; - let optionChanged = false; - if ("ibit_zscore_exit_mode" in rawOption) optionChanged = true; - if (inputs.plugin_mode === "none") { - const currentPluginMode = nextOption.plugin_mode || "none"; - if (currentPluginMode !== inputs.plugin_mode) { - nextOption.plugin_mode = inputs.plugin_mode; - optionChanged = true; - } - } - if (inputs.option_overlay_mode === "enabled" || inputs.option_overlay_mode === "disabled") { - if (nextOption.option_overlay_mode !== inputs.option_overlay_mode) { - nextOption.option_overlay_mode = inputs.option_overlay_mode; - optionChanged = true; - } - } - const cashOnlyExecutionMode = cashOnlyExecutionModeFromInputs(inputs); - if (cashOnlyExecutionMode === "enabled" || cashOnlyExecutionMode === "disabled") { - if (nextOption.cash_only_execution_mode !== cashOnlyExecutionMode) { - nextOption.cash_only_execution_mode = cashOnlyExecutionMode; - optionChanged = true; - } - } - const dcaControls = dcaControlsFromInputs(inputs); - if (isDcaProfile(inputs.strategy_profile)) { - if (dcaControls.dca_mode && nextOption.dca_mode !== dcaControls.dca_mode) { - nextOption.dca_mode = dcaControls.dca_mode; - optionChanged = true; - } - if ( - dcaControls.dca_base_investment_usd && - nextOption.dca_base_investment_usd !== dcaControls.dca_base_investment_usd - ) { - nextOption.dca_base_investment_usd = dcaControls.dca_base_investment_usd; - optionChanged = true; - } - } else { - for (const field of ["dca_mode", "dca_base_investment_usd"]) { - if (field in nextOption) { - delete nextOption[field]; - optionChanged = true; - } - } - } - if ("ibit_zscore_exit_mode" in nextOption) { - delete nextOption.ibit_zscore_exit_mode; - optionChanged = true; - } - changed = changed || optionChanged; - if (!optionChanged) return option; - return nextOption; - }); - if (!matched) throw new Error("switch inputs do not match configured account options"); - return { - options: { ...options, [inputs.platform]: updatedPlatformOptions }, - changed, - }; -} - -function normalizeSwitchInputs(raw) { - const platform = cleanChoice(raw.platform, SUPPORTED_PLATFORMS, "platform"); - const targetName = cleanSlug(raw.target_name, "target_name"); - const strategyProfile = cleanSlug(raw.strategy_profile, "strategy_profile").toLowerCase(); - assertDcaPlatform(platform, strategyProfile); - const executionMode = cleanExecutionMode(raw.execution_mode || "live"); - if (!supportedExecutionModesForPlatform(platform).includes(executionMode)) { - throw new Error(`${platform} does not support ${executionMode} control execution`); - } - const liveContinuity = normalizeLiveContinuityInputs(raw, executionMode); - // "current" is used only by internal deployment reconciliation to retain - // a service's existing plugin mount. It is deliberately not exposed as a - // console editing mode, where operators can still select only "none". - const requestedPluginMode = cleanChoice(raw.plugin_mode || "none", ["auto", "none", "current"], "plugin_mode"); - const pluginMode = requestedPluginMode === "auto" ? "none" : requestedPluginMode; - if (String(raw.custom_plugin_mounts_json || "").trim()) { - throw new Error("legacy custom plugin mounts are retired pending a P1/P2/P3-bound signal.v2 adapter"); - } - const optionOverlayMode = cleanChoice(raw.option_overlay_mode || "current", OPTION_OVERLAY_MODES, "option_overlay_mode"); - if (optionOverlayMode === "enabled") { - throw new Error("option overlay is retired; migrate existing option settings and obligations explicitly"); - } - const cashOnlyExecutionMode = cleanChoice( - raw.cash_only_execution_mode || "enabled", - CASH_ONLY_EXECUTION_MODES, - "cash_only_execution_mode", - ); - const variableScope = cleanChoice( - raw.variable_scope || "default", - ["default", "repository", "environment"], - "variable_scope", - ); - const apply = cleanBoolean(raw.apply); - const triggerPlatformSync = cleanBoolean(raw.trigger_platform_sync) && apply; - const extraVariablesJson = cleanOptionalJsonObject(raw.extra_variables_json || "", "extra_variables_json"); - const extraVariables = extraVariablesJson ? JSON.parse(extraVariablesJson) : {}; - const directDcaVariables = [DCA_MODE_VARIABLE, DCA_BASE_INVESTMENT_VARIABLE].filter((name) => - extraVariables[name] !== undefined && String(extraVariables[name] || "").trim() !== "", - ); - if (directDcaVariables.length) { - throw new Error("use dca_mode and dca_base_investment_usd control fields instead of DCA_MODE variables"); - } - const directIbitZscoreVariables = [ - IBIT_ZSCORE_EXIT_ENABLED_VARIABLE, - IBIT_ZSCORE_EXIT_MODE_VARIABLE, - IBIT_ZSCORE_EXIT_PARKING_SYMBOL_VARIABLE, - "IBIT_ZSCORE_EXIT_RISK_REDUCED_EXPOSURE", - "IBIT_ZSCORE_EXIT_RISK_OFF_EXPOSURE", - "IBIT_ZSCORE_EXIT_ALLOW_OUTSIDE_EXECUTION_WINDOW", - ].filter((name) => extraVariables[name] !== undefined && String(extraVariables[name] || "").trim() !== ""); - if (directIbitZscoreVariables.length) { - throw new Error( - "IBIT_ZSCORE_EXIT variables are derived from ibit_smart_dca smart DCA mode; do not set them directly", - ); - } - rejectResearchOnlyExtraVariables(extraVariables); - const directCashOnlyVariables = [ - LEGACY_CASH_ONLY_EXECUTION_VARIABLE, - ...Object.values(PLATFORM_CASH_ONLY_EXECUTION_VARIABLES), - ].filter((name) => extraVariables[name] !== undefined && String(extraVariables[name] || "").trim() !== ""); - if (directCashOnlyVariables.length) { - throw new Error("use cash_only_execution_mode instead of CASH_ONLY_EXECUTION variables"); - } - const dcaExtraControls = dcaPayloadFromObject(extraVariables); - stripLegacyIbitZscoreExitControls(extraVariables); - - const inputs = { - platform, - target_name: targetName, - strategy_profile: strategyProfile, - execution_mode: executionMode, - live_continuity_state: liveContinuity.state, - variable_scope: variableScope, - plugin_mode: pluginMode, - option_overlay_mode: optionOverlayMode, - service_targets_mode: "auto", - apply: apply ? "true" : "false", - trigger_platform_sync: triggerPlatformSync ? "true" : "false", - confirm_apply: apply ? (triggerPlatformSync ? "APPLY_AND_SYNC" : "APPLY") : "", - platform_sync_workflow: "sync-cloud-run-env.yml", - }; - - if (liveContinuity.state !== "NONE") { - inputs.live_continuity_baseline_id = liveContinuity.baseline_id; - inputs.live_continuity_captured_at = liveContinuity.captured_at; - } - - addOptional(inputs, "github_environment", raw.github_environment, cleanSlug); - addOptional(inputs, "deployment_selector", raw.deployment_selector, cleanSlug); - addOptional(inputs, "account_selector", raw.account_selector, cleanCsv); - addOptional(inputs, "account_scope", raw.account_scope, cleanSlug); - addOptional(inputs, "service_name", raw.service_name, cleanSlug); - addOptional(inputs, "reserved_cash_ratio", raw.reserved_cash_ratio, cleanRatio); - addOptional(inputs, "min_reserved_cash_usd", raw.min_reserved_cash_usd, cleanNonNegativeNumber); - addOptional(inputs, "income_layer_start_usd", raw.income_layer_start_usd, cleanNonNegativeNumber); - addOptional(inputs, "income_layer_max_ratio", raw.income_layer_max_ratio, cleanRatio); - const rawHasDcaMode = raw.dca_mode !== undefined && raw.dca_mode !== null && String(raw.dca_mode).trim() !== ""; - const rawHasDcaBase = raw.dca_base_investment_usd !== undefined && - raw.dca_base_investment_usd !== null && - String(raw.dca_base_investment_usd).trim() !== ""; - const dcaModeValue = rawHasDcaMode ? raw.dca_mode : dcaExtraControls.dca_mode; - const dcaBaseInvestmentValue = rawHasDcaBase - ? raw.dca_base_investment_usd - : dcaExtraControls.dca_base_investment_usd; - const hasDcaMode = Boolean(String(dcaModeValue || "").trim()); - const hasDcaBase = Boolean(String(dcaBaseInvestmentValue || "").trim()); - if (!isDcaProfile(strategyProfile) && (hasDcaMode || hasDcaBase)) { - throw new Error("DCA settings are only supported for DCA strategy profiles"); - } - if (isDcaProfile(strategyProfile)) { - if (hasDcaMode) extraVariables.dca_mode = cleanDcaMode(dcaModeValue); - if (hasDcaBase) extraVariables.dca_base_investment_usd = cleanPositiveNumber( - dcaBaseInvestmentValue, - "dca_base_investment_usd", - ); - } - const cashOnlyMode = cleanChoice( - raw.cash_only_execution_mode || extraVariables.cash_only_execution_mode || "enabled", - CASH_ONLY_EXECUTION_MODES, - "cash_only_execution_mode", - ); - if (cashOnlyMode !== "current") { - extraVariables.cash_only_execution_mode = cashOnlyMode; - } - if (Object.keys(extraVariables).length) inputs.extra_variables_json = JSON.stringify(extraVariables); - return inputs; -} - -function normalizeLiveContinuityInputs(raw, executionMode) { - const state = String(raw.live_continuity_state || "NONE").trim().toUpperCase(); - if (!LIVE_CONTINUITY_STATES.includes(state)) { - throw new Error(`live_continuity_state must be one of ${LIVE_CONTINUITY_STATES.join(", ")}`); - } - const rawBaselineId = String(raw.live_continuity_baseline_id || "").trim(); - const rawCapturedAt = String(raw.live_continuity_captured_at || "").trim(); - if (state === "NONE") { - if (rawBaselineId || rawCapturedAt) { - throw new Error("live continuity baseline fields require a non-NONE live_continuity_state"); - } - return { state }; - } - if (executionMode !== "live") { - throw new Error("live continuity is only valid for live execution_mode"); - } - return { - state, - baseline_id: cleanSlug(rawBaselineId, "live_continuity_baseline_id"), - captured_at: normalizeM0ResearchDate(rawCapturedAt, "live_continuity_captured_at"), - }; -} - -function assertSwitchIntent(inputs) { - if ( - inputs.apply !== "true" || - inputs.trigger_platform_sync !== "true" || - inputs.confirm_apply !== "APPLY_AND_SYNC" - ) { - throw new Error("switch endpoint requires apply=true and APPLY_AND_SYNC"); - } -} - -function assertConfiguredAccount(inputs, accountOptions) { - if (!accountOptions) throw new Error("account options are not configured"); - const matched = configuredAccountForInputs(inputs, accountOptions); - if (!matched) throw new Error("switch inputs do not match configured account options"); - return matched; -} - -function configuredAccountForInputs(inputs, accountOptions) { - if (!accountOptions) return null; - const options = accountOptions[inputs.platform] || []; - return options.find((option) => accountOptionMatchesInputs(option, inputs)) || null; -} - -function registerLegacyContinuityAccount(env, accountOptions, inputs, strategy) { - if (!hasConfigStore(env)) { - throw new Error("switch inputs do not match configured account options"); - } - if (!isEligibleLegacyContinuityInput(inputs, strategy)) { - throw new Error("switch inputs do not match configured account options"); - } - const platformOptions = accountOptions[inputs.platform] || []; - if (platformOptions.some((option) => option.target_name === inputs.target_name)) { - throw new Error("legacy continuity account target conflicts with configured account options"); - } - if (platformOptions.some((option) => option.service_name - && option.service_name === inputs.service_name - && accountOptionMatchesInputs(option, { ...inputs, target_name: option.target_name }))) { - throw new HttpError("deployment already has a configured account; use its existing target", 400); - } - if (platformOptions.length >= 20) { - throw new Error(`account options for ${inputs.platform} have reached the maximum`); - } - - const account = cleanAccountOption( - { - key: inputs.target_name, - label: `Legacy continuity ${inputs.target_name}`, - target_name: inputs.target_name, - account_selector: inputs.account_selector, - deployment_selector: inputs.deployment_selector, - account_scope: inputs.account_scope, - service_name: inputs.service_name, - github_environment: inputs.github_environment, - variable_scope: resolvedVariableScope(inputs.variable_scope, inputs), - supported_domains: [strategy.domain], - }, - inputs.platform, - platformOptions.length, - ); - const options = normalizeAccountOptionsPayload( - { ...accountOptions, [inputs.platform]: [...platformOptions, account] }, - ACCOUNT_OPTIONS_KEY, - ); - return { options, account: options[inputs.platform].at(-1), registered: true }; -} - -function assertStrategyAllowedForAccount(inputs, accountOption, strategyProfiles) { - const strategy = strategyProfiles.find((item) => sameStrategyProfile(item.profile, inputs.strategy_profile)); - if (!strategy) { - throw new Error(`strategy ${inputs.strategy_profile} is not configured`); - } - const supportedDomains = supportedDomainsForAccount(inputs.platform, accountOption); - if (!supportedDomains.includes(strategy.domain)) { - throw new Error( - `strategy domain ${strategy.domain} is not supported by ${inputs.platform}/${accountOption.key}`, - ); - } - const executionMode = cleanExecutionMode(inputs.execution_mode); - if (!supportedExecutionModesForPlatform(inputs.platform).includes(executionMode)) { - throw new Error(`${inputs.platform} does not support ${executionMode} control execution`); - } - const allowedModes = strategy.allowed_execution_modes || []; - if (isFrozenStrategyProfile(strategy) && !( - ["RECONCILE_ONLY", "RISK_REDUCTION_ONLY"].includes(inputs.live_continuity_state) - && isEligibleLegacyContinuityInput(inputs, strategy) - )) { - throw new HttpError(`strategy ${inputs.strategy_profile} is frozen for new selection`, 409); - } - if (executionMode === "live") { - if (isEligibleLegacyContinuityInput(inputs, strategy)) { - assertDcaPlatform(inputs.platform, inputs.strategy_profile); - return; - } - assertLiveSwitchAllowed(strategy, inputs.strategy_profile); - const lifecycleStage = cleanLifecycleStage(strategy.lifecycle_stage || "research_active"); - if (!["live_enabled", "runtime_enabled"].includes(lifecycleStage)) { - throw new Error(`strategy ${inputs.strategy_profile} is not live-enabled`); - } - if (!allowedModes.includes(executionMode)) { - throw new Error(`strategy ${inputs.strategy_profile} is not live-enabled`); - } - if (strategy.blocked_live_reason) { - throw new Error(`strategy ${inputs.strategy_profile} is blocked for live: ${strategy.blocked_live_reason}`); - } - } else if (allowedModes.length && !allowedModes.includes(executionMode)) { - throw new Error(`strategy ${inputs.strategy_profile} does not allow ${executionMode} execution`); - } - if (inputs.option_overlay_mode === "enabled" && strategy.option_overlay_enabled !== true) { - throw new Error(`strategy ${inputs.strategy_profile} does not define an option overlay`); - } - assertDcaPlatform(inputs.platform, inputs.strategy_profile); -} - -function isEligibleLegacyContinuityInput(inputs, strategy) { - if ( - inputs.execution_mode !== "live" || - !inputs.live_continuity_state || - inputs.live_continuity_state === "NONE" || - !inputs.live_continuity_baseline_id || - !inputs.live_continuity_captured_at || - inputs.plugin_mode !== "current" || - inputs.option_overlay_mode !== "current" || - inputs.reserved_cash_ratio || - inputs.min_reserved_cash_usd || - inputs.income_layer_start_usd || - inputs.income_layer_max_ratio - ) { - return false; - } - const extraVariables = inputs.extra_variables_json ? JSON.parse(inputs.extra_variables_json) : {}; - const extraVariableNames = Object.keys(extraVariables); - if ( - extraVariableNames.length > 1 || - (extraVariableNames.length === 1 && ( - extraVariableNames[0] !== "RUNTIME_TARGET_ENABLED" || - extraVariables.RUNTIME_TARGET_ENABLED !== "true" - )) - ) { - return false; - } - const policy = strategy?.live_continuity; - return policy?.eligible === true && Array.isArray(policy.allowed_platforms) && policy.allowed_platforms.includes(inputs.platform); -} - -function resolvedVariableScope(value, inputs) { - const text = String(value || "").trim(); - if (!text || text === "default") return defaultInputValue("variable_scope", inputs); - return text; -} - -function accountOptionMatchesInputs(option, inputs) { - if (option.target_name !== inputs.target_name) return false; - const fields = [ - "account_selector", - "deployment_selector", - "account_scope", - "service_name", - "github_environment", - "variable_scope", - ]; - for (const field of fields) { - if (field === "variable_scope") { - if (resolvedVariableScope(option[field], inputs) !== resolvedVariableScope(inputs[field], inputs)) { - return false; - } - continue; - } - const expected = option[field] || ""; - const actual = inputs[field] || ""; - if (expected && actual !== expected) return false; - if (!expected && actual && !["default", "auto", defaultInputValue(field, inputs)].includes(actual)) return false; - } - return true; -} - -function defaultInputValue(field, inputs) { - const platform = inputs.platform; - const targetName = inputs.target_name; - if (field === "variable_scope") return DEFAULT_VARIABLE_SCOPE[platform] || "repository"; - if (field === "plugin_mode") return "none"; - if (field === "deployment_selector") { - if (platform === "firstrade") return "firstrade"; - if (platform === "qmt") return "qmt"; - return ["sg", "hk", "paper"].includes(targetName.toLowerCase()) ? targetName.toUpperCase() : targetName; - } - if (field === "account_scope") { - if (platform === "firstrade") return "US"; - if (platform === "qmt") return "CN"; - return inputs.deployment_selector || defaultInputValue("deployment_selector", inputs); - } - if (field === "account_selector") { - if (platform === "firstrade") return "firstrade"; - if (platform === "qmt") return "qmt"; - return inputs.account_scope || defaultInputValue("account_scope", inputs); - } - if (field === "github_environment") { - const variableScope = inputs.variable_scope === "default" - ? defaultInputValue("variable_scope", inputs) - : inputs.variable_scope; - if (variableScope !== "environment") return ""; - return platform === "longbridge" ? `longbridge-${targetName.toLowerCase()}` : targetName; - } - if (field === "service_name") { - if (platform === "schwab") return "charles-schwab-quant-service"; - if (platform === "firstrade") return "firstrade-quant-service"; - if (platform === "qmt") return "qmt-quant-service"; - if (platform === "longbridge") return `longbridge-quant-${targetName.toLowerCase()}-service`; - if (platform === "ibkr") return `interactive-brokers-${targetName.toLowerCase()}-service`; - } - return ""; -} - -function parseAccountOptions(raw, fieldName = "account options") { - const text = String(raw || "").trim(); - if (!text) return null; - let payload; - try { - payload = JSON.parse(text); - } catch (error) { - throw new Error(`${fieldName} must be valid JSON`); - } - return normalizeAccountOptionsPayload(payload, fieldName); -} - -function normalizeAccountOptionsInput(value, fieldName) { - if (typeof value === "string") return parseAccountOptions(value, fieldName) || {}; - return normalizeAccountOptionsPayload(value, fieldName); -} - -function parseStrategyProfiles(raw, fieldName = "strategy profiles") { - const text = String(raw || "").trim(); - if (!text) return null; - let payload; - try { - payload = JSON.parse(text); - } catch (error) { - throw new Error(`${fieldName} must be valid JSON`); - } - return normalizeStrategyProfilesPayload(payload, fieldName); -} - -function isFrozenStrategyProfile(strategy) { - const profile = canonicalStrategyProfile(strategy?.profile); - // Normalize only the policy lookup; retain the original profile in payloads. - return strategy?.frozen === true || DEFAULT_STRATEGY_PROFILES.some((item) => item.profile === profile && item.frozen === true); -} - -function normalizeStrategyProfilesPayload(payload, fieldName = "strategy profiles") { - if (!Array.isArray(payload) || payload.length > 100) { - throw new Error(`${fieldName} must be an array with at most 100 items`); - } - - const result = []; - const seen = new Set(); - for (const [index, item] of payload.entries()) { - if (!item || Array.isArray(item) || typeof item !== "object") { - throw new Error(`${fieldName}[${index}] must be an object`); - } - const profile = cleanCurrentStrategy(item.profile || item.strategy_profile); - if (!profile) throw new Error(`${fieldName}[${index}].profile is invalid`); - if (seen.has(profile)) continue; - seen.add(profile); - const entry = { - profile, - label: cleanLabel(item.label || item.display_name || profile, `${fieldName}[${index}].label`), - runtime_enabled: cleanProfileBoolean(item.runtime_enabled ?? false), - }; - addConfigOptional( - entry, - "label_en", - item.label_en || item.display_name_en || item.label, - cleanLabel, - ); - addConfigOptional( - entry, - "label_zh", - item.label_zh || item.display_name_zh, - cleanLabel, - ); - const rawDomain = typeof item.domain === "string" ? item.domain.trim() : ""; - entry.domain = cleanStrategyDomain(rawDomain || "us_equity", `${fieldName}[${index}].domain`); - Object.defineProperty(entry, "domain_explicit", { value: rawDomain !== "", enumerable: false }); - if (isFrozenStrategyProfile(item)) entry.frozen = true; - const sourceLifecycleStage = item.lifecycle_stage || item.lifecycleStage; - const canSwitchLive = cleanOptionalBoolean(item.can_switch_live); - if (canSwitchLive !== null) entry.can_switch_live = canSwitchLive; - const allowedExecutionModes = cleanAllowedExecutionModes(item.allowed_execution_modes); - if (allowedExecutionModes.length) entry.allowed_execution_modes = allowedExecutionModes; - if (sourceLifecycleStage) { - entry.lifecycle_stage = canonicalLifecycleStage(sourceLifecycleStage, { - runtimeEnabled: entry.runtime_enabled === true, - canSwitchLive: canSwitchLive === true, - allowedExecutionModes, - }); - } - addConfigOptional(entry, "blocked_live_reason", item.blocked_live_reason, cleanLabel); - if (item.live_continuity !== undefined && item.live_continuity !== null) { - entry.live_continuity = normalizeLiveContinuityPolicy( - item.live_continuity, - `${fieldName}[${index}].live_continuity`, - ); - } - if (item.research_candidate_identity !== undefined && item.research_candidate_identity !== null) { - entry.research_candidate_identity = normalizeResearchCandidateIdentity( - item.research_candidate_identity, - `${fieldName}[${index}].research_candidate_identity`, - profile, - ); - } - addConfigOptional(entry, "latest_evidence_status", item.latest_evidence_status, cleanLifecycleStage); - addConfigOptional(entry, "plugin_gate_status", item.plugin_gate_status, cleanLifecycleStage); - // DCA detection: accept from item payload OR hardcoded DCA_PROFILE_CONFIG - const dcaEnabled = item.dca_enabled === true || Boolean(DCA_PROFILE_CONFIG[profile]); - if (dcaEnabled) { - const dcaDefaults = DCA_PROFILE_CONFIG[profile] || null; - entry.dca_enabled = true; - entry.dca_default_mode = cleanDcaMode(item.dca_default_mode || item.default_dca_mode || dcaDefaults?.default_mode || "fixed"); - entry.dca_default_base_investment_usd = cleanPositiveNumber( - item.dca_default_base_investment_usd || - item.default_dca_base_investment_usd || - dcaDefaults?.default_base_investment_usd || - "1000", - `${fieldName}[${index}].dca_default_base_investment_usd`, - ); - } - // Pass through combo_enabled and combo_mode from item payload - if (item.combo_enabled === true) { - entry.combo_enabled = true; - entry.combo_mode = String(item.combo_mode || "dynamic").trim() || "dynamic"; - } - const incomeLayerConfig = incomeLayerConfigFromProfileItem(item, `${fieldName}[${index}]`); - if (incomeLayerConfig) Object.assign(entry, incomeLayerConfig); - const optionOverlayConfig = optionOverlayConfigFromProfileItem(item, `${fieldName}[${index}]`); - if (optionOverlayConfig) Object.assign(entry, optionOverlayConfig); - result.push(entry); - } - return result; -} - -function normalizeResearchCandidateIdentity(value, fieldName, profile) { - if (!value || Array.isArray(value) || typeof value !== "object") { - throw new Error(`${fieldName} must be an object`); - } - assertExactFields(value, ["candidate_id", "config_sha256"], fieldName); - const candidateId = cleanCurrentStrategy(value.candidate_id); - if (!candidateId || candidateId !== profile) { - throw new Error(`${fieldName}.candidate_id must match profile`); - } - const configSha256 = String(value.config_sha256 || "").trim().toLowerCase(); - if (!/^[0-9a-f]{64}$/.test(configSha256)) { - throw new Error(`${fieldName}.config_sha256 must be a 64-character sha256`); - } - return { candidate_id: candidateId, config_sha256: configSha256 }; -} - -function normalizeLiveContinuityPolicy(value, fieldName) { - if (!value || Array.isArray(value) || typeof value !== "object") { - throw new Error(`${fieldName} must be an object`); - } - const unsupported = Object.keys(value).filter((key) => !["eligible", "allowed_platforms"].includes(key)); - if (unsupported.length) { - throw new Error(`${fieldName} contains unsupported fields: ${unsupported.sort().join(", ")}`); - } - if (typeof value.eligible !== "boolean") { - throw new Error(`${fieldName}.eligible must be boolean`); - } - if (!Array.isArray(value.allowed_platforms)) { - throw new Error(`${fieldName}.allowed_platforms must be an array`); - } - const allowedPlatforms = value.allowed_platforms.map((platform) => - cleanChoice(platform, SUPPORTED_PLATFORMS, `${fieldName}.allowed_platforms`), - ); - if (new Set(allowedPlatforms).size !== allowedPlatforms.length) { - throw new Error(`${fieldName}.allowed_platforms must not contain duplicates`); - } - if (value.eligible && !allowedPlatforms.length) { - throw new Error(`${fieldName}.eligible requires allowed_platforms`); - } - return { eligible: value.eligible, allowed_platforms: allowedPlatforms }; -} - -function rejectResearchOnlyExtraVariables(extraVariables) { - const blocked = [ - ...LEGACY_INCOME_LAYER_CONTROL_FIELDS, - ...LEGACY_INCOME_LAYER_VARIABLES, - ...OPTION_OVERLAY_CONTROL_FIELDS, - ...OPTION_OVERLAY_VARIABLES, - ].filter((name) => extraVariables[name] !== undefined); - if (blocked.length) { - throw new Error( - `direct option overlay settings and legacy income controls are research-only: ${blocked.join(", ")}`, - ); - } -} - -function incomeLayerConfigFromProfileItem(item, fieldName) { - const hasIncomeLayerConfig = [ - "income_layer_enabled", - "income_layer_start_usd", - "income_layer_max_ratio", - "income_layer_allocations", - ].some((field) => item[field] !== undefined && item[field] !== null && String(item[field]).trim() !== ""); - if (!hasIncomeLayerConfig) return null; - const enabled = item.income_layer_enabled === undefined || item.income_layer_enabled === null - ? true - : cleanProfileBoolean(item.income_layer_enabled); - if (!enabled) return { income_layer_enabled: false }; - return { - income_layer_enabled: true, - income_layer_start_usd: cleanNonNegativeNumber( - item.income_layer_start_usd, - `${fieldName}.income_layer_start_usd`, - ), - income_layer_max_ratio: cleanRatio(item.income_layer_max_ratio, `${fieldName}.income_layer_max_ratio`), - income_layer_allocations: cleanIncomeLayerAllocations( - item.income_layer_allocations, - `${fieldName}.income_layer_allocations`, - ), - }; -} - -function optionOverlayConfigFromProfileItem(item, fieldName) { - const hasOptionOverlayConfig = OPTION_OVERLAY_PROFILE_FIELDS.some((field) => - item[field] !== undefined && item[field] !== null && String(item[field]).trim() !== "", - ); - if (!hasOptionOverlayConfig) return null; - const enabled = item.option_overlay_enabled === undefined || item.option_overlay_enabled === null - ? true - : cleanProfileBoolean(item.option_overlay_enabled); - const result = { option_overlay_enabled: enabled }; - addConfigOptional(result, "option_overlay_live_gate", item.option_overlay_live_gate || (enabled ? "promotion_required" : "disabled"), (value, field) => - cleanChoice(value, ["promotion_required", "live_allowed", "disabled"], field), - ); - addConfigOptional(result, "option_overlay_live_status", item.option_overlay_live_status || (enabled ? "research_only" : "disabled"), (value, field) => - cleanChoice(value, ["research_only", "live_allowed", "disabled"], field), - ); - if (!enabled) return result; - - addOptionalOptionFamilyConfig(result, item, "growth", fieldName); - addOptionalOptionFamilyConfig(result, item, "income", fieldName); - return result; -} - -function addOptionalOptionFamilyConfig(target, item, family, fieldName) { - const prefix = `option_${family}_overlay`; - const enabledField = `${prefix}_enabled`; - if (item[enabledField] === undefined || item[enabledField] === null || String(item[enabledField]).trim() === "") { - return; - } - const enabled = cleanProfileBoolean(item[enabledField]); - target[enabledField] = enabled; - if (!enabled) return; - - target[`${prefix}_recipe`] = cleanSlug(item[`${prefix}_recipe`], `${fieldName}.${prefix}_recipe`); - target[`${prefix}_start_usd`] = cleanNonNegativeNumber( - item[`${prefix}_start_usd`], - `${fieldName}.${prefix}_start_usd`, - ); - if (family === "growth") { - target.option_growth_overlay_nav_budget_ratio = cleanRatio( - item.option_growth_overlay_nav_budget_ratio, - `${fieldName}.option_growth_overlay_nav_budget_ratio`, - ); - } else { - target.option_income_overlay_nav_risk_ratio = cleanRatio( - item.option_income_overlay_nav_risk_ratio, - `${fieldName}.option_income_overlay_nav_risk_ratio`, - ); - } -} - -function cleanIncomeLayerAllocations(value, fieldName) { - if (!value || Array.isArray(value) || typeof value !== "object") { - throw new Error(`${fieldName} must be an object`); - } - const result = {}; - let total = 0; - for (const [rawSymbol, rawWeight] of Object.entries(value)) { - const symbol = String(rawSymbol || "").trim().toUpperCase(); - if (!/^[A-Z0-9.-]{1,12}$/.test(symbol)) throw new Error(`${fieldName} contains an invalid symbol`); - const weight = Number(cleanPositiveNumber(rawWeight, `${fieldName}.${symbol}`)); - total += weight; - result[symbol] = weight; - } - if (!Object.keys(result).length || total <= 0) throw new Error(`${fieldName} must contain positive allocations`); - return result; -} - -function cleanProfileBoolean(value) { - if (value === true || value === "true" || value === "1" || value === 1) return true; - if (value === false || value === "false" || value === "0" || value === 0) return false; - throw new Error("runtime_enabled must be boolean"); -} - -function normalizeAccountOptionsPayload(payload, fieldName = "account options") { - if (!payload || Array.isArray(payload) || typeof payload !== "object") { - throw new Error(`${fieldName} must be an object`); - } - - // Structural gate from account_options_schema.js; cleanAccountOption keeps - // console-specific enrichment (domain inference, mode defaults, length caps). - validateAccountOptionsSchemaOrThrow(payload, fieldName); - - const result = {}; - for (const platform of SUPPORTED_PLATFORMS) { - const items = payload[platform]; - if (items === undefined) continue; - if (!Array.isArray(items) || items.length > 20) { - throw new Error(`${fieldName}.${platform} must be an array with at most 20 items`); - } - result[platform] = items.map((item, index) => cleanAccountOption(item, platform, index)); - } - return result; -} - -function cleanAccountOption(item, platform, index) { - if (!item || Array.isArray(item) || typeof item !== "object") { - throw new Error(`account option ${platform}[${index}] must be an object`); - } - const key = cleanSlug(item.key || item.target_name || `${platform}-${index}`, "account key"); - const label = cleanLabel(item.label || item.target_name || key, "account label"); - const option = { - key, - label, - target_name: cleanSlug(item.target_name || key, "target_name"), - }; - addConfigOptional(option, "account_selector", item.account_selector, cleanCsv); - addConfigOptional(option, "deployment_selector", item.deployment_selector, cleanSlug); - addConfigOptional(option, "account_scope", item.account_scope, cleanSlug); - addConfigOptional(option, "service_name", item.service_name, cleanSlug); - addConfigOptional(option, "runtime_status_target_id", item.runtime_status_target_id, cleanSlug); - addConfigOptional(option, "broker_environment", item.broker_environment, normalizeBrokerEnvironment); - addConfigOptional(option, "default_execution_mode", item.default_execution_mode, cleanExecutionMode); - addConfigOptional(option, "default_strategy_profile", item.default_strategy_profile, cleanSlug); - addConfigOptional( - option, - "cash_currency", - item.cash_currency || item.market_currency || item.trading_currency, - cleanCashCurrency, - ); - addConfigOptional(option, "github_environment", item.github_environment, cleanSlug); - addConfigOptional(option, "variable_scope", item.variable_scope, (value, field) => - cleanChoice(value || "default", ["default", "repository", "environment"], field), - ); - addConfigOptional(option, "plugin_mode", item.plugin_mode, (value, field) => - cleanChoice(value || "auto", ["auto", "none"], field), - ); - addConfigOptional(option, "option_overlay_mode", item.option_overlay_mode, (value, field) => - cleanChoice(value || "enabled", OPTION_OVERLAY_MODES, field), - ); - addConfigOptional(option, "cash_only_execution_mode", item.cash_only_execution_mode, (value, field) => - cleanChoice(value || "enabled", CASH_ONLY_EXECUTION_MODES, field), - ); - addConfigOptional(option, "dca_mode", item.dca_mode, cleanDcaMode); - addConfigOptional(option, "dca_base_investment_usd", item.dca_base_investment_usd, cleanPositiveNumber); - option.supported_domains = shouldInferSupportedDomains(item.supported_domains) - ? inferAccountSupportedDomains(platform, option) - : normalizeSupportedDomains(item.supported_domains, `account option ${platform}[${index}].supported_domains`); - return option; -} - -function shouldInferSupportedDomains(value) { - if (value === undefined || value === null) return true; - if (Array.isArray(value)) return value.length === 0; - return String(value).trim() === ""; -} - -function supportedDomainsForAccount(platform, option) { - const platformDomains = inferAccountSupportedDomains(platform, option || {}); - if (Array.isArray(option?.supported_domains) && option.supported_domains.length) { - return normalizeSupportedDomains(option.supported_domains, "supported_domains") - .filter((domain) => platformDomains.includes(domain)); - } - return platformDomains; -} - -function inferAccountSupportedDomains(platform, option) { - void option; - const domains = PLATFORM_CONFIG[platform]?.supported_domains; - return Array.isArray(domains) ? [...domains] : []; -} - -function platformRepositories(env) { - return resolvePlatformRepositories(env); -} - -function normalizeSupportedDomains(value, fieldName) { - const items = Array.isArray(value) - ? value - : String(value || "").split(/[\s,;]+/); - if (!items.length || items.length > SUPPORTED_STRATEGY_DOMAINS.length) { - throw new Error(`${fieldName} must list one or more strategy domains`); - } - const result = []; - for (const item of items) { - const domain = cleanStrategyDomain(item, fieldName); - if (!result.includes(domain)) result.push(domain); - } - if (!result.length) throw new Error(`${fieldName} must list one or more strategy domains`); - return result; -} - -function cleanStrategyDomain(value, fieldName) { - return cleanChoice(value, SUPPORTED_STRATEGY_DOMAINS, fieldName); -} - -function cleanCashCurrency(value, fieldName) { - return cleanChoice(String(value || "").trim().toUpperCase(), ["USD", "HKD", "CNY"], fieldName); -} - -function addConfigOptional(target, key, value, cleaner) { - if (value === undefined || value === null || String(value).trim() === "") return; - target[key] = cleaner(value, key); -} - -function addOptional(target, key, value, cleaner) { - if (value === undefined || value === null || String(value).trim() === "") return; - target[key] = cleaner(value, key); -} - -function cleanChoice(value, allowed, field) { - const text = String(value || "").trim(); - if (!allowed.includes(text)) throw new Error(`${field} is invalid`); - return text; -} - -function isDcaProfile(profile) { - return Boolean(DCA_PROFILE_CONFIG[canonicalStrategyProfile(profile)]); -} - -function assertDcaPlatform(platform, strategyProfile) { - if (isDcaProfile(strategyProfile) && !DCA_SUPPORTED_PLATFORMS.has(platform)) { - throw new Error( - `DCA strategy profiles are not supported on ${platform}; got strategy_profile=${strategyProfile}`, - ); - } -} - -function cleanDcaMode(value, field = "dca_mode") { - const mode = String(value || "").trim().toLowerCase(); - const aliases = { - ordinary: "fixed", - ordinary_dca: "fixed", - fixed_dca: "fixed", - smart_dca: "smart", - }; - const normalized = aliases[mode] || mode; - return cleanChoice(normalized, ["fixed", "smart"], field); -} - -function cleanBoolean(value) { - if (value === true || value === "true") return true; - if (value === false || value === "false" || value === "" || value === undefined || value === null) return false; - throw new Error("boolean input is invalid"); -} - -function cleanRatio(value, field) { - const text = cleanNumberText(value, field); - const numeric = Number(text); - if (numeric < 0 || numeric > 1) throw new Error(`${field} must be between 0 and 1`); - return text; -} - -function cleanNonNegativeNumber(value, field) { - const text = cleanNumberText(value, field); - if (Number(text) < 0) throw new Error(`${field} must be non-negative`); - return text; -} - -function cleanPositiveNumber(value, field) { - const text = cleanNumberText(value, field); - if (Number(text) <= 0) throw new Error(`${field} must be greater than 0`); - return text; -} - -function cleanNumberText(value, field) { - const text = String(value || "").trim(); - if (!text || text.length > 32 || !/^(?:\d+|\d*\.\d+)$/.test(text)) { - throw new Error(`${field} must be a finite decimal number`); - } - const numeric = Number(text); - if (!Number.isFinite(numeric)) throw new Error(`${field} must be finite`); - return text; -} - -function cleanSlug(value, field) { - const text = String(value || "").trim(); - if (!text || text.length > 120 || !/^[A-Za-z0-9._=-]+$/.test(text)) { - throw new Error(`${field} must use letters, numbers, dot, underscore, dash, or equals`); - } - return text; -} - -function cleanRepositoryName(value, field) { - const text = String(value || "").trim(); - if (!text || text.length > 160 || !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(text)) { - throw new Error(`${field} must be owner/repo`); - } - return text; -} - -function cleanLabel(value, field) { - const text = String(value || "").trim(); - if (!text || text.length > 80 || /[<>{}]/.test(text)) { - throw new Error(`${field} is invalid`); - } - return text; -} - -function cleanLifecycleStage(value, field = "lifecycle_stage") { - const text = String(value || "").trim().toLowerCase(); - if (!text || text.length > 80 || !/^[a-z0-9._-]+$/.test(text)) { - throw new Error(`${field} is invalid`); - } - return text; -} - -function canonicalLifecycleStage(value, deployment = {}) { - const stage = cleanLifecycleStage(value); - // Keep runtime_enabled as a first-class catalog stage so bundled SSOT and - // /api/strategy-profiles readback stay bit-identical for deploy verification. - if (["research_active", "shadow_active", "paper_active", "live_candidate", "live_enabled", "runtime_enabled"].includes(stage)) { - return stage; - } - if (["research", "research_backtest_only", "ai_monitored_candidate"].includes(stage)) return "research_active"; - if (stage === "shadow_candidate") return "shadow_active"; - void deployment; - throw new Error(`lifecycle_stage ${stage} is unsupported`); -} - -function cleanAllowedExecutionModes(value) { - const items = Array.isArray(value) - ? value - : String(value || "").split(/[,\s/|]+/); - const modes = []; - for (const item of items) { - const mode = String(item || "").trim().toLowerCase(); - if (!mode) continue; - if (!["live", "paper", "dry_run"].includes(mode)) { - throw new Error(`allowed_execution_modes contains unsupported mode ${mode}`); - } - if (!modes.includes(mode)) modes.push(mode); - } - return modes; -} - -function cleanExecutionMode(value) { - const mode = cleanChoice(value || "live", ["live", "paper", "dry_run"], "execution_mode"); - return mode === "paper" ? "dry_run" : mode; -} - -function supportedExecutionModesForPlatform(platform) { - const modes = PLATFORM_CONFIG[platform]?.supported_execution_modes; - if (!Array.isArray(modes)) return []; - return modes.filter((mode) => mode === "live" || mode === "dry_run"); -} - -function requireSameOrigin(request, options = {}) { - const origin = request.headers.get("Origin"); - if (!origin) { - if (options.requireOrigin) throw new HttpError("Origin header is required", 403); - return; - } - if (origin !== new URL(request.url).origin) throw new HttpError("cross-origin request rejected", 403); -} - -async function fetchGithubVariables(token, repository, scope, githubEnvironment, { reportReadErrors = false, onReadFailure = () => {} } = {}) { - const apiUrl = githubVariablesUrl(repository, scope, githubEnvironment); - const values = new Map(); - // Evidence-aware readers opt in. Other callers retain their existing fallback. - const failed = (reason = "read_failed") => { onReadFailure(reason); return reportReadErrors ? null : new Map(); }; - if (!apiUrl) return failed(); - try { - // Follow the variable-list pages, never arbitrary URLs returned by an upstream. - for (let page = 1; ; page += 1) { - const response = await fetchWithTimeout(`${apiUrl}?per_page=100&page=${page}`, { - headers: githubHeaders(token), - }); - if (!response.ok) return failed([401, 403].includes(response.status) ? "permission_denied" : "read_failed"); - const payload = await response.json(); - if (!Array.isArray(payload?.variables)) return failed(); - for (const item of payload.variables) { - if (typeof item.name === "string" && typeof item.value === "string") values.set(item.name, item.value); - } - if (!response.headers.get("Link")?.includes('rel="next"')) return values; - } - } catch { - return failed(); - } -} - - -function githubVariablesUrl(repository, scope, githubEnvironment) { - const [owner, repo] = String(repository || "").split("/"); - if (!owner || !repo) return ""; - const base = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`; - if (scope === "environment") { - if (!githubEnvironment) return ""; - return `${base}/environments/${encodeURIComponent(githubEnvironment)}/variables`; - } - return `${base}/actions/variables`; -} - -function resolveVariableScope(platform, option) { - const configured = String(option?.variable_scope || "").trim(); - if (configured && configured !== "default") return configured; - return DEFAULT_VARIABLE_SCOPE[platform] || "repository"; -} - -function resolveGithubEnvironment(platform, option, variableScope) { - if (variableScope !== "environment") return ""; - const configured = String(option?.github_environment || "").trim(); - if (configured) return configured; - const targetName = String(option?.target_name || option?.key || "").trim(); - if (!targetName) return ""; - if (platform === "longbridge") return `longbridge-${targetName.toLowerCase()}`; - return targetName; -} - -function runtimeTargetFromServiceTargets(rawValue, platform, option) { - const payload = parseJsonValue(rawValue); - const targets = Array.isArray(payload) - ? payload - : (Array.isArray(payload?.targets) ? payload.targets : []); - const matches = []; - for (const entry of targets) { - if (!entry || Array.isArray(entry) || typeof entry !== "object") continue; - const runtimeTarget = entry.runtime_target && typeof entry.runtime_target === "object" - ? entry.runtime_target - : {}; - if (!runtimeTargetMatchesAccount(runtimeTarget, platform, option, entry)) continue; - // Match deployment identity before reading overrides. The deploy planner - // supports nested env values and gives explicit top-level values precedence. - const nestedEnv = entry.env && typeof entry.env === "object" && !Array.isArray(entry.env) - ? entry.env : {}; - const settings = { ...nestedEnv, ...entry }; - matches.push({ - ...runtimeTarget, - strategy_profile: runtimeTarget.strategy_profile || entry.strategy_profile, - ...reservedCashPayloadFromObject(platform, settings), - ...incomeLayerPayloadFromObject(settings), - ...optionOverlayPayloadFromObject(settings), - ...runtimeTargetEnabledPayloadFromObject(settings), - ...dcaPayloadFromObject(settings), - ...cashOnlyPayloadFromObject(platform, settings), - }); - } - return matches.length === 1 ? matches[0] : null; -} - -async function readCashOnlyVariables({ platform, repository, variableScope, githubEnvironment, readVariable }) { - const platformVariable = PLATFORM_CASH_ONLY_EXECUTION_VARIABLES[platform]; - const [platformValue, legacyValue] = await Promise.all([ - readVariable(repository, variableScope, githubEnvironment, platformVariable), - readVariable(repository, variableScope, githubEnvironment, LEGACY_CASH_ONLY_EXECUTION_VARIABLE), - ]); - return cashOnlyPayloadFromValues(platformValue ?? legacyValue); -} - -function cashOnlyPayloadFromObject(platform, payload) { - if (!payload || Array.isArray(payload) || typeof payload !== "object") return {}; - return cashOnlyPayloadFromValues( - payload[PLATFORM_CASH_ONLY_EXECUTION_VARIABLES[platform]] ?? - payload[LEGACY_CASH_ONLY_EXECUTION_VARIABLE] ?? - payload.cash_only_execution, - ); -} - -function cashOnlyPayloadFromValues(value) { - const enabled = cleanOptionalBoolean(value); - if (enabled === null) return {}; - return { cash_only_execution: enabled }; -} - -async function readReservedCashVariables({ platform, repository, variableScope, githubEnvironment, readVariable }) { - const [floorValue, ratioValue] = await Promise.all([ - readVariable(repository, variableScope, githubEnvironment, PLATFORM_MIN_RESERVED_CASH_VARIABLES[platform]), - readVariable(repository, variableScope, githubEnvironment, PLATFORM_RESERVED_CASH_RATIO_VARIABLES[platform]), - ]); - return reservedCashPayloadFromValues(floorValue, ratioValue); -} - -async function readIncomeLayerVariables({ repository, variableScope, githubEnvironment, readVariable }) { - const [enabledValue, startUsdValue, maxRatioValue] = await Promise.all([ - readVariable(repository, variableScope, githubEnvironment, INCOME_LAYER_ENABLED_VARIABLE), - readVariable(repository, variableScope, githubEnvironment, INCOME_LAYER_START_USD_VARIABLE), - readVariable(repository, variableScope, githubEnvironment, INCOME_LAYER_MAX_RATIO_VARIABLE), - ]); - return incomeLayerPayloadFromValues(enabledValue, startUsdValue, maxRatioValue); -} - -async function readOptionOverlayVariables({ repository, variableScope, githubEnvironment, readVariable }) { - const enabledValue = await readVariable(repository, variableScope, githubEnvironment, OPTION_OVERLAY_ENABLED_VARIABLE); - return optionOverlayPayloadFromValue(enabledValue); -} - -async function readRuntimeTargetEnabledVariable({ repository, variableScope, githubEnvironment, readVariable }) { - const value = await readVariable(repository, variableScope, githubEnvironment, RUNTIME_TARGET_ENABLED_VARIABLE); - return runtimeTargetEnabledPayloadFromValue(value); -} - -async function readDcaVariables({ repository, variableScope, githubEnvironment, readVariable }) { - const [modeValue, baseInvestmentValue] = await Promise.all([ - readVariable(repository, variableScope, githubEnvironment, DCA_MODE_VARIABLE), - readVariable(repository, variableScope, githubEnvironment, DCA_BASE_INVESTMENT_VARIABLE), - ]); - return dcaPayloadFromValues(modeValue, baseInvestmentValue); -} - -function reservedCashPayloadFromObject(platform, payload) { - if (!payload || Array.isArray(payload) || typeof payload !== "object") return {}; - return reservedCashPayloadFromValues( - payload[PLATFORM_MIN_RESERVED_CASH_VARIABLES[platform]] ?? - payload.min_reserved_cash_usd ?? - payload.reserved_cash_floor_usd, - payload[PLATFORM_RESERVED_CASH_RATIO_VARIABLES[platform]] ?? - payload.reserved_cash_ratio, - ); -} - -function reservedCashPayloadFromValues(floorValue, ratioValue) { - const result = {}; - const floor = cleanCurrentNonNegativeNumber(floorValue); - const ratio = cleanCurrentRatio(ratioValue); - if (floor) result.min_reserved_cash_usd = floor; - if (ratio) result.reserved_cash_ratio = ratio; - return result; -} - -function incomeLayerPayloadFromObject(payload) { - if (!payload || Array.isArray(payload) || typeof payload !== "object") return {}; - return incomeLayerPayloadFromValues( - payload[INCOME_LAYER_ENABLED_VARIABLE] ?? payload.income_layer_enabled, - payload[INCOME_LAYER_START_USD_VARIABLE] ?? payload.income_layer_start_usd, - payload[INCOME_LAYER_MAX_RATIO_VARIABLE] ?? payload.income_layer_max_ratio, - ); -} - -function incomeLayerPayloadFromValues(enabledValue, startUsdValue, maxRatioValue) { - const result = {}; - const enabled = cleanOptionalBoolean(enabledValue); - const startUsd = cleanCurrentNonNegativeNumber(startUsdValue); - const maxRatio = cleanCurrentRatio(maxRatioValue); - if (enabled !== null) result.income_layer_enabled = enabled; - if (startUsd) result.income_layer_start_usd = startUsd; - if (maxRatio) result.income_layer_max_ratio = maxRatio; - return result; -} - -function optionOverlayPayloadFromObject(payload) { - if (!payload || Array.isArray(payload) || typeof payload !== "object") return {}; - return optionOverlayPayloadFromValue( - payload[OPTION_OVERLAY_ENABLED_VARIABLE] ?? payload.option_overlay_enabled, - ); -} - -function optionOverlayPayloadFromValue(value) { - const enabled = cleanOptionalBoolean(value); - return enabled === null ? {} : { option_overlay_enabled: enabled }; -} - -function runtimeTargetEnabledPayloadFromObject(payload) { - if (!payload || Array.isArray(payload) || typeof payload !== "object") return {}; - return runtimeTargetEnabledPayloadFromValue( - payload[RUNTIME_TARGET_ENABLED_VARIABLE] ?? payload.runtime_target_enabled, - ); -} - -function runtimeTargetEnabledPayloadFromValue(value) { - const enabled = cleanOptionalBoolean(value); - return enabled === null ? {} : { runtime_target_enabled: enabled }; -} - -function dcaPayloadFromObject(payload) { - if (!payload || Array.isArray(payload) || typeof payload !== "object") return {}; - return dcaPayloadFromValues( - payload[DCA_MODE_VARIABLE] ?? payload.dca_mode, - payload[DCA_BASE_INVESTMENT_VARIABLE] ?? - payload.dca_base_investment_usd ?? - payload.base_investment_usd, - ); -} - -function stripLegacyIbitZscoreExitControls(payload) { - if (!payload || Array.isArray(payload) || typeof payload !== "object") return; - for (const field of [ - "ibit_zscore_exit_mode", - "ibit_zscore_exit_enabled", - "ibit_zscore_exit_parking_symbol", - "ibit_zscore_exit_risk_reduced_exposure", - "ibit_zscore_exit_risk_off_exposure", - "ibit_zscore_exit_allow_outside_execution_window", - ]) { - delete payload[field]; - } -} - -function dcaPayloadFromValues(modeValue, baseInvestmentValue) { - const result = {}; - const mode = cleanCurrentDcaMode(modeValue); - const baseInvestmentUsd = cleanCurrentPositiveNumber(baseInvestmentValue); - if (mode) result.dca_mode = mode; - if (baseInvestmentUsd) result.dca_base_investment_usd = baseInvestmentUsd; - return result; -} - -function dcaControlsFromInputs(inputs) { - const payload = inputs?.extra_variables_json ? JSON.parse(inputs.extra_variables_json) : {}; - return { - ...dcaPayloadFromObject(payload), - ...dcaPayloadFromObject(inputs), - }; -} - -function cashOnlyExecutionModeFromInputs(inputs) { - const direct = String(inputs?.cash_only_execution_mode || "").trim().toLowerCase(); - if (direct === "enabled" || direct === "disabled") return direct; - try { - const payload = inputs?.extra_variables_json ? JSON.parse(inputs.extra_variables_json) : {}; - const mode = String(payload.cash_only_execution_mode || "").trim().toLowerCase(); - return mode === "enabled" || mode === "disabled" ? mode : ""; - } catch { - return ""; - } -} - -function dcaPayloadForProfile(profile, payload) { - return isDcaProfile(profile) ? payload : {}; -} - -function runtimeModePayload(runtimeTarget) { - const executionMode = normalizeRuntimeExecutionMode(runtimeTarget?.execution_mode, runtimeTarget?.dry_run_only); - const payload = {}; - if (executionMode) payload.execution_mode = executionMode; - const dryRunOnly = cleanOptionalBoolean(runtimeTarget?.dry_run_only); - if (dryRunOnly !== null) payload.dry_run_only = dryRunOnly; - return payload; -} - -function normalizeRuntimeExecutionMode(value, dryRunOnly) { - const mode = String(value || "").trim().toLowerCase(); - if (mode === "live" || mode === "dry_run") return mode; - if (mode === "paper" && cleanOptionalBoolean(dryRunOnly) !== true) return mode; - const dryRun = cleanOptionalBoolean(dryRunOnly); - if (dryRun === true) return "dry_run"; - if (dryRun === false) return "live"; - return ""; -} - -function cleanOptionalBoolean(value) { - if (value === true || value === 1) return true; - if (value === false || value === 0) return false; - if (typeof value === "string") { - const text = value.trim().toLowerCase(); - if (text === "true" || text === "1") return true; - if (text === "false" || text === "0") return false; - } - return null; -} - -function cleanCurrentNonNegativeNumber(value) { - const text = String(value ?? "").trim(); - if (!text || text.length > 32 || !/^(?:\d+|\d*\.\d+)$/.test(text)) return ""; - const numeric = Number(text); - if (!Number.isFinite(numeric) || numeric < 0) return ""; - return text; -} - -function cleanCurrentRatio(value) { - const text = cleanCurrentNonNegativeNumber(value); - if (!text) return ""; - const numeric = Number(text); - if (numeric < 0 || numeric > 1) return ""; - return text; -} - -function cleanCurrentPositiveNumber(value) { - const text = cleanCurrentNonNegativeNumber(value); - if (!text || Number(text) <= 0) return ""; - return text; -} - -function cleanCurrentDcaMode(value) { - try { - return cleanDcaMode(value || ""); - } catch { - return ""; - } -} - -function runtimeTargetMatchesAccount(runtimeTarget, platform, option, entry = {}) { - const runtimePlatform = String(runtimeTarget?.platform_id || "").trim().toLowerCase(); - if (runtimePlatform && runtimePlatform !== platform) return false; - - const serviceName = String(option?.service_name || defaultCurrentServiceName(platform, option?.target_name || option?.key) || ""); - const serviceCandidates = [ - runtimeTarget?.service_name, - entry?.service, - entry?.service_name, - ].filter((value) => normalizeMatchValue(value)); - if (serviceName && serviceCandidates.length) { - return hasCandidate(serviceName, serviceCandidates); - } - - if (hasCandidate(option?.account_scope, [ - runtimeTarget?.account_scope, - entry?.ACCOUNT_GROUP, - entry?.account_scope, - ])) return true; - - if (hasCandidate(option?.deployment_selector, [ - runtimeTarget?.deployment_selector, - entry?.deployment_selector, - ])) return true; - - const optionSelectors = splitSelectorValues(option?.account_selector); - const runtimeSelectors = splitSelectorValues(runtimeTarget?.account_selector || entry?.account_selector); - if (optionSelectors.some((value) => runtimeSelectors.includes(value))) return true; - - const targetName = String(option?.target_name || option?.key || "").trim(); - return Boolean(targetName && hasCandidate(targetName, [ - runtimeTarget?.target_name, - runtimeTarget?.deployment_selector, - runtimeTarget?.account_scope, - entry?.target_name, - ])); -} - -function defaultCurrentServiceName(platform, targetName) { - const normalized = String(targetName || "").trim().toLowerCase(); - if (!normalized) return ""; - if (platform === "longbridge") return `longbridge-quant-${normalized}-service`; - if (platform === "ibkr") return `interactive-brokers-${normalized}-service`; - if (platform === "schwab") return "charles-schwab-quant-service"; - if (platform === "firstrade") return "firstrade-quant-service"; - if (platform === "qmt") return "qmt-quant-service"; - return ""; -} - -function hasCandidate(expected, candidates) { - const normalizedExpected = normalizeMatchValue(expected); - if (!normalizedExpected) return false; - return candidates.some((candidate) => normalizeMatchValue(candidate) === normalizedExpected); -} - -function splitSelectorValues(value) { - if (Array.isArray(value)) return value.map(normalizeMatchValue).filter(Boolean); - return String(value || "") - .split(/[,\s]+/) - .map(normalizeMatchValue) - .filter(Boolean); -} - -function normalizeMatchValue(value) { - return String(value || "").trim().toLowerCase(); -} - -function parseJsonObject(value) { - const text = String(value || "").trim(); - if (!text) return null; - for (const candidate of [text, text.replaceAll("\\n", "\n")]) { - try { - const payload = JSON.parse(candidate); - return payload && !Array.isArray(payload) && typeof payload === "object" ? payload : null; - } catch { - // Try the next representation. - } - } - return null; -} - -function parseJsonValue(value) { - const text = String(value || "").trim(); - if (!text) return null; - for (const candidate of [text, text.replaceAll("\\n", "\n")]) { - try { - return JSON.parse(candidate); - } catch { - // Try the next representation. - } - } - return null; -} - -function cleanCurrentStrategy(value) { - const text = String(value || "").trim().toLowerCase(); - if (!text || text.length > 120 || !/^[a-z0-9._=-]+$/.test(text)) return ""; - return text; -} - -function cleanCsv(value, field) { - const text = String(value || "").trim(); - if (text.length > 300 || !/^[A-Za-z0-9._=,\-\s]+$/.test(text)) { - throw new Error(`${field} is invalid`); - } - return text; -} - -function cleanJson(value, field) { - const text = String(value || "").trim(); - if (!text) return ""; - if (text.length > 8000) throw new Error(`${field} is too long`); - JSON.parse(text); - return text; -} - -function cleanOptionalJsonObject(value, field) { - const text = cleanJson(value, field); - if (!text) return ""; - const payload = JSON.parse(text); - if (!payload || Array.isArray(payload) || typeof payload !== "object") { - throw new Error(`${field} must be a JSON object`); - } - for (const name of Object.keys(payload)) { - if (looksLikeSecretName(name)) { - throw new Error(`${field}.${name} looks like a secret and must not be stored here`); - } - } - return text; -} - -function looksLikeSecretName(name) { - const upperName = String(name || "").toUpperCase(); - if (/_SECRET_(ID|NAME|REF|RESOURCE|RESOURCE_NAME|VERSION)$/.test(upperName)) return false; - return /PASSWORD|PRIVATE_KEY|TOKEN|API_KEY|ACCESS_KEY|CLIENT_SECRET|SECRET/.test(upperName); -} - -function githubHeaders(token) { - return { - Authorization: `Bearer ${token}`, - Accept: "application/vnd.github+json", - "Content-Type": "application/json", - "User-Agent": "QuantRuntimeSettings-StrategySwitchConsole", - "X-GitHub-Api-Version": "2022-11-28", - }; -} - -async function fetchGithubOrgLogins(token, timeoutMs = GITHUB_API_TIMEOUT_MS) { - const orgs = []; - for (let page = 1; page <= 5; page += 1) { - const response = await fetchWithTimeout(`https://api.github.com/user/orgs?per_page=100&page=${page}`, { - headers: githubHeaders(token), - }, timeoutMs); - if (!response.ok) return orgs; - const payload = await response.json(); - if (!Array.isArray(payload) || !payload.length) break; - for (const org of payload) { - const login = cleanGithubOrg(org?.login || "", "github org"); - if (login) orgs.push(login); - } - if (payload.length < 100) break; - } - return uniqueStrings(orgs); -} - -function requireEnv(env, name) { - if (!env[name]) throw new Error(`${name} is not configured`); -} - -async function loadAuthConfig(env) { - const bootstrapAdmins = parseLoginList(env.STRATEGY_SWITCH_ADMIN_LOGINS || "", "STRATEGY_SWITCH_ADMIN_LOGINS"); - const bootstrapAdminOrgs = parseOrgList(env.STRATEGY_SWITCH_ADMIN_ORGS || "", "STRATEGY_SWITCH_ADMIN_ORGS"); - const envAllowed = parseLoginList( - env.ALLOWED_GITHUB_LOGINS || env.ALLOWED_GITHUB_LOGIN || "", - "ALLOWED_GITHUB_LOGINS", - ); - const envAllowedOrgs = parseOrgList( - env.ALLOWED_GITHUB_ORGS || env.ALLOWED_GITHUB_ORG || "", - "ALLOWED_GITHUB_ORGS", - ); - let storedAllowed = []; - let storedAllowedOrgs = []; - let storedAdmins = []; - let storedAdminOrgs = []; - let source = "secret"; - if (hasConfigStore(env)) { - const stored = await readConfigJson(env, AUTH_CONFIG_KEY); - if (stored) { - const normalized = normalizeAuthConfigPayload(stored, AUTH_CONFIG_KEY); - storedAllowed = normalized.allowed_logins; - storedAllowedOrgs = normalized.allowed_orgs; - storedAdmins = normalized.admin_logins; - storedAdminOrgs = normalized.admin_orgs; - source = "kv"; - } - } - const adminLogins = uniqueStrings([...bootstrapAdmins, ...storedAdmins]); - const adminOrgs = uniqueStrings([...bootstrapAdminOrgs, ...storedAdminOrgs]); - const allowedLogins = uniqueStrings([...envAllowed, ...storedAllowed, ...adminLogins]); - const allowedOrgs = uniqueStrings([...envAllowedOrgs, ...storedAllowedOrgs]); - return { - allowed_logins: allowedLogins, - allowed_orgs: allowedOrgs, - admin_logins: adminLogins, - admin_orgs: adminOrgs, - bootstrap_admin_logins: bootstrapAdmins, - bootstrap_admin_orgs: bootstrapAdminOrgs, - env_allowed_logins: envAllowed, - env_allowed_orgs: envAllowedOrgs, - source, - kv_available: hasConfigStore(env), - }; -} - -function normalizeAuthConfigPayload(payload, fieldName) { - if (!payload || Array.isArray(payload) || typeof payload !== "object") { - throw new Error(`${fieldName} must be an object`); - } - return { - allowed_logins: normalizeLoginList(payload.allowed_logins || [], `${fieldName}.allowed_logins`), - allowed_orgs: normalizeOrgList(payload.allowed_orgs || [], `${fieldName}.allowed_orgs`), - admin_logins: normalizeLoginList(payload.admin_logins || [], `${fieldName}.admin_logins`), - admin_orgs: normalizeOrgList(payload.admin_orgs || [], `${fieldName}.admin_orgs`), - }; -} - -function parseLoginList(value, fieldName) { - return normalizeLoginList(value, fieldName); -} - -function normalizeLoginList(value, fieldName) { - const items = Array.isArray(value) ? value : String(value || "").split(/[\s,]+/); - if (items.length > 80) throw new Error(`${fieldName} supports at most 80 logins`); - return uniqueStrings(items.map((item) => cleanGithubLogin(item, fieldName)).filter(Boolean)); -} - -function parseOrgList(value, fieldName) { - return normalizeOrgList(value, fieldName); -} - -function normalizeOrgList(value, fieldName) { - const items = Array.isArray(value) ? value : String(value || "").split(/[\s,]+/); - if (items.length > 80) throw new Error(`${fieldName} supports at most 80 orgs`); - return uniqueStrings(items.map((item) => cleanGithubOrg(item, fieldName)).filter(Boolean)); -} - -function cleanGithubLogin(value, fieldName) { - const login = String(value || "").trim().toLowerCase(); - if (!login) return ""; - if ( - login.length > 39 || - !/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(login) || - login.includes("--") - ) { - throw new Error(`${fieldName} contains an invalid GitHub login`); - } - return login; -} - -function cleanGithubOrg(value, fieldName) { - return cleanGithubLogin(value, fieldName); -} - -function isAdminLogin(login, orgLogins, authConfig) { - return authConfig.admin_logins.includes(String(login || "").toLowerCase()); -} - -function isAdminPrincipal(login, orgLogins, authConfig) { - return isAdminLogin(login, orgLogins, authConfig) || hasOrgMatch(orgLogins, authConfig.admin_orgs); -} - -function isAllowedPrincipal(login, orgLogins, authConfig) { - const normalizedLogin = String(login || "").toLowerCase(); - return ( - authConfig.allowed_logins.includes(normalizedLogin) || - hasOrgMatch(orgLogins, authConfig.allowed_orgs) || - isAdminPrincipal(normalizedLogin, orgLogins, authConfig) - ); -} - -function authorizedOrgLogins(orgLogins, authConfig) { - const authorized = new Set([...authConfig.allowed_orgs, ...authConfig.admin_orgs]); - return uniqueStrings(orgLogins).filter((org) => authorized.has(org)); -} - -function hasOrgMatch(orgLogins, configuredOrgs) { - const orgs = new Set(uniqueStrings(orgLogins)); - return configuredOrgs.some((org) => orgs.has(String(org || "").toLowerCase())); -} - -async function loadAccountOptionsConfig(env) { - if (hasRuntimeInstanceStore(env)) { - const state = await runtimeInstanceCommand(env); - if (state.initialized) return { options: state.account_options, source: "durable_object", revision: state.revision }; - // Existing execution can read the frozen legacy configuration during import; - // every instance writer rejects this uninitialized state. - } - return loadLegacyAccountOptionsConfig(env); -} - -async function loadLegacyAccountOptionsConfig(env) { - if (hasConfigStore(env)) { - const stored = await readConfigJson(env, ACCOUNT_OPTIONS_KEY); - if (stored) { - return { - options: normalizeAccountOptionsPayload(stored, ACCOUNT_OPTIONS_KEY), - source: "kv", - }; - } - } - return { - options: parseAccountOptions(env.STRATEGY_SWITCH_ACCOUNT_OPTIONS_JSON || "", "STRATEGY_SWITCH_ACCOUNT_OPTIONS_JSON"), - source: env.STRATEGY_SWITCH_ACCOUNT_OPTIONS_JSON ? "secret" : "none", - }; -} - -async function loadStrategyProfilesConfig(env) { - if (hasConfigStore(env)) { - const stored = await readConfigJson(env, STRATEGY_PROFILES_KEY); - if (stored) return normalizeStrategyProfilesPayload(stored, STRATEGY_PROFILES_KEY); - } - const configured = parseStrategyProfiles( - env.STRATEGY_SWITCH_STRATEGY_PROFILES_JSON || "", - "STRATEGY_SWITCH_STRATEGY_PROFILES_JSON", - ); - if (configured) return configured; - return normalizeStrategyProfilesPayload(DEFAULT_STRATEGY_PROFILES, "DEFAULT_STRATEGY_PROFILES"); -} - -function riskProfileScopeId(platform, targetName) { - return `${platform}--${targetName}`; -} - -function riskProfileBindingTargets(accountOptions) { - const targets = []; - for (const platform of SUPPORTED_PLATFORMS) { - const options = Array.isArray(accountOptions?.[platform]) ? accountOptions[platform] : []; - for (const option of options) { - targets.push({ - scope_id: riskProfileScopeId(platform, option.target_name), - platform, - target_name: option.target_name, - }); - } - } - return targets.sort((left, right) => left.scope_id.localeCompare(right.scope_id)); -} - -function utcTimestampSeconds(now = new Date()) { - return now.toISOString().replace(/\.\d{3}Z$/, "Z"); -} - -async function calculateRiskProfileSelectionSha256(payload) { - const material = { ...payload }; - delete material.selection_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 normalizeRiskProfileSelection(payload, fieldName) { - const value = assertExactFields(payload, ["schema", "profile_id", "risk_preference", "selection_sha256"], fieldName); - const riskPreference = cleanChoice(value.risk_preference, RISK_PROFILE_PREFERENCES, `${fieldName}.risk_preference`); - const normalized = { - schema: RISK_PROFILE_SELECTION_SCHEMA_VERSION, - profile_id: RISK_PROFILE_IDS[riskPreference], - risk_preference: riskPreference, - selection_sha256: normalizeResearchTaskDigest(value.selection_sha256, `${fieldName}.selection_sha256`), - }; - if (value.schema !== RISK_PROFILE_SELECTION_SCHEMA_VERSION) { - throw new Error(`${fieldName}.schema is unsupported`); - } - if (value.profile_id !== normalized.profile_id) { - throw new Error(`${fieldName}.profile_id does not match risk_preference`); - } - if (normalized.selection_sha256 !== await calculateRiskProfileSelectionSha256(normalized)) { - throw new Error(`${fieldName}.selection_sha256 mismatch`); - } - return normalized; -} - -async function calculateRiskProfileBindingSha256(payload) { - const material = { ...payload }; - delete material.binding_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 normalizeRiskProfileBinding(payload, fieldName) { - const value = assertExactFields(payload, [ - "schema_version", "scope_id", "platform", "target_name", "profile_selection", "updated_at", "updated_by", - "no_order", "execution_authority_granted", "binding_sha256", - ], fieldName); - const platform = cleanChoice(value.platform, SUPPORTED_PLATFORMS, `${fieldName}.platform`); - const targetName = cleanSlug(value.target_name, `${fieldName}.target_name`); - const normalized = { - schema_version: RISK_PROFILE_BINDING_SCHEMA_VERSION, - scope_id: riskProfileScopeId(platform, targetName), - platform, - target_name: targetName, - profile_selection: await normalizeRiskProfileSelection(value.profile_selection, `${fieldName}.profile_selection`), - updated_at: normalizeResearchTaskTimestamp(value.updated_at, `${fieldName}.updated_at`), - updated_by: cleanGithubLogin(value.updated_by, `${fieldName}.updated_by`), - no_order: value.no_order, - execution_authority_granted: value.execution_authority_granted, - binding_sha256: normalizeResearchTaskDigest(value.binding_sha256, `${fieldName}.binding_sha256`), - }; - if (value.schema_version !== RISK_PROFILE_BINDING_SCHEMA_VERSION) { - throw new Error(`${fieldName}.schema_version is unsupported`); - } - if (value.scope_id !== normalized.scope_id) { - throw new Error(`${fieldName}.scope_id does not match platform and target_name`); - } - if (normalized.no_order !== true || normalized.execution_authority_granted !== false) { - throw new Error(`${fieldName} must remain no-order and non-executable`); - } - if (normalized.binding_sha256 !== await calculateRiskProfileBindingSha256(normalized)) { - throw new Error(`${fieldName}.binding_sha256 mismatch`); - } - return normalized; -} - -async function normalizeRiskProfileBindingRegistry(payload, fieldName = RISK_PROFILE_BINDINGS_KEY) { - const value = assertExactFields(payload, ["schema_version", "bindings"], fieldName); - if (value.schema_version !== RISK_PROFILE_BINDING_REGISTRY_SCHEMA_VERSION) { - throw new Error(`${fieldName}.schema_version is unsupported`); - } - if (!Array.isArray(value.bindings) || value.bindings.length > SUPPORTED_PLATFORMS.length * 20) { - throw new Error(`${fieldName}.bindings must be a bounded array`); - } - const bindings = []; - const scopes = new Set(); - for (const [index, item] of value.bindings.entries()) { - const binding = await normalizeRiskProfileBinding(item, `${fieldName}.bindings[${index}]`); - if (scopes.has(binding.scope_id)) throw new Error(`${fieldName}.bindings contains duplicate scope_id`); - scopes.add(binding.scope_id); - bindings.push(binding); - } - return bindings.sort((left, right) => left.scope_id.localeCompare(right.scope_id)); -} - -async function buildRiskProfileBindings(payload, accountOptions, updatedBy) { - const value = assertExactFields(payload, ["bindings"], "risk profile bindings request"); - if (!Array.isArray(value.bindings) || value.bindings.length > SUPPORTED_PLATFORMS.length * 20) { - throw new Error("risk profile bindings request.bindings must be a bounded array"); - } - const bindings = []; - const scopes = new Set(); - const updatedAt = utcTimestampSeconds(); - for (const [index, item] of value.bindings.entries()) { - const itemValue = assertExactFields(item, ["platform", "target_name", "risk_preference"], `risk profile bindings request.bindings[${index}]`); - const platform = cleanChoice(itemValue.platform, SUPPORTED_PLATFORMS, `risk profile bindings request.bindings[${index}].platform`); - const targetName = cleanSlug(itemValue.target_name, `risk profile bindings request.bindings[${index}].target_name`); - if (!riskProfileBindingTargets(accountOptions).some((target) => target.platform === platform && target.target_name === targetName)) { - throw new Error(`risk profile binding target is not configured: ${platform}/${targetName}`); - } - const scopeId = riskProfileScopeId(platform, targetName); - if (scopes.has(scopeId)) throw new Error("risk profile bindings request contains duplicate target"); - scopes.add(scopeId); - const riskPreference = cleanChoice( - itemValue.risk_preference, - RISK_PROFILE_PREFERENCES, - `risk profile bindings request.bindings[${index}].risk_preference`, - ); - const profileSelection = { - schema: RISK_PROFILE_SELECTION_SCHEMA_VERSION, - profile_id: RISK_PROFILE_IDS[riskPreference], - risk_preference: riskPreference, - selection_sha256: "", - }; - profileSelection.selection_sha256 = await calculateRiskProfileSelectionSha256(profileSelection); - const binding = { - schema_version: RISK_PROFILE_BINDING_SCHEMA_VERSION, - scope_id: scopeId, - platform, - target_name: targetName, - profile_selection: profileSelection, - updated_at: updatedAt, - updated_by: cleanGithubLogin(updatedBy, "risk profile binding.updated_by"), - no_order: true, - execution_authority_granted: false, - binding_sha256: "", - }; - binding.binding_sha256 = await calculateRiskProfileBindingSha256(binding); - bindings.push(await normalizeRiskProfileBinding(binding, `risk profile bindings request.bindings[${index}]`)); - } - return bindings.sort((left, right) => left.scope_id.localeCompare(right.scope_id)); -} - -function accountSettingsIdentity(instance) { - const config = instance?.config || {}; - const broker = config.broker_environment; - return { - platform: instance.platform, - key: instance.key, - target_name: typeof config.target_name === "string" ? config.target_name : "", - broker_environment: broker === "live" || broker === "paper" ? broker : null, - account_selector: typeof config.account_selector === "string" ? config.account_selector : "", - deployment_selector: typeof config.deployment_selector === "string" ? config.deployment_selector : "", - account_scope: typeof config.account_scope === "string" ? config.account_scope : "", - service_name: typeof config.service_name === "string" ? config.service_name : "", - }; -} - -function accountSettingsIdentityIsComplete(identity) { - if (!identity || Array.isArray(identity) || typeof identity !== "object" - || !SUPPORTED_PLATFORMS.includes(identity.platform) - || (identity.broker_environment !== "paper" && identity.broker_environment !== "live")) return false; - return ["key", "target_name", "account_selector", "deployment_selector", "account_scope", "service_name"] - .every((field) => typeof identity[field] === "string" && identity[field].trim() !== ""); -} - -function runtimeControlIdentityIsComplete(identity) { - return Boolean(identity && SUPPORTED_PLATFORMS.includes(identity.platform) - && [null, "live", "paper"].includes(identity.broker_environment) - && ["key", "target_name", "account_selector", "deployment_selector", "account_scope", "service_name"] - .every((field) => typeof identity[field] === "string" && identity[field].trim() !== "")); -} - -function accountSettingsProfileName(value) { - if (typeof value !== "string" || value.length > 128 || value !== value.trim() - || !/^[a-z0-9][a-z0-9._-]*$/.test(value)) return ""; - return value; -} - -function accountSettingPositiveAmount(value) { - if (typeof value !== "string" || value.length > 32 || !/^(?:0|[1-9]\d*)(?:\.\d+)?$/.test(value)) return false; - const numeric = Number(value); - return Number.isFinite(numeric) && numeric > 0; -} - -function accountSettingRatio(value) { - if (typeof value !== "string" || value.length > 32 || !/^(?:0|[1-9]\d*)(?:\.\d+)?$/.test(value)) return null; - const [whole, frac = ""] = value.split("."); - if (whole === "0") return value; - if (whole === "1" && (frac === "" || /^0+$/.test(frac))) return value; - return null; -} - -function applyAccountSettingOverrides(current, patch) { - if (!patch || Array.isArray(patch) || typeof patch !== "object") throw new HttpError("invalid_account_setting_overrides", 400); - const next = current && typeof current === "object" && !Array.isArray(current) ? { ...current } : {}; - for (const key of Object.keys(patch)) { - if (!ACCOUNT_SETTING_OVERRIDE_FIELDS.includes(key)) throw new HttpError("invalid_account_setting_overrides", 400); - const value = patch[key]; - if (value === null) { - delete next[key]; - continue; - } - if (typeof value === "number") throw new HttpError("invalid_account_setting_overrides", 400); - if (key === "income_layer_enabled" || key === "option_overlay_enabled") { - if (typeof value !== "boolean") throw new HttpError("invalid_account_setting_overrides", 400); - next[key] = value; - } else if (key === "reserved_cash_floor") { - if (typeof value !== "string" || value.length > 32 || !/^\d+(?:\.\d+)?$/.test(value) || !Number.isFinite(Number(value))) { - throw new HttpError("invalid_account_setting_overrides", 400); - } - next[key] = value; - } else if (key === "reserved_cash_ratio") { - const ratio = accountSettingRatio(value); - if (ratio === null) throw new HttpError("invalid_account_setting_overrides", 400); - next[key] = ratio; - } else if (key === "dca_mode") { - if (value !== "fixed" && value !== "smart") throw new HttpError("invalid_account_setting_overrides", 400); - next[key] = value; - } else if (key === "dca_base_investment_usd") { - if (!accountSettingPositiveAmount(value)) throw new HttpError("invalid_account_setting_overrides", 400); - next[key] = value; - } else if (typeof value !== "string" || value.trim() === "" || value !== value.trim()) { - throw new HttpError("invalid_account_setting_overrides", 400); - } else { - next[key] = value; - } - } - return next; -} - -async function readRiskProfileLegacy(env) { - if (!hasConfigStore(env)) return { state: "absent" }; - try { - const stored = await readConfigJson(env, RISK_PROFILE_BINDINGS_KEY); - if (!stored) return { state: "absent" }; - return { state: "valid", bindings: await normalizeRiskProfileBindingRegistry(stored) }; - } catch { - return { state: "blocked" }; - } -} - -async function loadRiskProfileBindings(env) { - if (!hasRuntimeInstanceStore(env)) { - return { bindings: [], revision: null, account_options: null, error: "runtime_instances_not_bound", status: 503 }; - } - const legacy = await readRiskProfileLegacy(env); - try { - const state = await runtimeInstanceCommand(env, { action: "risk_profile_read", actor: "risk-profile-read", legacy }); - return { bindings: state.bindings, revision: state.revision, account_options: state.account_options, error: null, status: 200 }; - } catch (error) { - const status = error instanceof HttpError ? error.status : 503; - return { - bindings: [], - revision: null, - account_options: null, - error: error instanceof HttpError ? error.message : "runtime_instances_unavailable", - status, - }; - } -} - -// Desk reads deliberately bypass the current-strategies memory/KV cache. Raw -// variable maps stay local; only explicit resolver fields enter the projection. -async function deskTargetReadback(env, target, actor, suppliedObserved = undefined) { - let observed = suppliedObserved; - if (observed === undefined) { - try { - observed = await runtimeInstanceCommand(env, { action: "account_settings_read", actor, - platform: target.platform, key: target.key, legacy: await readRiskProfileLegacy(env) }); - } catch { observed = null; } - } - if (observed && observed.config?.target_name !== target.config.target_name) throw new DeskError("not_found", 404); - const config = observed?.config || target.config; - const token = env.RUNTIME_SETTINGS_DISPATCH_TOKEN; - const repository = platformRepositories(env)?.[target.platform]; - let platform = {}, platformReason = "source_unavailable"; - if (token && repository && config) { - const cache = new Map(); - let failed = false; - const readVariable = async (repo, scope, environment, name) => { - const key = JSON.stringify([repo, scope, environment]); - if (!cache.has(key)) cache.set(key, fetchGithubVariables(token, repo, scope, environment, { reportReadErrors: true })); - const variables = await cache.get(key); - if (variables === null) failed = true; - return variables?.get(name) || ""; - }; - try { - const options = observed?.account_options?.[target.platform] || []; - const current = await resolveCurrentStrategyForAccount({ platform: target.platform, option: config, - optionsCount: options.length || target.optionsCount || 1, repository, readVariable }); - if (failed) throw new Error("read_failed"); - const scope = resolveVariableScope(target.platform, config); - const environment = resolveGithubEnvironment(target.platform, config, scope); - // Identity must come from a uniquely matched runtime record, never config. - const service = current?.source === "CLOUD_RUN_SERVICE_TARGETS_JSON" - ? runtimeTargetFromServiceTargets(await readVariable(repository, "repository", "", "CLOUD_RUN_SERVICE_TARGETS_JSON"), target.platform, config) : null; - const runtime = current?.source === "RUNTIME_TARGET_JSON" - ? parseJsonObject(await readVariable(repository, scope, environment, "RUNTIME_TARGET_JSON")) : null; - if (failed) throw new Error("read_failed"); - const identity = current?.source === "CLOUD_RUN_SERVICE_TARGETS_JSON" ? service - : runtime && runtimeTargetMatchesAccount(runtime, target.platform, config) ? runtime : null; - platform = { strategy_profile: current?.strategy_profile, enabled: current?.runtime_target_enabled, - income_layer_enabled: current?.income_layer_enabled, option_overlay_enabled: current?.option_overlay_enabled, - reserved_cash_floor: current?.min_reserved_cash_usd, reserved_cash_ratio: current?.reserved_cash_ratio, - dca_mode: current?.dca_mode, dca_base_investment_usd: current?.dca_base_investment_usd, - broker_environment: identity?.broker_environment, account_selector: identity?.account_selector, - cash_currency: identity?.cash_currency, runtime_status_target_id: identity?.runtime_status_target_id }; - platformReason = "never_read"; - } catch { platform = {}; platformReason = "read_failed"; } - } - return buildReadback({ target, observed, platform, platformReason, now: Date.now(), cleanRatio: cleanCurrentRatio, splitSelectorValues }); -} - -async function effectiveAccountSettings(env, observed) { - const broker = observed.identity?.broker_environment === "live" || observed.identity?.broker_environment === "paper" - ? { status: "known", value: observed.identity.broker_environment } - : { status: "unknown" }; - // Keep the legacy value shape: non-DCA settings remain absent for old clients. - const effective = { - strategy_profile: { status: "unknown" }, - broker_environment: broker, - income_layer_enabled: { status: "unknown" }, - option_overlay_enabled: { status: "unknown" }, - reserved_cash_floor: { status: "unknown" }, - reserved_cash_ratio: { status: "unknown" }, - }; - const fields = ["strategy_profile", "dca_mode", "dca_base_investment_usd", "income_layer_enabled", - "option_overlay_enabled", "reserved_cash_floor", "reserved_cash_ratio"]; - // This names the resolver path, not a particular variable, deployment or broker readback. - const finish = (reason = "runtime_setting_unresolved", dcaReason = reason) => ({ - effective, - effectiveRead: Object.fromEntries(fields.map(field => [field, { - status: effective[field]?.status === "known" ? "known" : "unknown", - source: "github_runtime_configuration", - source_as_of: null, - reason: effective[field]?.status === "known" ? null : field.startsWith("dca_") ? dcaReason : reason, - }])), - }); - const token = env.RUNTIME_SETTINGS_DISPATCH_TOKEN; - const repository = platformRepositories(env)?.[observed.platform]; - if (!token) return finish("reader_not_configured"); - if (!repository) return finish("repository_not_configured"); - if (!observed.config) return finish("account_config_unavailable"); - const options = Array.isArray(observed.account_options?.[observed.platform]) ? observed.account_options[observed.platform] : []; - const variableCache = new Map(); - let readFailed = false; - const readVariable = async (repo, scope, githubEnvironment, name) => { - const cacheKey = [repo, scope, githubEnvironment || ""].join("|"); - if (!variableCache.has(cacheKey)) variableCache.set(cacheKey, fetchGithubVariables(token, repo, scope, githubEnvironment, { reportReadErrors: true })); - const values = await variableCache.get(cacheKey); - if (values === null) readFailed = true; - return values?.get(name) || ""; - }; - let current = null; - try { - current = await resolveCurrentStrategyForAccount({ - platform: observed.platform, - option: observed.config, - optionsCount: options.length || 1, - repository, - readVariable, - }); - } catch { - return finish("runtime_configuration_read_failed"); - } - // A failed later page must discard earlier values, never certify missing settings. - if (readFailed) return finish("runtime_configuration_read_failed"); - if (!current || typeof current !== "object") current = {}; - if (typeof current.strategy_profile === "string" && current.strategy_profile) { - effective.strategy_profile = { status: "known", value: current.strategy_profile }; - } - if (typeof current.income_layer_enabled === "boolean") effective.income_layer_enabled = { status: "known", value: current.income_layer_enabled }; - if (typeof current.option_overlay_enabled === "boolean") effective.option_overlay_enabled = { status: "known", value: current.option_overlay_enabled }; - if (typeof current.min_reserved_cash_usd === "string" && current.min_reserved_cash_usd) { - effective.reserved_cash_floor = { status: "known", value: current.min_reserved_cash_usd }; - } - if (typeof current.reserved_cash_ratio === "string") { - const ratio = cleanCurrentRatio(current.reserved_cash_ratio); - if (ratio) effective.reserved_cash_ratio = { status: "known", value: ratio }; - } - const profile = effective.strategy_profile.status === "known" ? effective.strategy_profile.value : ""; - if (isDcaProfile(profile) && DCA_SUPPORTED_PLATFORMS.has(observed.platform)) { - const mode = current.dca_mode === "fixed" || current.dca_mode === "smart" ? current.dca_mode : ""; - const amount = typeof current.dca_base_investment_usd === "string" ? current.dca_base_investment_usd : ""; - effective.dca_mode = mode ? { status: "known", value: mode } : { status: "unknown" }; - effective.dca_base_investment_usd = amount ? { status: "known", value: amount } : { status: "unknown" }; - return finish(); - } - return finish("runtime_setting_unresolved", !DCA_SUPPORTED_PLATFORMS.has(observed.platform) || profile - ? "dca_not_applicable" : "strategy_profile_unresolved"); -} - -async function accountSettingsProfileRuntimeReadback(env, observed, resolved = null) { - const token = env.RUNTIME_SETTINGS_DISPATCH_TOKEN; - const repository = resolved?.repository ?? platformRepositories(env)?.[observed.platform]; - if (!token || !repository || !observed.config) return null; - const options = Array.isArray(observed.account_options?.[observed.platform]) - ? observed.account_options[observed.platform] : []; - const variableCache = new Map(); - const readVariable = async (repo, scope, githubEnvironment, name) => { - const cacheKey = [repo, scope, githubEnvironment || ""].join("|"); - if (!variableCache.has(cacheKey)) { - variableCache.set(cacheKey, fetchGithubVariables(token, repo, scope, githubEnvironment)); - } - return (await variableCache.get(cacheKey)).get(name) || ""; - }; - try { - const current = await resolveCurrentStrategyForAccount({ - platform: observed.platform, - option: observed.config, - optionsCount: options.length || 1, - repository, - readVariable, - }); - if (!current) return null; - const variableScope = resolved?.variableScope ?? resolveVariableScope(observed.platform, observed.config); - const githubEnvironment = resolved?.githubEnvironment ?? resolveGithubEnvironment(observed.platform, observed.config, variableScope); - const variables = await variableCache.get([repository, variableScope, githubEnvironment || ""].join("|")); - return { - strategy_profile: current.strategy_profile || null, - runtime_target_enabled: variables?.get(RUNTIME_TARGET_ENABLED_VARIABLE) || null, - }; - } catch { - return null; - } -} - -function hasConfigStore(env) { - return Boolean(configStore(env)); -} - -function configStore(env) { - const store = env.STRATEGY_SWITCH_CONFIG; - if (!store || typeof store.get !== "function" || typeof store.put !== "function") return null; - return store; -} - -async function readConfigJson(env, key) { - const store = configStore(env); - if (!store) return null; - const text = await store.get(key); - if (!text) return null; - try { - return JSON.parse(text); - } catch (error) { - throw new Error(`STRATEGY_SWITCH_CONFIG.${key} must be valid JSON`); - } -} - -async function writeConfigJson(env, key, value) { - const store = configStore(env); - if (!store) throw new Error("STRATEGY_SWITCH_CONFIG KV binding is required"); - const text = JSON.stringify(value, null, 2); - if (indexedKey(key)) await directoryCommand(env, { action: "put", key, value: text }); - else await store.put(key, text); -} - -async function loadAuditLog(env) { - if (!hasConfigStore(env)) return []; - const payload = await readConfigJson(env, AUDIT_LOG_KEY); - if (!Array.isArray(payload)) return []; - return payload - .filter((entry) => entry && !Array.isArray(entry) && typeof entry === "object") - .slice(0, AUDIT_LOG_LIMIT); -} - -async function appendAuditLog(env, entry) { - if (!hasConfigStore(env)) return; - let current = []; - try { - current = await loadAuditLog(env); - } catch (error) { - current = []; - } - await writeConfigJson(env, AUDIT_LOG_KEY, [entry, ...current].slice(0, AUDIT_LOG_LIMIT)); -} - -function accountCounts(accountOptions) { - const counts = {}; - for (const platform of SUPPORTED_PLATFORMS) { - counts[platform] = Array.isArray(accountOptions[platform]) ? accountOptions[platform].length : 0; - } - return counts; -} - -function uniqueStrings(items) { - const result = []; - const seen = new Set(); - for (const item of items) { - const text = String(item || "").trim().toLowerCase(); - if (!text || seen.has(text)) continue; - seen.add(text); - result.push(text); - } - return result; -} - -async function makeSession(login, orgs, env) { - const payload = base64UrlEncodeJson({ - login, - orgs: uniqueStrings(orgs), - exp: Math.floor(Date.now() / 1000) + SESSION_TTL_SECONDS, - }); - const signature = await hmac(payload, env.SESSION_SECRET); - return `${payload}.${signature}`; -} - -async function readSession(request, env) { - if (!env.SESSION_SECRET) return null; - const cookies = parseCookies(request.headers.get("Cookie") || ""); - const token = cookies[SESSION_COOKIE]; - if (!token || !token.includes(".")) return null; - const [payload, signature] = token.split(".", 2); - const expected = await hmac(payload, env.SESSION_SECRET); - if (signature !== expected) return null; - const session = JSON.parse(base64UrlDecode(payload)); - if (!session.exp || session.exp < Math.floor(Date.now() / 1000)) return null; - const login = String(session.login || "").toLowerCase(); - const orgs = normalizeOrgList(session.orgs || [], "session.orgs"); - const authConfig = await loadAuthConfig(env); - const admin = isAdminPrincipal(login, orgs, authConfig); - return { login, orgs, allowed: isAllowedPrincipal(login, orgs, authConfig), admin }; -} - -async function hmac(value, secret) { - const key = await crypto.subtle.importKey( - "raw", - new TextEncoder().encode(secret), - { name: "HMAC", hash: "SHA-256" }, - false, - ["sign"], - ); - const signature = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(value)); - return base64UrlEncodeBytes(new Uint8Array(signature)); -} - -function randomToken() { - const bytes = new Uint8Array(24); - crypto.getRandomValues(bytes); - return base64UrlEncodeBytes(bytes); -} - -function base64UrlEncodeJson(value) { - return base64UrlEncodeBytes(new TextEncoder().encode(JSON.stringify(value))); -} - -function base64UrlEncodeBytes(bytes) { - let binary = ""; - for (const byte of bytes) binary += String.fromCharCode(byte); - return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", ""); -} - -function base64UrlDecode(value) { - const base64 = value.replaceAll("-", "+").replaceAll("_", "/").padEnd(Math.ceil(value.length / 4) * 4, "="); - const binary = atob(base64); - const bytes = new Uint8Array(binary.length); - for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index); - return new TextDecoder().decode(bytes); -} - -function parseCookies(header) { - const result = {}; - for (const part of header.split(";")) { - const [name, ...rest] = part.trim().split("="); - if (!name) continue; - result[name] = decodeURIComponent(rest.join("=")); - } - return result; -} - -function cookie(name, value, maxAge) { - return `${name}=${encodeURIComponent(value)}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=${maxAge}`; -} - -function clearCookie(name) { - return `${name}=; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=0`; -} - -function clearOAuthCookie() { - return { "Set-Cookie": clearCookie(OAUTH_STATE_COOKIE) }; -} - - -function requireMarketBenchmarkSyncToken(request, env) { - const expected = String(env.MARKET_BENCHMARK_SYNC_TOKEN || env.STRATEGY_SWITCH_SYNC_TOKEN || env.RUNTIME_SETTINGS_DISPATCH_TOKEN || ""); - if (!expected) throw new HttpError("market_benchmark_sync_token_missing", 503); - const token = (request.headers.get("Authorization") || "").match(/^Bearer\s+(.+)$/i)?.[1] || ""; - if (!token || token !== expected) throw new HttpError("unauthorized", 401); - // Fail closed if the bearer equals another dedicated sync token family when those are set differently. - const forbidden = [ - env.ACCOUNT_FACTS_SYNC_TOKEN, - env.IBKR_ACCOUNT_FACTS_SYNC_TOKEN, - env.SCHWAB_ACCOUNT_FACTS_SYNC_TOKEN, - env.FIRSTRADE_ACCOUNT_FACTS_SYNC_TOKEN, - env.BINANCE_ACCOUNT_FACTS_SYNC_TOKEN, - env.EXECUTION_EVIDENCE_SYNC_TOKEN, - ].filter((value) => typeof value === "string" && value && value !== expected); - if (forbidden.includes(token)) throw new HttpError("unauthorized", 401); -} - -async function marketBenchmarkResponse(request, env) { - if (request.method !== "GET" && request.method !== "HEAD") { - return json({ ok: false, error: "method_not_allowed" }, 405); - } - if (!hasRuntimeInstanceStore(env)) { - return json({ ok: false, error: "market_benchmark_store_unavailable" }, 503); - } - try { - const stored = await runtimeInstanceCommand(env, { - action: "market_benchmark_read", - }); - const body = { - ok: true, - schema_version: MARKET_BENCHMARK_SCHEMA, - inception_date: stored.inception_date ?? null, - timezone: stored.timezone || "Asia/Shanghai", - points: Array.isArray(stored.points) ? stored.points : [], - }; - const headers = responseHeaders({ "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" }); - return new Response(request.method === "HEAD" ? null : JSON.stringify(body), { status: 200, headers }); - } catch (error) { - if (error instanceof HttpError) return json({ ok: false, error: error.message }, error.status || 500); - return json({ ok: false, error: "market_benchmark_unavailable" }, 503); - } -} - - -function requireMarketBenchmarkRevisionToken(request, env) { - // Dedicated secret only — never fall back to sync/dispatch tokens. - const expected = String(env.MARKET_BENCHMARK_REVISION_TOKEN || ""); - if (!expected) throw new HttpError("market_benchmark_revision_token_missing", 503); - const token = (request.headers.get("Authorization") || "").match(/^Bearer\s+(.+)$/i)?.[1] || ""; - if (!token || token !== expected) throw new HttpError("unauthorized", 401); - const forbidden = [ - env.MARKET_BENCHMARK_SYNC_TOKEN, - env.STRATEGY_SWITCH_SYNC_TOKEN, - env.RUNTIME_SETTINGS_DISPATCH_TOKEN, - env.ACCOUNT_FACTS_SYNC_TOKEN, - env.IBKR_ACCOUNT_FACTS_SYNC_TOKEN, - env.SCHWAB_ACCOUNT_FACTS_SYNC_TOKEN, - env.FIRSTRADE_ACCOUNT_FACTS_SYNC_TOKEN, - env.BINANCE_ACCOUNT_FACTS_SYNC_TOKEN, - env.EXECUTION_EVIDENCE_SYNC_TOKEN, - ].filter((value) => typeof value === "string" && value && value !== expected); - if (forbidden.includes(token)) throw new HttpError("unauthorized", 401); -} - -async function reviseMarketBenchmarkResponse(request, env) { - try { - requireMarketBenchmarkRevisionToken(request, env); - } catch (error) { - if (error instanceof HttpError) return json({ ok: false, error: error.message }, error.status || 401); - return json({ ok: false, error: "unauthorized" }, 401); - } - if (!hasRuntimeInstanceStore(env)) { - return json({ ok: false, error: "market_benchmark_store_unavailable" }, 503); - } - let raw; - try { - raw = await readBoundedJson(request, 64 * 1024); - } catch (error) { - if (error instanceof HttpError) return json({ ok: false, error: error.message }, error.status || 400); - return json({ ok: false, error: "invalid_market_benchmark_revision_payload" }, 400); - } - try { - const stored = await runtimeInstanceCommand(env, { - action: "market_benchmark_read", - }); - const appliedAt = typeof raw.applied_at === "string" && !Number.isNaN(Date.parse(raw.applied_at)) - ? raw.applied_at - : new Date().toISOString(); - const digestHex = async (value) => sha256Hex(String(value)); - // planRevisionApply expects sync digest; pre-hash with Web Crypto then use a map. - const strings = new Set(); - const proposal = raw.proposal; - if (proposal && Array.isArray(proposal.entries)) { - strings.add(JSON.stringify(proposal.entries)); - for (const entry of proposal.entries) { - if (entry && typeof entry.series_id_sha256 === "string") strings.add(entry.series_id_sha256); - } - } - for (const point of (raw.replacements || [])) { - if (point?.seriesId) strings.add(point.seriesId); - if (point?.close) strings.add(point.close); - } - for (const point of (Array.isArray(stored.points) ? stored.points : [])) { - if (point?.close) strings.add(point.close); - if (point?.seriesId) strings.add(point.seriesId); - } - const cache = new Map(); - for (const value of strings) cache.set(value, await digestHex(value)); - // Also hash every seriesId/close used by planRevisionApply via digest() - const digest = (value) => { - const text = String(value); - if (cache.has(text)) return cache.get(text); - throw new HttpError("benchmark_revision_digest_cache_miss", 500); - }; - // Ensure proposal.entries JSON and all replacement/stored closes/seriesIds are cached — - // re-walk after building any missing by computing individually. - async function ensure(value) { - const text = String(value); - if (!cache.has(text)) cache.set(text, await digestHex(text)); - return cache.get(text); - } - await ensure(JSON.stringify(proposal?.entries || [])); - for (const point of (raw.replacements || [])) { - await ensure(point.seriesId); - await ensure(point.close); - } - for (const point of (Array.isArray(stored.points) ? stored.points : [])) { - await ensure(point.seriesId); - await ensure(point.close); - } - const plan = planRevisionApply({ - proposal: raw.proposal, - approvedProposalSha256: raw.approved_proposal_sha256, - replacements: raw.replacements || [], - storedPoints: Array.isArray(stored.points) ? stored.points : [], - digest: (value) => cache.get(String(value)) || (() => { throw new HttpError("benchmark_revision_digest_cache_miss", 500); })(), - appliedAt, - previouslyAppliedProposalSha256s: [], - }); - const result = await runtimeInstanceCommand(env, { - action: "market_benchmark_revise", - plan: { - proposal_sha256: plan.proposal_sha256, - overwrites: plan.overwrites, - deletes: plan.deletes, - updates: plan.updates, - ledger_entries: plan.ledger_entries, - }, - applied_at: appliedAt, - }); - return json({ - ok: true, - applied: true, - proposal_sha256: result.proposal_sha256 || plan.proposal_sha256, - overwrites: result.overwrites || plan.overwrites, - deletes: 0, - ledger_entries: result.ledger_entries || plan.ledger_entries.length, - log: revisionApplyLogSummary(plan), - }); - } catch (error) { - if (error instanceof HttpError) return json({ ok: false, error: error.message }, error.status || 500); - if (error instanceof MarketBenchmarkRevisionError) return json({ ok: false, error: error.code }, error.status || 400); - if (error instanceof MarketBenchmarkError) return json({ ok: false, error: error.code }, error.status || 400); - return json({ ok: false, error: "market_benchmark_revision_failed" }, 502); - } -} - -async function syncMarketBenchmarkResponse(request, env) { - try { - requireMarketBenchmarkSyncToken(request, env); - } catch (error) { - if (error instanceof HttpError) return json({ ok: false, error: error.message }, error.status || 401); - return json({ ok: false, error: "unauthorized" }, 401); - } - if (!hasRuntimeInstanceStore(env)) { - return json({ ok: false, error: "market_benchmark_store_unavailable" }, 503); - } - let raw; - try { - raw = await readBoundedJson(request, 64 * 1024); - } catch (error) { - if (error instanceof HttpError) return json({ ok: false, error: error.message }, error.status || 400); - return json({ ok: false, error: "invalid_market_benchmark_payload" }, 400); - } - try { - const result = await runtimeInstanceCommand(env, { - action: "market_benchmark_put", - payload: raw, - }); - return json({ - ok: true, - stored: true, - schema_version: MARKET_BENCHMARK_SCHEMA, - inception_date: result.inception_date ?? null, - written: result.written || 0, - unchanged: result.unchanged || 0, - points: result.points || 0, - }); - } catch (error) { - if (error instanceof HttpError) return json({ ok: false, error: error.message }, error.status || 500); - if (error instanceof MarketBenchmarkError) return json({ ok: false, error: error.code }, error.status || 400); - return json({ ok: false, error: "market_benchmark_sync_failed" }, 502); - } -} - -async function overviewFxResponse(request, fetchImpl = fetch) { - return overviewFxResponseImpl(request, { - fetchImpl, - fetchWithTimeout, - json, - responseHeaders, - }); -} - -function json(payload, status = 200, headers = {}) { - return new Response(JSON.stringify(payload, null, 2), { - status, - headers: responseHeaders({ - "Content-Type": "application/json; charset=utf-8", - "Cache-Control": "no-store", - }, headers), - }); -} - -function html(body, status = 200, headers = {}) { - return new Response(body, { - status, - headers: responseHeaders({ - "Content-Type": "text/html; charset=utf-8", - "Cache-Control": "no-store", - }, headers), - }); -} - -function redirect(location, headers = {}) { - return new Response(null, { - status: 302, - headers: responseHeaders({ Location: location }, headers), - }); -} - -function responseHeaders(base = {}, extra = {}) { - const headers = new Headers(SECURITY_HEADERS); - appendHeaderEntries(headers, base); - appendHeaderEntries(headers, extra); - return headers; -} - -function appendHeaderEntries(headers, values) { - for (const [name, value] of Object.entries(values)) { - if (Array.isArray(value)) { - for (const item of value) headers.append(name, item); - } else { - headers.set(name, value); - } - } -} - -function renderMessage(title, message) { - return `${escapeHtml(title)} - -

${escapeHtml(title)}

${escapeHtml(message)}

返回控制台

`; -} - -function escapeHtml(value) { - return String(value) - .replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">") - .replaceAll('"', """); -} - export const __test = { splitSelectorValues, deskAuth,