Skip to content

Commit 785e684

Browse files
Pigbibicodex
andcommitted
feat: generate frozen legacy live continuity targets
Co-Authored-By: Codex <noreply@openai.com>
1 parent 16ef553 commit 785e684

4 files changed

Lines changed: 127 additions & 0 deletions

File tree

.github/workflows/manual-strategy-switch.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,27 @@ on:
3131
- live
3232
- dry_run
3333
- paper
34+
live_continuity_state:
35+
description: "Optional frozen legacy live baseline state. This cannot create a new live permission or increase risk."
36+
required: true
37+
type: choice
38+
default: NONE
39+
options:
40+
- NONE
41+
- ACTIVE_LKG
42+
- ACTIVE_REDUCED
43+
- RECONCILE_ONLY
44+
- RISK_REDUCTION_ONLY
45+
- PAUSED
46+
- ROLLBACK_LKG
47+
live_continuity_baseline_id:
48+
description: "Required only with a continuity state: audited identifier of the existing authorised baseline."
49+
required: false
50+
type: string
51+
live_continuity_captured_at:
52+
description: "Required only with a continuity state: ISO date when the exact legacy target was captured."
53+
required: false
54+
type: string
3455
variable_scope:
3556
description: "Where GitHub variables are written. blank = platform default."
3657
required: false
@@ -146,6 +167,9 @@ jobs:
146167
TARGET_NAME: ${{ inputs.target_name }}
147168
STRATEGY_PROFILE: ${{ inputs.strategy_profile }}
148169
EXECUTION_MODE: ${{ inputs.execution_mode }}
170+
LIVE_CONTINUITY_STATE: ${{ inputs.live_continuity_state }}
171+
LIVE_CONTINUITY_BASELINE_ID: ${{ inputs.live_continuity_baseline_id }}
172+
LIVE_CONTINUITY_CAPTURED_AT: ${{ inputs.live_continuity_captured_at }}
149173
VARIABLE_SCOPE: ${{ inputs.variable_scope }}
150174
GITHUB_ENVIRONMENT_NAME: ${{ inputs.github_environment }}
151175
DEPLOYMENT_SELECTOR: ${{ inputs.deployment_selector }}
@@ -345,6 +369,11 @@ jobs:
345369
if [ -n "${SERVICE_NAME:-}" ]; then
346370
args+=(--service-name "${SERVICE_NAME}")
347371
fi
372+
if [ "${LIVE_CONTINUITY_STATE}" != "NONE" ]; then
373+
args+=(--live-continuity-state "${LIVE_CONTINUITY_STATE}")
374+
args+=(--live-continuity-baseline-id "${LIVE_CONTINUITY_BASELINE_ID}")
375+
args+=(--live-continuity-captured-at "${LIVE_CONTINUITY_CAPTURED_AT}")
376+
fi
348377
if [ -n "${EXTRA_VARIABLES_JSON:-}" ]; then
349378
args+=(--extra-variables-json "${EXTRA_VARIABLES_JSON}")
350379
fi
@@ -391,6 +420,10 @@ jobs:
391420
print("- strategy_profile: `<redacted>`")
392421
print("- service_name: `<redacted>`")
393422
print(f"- execution_mode: `{target['runtime_target']['execution_mode']}`")
423+
continuity = target["runtime_target"].get("live_continuity")
424+
if isinstance(continuity, dict):
425+
print(f"- live_continuity_state: `{continuity['state']}`")
426+
print(f"- live_continuity_baseline_kind: `{continuity['baseline_kind']}`")
394427
print("- values: `<redacted>`")
395428
print()
396429
print("### Variables")

README.zh-CN.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ python3 -m unittest discover -s python/tests -v
6565
3. 再运行 `apply=true`,并填写 `confirm_apply=APPLY`,写入目标仓库变量。
6666
4. 对 Cloud Run 平台,如需同步运行环境,额外设置 `trigger_platform_sync=true`,并填写 `confirm_apply=APPLY_AND_SYNC`
6767

68+
若是在**恢复已明确授权的旧实盘基线**,可额外填写 `live_continuity_state``live_continuity_baseline_id``live_continuity_captured_at`。工具会用完整 target 自动计算冻结 hash;它只接受 `legacy_authorized` 基线,不能借此创建新的实盘权限、扩大资金/杠杆或把研究候选直接提升为实盘。
69+
6870
常用例子:
6971

