Skip to content

Commit a0715f6

Browse files
Pigbibicodex
andcommitted
feat: separate live baseline continuity from candidate gate
Co-Authored-By: Codex <noreply@openai.com>
1 parent c80f79c commit a0715f6

8 files changed

Lines changed: 403 additions & 1 deletion

platform-config.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,14 @@
416416
"dry_run"
417417
],
418418
"blocked_live_reason": "missing_current_promotion_evidence_and_preauthorized_autonomy_policy",
419+
"live_continuity": {
420+
"eligible": true,
421+
"allowed_platforms": [
422+
"longbridge",
423+
"ibkr",
424+
"schwab"
425+
]
426+
},
419427
"features": {
420428
"income_layer": true,
421429
"option_overlay": true,
@@ -483,6 +491,12 @@
483491
"dry_run"
484492
],
485493
"blocked_live_reason": "missing_current_promotion_evidence_and_preauthorized_autonomy_policy",
494+
"live_continuity": {
495+
"eligible": true,
496+
"allowed_platforms": [
497+
"firstrade"
498+
]
499+
},
486500
"features": {
487501
"income_layer": false,
488502
"option_overlay": false,
@@ -922,6 +936,12 @@
922936
"dry_run"
923937
],
924938
"blocked_live_reason": "missing_current_promotion_evidence_and_preauthorized_autonomy_policy",
939+
"live_continuity": {
940+
"eligible": true,
941+
"allowed_platforms": [
942+
"binance"
943+
]
944+
},
925945
"features": {
926946
"income_layer": false,
927947
"option_overlay": false,

python/scripts/build_config.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@
3737
"can_switch_live",
3838
"allowed_execution_modes",
3939
"blocked_live_reason",
40+
"live_continuity",
4041
}
42+
LIVE_CONTINUITY_POLICY_FIELDS = {"eligible", "allowed_platforms"}
4143
SCHEDULER_FIELDS = {"timezone", "main_time", "probe_time", "precheck_time"}
4244
MARKET_FIELDS = {"market", "market_calendar", "market_timezone"}
4345
FEATURE_SNAPSHOT_FIELDS = {"required", "path", "manifest_path"}
@@ -299,6 +301,31 @@ def validate(config: dict) -> list[str]:
299301
errors.append(
300302
f"strategy {sid}: live feature snapshot requires path and manifest_path"
301303
)
304+
continuity = sdata.get("live_continuity")
305+
if continuity is None:
306+
continue
307+
if not isinstance(continuity, dict):
308+
errors.append(f"strategy {sid}: live_continuity must be an object")
309+
continue
310+
unsupported_continuity = sorted(set(continuity) - LIVE_CONTINUITY_POLICY_FIELDS)
311+
if unsupported_continuity:
312+
errors.append(
313+
f"strategy {sid}: unsupported live_continuity fields {unsupported_continuity}"
314+
)
315+
eligible = continuity.get("eligible")
316+
if not isinstance(eligible, bool):
317+
errors.append(f"strategy {sid}: live_continuity.eligible must be boolean")
318+
allowed_platforms = continuity.get("allowed_platforms")
319+
if not isinstance(allowed_platforms, list) or not all(
320+
isinstance(platform, str) and platform in platforms for platform in allowed_platforms
321+
):
322+
errors.append(
323+
f"strategy {sid}: live_continuity.allowed_platforms must contain configured platforms"
324+
)
325+
elif len(set(allowed_platforms)) != len(allowed_platforms):
326+
errors.append(f"strategy {sid}: live_continuity.allowed_platforms must not contain duplicates")
327+
elif eligible is True and not allowed_platforms:
328+
errors.append(f"strategy {sid}: live_continuity.eligible requires allowed_platforms")
302329
return errors
303330

304331

