11#! /usr/bin/env bash
2- # Source this file from a native client suite after WORKSPACE, NIAKVIO,
3- # TARGET_MANIFEST, SOURCE_SHA and SOURCE_REPOSITORY are defined.
2+ # Source from a native client suite after WORKSPACE, NIAKVIO, TARGET_MANIFEST,
3+ # SOURCE_SHA and SOURCE_REPOSITORY are defined.
4+ #
5+ # Human-UX invariant: repository staging may make the NiakVIO candidate reachable,
6+ # but it must never grant the Nuvio process privileges or network capabilities that
7+ # a normal user process does not have. No sudo/root, proxy, DNS, TLS or OS-policy
8+ # bypass belongs here. If ordinary process access cannot reach the staged candidate,
9+ # the evidence is infrastructure-invalid and the suite must fail closed.
410
511NIAKVIO_RESOLVED_MANIFEST_URL=" "
612NIAKVIO_RESOLVED_ALLOW_LOCAL=" 0"
713NIAKVIO_LOCAL_REPOSITORY_PID=" "
814NIAKVIO_LOCAL_REPOSITORY_LOG=" "
915NIAKVIO_LOCAL_REPOSITORY_ROOT=" "
1016NIAKVIO_LOCAL_REPOSITORY_KEY=" "
11- NIAKVIO_LOCAL_REPOSITORY_PRIVILEGED_CLIENT=" 0"
1217
1318probe_native_repository_loopback () {
1419 local port=" ${1:? port required} "
1520 local candidate_dir=" ${2:? candidate dir required} "
16- local use_privileged_client=" ${3:- 0} "
17- local python_args=(python3 - " $port " " $candidate_dir " )
18-
19- if [[ " $use_privileged_client " == " 1" ]]; then
20- sudo -n " ${python_args[@]} " << 'PY '
21- import http.client
22- import sys
23-
24- port = int(sys.argv[1])
25- candidate = sys.argv[2]
26- connection = http.client.HTTPConnection("127.0.0.1", port, timeout=1.0)
27- try:
28- connection.request("GET", f"/{candidate}/manifest.json", headers={"Connection": "close"})
29- response = connection.getresponse()
30- if response.status != 200:
31- raise SystemExit(1)
32- response.read(64)
33- finally:
34- connection.close()
35- PY
36- else
37- " ${python_args[@]} " << 'PY '
21+ python3 - " $port " " $candidate_dir " << 'PY '
3822import http.client
3923import sys
4024
5034finally:
5135 connection.close()
5236PY
53- fi
5437}
5538
5639resolve_native_repository () {
@@ -61,14 +44,12 @@ resolve_native_repository() {
6144 if [[ -n " ${NIAKVIO_MANIFEST_URL:- } " ]]; then
6245 NIAKVIO_RESOLVED_MANIFEST_URL=" $NIAKVIO_MANIFEST_URL "
6346 NIAKVIO_RESOLVED_ALLOW_LOCAL=" ${NIAKVIO_ALLOW_LOCAL_MANIFEST:- 0} "
64- echo " FIELD_NATIVE_REPOSITORY_SOURCE client=$client mode=explicit local=$NIAKVIO_RESOLVED_ALLOW_LOCAL "
47+ echo " FIELD_NATIVE_REPOSITORY_SOURCE client=$client mode=explicit local=$NIAKVIO_RESOLVED_ALLOW_LOCAL observational=true "
6548 return 0
6649 fi
6750
68- # raw.githubusercontent.com resolves repository-relative provider filenames only
69- # when the manifest itself is at the repository root. It must also be a tracked,
70- # unchanged file from the exact SOURCE_SHA under test. The SHA in the URL makes
71- # Nuvio's persistent repository/provider cache safe across CI runs.
51+ # Prefer the exact SHA-pinned public repository whenever the selected manifest is
52+ # a tracked unchanged root file. This is closest to what a real Nuvio user loads.
7253 local pinned=false
7354 if [[ " $TARGET_MANIFEST " != * /* ]] \
7455 && git -C " $NIAKVIO " ls-files --error-unmatch " $TARGET_MANIFEST " > /dev/null 2>&1 \
@@ -80,19 +61,13 @@ resolve_native_repository() {
8061 if [[ " $pinned " == true ]]; then
8162 NIAKVIO_RESOLVED_MANIFEST_URL=" https://raw.githubusercontent.com/${SOURCE_REPOSITORY} /${SOURCE_SHA} /${TARGET_MANIFEST} "
8263 NIAKVIO_RESOLVED_ALLOW_LOCAL=" 0"
83- echo " FIELD_NATIVE_REPOSITORY_SOURCE client=$client mode=pinned_github local=0 cache_key=${SOURCE_SHA} "
64+ echo " FIELD_NATIVE_REPOSITORY_SOURCE client=$client mode=pinned_github local=0 cache_key=${SOURCE_SHA} observational=true "
8465 return 0
8566 fi
8667
87- # Generated, modified, or nested manifests are materialized as a root repository
88- # and served only on the runner bridge. Android emulators must reach the host at
89- # 10.0.2.2, so they bind all interfaces. Desktop only consumes 127.0.0.1 and is
90- # deliberately bound to loopback; this avoids macOS Local Network Privacy treating
91- # an otherwise local-only CI repository as a LAN listener.
92- #
93- # IMPORTANT: the URL path is content-addressed from manifest + every provider
94- # byte. A persisted AVD/profile may therefore reuse the cache for the exact same
95- # candidate, but can never silently reuse old JS after the candidate changes.
68+ # Generated/modified manifests must still resolve repository-relative provider
69+ # filenames. Materialize manifest + every provider into a content-addressed local
70+ # repository so persistent Nuvio caches cannot reuse stale JS.
9671 local serve_root=" ${WORKSPACE} /native-candidate-repository-${client} "
9772 local prepared_root=" ${serve_root} .prepared"
9873 local server_log=" ${WORKSPACE} /native-candidate-repository-${client} .server.log"
@@ -104,7 +79,9 @@ resolve_native_repository() {
10479 local content_key
10580 content_key=" $( python3 - " $prepared_root " << 'PY '
10681from pathlib import Path
107- import hashlib, sys
82+ import hashlib
83+ import sys
84+
10885root = Path(sys.argv[1]).resolve()
10986h = hashlib.sha256()
11087files = sorted((p for p in root.rglob('*') if p.is_file()), key=lambda p: p.relative_to(root).as_posix())
142119 NIAKVIO_LOCAL_REPOSITORY_PID=$!
143120 NIAKVIO_LOCAL_REPOSITORY_LOG=" $server_log "
144121
145- # Readiness always starts through the same unprivileged loopback path the client
146- # should use. On macOS CI only, sudo is a bounded fallback after several ordinary
147- # attempts; it is never required just because the runner happens to be macOS.
148- local allow_privileged_probe=0
149- if [[ " $client " == " desktop" && " $( uname -s) " == " Darwin" && " ${CI:- } " == " true" ]]; then
150- if sudo -n true > /dev/null 2>&1 ; then
151- allow_privileged_probe=1
152- fi
153- fi
154-
155122 local ready=0
156123 local attempt
157124 for attempt in $( seq 1 40) ; do
162129 NIAKVIO_LOCAL_REPOSITORY_PID=" "
163130 return 2
164131 fi
165- if probe_native_repository_loopback " $port " " $candidate_dir " 0 > /dev/null 2>&1 ; then
166- ready=1
167- break
168- fi
169- if [[ " $allow_privileged_probe " == " 1" && " $attempt " -ge 5 ]] \
170- && probe_native_repository_loopback " $port " " $candidate_dir " 1 > /dev/null 2>&1 ; then
132+ if probe_native_repository_loopback " $port " " $candidate_dir " > /dev/null 2>&1 ; then
171133 ready=1
172- NIAKVIO_LOCAL_REPOSITORY_PRIVILEGED_CLIENT=" 1"
173134 break
174135 fi
175136 sleep 0.2
176137 done
177138 if [[ " $ready " != " 1" ]]; then
178- echo " FIELD_NATIVE_REPOSITORY_SOURCE_ERROR client=$client reason=local_server_not_ready cache_key=$content_key bind_host=$bind_host privileged_fallback= $allow_privileged_probe " >&2
139+ echo " FIELD_NATIVE_REPOSITORY_SOURCE_ERROR client=$client reason=local_server_not_ready_unprivileged cache_key=$content_key bind_host=$bind_host " >&2
179140 cat " $server_log " >&2 2> /dev/null || true
180141 kill " $NIAKVIO_LOCAL_REPOSITORY_PID " 2> /dev/null || true
181142 rm -rf " $serve_root "
143+ NIAKVIO_LOCAL_REPOSITORY_PID=" "
182144 return 2
183145 fi
184146
185147 NIAKVIO_RESOLVED_MANIFEST_URL=" http://${device_host} :${port} /${candidate_dir} /manifest.json"
186148 NIAKVIO_RESOLVED_ALLOW_LOCAL=" 1"
187- echo " FIELD_NATIVE_REPOSITORY_SOURCE client=$client mode=local_candidate local=1 port=$port bind_host=$bind_host cache_key=$content_key privileged_client= $NIAKVIO_LOCAL_REPOSITORY_PRIVILEGED_CLIENT "
149+ echo " FIELD_NATIVE_REPOSITORY_SOURCE client=$client mode=local_candidate local=1 port=$port bind_host=$bind_host cache_key=$content_key observational=true privileged=false "
188150}
189151
190152cleanup_native_repository () {
0 commit comments