|
1 | 1 | #!/usr/bin/env python3 |
2 | | -"""Inject passive sanitized HTTP evidence into the accepted NuvioDesktop runtime. |
| 2 | +"""Compatibility shim: NuvioDesktop runtime instrumentation is disabled. |
3 | 3 |
|
4 | | -Desktop test stdout is not a reliable evidence transport because Gradle may capture |
5 | | -it. The injected bridge therefore appends only NiakVIO's sanitized FIELD_NATIVE_HTTP |
6 | | -records to a dedicated workspace file consumed by the lab after the test. |
| 4 | +The native Desktop lab must observe the official runtime without patching |
| 5 | +PluginRuntime, FetchBridge or any repository/network loader. This historical entry |
| 6 | +point is retained only so old callers remain harmless and policy-compliant. |
7 | 7 | """ |
8 | 8 | from __future__ import annotations |
9 | 9 |
|
10 | 10 | import argparse |
11 | 11 | from pathlib import Path |
12 | 12 |
|
13 | 13 |
|
14 | | -def replace_once(text: str, old: str, new: str, label: str) -> str: |
15 | | - count = text.count(old) |
16 | | - if count != 1: |
17 | | - raise SystemExit(f"desktop evidence instrumentation anchor {label!r} count={count}") |
18 | | - return text.replace(old, new, 1) |
19 | | - |
20 | | - |
21 | | -def evidence_write(expression: str) -> str: |
22 | | - return ( |
23 | | - 'runCatching { java.io.File(System.getenv("GITHUB_WORKSPACE") ?: ".", ' |
24 | | - f'"desktop-native-http-evidence.log").appendText({expression} + "\\n") }}' |
25 | | - ) |
26 | | - |
27 | | - |
28 | 14 | def main() -> int: |
29 | 15 | parser = argparse.ArgumentParser() |
30 | 16 | parser.add_argument("repo") |
31 | 17 | args = parser.parse_args() |
32 | 18 | repo = Path(args.repo).resolve() |
33 | | - runtime = repo / "composeApp/src/fullCommonMain/kotlin/com/nuvio/app/features/plugins/runtime/PluginRuntime.kt" |
34 | | - bridge = repo / "composeApp/src/fullCommonMain/kotlin/com/nuvio/app/features/plugins/runtime/network/FetchBridge.kt" |
35 | | - runtime_text = runtime.read_text(encoding="utf-8") |
36 | | - bridge_text = bridge.read_text(encoding="utf-8") |
37 | | - if "FIELD_NATIVE_HTTP_REQUEST client=desktop" in bridge_text: |
38 | | - print(f"FIELD_NATIVE_EVIDENCE_INSTRUMENTED client=desktop bridge={bridge}") |
39 | | - return 0 |
40 | | - |
41 | | - runtime_text = replace_once( |
42 | | - runtime_text, |
43 | | - "addModule(FetchBridge())", |
44 | | - "addModule(FetchBridge(scraperId, mediaType))", |
45 | | - "bridge ownership", |
46 | | - ) |
47 | | - |
48 | | - # Nuvio intentionally turns a thrown getStreams() into [] inside QuickJS. Keep |
49 | | - # that production behavior unchanged, but surface the swallowed reason to the |
50 | | - # lab in sanitized form so an empty provider result is diagnosable evidence. |
51 | | - # This diagnostic is deliberately optional for minimal/synthetic runtimes used |
52 | | - # by instrumentation contract tests. HTTP evidence remains mandatory. On the |
53 | | - # accepted real NuvioDesktop runtime both anchors are present and the callback |
54 | | - # is injected; a partial anchor match is treated as drift and fails closed. |
55 | | - plugin_error_binding_anchor = ''' val callCode = """\n (async function() {\n''' |
56 | | - plugin_error_callback_anchor = ''' console.error("getStreams error:", e && e.message ? e.message : e, e && e.stack ? e.stack : "");\n __capture_result(JSON.stringify([]));\n''' |
57 | | - binding_count = runtime_text.count(plugin_error_binding_anchor) |
58 | | - callback_count = runtime_text.count(plugin_error_callback_anchor) |
59 | | - plugin_error_capture = False |
60 | | - if binding_count == 1 and callback_count == 1: |
61 | | - plugin_error_line = evidence_write( |
62 | | - '"FIELD_NATIVE_PLUGIN_ERROR client=desktop provider=$scraperId request_type=${mediaType.lowercase()} error=$safePluginError"' |
63 | | - ) |
64 | | - runtime_text = runtime_text.replace( |
65 | | - plugin_error_binding_anchor, |
66 | | - f''' function("__niakvio_capture_plugin_error") {{ args: Array<Any?> ->\n val rawPluginError = args.getOrNull(0)?.toString().orEmpty()\n val safePluginError = rawPluginError\n .replace(Regex("https?://\\\\S+", RegexOption.IGNORE_CASE), "<url>")\n .replace(Regex("(?i)(authorization|cookie|token|secret)\\\\s*[:=]\\\\s*\\\\S+"), "$1=<redacted>")\n .replace(Regex("\\\\s+"), "_")\n .take(360)\n {plugin_error_line}\n null\n }}\n\n val callCode = """\n (async function() {{\n''', |
67 | | - 1, |
68 | | - ) |
69 | | - runtime_text = runtime_text.replace( |
70 | | - plugin_error_callback_anchor, |
71 | | - ''' console.error("getStreams error:", e && e.message ? e.message : e, e && e.stack ? e.stack : "");\n __niakvio_capture_plugin_error(String(e && e.message ? e.message : e));\n __capture_result(JSON.stringify([]));\n''', |
72 | | - 1, |
73 | | - ) |
74 | | - plugin_error_capture = True |
75 | | - elif binding_count != 0 or callback_count != 0: |
76 | | - raise SystemExit( |
77 | | - "desktop evidence instrumentation partial plugin-error anchor drift " |
78 | | - f"binding={binding_count} callback={callback_count}" |
79 | | - ) |
80 | | - |
81 | | - bridge_text = replace_once( |
82 | | - bridge_text, |
83 | | - "internal class FetchBridge : HostModule {", |
84 | | - "internal class FetchBridge(private val scraperId: String, private val mediaType: String) : HostModule {", |
85 | | - "bridge constructor", |
86 | | - ) |
87 | | - error_line = evidence_write('"FIELD_NATIVE_HTTP_ERROR client=desktop provider=$scraperId request_type=$requestType method=${method.uppercase()} endpoint=$endpoint error_class=$errorName"') |
88 | | - bridge_text = replace_once( |
89 | | - bridge_text, |
90 | | - ''' } catch (t: Throwable) {\n log.e(t) { "Fetch bridge error for $method $url" }\n''', |
91 | | - f''' }} catch (t: Throwable) {{\n val endpoint = url.substringBefore('?').substringBefore('#').replace(Regex("\\\\s+"), "%20")\n val errorName = t::class.qualifiedName.orEmpty().replace(Regex("\\\\s+"), "_")\n val requestType = mediaType.lowercase().replace(Regex("[^a-z0-9_-]"), "_")\n {error_line}\n log.e(t) {{ "Fetch bridge error for $method <redacted-url>" }}\n''', |
92 | | - "bridge error", |
93 | | - ) |
94 | | - request_line = evidence_write('"FIELD_NATIVE_HTTP_REQUEST client=desktop provider=$scraperId request_type=$requestType method=${method.uppercase()} endpoint=$endpoint header_names=$headerNames body_chars=${body.length} follow_redirects=$followRedirects"') |
95 | | - bridge_text = replace_once( |
96 | | - bridge_text, |
97 | | - ''' val headers = parseHeaders(headersJson).toMutableMap()\n if (!headers.containsKey("User-Agent")) {\n headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"\n }\n\n val response = httpRequestRaw(\n''', |
98 | | - f''' val headers = parseHeaders(headersJson).toMutableMap()\n if (!headers.containsKey("User-Agent")) {{\n headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"\n }}\n val evidenceStartedAt = kotlin.time.TimeSource.Monotonic.markNow()\n val endpoint = url.substringBefore('?').substringBefore('#').replace(Regex("\\\\s+"), "%20")\n val headerNames = headers.keys.map {{ it.lowercase() }}.distinct().sorted().joinToString(",")\n val requestType = mediaType.lowercase().replace(Regex("[^a-z0-9_-]"), "_")\n {request_line}\n\n val response = httpRequestRaw(\n''', |
99 | | - "request evidence", |
100 | | - ) |
101 | | - response_line = evidence_write('"FIELD_NATIVE_HTTP_RESPONSE client=desktop provider=$scraperId request_type=$requestType method=${method.uppercase()} endpoint=$endpoint final_endpoint=$finalEndpoint status=${response.status} duration_ms=${evidenceStartedAt.elapsedNow().inWholeMilliseconds} response_header_names=$responseHeaderNames body_chars=${response.body.length}"') |
102 | | - bridge_text = replace_once( |
103 | | - bridge_text, |
104 | | - ''' val responseHeaders = response.headers.mapKeys { (key, _) -> key.lowercase() }\n .mapValues { (_, value) -> truncateString(value, MAX_FETCH_HEADER_VALUE_CHARS) }\n val result = JsonObject(\n''', |
105 | | - f''' val responseHeaders = response.headers.mapKeys {{ (key, _) -> key.lowercase() }}\n .mapValues {{ (_, value) -> truncateString(value, MAX_FETCH_HEADER_VALUE_CHARS) }}\n val finalEndpoint = response.url.substringBefore('?').substringBefore('#').replace(Regex("\\\\s+"), "%20")\n val responseHeaderNames = responseHeaders.keys.sorted().joinToString(",")\n {response_line}\n val result = JsonObject(\n''', |
106 | | - "response evidence", |
107 | | - ) |
108 | | - |
109 | | - runtime.write_text(runtime_text, encoding="utf-8") |
110 | | - bridge.write_text(bridge_text, encoding="utf-8") |
| 19 | + if not (repo / ".git").exists(): |
| 20 | + raise SystemExit(f"official NuvioDesktop checkout missing: {repo}") |
111 | 21 | print( |
112 | | - f"FIELD_NATIVE_EVIDENCE_INSTRUMENTED client=desktop runtime={runtime} bridge={bridge} " |
113 | | - f"plugin_error_capture={str(plugin_error_capture).lower()}" |
| 22 | + "FIELD_NATIVE_RUNTIME_INSTRUMENTATION client=desktop " |
| 23 | + "status=disabled_by_human_ux_policy runtime_mutation=false" |
114 | 24 | ) |
115 | 25 | return 0 |
116 | 26 |
|
|
0 commit comments