ZaStoGram/Tools/check_mtproxy_verdict_reducer.py
loop-uh cfafa0c98c
Some checks failed
ZaStoGram source guards / guards (push) Failing after 48s
Build three ZaStoGram APKs / build (armeabi-v7a, ZaStoGram-standalone-armeabi-v7a, Armv7, armv7) (push) Failing after 2m46s
Build three ZaStoGram APKs / build (x86, ZaStoGram-standalone-x86, X86, x86) (push) Failing after 2m59s
Build three ZaStoGram APKs / build (arm64-v8a, ZaStoGram-standalone-arm64-v8a, Arm64, arm64) (push) Failing after 3m14s
Обновить Telegram до 12.10.3 и подключить WEB-прокси
Влит DrKLO/Telegram master 9552e5541 (12.10.2 7086, 12.10.3 7089,
update submodules, PR #1833 с вибрацией звонков на Android 13+).
Сабмодули td, boringssl и media добавлены, tlottie обновлён, как в апстриме.

WEB-прокси из апстрима встроен в прокси-слой ZaStoGram: ProxyInfo хранит
ProxySettings, список прокси получил схему V5 с типом (V3 уже занят старыми
WSS-полями), ссылки tg://webproxy и t.me/webproxy разбираются ProxyLinkHelper,
тип WEB есть в редакторе прокси. Нативный слой видит WEB как обычный MTProxy
на локальном мосте с секретом самого прокси и MtProxyOptions.disabled(): без
FakeTLS, фрагментации, WSS и soft mux. Страж check_web_proxy_isolation.py
держит этот контракт.

Standalone оставлен без shrinkResources: плагины ищут ресурсы по имени.
2026-09-23 16:31:29 +03:00

513 lines
28 KiB
Python

#!/usr/bin/env python3
from pathlib import Path
import re
import sys
ROOT = Path(__file__).resolve().parents[1]
MESSENGER = ROOT / "TMessagesProj/src/main/java/org/telegram/messenger"
TGNET_JAVA = ROOT / "TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java"
TGNET = ROOT / "TMessagesProj/jni/tgnet"
JNI = ROOT / "TMessagesProj/jni"
VALUES = ROOT / "TMessagesProj/src/main/res/values/strings.xml"
VALUES_RU = ROOT / "TMessagesProj/src/main/res/values-ru/strings.xml"
def read(path: Path) -> str:
return path.read_text(encoding="utf-8")
def require(condition: bool, message: str, failures: list[str]) -> None:
if not condition:
failures.append(message)
def method_body(source: str, signature: str) -> str:
start = source.find(signature)
if start < 0:
return ""
brace = source.find("{", start)
if brace < 0:
return ""
depth = 0
for index in range(brace, len(source)):
char = source[index]
if char == "{":
depth += 1
elif char == "}":
depth -= 1
if depth == 0:
return source[brace:index + 1]
return ""
def string_has_case(source: str, method: str, constant: str, resource: str) -> bool:
body = method_body(source, method)
case = f"case {constant}:"
case_index = body.find(case)
if case_index < 0:
return False
next_case = body.find("case ", case_index + len(case))
section = body[case_index: next_case if next_case >= 0 else len(body)]
return resource in section
def main() -> int:
failures: list[str] = []
diagnostics = read(MESSENGER / "ProxyCheckDiagnostics.java")
phase_policy = read(MESSENGER / "ProxyPhasePolicy.java")
verdict = read(MESSENGER / "ProxyEndpointVerdict.java")
visible = read(MESSENGER / "ProxyVisibleStateStore.java")
health = read(MESSENGER / "ProxyHealthStore.java")
runtime = read(MESSENGER / "ProxyRuntimeStateStore.java")
reducer = read(MESSENGER / "ProxyEventReducer.java")
event = read(MESSENGER / "ProxyConnectionEvent.java")
java_connections = read(TGNET_JAVA)
proxy_scheduler = read(MESSENGER / "ProxyCheckScheduler.java")
shared_config = read(MESSENGER / "SharedConfig.java")
status_mirror = read(MESSENGER / "ProxyStatusMirror.java")
proxy_list = read(ROOT / "TMessagesProj/src/main/java/org/telegram/ui/ProxyListActivity.java")
proxy_settings = read(ROOT / "TMessagesProj/src/main/java/org/telegram/ui/ProxySettingsActivity.java")
android_utilities = read(MESSENGER / "AndroidUtilities.java")
connection_socket_h = read(TGNET / "ConnectionSocket.h")
connection_socket_cpp = read(TGNET / "ConnectionSocket.cpp")
startup_timeline = read(TGNET.parent / "mtproxy/MtProxyStartupTimeline.cpp")
manager_h = read(TGNET / "ConnectionsManager.h")
manager_cpp = read(TGNET / "ConnectionsManager.cpp")
defines_h = read(TGNET / "Defines.h")
wrapper = read(JNI / "TgNetWrapper.cpp")
values = read(VALUES)
values_ru = read(VALUES_RU)
reducer_body = method_body(reducer, "static ProxyRuntimeStateStore.Decision reduce")
usable_success_body = method_body(reducer, "private static ProxyRuntimeStateStore.Decision applyVisibleUsableSuccess")
shadow_idx = reducer_body.find("ProxyCheckDiagnostics.SHADOWED_SOCKET_FAILURE.equals(normalizedPhase)")
shadow_end = reducer_body.find("boolean concretePhase", shadow_idx)
shadow_body = reducer_body[shadow_idx:shadow_end] if shadow_idx >= 0 and shadow_end >= 0 else ""
for field in ("layer", "failureClass", "confidence", "action", "userTextKey", "endpointKey", "networkKey"):
require(f"final String {field}" in verdict, f"ProxyEndpointVerdict must expose {field}", failures)
require("final long stickyUntilMs" in verdict, "ProxyEndpointVerdict must expose stickyUntilMs", failures)
require("final int activationGeneration" in verdict, "ProxyEndpointVerdict must expose activationGeneration", failures)
require("final ProxyConnectionEvent.Origin origin" in verdict, "ProxyEndpointVerdict must expose origin", failures)
for layer in (
"scheduler_local",
"dns",
"tcp",
"mtproxy_plain",
"faketls_handshake",
"post_handshake_data",
"lifecycle_cancelled",
):
require(f'"{layer}"' in verdict, f"ProxyEndpointVerdict layer taxonomy must include {layer}", failures)
for old_layer in ('"local"', '"faketls"', '"mtproto"', '"config"', '"lifecycle"'):
require(old_layer not in verdict and old_layer not in phase_policy, f"verdict layer taxonomy must not use old layer {old_layer}", failures)
for failure_class in (
"tcp_refused",
"tcp_timeout",
"tcp_gate_wait_timeout",
"dns_failed",
"mtproxy_no_response_after_send",
"post_success_data_path_degraded",
"faketls_no_server_hello",
"faketls_bad_server_flight",
"secret_invalid",
"probe_wait_timeout",
"stale_generation_cancelled",
):
require(f'"{failure_class}"' in verdict or f'"{failure_class}"' in phase_policy, f"failureClass taxonomy must include {failure_class}", failures)
for old_class in (
"pre_tcp_local_wait",
"dns_failure",
"tcp_failure",
"no_bytes_after_client_hello",
"server_bytes_parser_failure",
"server_hello_hmac_mismatch",
"post_handshake_no_app_data",
"config_invalid_secret",
"cancelled_or_shadowed",
):
require(old_class not in verdict and old_class not in phase_policy, f"failureClass taxonomy must not use old evidence bucket {old_class}", failures)
require(
"public static ProxyEndpointVerdict verdictForPhase" in phase_policy
and "layerForPhase" in phase_policy
and "failureClassForPhase" in phase_policy
and "verdictForEvent" in phase_policy
and "userTextKeyForFailureClass" in phase_policy
and "freshFailureHoldEarlyRetryMs()" in phase_policy,
"ProxyPhasePolicy must build a unified ProxyEndpointVerdict with identity/layer/failure/action/text/sticky data",
failures,
)
require(
"ProxyPhasePolicy.verdictForPhase(diagnostic, 0)" in diagnostics
and "diagnosticResourceId(verdict.userTextKey)" in diagnostics,
"diagnostic UI text must resolve through ProxyEndpointVerdict.userTextKey",
failures,
)
require(
"userTextKeyForFailureClass" in phase_policy
and "FAILURE_CLASS_TCP_REFUSED" in phase_policy
and "ProxyStatusTcpConnectionRefused" in phase_policy
and "FAILURE_CLASS_MTPROXY_NO_RESPONSE_AFTER_SEND" in phase_policy
and "ProxyStatusMtproxyPacketSentNoResponse" in phase_policy
and "FAILURE_CLASS_POST_SUCCESS_DATA_PATH_DEGRADED" in phase_policy
and "ProxyStatusDroppedAfterAppData" in phase_policy,
"failure UI text must be selected from failureClass taxonomy, not only raw phase strings",
failures,
)
failure_text = method_body(phase_policy, "static String userTextKeyForFailureClass")
require(
"FAILURE_CLASS_FAKETLS_NO_SERVER_HELLO" in failure_text
and "FAKETLS_SERVER_HELLO_WAIT_TIMEOUT" in failure_text
and "SERVER_CLOSED_AFTER_CLIENT_HELLO" in failure_text
and "FAKETLS_NO_SERVER_HELLO_TERMINAL" in failure_text
and "FAKETLS_SERVER_CLOSED_TERMINAL" in failure_text
and "return userTextKeyForPhase(phase)" in failure_text,
"faketls no-server-hello failureClass must preserve phase-specific UI keys for timeout/server-close/terminal phases",
failures,
)
require(
"FAILURE_CLASS_FAKETLS_BAD_SERVER_FLIGHT" in failure_text
and "FAKETLS_NOT_MTPROXY_RESPONSE" in failure_text
and "return userTextKeyForPhase(phase)" in failure_text,
"faketls bad-server-flight failureClass must preserve the terminal not-MTProxy response UI key",
failures,
)
missing_map = {
"MTPROXY_PROBE_WAIT": "ProxyStatusMtproxyProbeWait",
"MTPROXY_PROBE_WAIT_TIMEOUT": "ProxyStatusMtproxyProbeWaitTimeout",
"FAKETLS_SERVER_HELLO_WAIT_TIMEOUT": "ProxyStatusFaketlsServerHelloWaitTimeout",
"SERVER_CLOSED_AFTER_CLIENT_HELLO": "ProxyStatusServerClosedAfterClientHello",
"FAKETLS_NOT_MTPROXY_RESPONSE": "ProxyStatusFaketlsNotMtproxyResponse",
"FAKETLS_NO_SERVER_HELLO_TERMINAL": "ProxyStatusFaketlsNoServerHelloTerminal",
"FAKETLS_SERVER_CLOSED_TERMINAL": "ProxyStatusFaketlsServerClosedTerminal",
"BACKGROUND_HANDSHAKE_ABORTED": "ProxyStatusBackgroundHandshakeAborted",
"TCP_CONNECTION_REFUSED": "ProxyStatusTcpConnectionRefused",
"TCP_CONNECT_TIMEOUT": "ProxyStatusTcpConnectTimeout",
}
for constant, resource in missing_map.items():
require(resource in values, f"English strings must define {resource}", failures)
require(resource in values_ru, f"Russian strings must define {resource}", failures)
require(
string_has_case(diagnostics, "private static HeaderStatusTitle diagnosticTitle", constant, f"R.string.{resource}"),
f"diagnosticTitle must map {constant} to {resource}",
failures,
)
require(
string_has_case(diagnostics, "public static String diagnosticText", constant, f"R.string.{resource}"),
f"diagnosticText must map {constant} to {resource}",
failures,
)
keep_failure = method_body(diagnostics, "private static boolean shouldKeepFreshFailure")
weak_method = method_body(diagnostics, "public static boolean isWeakRetryLivePhase")
breakthrough_method = method_body(diagnostics, "public static boolean isFreshFailureBreakthroughPhase")
for constant in (
"MTPROXY_PROBE_WAIT",
"TCP_CONNECT_GATE",
"DNS_CACHE_HIT",
"ENDPOINT_COOLDOWN",
"CONNECT_START",
"CLIENT_HELLO_SENT",
"ADMISSION_HOLD_AFTER_CLIENT_HELLO_FAILURE",
):
require(
f"case {constant}:" in weak_method or f"case ProxyCheckDiagnostics.{constant}:" in weak_method,
f"fresh failure sticky window must treat {constant} as weak retry/live telemetry",
failures,
)
require(
"isWeakRetryLivePhase(incomingDiagnostic)" in keep_failure,
"fresh failure hold must use the weak retry/live phase classifier",
failures,
)
require(
"isFreshFailureBreakthroughPhase(incomingDiagnostic)" in keep_failure,
"fresh failure hold must explicitly allow only real progress/success breakthrough phases",
failures,
)
for constant in ("SERVER_HELLO_HMAC_OK", "ON_CONNECTED", "FIRST_TLS_APP_RECV"):
require(
f"case {constant}:" in breakthrough_method
and f"case {constant}:" not in weak_method,
f"{constant} must break fresh failure hold without being weak retry/live telemetry",
failures,
)
require(
"shouldKeepFreshFailure(SharedConfig.ProxyInfo proxyInfo, String incomingDiagnostic, int incomingActivationGeneration)" in diagnostics
and (
"incomingActivationGeneration == proxyInfo.lastCheckActivationGeneration" in diagnostics
or "incomingActivationGeneration != proxyInfo.lastCheckActivationGeneration" in diagnostics
)
and "ProxyCheckDiagnostics.shouldKeepFreshFailure(proxyInfo, event.phase, event.activationGeneration)" in visible,
"fresh failure sticky hold must be bound to the visible failure activation generation",
failures,
)
require(
"int lastCheckActivationGeneration" in shared_config
and "mirrorVisiblePhase(SharedConfig.ProxyInfo proxyInfo, String phase, long now, int activationGeneration)" in status_mirror
and "proxyInfo.lastCheckActivationGeneration = activationGeneration" in status_mirror
and "ProxyStatusMirror.mirrorVisiblePhase(proxyInfo, event, visiblePhase)" in visible
and "ProxyRuntimeStateStore.applyConnectionUsable(currentProxy, event.phase, event.timestamp, event.activationGeneration)" in usable_success_body,
"visible diagnostic state must record the activationGeneration from native socket verdict events",
failures,
)
# isPunitiveFailure delegates to the generated ProxyPhaseClassification.
policy_punitive = method_body(
read(MESSENGER / "ProxyPhaseClassification.java"),
"public static boolean needsReconnectBackoff",
)
policy_failure_class = method_body(phase_policy, "public static String failureClassForPhase")
policy_classify = method_body(phase_policy, "private static PhaseInfo classify")
required_failure_classes = {
"TCP_CONNECTION_REFUSED": "FAILURE_CLASS_TCP_REFUSED",
"TCP_CONNECT_TIMEOUT": "FAILURE_CLASS_TCP_TIMEOUT",
"TCP_CONNECT_GATE_TIMEOUT": "FAILURE_CLASS_TCP_GATE_WAIT_TIMEOUT",
"MTPROXY_PROBE_WAIT_TIMEOUT": "FAILURE_CLASS_PROBE_WAIT_TIMEOUT",
"MTPROXY_PACKET_SENT_NO_RESPONSE": "FAILURE_CLASS_MTPROXY_NO_RESPONSE_AFTER_SEND",
"DROPPED_AFTER_APPDATA": "FAILURE_CLASS_POST_SUCCESS_DATA_PATH_DEGRADED",
"SECRET_PARSE_INVALID_DOMAIN": "FAILURE_CLASS_SECRET_INVALID",
"IGNORED_CANCELLED_GENERATION": "FAILURE_CLASS_STALE_GENERATION_CANCELLED",
}
for constant, failure_class in required_failure_classes.items():
require(
f"case ProxyCheckDiagnostics.{constant}:" in policy_failure_class
and failure_class in policy_failure_class[policy_failure_class.find(f"case ProxyCheckDiagnostics.{constant}:"):],
f"{constant} must map to {failure_class}",
failures,
)
from mtproxy_phase_contract import java_policy
for constant in ("TCP_CONNECTION_REFUSED", "TCP_CONNECT_TIMEOUT"):
require(
f'case "{constant.lower()}":' in policy_punitive
and java_policy(constant.lower()) == ("failure", "network", True, True)
and f"case ProxyCheckDiagnostics.{constant}:" in policy_failure_class,
f"{constant} must be a punitive network TCP failure with exact failureClass",
failures,
)
require(
"ProxyEndpointVerdict verdict = ProxyPhasePolicy.verdictForEvent(event)" in reducer_body
and "verdict.failureClass" in reducer_body
and "failure_class=" in reducer_body
and "verdict.stickyUntilMs" in reducer_body
and "new ProxyRuntimeStateStore.Decision(\"backoff\", verdict.phase" in reducer_body,
"reducer must use ProxyEndpointVerdict as the visible/backoff decision contract, not a decorative object",
failures,
)
require(
"ProxyPhasePolicy.postSuccessDataPathVerdict(verdict)" in reducer_body
and "FAILURE_CLASS_POST_SUCCESS_DATA_PATH_DEGRADED" in phase_policy,
"reducer must upgrade post-success no-response failures to post_success_data_path_degraded",
failures,
)
require(
"visiblePhaseForVerdict(verdict)" in reducer_body
and "ProxyEndpointVerdict.FAILURE_CLASS_POST_SUCCESS_DATA_PATH_DEGRADED.equals(verdict.failureClass)" in reducer
and "ProxyCheckDiagnostics.DROPPED_AFTER_APPDATA" in reducer,
"post-success degraded verdict must write a visible diagnostic that resolves to the degraded data-path UI text",
failures,
)
visible_write = reducer_body.find("ProxyVisibleStateStore.mirrorVisiblePhaseIfAllowed")
stale_generation = reducer_body.find("ProxyRuntimeStateStore.shouldIgnoreStaleActivationGeneration(event)")
coalesce_probe = reducer_body.find("ProxyVisibleStateStore.shouldCoalesceProbeWait(currentProxy, event)")
fresh_failure_hold = reducer_body.find("ProxyVisibleStateStore.shouldHoldVisiblePhaseByFreshFailure(currentProxy, event)")
shadow_failure = reducer_body.find("ProxyCheckDiagnostics.SHADOWED_SOCKET_FAILURE.equals(normalizedPhase)")
remember_shadow = reducer_body.find("ProxyHealthStore.rememberPostSuccessDataPathShadow")
require(
stale_generation >= 0
and shadow_failure >= 0
and remember_shadow >= 0
and stale_generation < shadow_failure
and stale_generation < remember_shadow,
"shadowed_socket_failure must obey stale activation generation before consuming post-success shadow budget",
failures,
)
require(
stale_generation >= 0 and stale_generation < reducer_body.find("ProxyWarmupGate.onProxyLivePhase"),
"reducer must ignore stale activation generations before live/failure state changes",
failures,
)
require(
fresh_failure_hold >= 0 and coalesce_probe >= 0 and visible_write >= 0 and fresh_failure_hold < coalesce_probe < visible_write,
"probe-wait coalescing must run after sticky failure hold and before visible writes",
failures,
)
require(
'return new ProxyRuntimeStateStore.Decision("telemetry_only"' in reducer_body[coalesce_probe:visible_write],
"coalesced probe-wait joiners must become telemetry-only decisions",
failures,
)
require(
"PROBE_WAIT_VISIBLE_REPEAT_MS" in visible
and "lastVisibleProbeWaitEndpointKey" in visible
and "lastVisibleProbeWaitProbeKey" in visible
and "lastVisibleProbeWaitActivationGeneration" in visible
and "event.activationGeneration == lastVisibleProbeWaitActivationGeneration" in visible
and "resetProbeWaitCoalescing" in visible,
"visible state store must coalesce repeated mtproxy_probe_wait per endpoint/probe/generation",
failures,
)
require("activationGeneration" in event and "final int activationGeneration" in event, "ProxyConnectionEvent must carry activationGeneration", failures)
require("final String networkKey" in event and "ProxyEndpointKey.networkFromLiveStage" in event, "ProxyConnectionEvent must carry/derive networkKey", failures)
require("ACTIVE_SOCKET(\"active_socket\")" in event and "ACTIVE_PROXY" not in event, "ProxyConnectionEvent must expose active_socket, not active_proxy, as the active origin", failures)
require("USER_SELECT(\"user_select\")" in event and "ROTATION_CANDIDATE(\"rotation_candidate\")" in event, "ProxyConnectionEvent origins must include user_select and rotation_candidate causes", failures)
require(
"isActiveProxyOrigin" in event
and "case USER_SELECT:" in event
and "case STARTUP_RESTORE:" not in method_body(event, "public static boolean isActiveProxyOrigin")
and "case BACKGROUND_KEEPALIVE:" not in method_body(event, "public static boolean isActiveProxyOrigin")
and "isHealthOrigin" in event
and "canDriveVisible(ProxyConnectionEvent event)" in event
and "canDriveRotation(ProxyConnectionEvent event, ProxyEndpointVerdict verdict)" in event
and "ProxyConnectionEvent.isHealthOrigin(event.origin)" in read(MESSENGER / "ProxyEventReducer.java"),
"active origins must be split from lifecycle health-only and visible/rotation ownership",
failures,
)
require(
"noteProxySettingsActivation(ProxyConnectionEvent.Origin origin)" in runtime
and "noteProxyStartupRestoreActivation(int account)" in runtime
and "shouldIgnoreStaleActivationGeneration" in runtime,
"runtime store must own settings/startup activation generation floors",
failures,
)
stale_generation_method = method_body(runtime, "static boolean shouldIgnoreStaleActivationGeneration")
require(
"event.activationGeneration <= 0" not in stale_generation_method
and "event.activationGeneration != floor" in stale_generation_method,
"activation generation gate must drop any active socket event whose generation differs from the current account generation",
failures,
)
require("proxyActivationGeneration" in manager_h and "getProxyActivationGeneration" in manager_h and "proxyActivationOrigin" in manager_h and "getProxyActivationOrigin" in manager_h, "native ConnectionsManager must own proxy activation generation and origin", failures)
require(
"uint32_t activationGeneration" in manager_h
and "uint32_t activationGeneration" in manager_cpp
and "proxyActivationGeneration = activationGeneration" in manager_cpp
and "proxyActivationOrigin = activationOrigin.empty() ? \"active_socket\" : activationOrigin" in manager_cpp
and "setProxyActivationContext" in manager_h
and "setProxyActivationContext" in manager_cpp
and "getProxyActivationGeneration()" in manager_cpp
and "getProxyActivationOrigin()" in manager_cpp,
"native setProxySettings/resume context must accept the Java activation generation and active_socket origin instead of counting independently",
failures,
)
require(
"proxyActivationGeneration = manager.getProxyActivationGeneration()" in connection_socket_cpp
and "proxyActivationOrigin = manager.getProxyActivationOrigin()" in connection_socket_cpp
and "origin == \"active_socket\"" in connection_socket_cpp
and "proxyActivationOrigin" in connection_socket_cpp,
"ConnectionSocket must capture activation generation/origin at open and only override ordinary active_socket origin",
failures,
)
require("proxyActivationGeneration" in connection_socket_h and "proxyActivationOrigin" in connection_socket_h, "ConnectionSocket must store captured activation generation and origin", failures)
require("onProxyConnectionStageChanged(int32_t instanceNum, std::string diagnostic, std::string endpointKey, std::string probeKey, std::string origin, std::string socketRole, int32_t activationGeneration, int32_t suggestedReconnectHoldMs)" in defines_h, "native delegate must pass socketRole, activationGeneration and the retry-authority hold", failures)
require("CallStaticVoidMethod" in wrapper and "socketRoleString" in wrapper and "activationGeneration" in wrapper and "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;II)V" in wrapper, "JNI stage wrapper must forward socketRole, activationGeneration and the native hold to Java", failures)
require(
"int activationGeneration = ProxyRuntimeStateStore.noteProxyStartupRestoreActivation(currentAccount)" in java_connections
and "ProxyConnectionEvent.Origin.STARTUP_RESTORE.wireName" in java_connections
and "boolean hasSelectedProxy = enabled && !TextUtils.isEmpty(address)" in java_connections
and "hasSelectedProxy ? ProxyRuntimeStateStore.noteProxySettingsActivation(activationOrigin) : 0" in java_connections
and "native_setProxySettings(a, address, port, username, password, secret, enabledOptions, activationGeneration, activationOrigin.wireName)" in java_connections
and "publishProxyActivationContext" not in java_connections,
"Java must pass settings/startup generations to native without lifecycle proxy mutation",
failures,
)
require(
"jint activationGeneration" in wrapper
and "jstring activationOrigin" in wrapper
and "MtProxyOptions;ILjava/lang/String;)V" in wrapper
and "native_setProxyActivationContext" in wrapper
and "activationGeneration > 0 ? (uint32_t) activationGeneration : 0" in wrapper,
"JNI native_setProxySettings/native_setProxyActivationContext must bridge activationGeneration and origin to native",
failures,
)
require(
"decision.diagnostic = \"tcp_connect_timeout\"" in startup_timeline
and "return \"tcp_connect_timeout\"" in startup_timeline,
"startup timeline must expose real TCP connect timeout as tcp_connect_timeout",
failures,
)
terminal_module_cpp = read(TGNET.parent / "mtproxy/MtProxyTerminalDiagnostic.cpp")
require(
"input.socketError == ECONNREFUSED" in terminal_module_cpp
and "TcpConnectionRefused" in terminal_module_cpp
and "input.socketError == ETIMEDOUT" in terminal_module_cpp
and "TcpConnectTimeout" in terminal_module_cpp
and "input.closeReason == 2" in terminal_module_cpp,
"native terminal diagnostic must split ECONNREFUSED and TCP timeout before generic tcp_not_connected",
failures,
)
analyzer = read(ROOT / "Tools/analyze_mtproxy_markers.py")
require(
'return "tcp_connection_refused"' in analyzer
and 'return "tcp_connect_timeout"' in analyzer,
"analyzer verdicts must preserve split TCP refused/timeout failures",
failures,
)
java_stage = method_body(java_connections, "private static void processProxyConnectionStage(ProxyConnectionEvent event)")
require(
"ProxyRuntimeStateStore.Decision decision = ProxyRuntimeStateStore.onNativeStage(event)" in java_stage
and "if (!shouldNotifyProxyConnectionStage(decision))" in java_stage
and "return;" in java_stage[java_stage.find("if (!shouldNotifyProxyConnectionStage(decision))"):],
"Java UI-stage processor must suppress notifications for telemetry-only reducer decisions",
failures,
)
notify_method = method_body(java_connections, "private static boolean shouldNotifyProxyConnectionStage")
require(
"decision.visibleChanged" in notify_method and "decision.rotationTrigger" in notify_method,
"notification gate must allow visible changes and rotation triggers",
failures,
)
mark_start = method_body(visible, "static boolean markConnectionStarting")
require(
"origin == ProxyConnectionEvent.Origin.USER_SELECT" in mark_start
and "origin == ProxyConnectionEvent.Origin.SETTINGS_CHANGE" in mark_start
and "ProxyHealthStore.clearUsableSuccessHold(proxyInfo" in mark_start
and "ProxyStatusMirror.markConnectionStarting(proxyInfo, now, origin)" in mark_start,
"explicit user/settings activation must force connect_start and clear stale usable-success hold",
failures,
)
require(
"ProxyCheckDiagnostics.shouldKeepFreshFailure(proxyInfo, ProxyCheckDiagnostics.CONNECT_START)" in mark_start
and "decision=held_by_fresh_failure" in mark_start
and mark_start.find("ProxyCheckDiagnostics.shouldKeepFreshFailure(proxyInfo, ProxyCheckDiagnostics.CONNECT_START)") < mark_start.rfind("ProxyStatusMirror.markConnectionStarting(proxyInfo, now, origin)"),
"routine Java connect_start must not overwrite a fresh terminal failure",
failures,
)
require("clearUsableSuccessHold" in health, "ProxyHealthStore must expose explicit activation usable-success clearing", failures)
require("markConnectionStarting(SharedConfig.ProxyInfo proxyInfo, ProxyConnectionEvent.Origin origin)" in runtime, "runtime store must accept markConnectionStarting origin", failures)
require("markConnectionStarting(SharedConfig.ProxyInfo proxyInfo, ProxyConnectionEvent.Origin origin)" in proxy_scheduler, "scheduler facade must accept markConnectionStarting origin", failures)
require(
"SharedConfig.currentProxy = info" in proxy_list
and "ProxyCheckScheduler.markConnectionStarting(SharedConfig.currentProxy, ProxyConnectionEvent.Origin.USER_SELECT)" in proxy_list
and "ConnectionsManager.setProxySettings(useProxySettings, SharedConfig.currentProxy.settings, ProxyConnectionEvent.Origin.USER_SELECT)" in proxy_list,
"ProxyListActivity saved-proxy tap must select the proxy with USER_SELECT generation/origin",
failures,
)
require("ProxyConnectionEvent.Origin.SETTINGS_CHANGE" in proxy_settings, "ProxySettingsActivity apply must mark settings_change activation", failures)
require("ProxyConnectionEvent.Origin.USER_SELECT" in android_utilities, "proxy link apply must mark user_select activation", failures)
require("ProxyConnectionEvent.Origin.ROTATION_CANDIDATE" in read(MESSENGER / "ProxyRotationController.java"), "rotation controller must mark rotation_candidate activation", failures)
require(
"POST_SUCCESS_DATA_PATH_SHADOWS = 1" in health
and "POST_SUCCESS_DATA_PATH_SHADOWS = 1" in connection_socket_cpp + manager_cpp + read(TGNET.parent / "mtproxy/MtProxyEndpointPolicy.cpp")
and "postSuccessDataPathShadowCount >= POST_SUCCESS_DATA_PATH_SHADOWS" in health
and "postSuccessDataPathShadowCount >= MT_PROXY_ENDPOINT_POST_SUCCESS_DATA_PATH_SHADOWS" in read(TGNET.parent / "mtproxy/MtProxyEndpointPolicy.cpp"),
"post-success data-path shadow budget must stay bounded to one in Java and native",
failures,
)
if failures:
print("MTProxy verdict reducer guard failed:", file=sys.stderr)
for failure in failures:
print(f" - {failure}", file=sys.stderr)
return 1
print("MTProxy verdict reducer guard passed.")
return 0
if __name__ == "__main__":
raise SystemExit(main())