diff --git a/python/scripts/execution_evidence_projection.py b/python/scripts/execution_evidence_projection.py index c12cb0c..611a017 100644 --- a/python/scripts/execution_evidence_projection.py +++ b/python/scripts/execution_evidence_projection.py @@ -50,6 +50,8 @@ { "not_due", "no_action", + "no_signal", + "no_rebalance", "risk_blocked", "submitted", "broker_acknowledged", @@ -72,6 +74,8 @@ _EXECUTION_RECEIPT_OUTCOME_CONFIRMATIONS = { "not_due": frozenset({"not_applicable"}), "no_action": frozenset({"not_applicable"}), + "no_signal": frozenset({"not_applicable"}), + "no_rebalance": frozenset({"not_applicable"}), "risk_blocked": frozenset({"not_applicable"}), "submitted": frozenset({"not_observed"}), "broker_acknowledged": frozenset({"acknowledged"}), @@ -298,6 +302,8 @@ def _execution_evidence_from_receipt( if receipt is None: return "pending", "target_execution_evidence_missing" outcome = receipt["outcome"] + if outcome in {"no_signal", "no_rebalance"}: + return "not_applicable", "target_execution_receipt_observed" if outcome == "reconciliation_required": return "unavailable", "target_execution_reconciliation_required" if outcome == "failed": diff --git a/python/tests/test_execution_evidence_projection.py b/python/tests/test_execution_evidence_projection.py index e30c567..5d2ebd8 100644 --- a/python/tests/test_execution_evidence_projection.py +++ b/python/tests/test_execution_evidence_projection.py @@ -52,6 +52,8 @@ def _execution_receipt(self, *, outcome: str = "filled") -> dict[str, str]: "filled": "filled", "failed": "not_observed", "reconciliation_required": "reconciliation_required", + "no_signal": "not_applicable", + "no_rebalance": "not_applicable", }[outcome] observed_at = "2026-08-25T16:00:00Z" return { @@ -143,6 +145,27 @@ def test_projects_a_matching_minimal_execution_receipt_without_order_details(sel for forbidden in ("receipt_id", "must-not-be-projected", "account_ids", "api_token", "gs://"): self.assertNotIn(forbidden, serialized) + def test_accepts_non_action_receipts_without_granting_execution_authority(self): + for outcome in ("no_signal", "no_rebalance"): + with self.subTest(outcome=outcome): + report = self._report() + report["execution_receipt"] = self._execution_receipt(outcome=outcome) + + snapshot = projection.build_execution_evidence_source_snapshot( + [report], + source_id="runtime-reports", + now=datetime(2026, 8, 25, 16, 5, tzinfo=UTC), + ) + + deployment = snapshot["deployments"][0] + self.assertEqual(deployment["evidence"]["target_execution"], "not_applicable") + self.assertEqual(deployment["recommendation"], { + "code": "parked", + "reason_code": "target_execution_receipt_observed", + }) + self.assertEqual(deployment["execution_receipt"]["outcome"], outcome) + self.assertEqual(deployment["execution_receipt"]["broker_confirmation"], "not_applicable") + def test_rejects_a_tampered_execution_receipt_without_claiming_execution(self): report = self._report() receipt = self._execution_receipt() diff --git a/tests/console_runtime_state_validation.mjs b/tests/console_runtime_state_validation.mjs index 678aefc..699a444 100644 --- a/tests/console_runtime_state_validation.mjs +++ b/tests/console_runtime_state_validation.mjs @@ -333,14 +333,18 @@ test('buildInputs keeps untouched policy layers current and serializes only touc assert.equal(touched.min_reserved_cash_usd, '100'); }); -test('account settings copy removes guesswork and keeps the two research views', () => { +test('account settings copy removes guesswork and prioritizes research candidates', () => { const html = readFileSync(new URL('../web/strategy-switch-console/index.html', import.meta.url), 'utf8'); const app = readFileSync(new URL('../web/strategy-switch-console/app.js', import.meta.url), 'utf8'); assert.ok(html.includes('id="mode-display"')); assert.ok(html.includes('id="execution-mode-select"')); assert.equal(html.includes('data-mode="live"'), false); - assert.ok(html.includes('data-research-internal-view="monitoring"')); - assert.ok(html.includes('data-research-internal-view="research"')); + assert.equal(html.includes('research-internal-tabs'), false); + assert.equal(html.includes('data-research-internal-view'), false); + assert.ok(html.includes('id="monitoring-diagnostics" hidden')); + assert.ok(html.indexOf('id="promotion-decision-panel"') < html.indexOf('id="monitoring-diagnostics"')); + assert.equal(app.includes('researchInternalView'), false); + assert.ok(app.includes('if (state.view === "research") void refreshResearchWorkspace()')); assert.equal(html.includes('需要启停或调整策略时展开'), false); assert.equal(html.includes('插件、收入层和期权的选择不授予运行许可'), false); assert.equal(app.includes('target {target} · service {service} · market {domains}'), false); @@ -750,7 +754,7 @@ test('account details keep saved, deployed and application state separate', () = const app = readFileSync(new URL('../web/strategy-switch-console/app.js', import.meta.url), 'utf8'); assert.ok(html.includes('data-i18n="overviewRuntime"')); assert.ok(html.includes('data-i18n="latestReadback"')); - assert.ok(html.indexOf('data-research-internal-view="monitoring"') < html.indexOf('id="promotion-decision-panel"')); + assert.ok(html.indexOf('id="promotion-decision-panel"') < html.indexOf('id="monitoring-diagnostics"')); assert.ok(html.includes('class="account-facts"')); assert.ok(app.includes('["deployedSwitch", accountDeploymentText(platform, account)]')); assert.ok(app.includes('["applicationStatus", application]')); diff --git a/tests/strategy_switch_worker_validation.mjs b/tests/strategy_switch_worker_validation.mjs index 9f8b5d0..9dccd56 100644 --- a/tests/strategy_switch_worker_validation.mjs +++ b/tests/strategy_switch_worker_validation.mjs @@ -147,8 +147,8 @@ assert.ok(indexHtml.includes('switchSurface.classList.toggle("summary-hidden", ! assert.equal(indexHtml.match(/Generated by inject_platform_config\.py/g)?.length, 1); assert.ok(indexHtml.includes('')); assert.ok(indexHtml.includes('')); -assert.ok(indexHtml.includes('/app.js?v=console-clarity-v1')); -assert.ok(indexHtml.includes('/app.css?v=console-clarity-v1')); +assert.ok(indexHtml.includes('/app.js?v=console-clarity-v2')); +assert.ok(indexHtml.includes('/app.css?v=console-clarity-v2')); assert.equal(indexHtml.includes(' @@ -112,11 +115,7 @@

恢复前确认