@@ -399,6 +426,7 @@ def _automation_policy_for_strategy(profile: str, strategy: dict) -> dict[str, o
399426
runtime_enabled = strategy.get("runtime_enabled") is True
400427
blocked_reason = str(strategy.get("blocked_live_reason") or "").strip()
401428
features = strategy.get("features") if isinstance(strategy.get("features"), dict) else {}
429+
continuity = strategy.get("live_continuity") if isinstance(strategy.get("live_continuity"), dict) else {}
402430
if runtime_enabled and can_switch_live and lifecycle_stage == "runtime_enabled":
403431
lane = "live_equivalent_optimization"
404432
triggers = ["health_degradation", "parameter_drift", "scheduled_retest", "market_regime_shift"]
@@ -438,6 +466,10 @@ def _automation_policy_for_strategy(profile: str, strategy: dict) -> dict[str, o
438466
"operating_policy_status": operating_policy_status,
439467
"can_switch_live": can_switch_live,
440468
"blocked_live_reason": blocked_reason,
469+
"live_continuity": {
470+
"eligible": continuity.get("eligible") is True,
471+
"allowed_platforms": list(continuity.get("allowed_platforms") or []),
472+
},
441473
"triggers": triggers,
442474
"evidence_required": evidence_required,
443475
"position_control_sensitive": bool(features.get("combo") or features.get("option_overlay")),
@@ -665,11 +697,16 @@ def _strategy_profile_gate_fields(sdata: dict) -> dict[str, object]:
665697
can_switch_live = sdata.get("can_switch_live", runtime_enabled and lifecycle_stage == "runtime_enabled")
666698
if blocked_live_reason is None and not can_switch_live:
667699
blocked_live_reason = lifecycle_stage or "not_runtime_enabled"
700+
continuity = sdata.get("live_continuity") if isinstance(sdata.get("live_continuity"), dict) else {}
668701
return {
669702
"lifecycle_stage": lifecycle_stage,
670703
"can_switch_live": can_switch_live,
671704
"allowed_execution_modes": _normalize_allowed_execution_modes(sdata.get("allowed_execution_modes")),
672705
"blocked_live_reason": "" if blocked_live_reason is None else str(blocked_live_reason).strip(),
706+
"live_continuity": {
707+
"eligible": continuity.get("eligible") is True,
708+
"allowed_platforms": list(continuity.get("allowed_platforms") or []),
709+
},
673710
}
674711

675712

python/scripts/build_platform_config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,11 +282,16 @@ def _strategy_profile_gate_fields(sdata: dict) -> dict[str, object]:
282282
can_switch_live = sdata.get("can_switch_live", runtime_enabled and lifecycle_stage == "runtime_enabled")
283283
if blocked_live_reason is None and not can_switch_live:
284284
blocked_live_reason = lifecycle_stage or "not_runtime_enabled"
285+
continuity = sdata.get("live_continuity") if isinstance(sdata.get("live_continuity"), dict) else {}
285286
return {
286287
"lifecycle_stage": lifecycle_stage,
287288
"can_switch_live": can_switch_live,
288289
"allowed_execution_modes": _normalize_allowed_execution_modes(sdata.get("allowed_execution_modes")),
289290
"blocked_live_reason": "" if blocked_live_reason is None else str(blocked_live_reason).strip(),
291+
"live_continuity": {
292+
"eligible": continuity.get("eligible") is True,
293+
"allowed_platforms": list(continuity.get("allowed_platforms") or []),
294+
},
290295
}
291296

292297

python/scripts/inject_platform_config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ def _strategy_profile_entry(sid: str, sdata: dict) -> dict:
155155
blocked_live_reason = sdata.get("blocked_live_reason")
156156
if blocked_live_reason is None and not can_switch_live:
157157
blocked_live_reason = lifecycle_stage or "not_runtime_enabled"
158+
continuity = sdata.get("live_continuity") if isinstance(sdata.get("live_continuity"), dict) else {}
158159
entry = {
159160
"profile": sid,
160161
"label": sdata.get("label", sid),
@@ -166,6 +167,10 @@ def _strategy_profile_entry(sid: str, sdata: dict) -> dict:
166167
"can_switch_live": can_switch_live,
167168
"allowed_execution_modes": _normalize_allowed_execution_modes(sdata.get("allowed_execution_modes")),
168169
"blocked_live_reason": "" if blocked_live_reason is None else str(blocked_live_reason).strip(),
170+
"live_continuity": {
171+
"eligible": continuity.get("eligible") is True,
172+
"allowed_platforms": list(continuity.get("allowed_platforms") or []),
173+
},
169174
"income_layer_enabled": feat.get("income_layer", False),
170175
"option_overlay_enabled": feat.get("option_overlay", False),
171176
"combo_enabled": feat.get("combo", False),

python/scripts/runtime_settings.py

Lines changed: 162 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
from __future__ import annotations
55

66
import argparse
7+
import hashlib
78
import json
89
import os
910
import re
1011
import shlex
1112
import subprocess
1213
import sys
1314
from dataclasses import dataclass
15+
from datetime import date
1416
from pathlib import Path
1517
from typing import Any
1618
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
@@ -53,6 +55,48 @@
5355
}
5456
SCHEDULER_FIELDS = frozenset({"timezone", "main_time", "probe_time", "precheck_time"})
5557
MARKET_FIELDS = ("market", "market_calendar", "market_timezone")
58+
STRATEGY_RELEASE_REQUIRED_FIELDS = (
59+
"release_id",
60+
"manifest_sha256",
61+
"strategy_revision",
62+
"config_sha256",
63+
"risk_policy_sha256",
64+
"evidence_sha256",
65+
"plugin_bundle_sha256",
66+
"effective_session",
67+
)
68+
STRATEGY_RELEASE_DIGEST_FIELDS = frozenset(
69+
{
70+
"manifest_sha256",
71+
"config_sha256",
72+
"risk_policy_sha256",
73+
"evidence_sha256",
74+
"plugin_bundle_sha256",
75+
}
76+
)
77+
STRATEGY_RELEASE_ID_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{2,127}$")
78+
SHA256_PATTERN = re.compile(r"^(?:sha256:)?[0-9a-fA-F]{64}$")
79+
LIVE_CONTINUITY_STATES = frozenset(
80+
{
81+
"ACTIVE_LKG",
82+
"ACTIVE_REDUCED",
83+
"RECONCILE_ONLY",
84+
"RISK_REDUCTION_ONLY",
85+
"PAUSED",
86+
"ROLLBACK_LKG",
87+
}
88+
)
89+
LIVE_CONTINUITY_BASELINE_KINDS = frozenset({"legacy_authorized", "release_attested"})
90+
LIVE_CONTINUITY_FIELDS = frozenset(
91+
{
92+
"state",
93+
"baseline_kind",
94+
"baseline_id",
95+
"baseline_target_sha256",
96+
"captured_at",
97+
}
98+
)
99+
LIVE_CONTINUITY_BASELINE_ID_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{2,127}$")
56100
GENERATED_VARIABLES = {"RUNTIME_TARGET_JSON", "STRATEGY_PROFILE"}
57101
SECRET_MARKERS = ("PASSWORD", "PRIVATE_KEY", "TOKEN", "API_KEY", "ACCESS_KEY", "CLIENT_SECRET", "SECRET")
58102
LEGACY_INCOME_LAYER_VARIABLES = frozenset(
@@ -165,6 +209,15 @@ def compact_json(value: Any) -> str:
165209
return json.dumps(value, ensure_ascii=False, separators=(",", ":"))
166210

167211

212+
def runtime_target_fingerprint(runtime_target: dict[str, Any]) -> str:
213+
"""Fingerprint every frozen target field except its current run state."""
214+
215+
payload = dict(runtime_target)
216+
payload.pop("live_continuity", None)
217+
encoded = json.dumps(payload, ensure_ascii=True, sort_keys=True, separators=(",", ":"))
218+
return hashlib.sha256(encoded.encode("utf-8")).hexdigest()
219+
220+
168221
def env_string(value: Any) -> str:
169222
if isinstance(value, str):
170223
return value
@@ -425,8 +478,102 @@ def validate_runtime_target(target: dict[str, Any], errors: list[str]) -> None:
425478
ZoneInfo(market_timezone)
426479
except (ZoneInfoNotFoundError, ValueError):
427480
errors.append(f"runtime_target.market_timezone is invalid: {market_timezone!r}")
481+
validate_strategy_release(runtime_target, errors)
482+
validate_live_continuity(runtime_target, errors)
483+
484+
485+
def validate_strategy_release(runtime_target: dict[str, Any], errors: list[str]) -> None:
486+
"""Validate an optional immutable release identity without enabling it.
428487
488+
Existing targets intentionally remain valid without ``strategy_release``
489+
during the read-only migration. Once present, however, a partial identity
490+
is never accepted because it could be mistaken for a verified release.
491+
"""
492+
493+
release = runtime_target.get("strategy_release")
494+
if release is None:
495+
return
496+
if not isinstance(release, dict):
497+
errors.append("runtime_target.strategy_release must be an object when present")
498+
return
499+
unexpected = sorted(set(release) - set(STRATEGY_RELEASE_REQUIRED_FIELDS))
500+
if unexpected:
501+
errors.append(
502+
"runtime_target.strategy_release contains unsupported fields: "
503+
+ ", ".join(unexpected)
504+
)
505+
for field in STRATEGY_RELEASE_REQUIRED_FIELDS:
506+
value = release.get(field)
507+
if not isinstance(value, str) or not value.strip():
508+
errors.append(f"runtime_target.strategy_release.{field} is required")
509+
continue
510+
if field == "release_id" and not STRATEGY_RELEASE_ID_PATTERN.fullmatch(value.strip()):
511+
errors.append("runtime_target.strategy_release.release_id has invalid characters")
512+
if field in STRATEGY_RELEASE_DIGEST_FIELDS and not SHA256_PATTERN.fullmatch(value.strip()):
513+
errors.append(f"runtime_target.strategy_release.{field} must be a SHA-256 digest")
514+
if field == "effective_session":
515+
try:
516+
date.fromisoformat(value.strip())
517+
except ValueError:
518+
errors.append(
519+
"runtime_target.strategy_release.effective_session must be an ISO-8601 date"
520+
)
429521

522+
523+
def validate_live_continuity(runtime_target: dict[str, Any], errors: list[str]) -> None:
524+
"""Validate a frozen incumbent baseline independently of P0--P6 policy."""
525+
526+
continuity = runtime_target.get("live_continuity")
527+
if continuity is None:
528+
return
529+
if not isinstance(continuity, dict):
530+
errors.append("runtime_target.live_continuity must be an object when present")
531+
return
532+
unsupported = sorted(set(continuity) - LIVE_CONTINUITY_FIELDS)
533+
if unsupported:
534+
errors.append(
535+
"runtime_target.live_continuity contains unsupported fields: " + ", ".join(unsupported)
536+
)
537+
for field in sorted(LIVE_CONTINUITY_FIELDS):
538+
value = continuity.get(field)
539+
if not isinstance(value, str) or not value.strip():
540+
errors.append(f"runtime_target.live_continuity.{field} is required")
541+
542+
state = str(continuity.get("state") or "").strip().upper()
543+
if state not in LIVE_CONTINUITY_STATES:
544+
errors.append(
545+
"runtime_target.live_continuity.state must be one of "
546+
+ ", ".join(sorted(LIVE_CONTINUITY_STATES))
547+
)
548+
baseline_kind = str(continuity.get("baseline_kind") or "").strip()
549+
if baseline_kind not in LIVE_CONTINUITY_BASELINE_KINDS:
550+
errors.append(
551+
"runtime_target.live_continuity.baseline_kind must be one of "
552+
+ ", ".join(sorted(LIVE_CONTINUITY_BASELINE_KINDS))
553+
)
554+
baseline_id = str(continuity.get("baseline_id") or "").strip()
555+
if baseline_id and not LIVE_CONTINUITY_BASELINE_ID_PATTERN.fullmatch(baseline_id):
556+
errors.append("runtime_target.live_continuity.baseline_id has invalid characters")
557+
digest = str(continuity.get("baseline_target_sha256") or "").strip().lower()
558+
digest = digest.removeprefix("sha256:")
559+
if not SHA256_PATTERN.fullmatch(digest):
560+
errors.append("runtime_target.live_continuity.baseline_target_sha256 must be a SHA-256 digest")
561+
elif digest != runtime_target_fingerprint(runtime_target):
562+
errors.append(
563+
"runtime_target.live_continuity.baseline_target_sha256 does not match the runtime target"
564+
)
565+
captured_at = str(continuity.get("captured_at") or "").strip()
566+
if captured_at:
567+
try:
568+
date.fromisoformat(captured_at)
569+
except ValueError:
570+
errors.append("runtime_target.live_continuity.captured_at must be an ISO-8601 date")
571+
if baseline_kind == "release_attested" and not isinstance(runtime_target.get("strategy_release"), dict):
572+
errors.append("release_attested live_continuity requires runtime_target.strategy_release")
573+
574+
575+
def is_live_continuity_target(runtime_target: dict[str, Any]) -> bool:
576+
return isinstance(runtime_target.get("live_continuity"), dict)
430577
def validate_live_ibkr_us_scheduler(
431578
runtime_target: dict[str, Any],
432579
scheduler: dict[str, Any],
@@ -521,12 +668,26 @@ def validate_runtime_target_strategy_policy(runtime_target: dict[str, Any], erro
521668
and deployment.get("live_configured") is False
522669
):
523670
errors.append(f"platform {platform_id} has no live runtime configuration")
671+
continuity_target = is_live_continuity_target(runtime_target)
524672
allowed_modes = normalize_allowed_execution_modes(strategy.get("allowed_execution_modes"))
525-
if allowed_modes and execution_mode not in allowed_modes:
673+
if allowed_modes and execution_mode not in allowed_modes and not continuity_target:
526674
errors.append(f"runtime_target.strategy_profile {profile} does not allow {execution_mode} execution")
527675

528676
if execution_mode != "live":
529677
return
678+
if continuity_target:
679+
continuity_policy = strategy.get("live_continuity")
680+
if not isinstance(continuity_policy, dict) or continuity_policy.get("eligible") is not True:
681+
errors.append(
682+
f"runtime_target.strategy_profile {profile} is not eligible for live continuity"
683+
)
684+
return
685+
allowed_platforms = continuity_policy.get("allowed_platforms")
686+
if not isinstance(allowed_platforms, list) or platform_id not in allowed_platforms:
687+
errors.append(
688+
f"runtime_target.strategy_profile {profile} live continuity is not allowed on {platform_id}"
689+
)
690+
return
530691
lifecycle_stage = str(strategy.get("lifecycle_stage") or "").strip()
531692
if strategy.get("runtime_enabled") is not True:
532693
errors.append(f"runtime_target.strategy_profile {profile} is not runtime_enabled")

python/scripts/sync_strategy_switch_page_asset.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,15 @@ def _enrich_profiles_from_config(profiles: list[dict]) -> list[dict]:
7777
"blocked_live_reason",
7878
"" if blocked_live_reason is None else str(blocked_live_reason).strip(),
7979
)
80+
continuity = config_fields.get("live_continuity")
81+
if isinstance(continuity, dict):
82+
item.setdefault(
83+
"live_continuity",
84+
{
85+
"eligible": continuity.get("eligible") is True,
86+
"allowed_platforms": list(continuity.get("allowed_platforms") or []),
87+
},
88+
)
8089
item.setdefault("runtime_enabled", runtime_enabled)
8190
enriched.append(item)
8291
return enriched

0 commit comments

Comments
 (0)