7072
```text

python/scripts/build_runtime_switch.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616
ROOT = SCRIPT_DIR.parents[1]
1717

1818
from runtime_settings import ( # noqa: E402
19+
LIVE_CONTINUITY_STATES,
1920
SUPPORTED_PLATFORMS,
2021
compact_json,
2122
env_string,
2223
platform_repository,
24+
runtime_target_fingerprint,
2325
select_service_target_entry_index,
2426
validate_target,
2527
)
@@ -820,9 +822,35 @@ def _build_runtime_target(args: argparse.Namespace) -> dict[str, Any]:
820822
execution_windows = _load_json_object(args.execution_windows_json, field_name="execution_windows_json")
821823
if execution_windows:
822824
runtime_target["execution_windows"] = execution_windows
825+
_apply_live_continuity(runtime_target, args)
823826
return runtime_target
824827

825828

829+
def _apply_live_continuity(runtime_target: dict[str, Any], args: argparse.Namespace) -> None:
830+
"""Attach a frozen legacy baseline without granting new live authority."""
831+
832+
state = str(getattr(args, "live_continuity_state", "") or "").strip().upper()
833+
if not state or state == "NONE":
834+
return
835+
if state not in LIVE_CONTINUITY_STATES:
836+
raise ValueError(f"live_continuity_state must be one of NONE, {', '.join(sorted(LIVE_CONTINUITY_STATES))}")
837+
if runtime_target.get("execution_mode") != "live" or runtime_target.get("dry_run_only") is not False:
838+
raise ValueError("live_continuity is only valid for an execution_mode=live, non-dry-run target")
839+
baseline_id = str(getattr(args, "live_continuity_baseline_id", "") or "").strip()
840+
captured_at = str(getattr(args, "live_continuity_captured_at", "") or "").strip()
841+
if not baseline_id or not captured_at:
842+
raise ValueError(
843+
"live_continuity_baseline_id and live_continuity_captured_at are required when live_continuity_state is set"
844+
)
845+
runtime_target["live_continuity"] = {
846+
"state": state,
847+
"baseline_kind": "legacy_authorized",
848+
"baseline_id": baseline_id,
849+
"baseline_target_sha256": runtime_target_fingerprint(runtime_target),
850+
"captured_at": captured_at,
851+
}
852+
853+
826854
def _build_target_entry(
827855
*,
828856
platform: str,
@@ -1041,6 +1069,14 @@ def build_parser() -> argparse.ArgumentParser:
10411069
parser.add_argument("--account-scope", default="")
10421070
parser.add_argument("--service-name", default="")
10431071
parser.add_argument("--execution-windows-json", default="")
1072+
parser.add_argument(
1073+
"--live-continuity-state",
1074+
choices=("NONE", *sorted(LIVE_CONTINUITY_STATES)),
1075+
default="NONE",
1076+
help="Freeze an already authorised legacy baseline; does not create new live authority.",
1077+
)
1078+
parser.add_argument("--live-continuity-baseline-id", default="")
1079+
parser.add_argument("--live-continuity-captured-at", default="")
10441080
parser.add_argument("--plugin-mode", choices=("auto", "none", "custom"), default="none")
10451081
parser.add_argument("--custom-plugin-mounts-json", default="")
10461082
parser.add_argument("--artifact-bucket-uri", default=DEFAULT_ARTIFACT_BUCKET_URI)

python/tests/test_runtime_settings.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,6 +1373,62 @@ def test_build_switch_target_defaults_longbridge_sg_tqqq(self):
13731373
self.assertEqual(plugin_payload["strategy_plugins"], [])
13741374
self.assertEqual(runtime_settings.validate_target(target), [])
13751375

1376+
def test_build_switch_target_freezes_eligible_legacy_live_baseline(self):
1377+
parser = build_runtime_switch.build_parser()
1378+
args = parser.parse_args(
1379+
[
1380+
"--platform",
1381+
"longbridge",
1382+
"--target-name",
1383+
"sg",
1384+
"--strategy-profile",
1385+
"soxl_soxx_trend_income",
1386+
"--execution-mode",
1387+
"live",
1388+
"--live-continuity-state",
1389+
"ACTIVE_LKG",
1390+
"--live-continuity-baseline-id",
1391+
"soxl-longbridge-lkg-20260830",
1392+
"--live-continuity-captured-at",
1393+
"2026-08-30",
1394+
]
1395+
)
1396+
1397+
target = build_runtime_switch.build_switch_target(args)
1398+
continuity = target["runtime_target"]["live_continuity"]
1399+
1400+
self.assertEqual(continuity["state"], "ACTIVE_LKG")
1401+
self.assertEqual(continuity["baseline_kind"], "legacy_authorized")
1402+
self.assertEqual(
1403+
continuity["baseline_target_sha256"],
1404+
runtime_settings.runtime_target_fingerprint(target["runtime_target"]),
1405+
)
1406+
self.assertEqual(runtime_settings.validate_target(target), [])
1407+
1408+
def test_build_switch_target_rejects_continuity_for_dry_run(self):
1409+
parser = build_runtime_switch.build_parser()
1410+
args = parser.parse_args(
1411+
[
1412+
"--platform",
1413+
"longbridge",
1414+
"--target-name",
1415+
"sg",
1416+
"--strategy-profile",
1417+
"tqqq_growth_income",
1418+
"--execution-mode",
1419+
"dry_run",
1420+
"--live-continuity-state",
1421+
"ACTIVE_LKG",
1422+
"--live-continuity-baseline-id",
1423+
"tqqq-longbridge-lkg-20260830",
1424+
"--live-continuity-captured-at",
1425+
"2026-08-30",
1426+
]
1427+
)
1428+
1429+
with self.assertRaisesRegex(ValueError, "only valid for an execution_mode=live"):
1430+
build_runtime_switch.build_switch_target(args)
1431+
13761432
def test_build_switch_target_treats_legacy_auto_plugin_mode_as_none(self):
13771433
parser = build_runtime_switch.build_parser()
13781434
args = parser.parse_args(

0 commit comments

Comments
 (0)