diff --git a/build/dependencies.lock b/build/dependencies.lock
index dad34c748..5dfb86d74 100644
--- a/build/dependencies.lock
+++ b/build/dependencies.lock
@@ -207,11 +207,11 @@ legacy_channels = ["development"]
[legacy_inventory]
manifest = "build/legacy-manifest.json"
schema = 1
-component_count = 331
-file_count = 1972
-byte_count = 260958446
+component_count = 328
+file_count = 1969
+byte_count = 260922113
source_build_components = 232
-unresolved_components = 99
+unresolved_components = 96
inherited_baseline = "07505ea58c7bba698d6b9220ff43946a43cac76b"
[themes]
diff --git a/build/legacy-manifest.json b/build/legacy-manifest.json
index ce1ee0032..59eeb579e 100644
--- a/build/legacy-manifest.json
+++ b/build/legacy-manifest.json
@@ -2043,45 +2043,6 @@
"build_recipe": "Makefile",
"resolution": "source-build"
},
- {
- "id": "runtime-tmp-update-res-wpsconnected-png",
- "kind": "runtime",
- "path": "static/build/.tmp_update/res/wpsconnected.png",
- "file_count": 1,
- "byte_count": 11671,
- "tree_sha256": "e4d1745e83b077fec29ddb90406d9b5aecda2c84c6ed6b089042984e3c0ef76c",
- "source": null,
- "source_revision": null,
- "license": null,
- "build_recipe": null,
- "resolution": "replace-source-build-or-exclude"
- },
- {
- "id": "runtime-tmp-update-res-wpsfail-png",
- "kind": "runtime",
- "path": "static/build/.tmp_update/res/wpsfail.png",
- "file_count": 1,
- "byte_count": 10951,
- "tree_sha256": "5b645131dd40065c4e5dd96ea44bea87c1e12aad39ce272f89005d1cbc5ebdda",
- "source": null,
- "source_revision": null,
- "license": null,
- "build_recipe": null,
- "resolution": "replace-source-build-or-exclude"
- },
- {
- "id": "runtime-tmp-update-res-wpstrying-png",
- "kind": "runtime",
- "path": "static/build/.tmp_update/res/wpstrying.png",
- "file_count": 1,
- "byte_count": 11408,
- "tree_sha256": "b50c1b13ba01e27a02a03d25d8c6a7b015fee2aedf59b1e3e8a96b059829dfcf",
- "source": null,
- "source_revision": null,
- "license": null,
- "build_recipe": null,
- "resolution": "replace-source-build-or-exclude"
- },
{
"id": "runtime-tmp-update-script-app-sorter-sh",
"kind": "runtime",
@@ -2321,8 +2282,8 @@
"kind": "runtime",
"path": "static/build/.tmp_update/script/network",
"file_count": 5,
- "byte_count": 22264,
- "tree_sha256": "5ba1040d9523790b3d6c57ac31bf9cf3819eef365567f9af2f4057daf96d4562",
+ "byte_count": 19961,
+ "tree_sha256": "13e9a2fbae9c29f9f1909ef750630d8e04c6ecdd9b4e4cb958477ec5228d330b",
"source": "https://github.com/OnionUI/Onion",
"source_revision": "07505ea58c7bba698d6b9220ff43946a43cac76b",
"license": "GPL-3.0-only",
diff --git a/docs/1.0_TEST_MATRIX.md b/docs/1.0_TEST_MATRIX.md
index a3d6e95c5..bb21cccb9 100644
--- a/docs/1.0_TEST_MATRIX.md
+++ b/docs/1.0_TEST_MATRIX.md
@@ -52,7 +52,7 @@ shoulder button.
| Critical lifecycle | Exact 640x480 and 752x560 built-in projections; source/target/path and legacy-file rejection; boot/shutdown/save/low-battery/recovery/Safe Mode ignore theme/config/media; charging reflects bounded actual level; installer uses one deterministic surface; missing optional text/font fallback; signed Full-update boot/charging/shutdown/recovery checkpoint |
| Settings | One sectioned surface; inline values; capability filtering; one-layer details; hidden Developer section; optional overlapping L1/R1 list paging |
| Quick Settings | START from core surfaces; immediate brightness/volume/mute; capability-gated Wi-Fi opens the dedicated detail surface; Wi-Fi left/right never mutates the radio; readable battery; B/START close |
-| Network services | Desired/runtime separation; carrier without IPv4 remains acquiring/not-ready; canonical DHCP restart and bounded post-address listener reconciliation; SSH shell and SFTP independent key-only states; canonical transfer roots and modes; Saves read-only; private roots denied; traversal/symlink/path bounds; same-filesystem atomic publication; authorized-key import; quiesce/health; stale FTP/Telnet/FileBrowser/inherited-Samba/WPS/hotspot flags cannot activate listeners; process-aware listener audit; inherited FTP/Telnet/FileBrowser HTTP and rejected Samba launchers, configurations, and Tweaks exposure absent from newly assembled stable artifacts while older-card remnants remain locked down |
+| Network services | Desired/runtime separation; carrier without IPv4 remains acquiring/not-ready; canonical DHCP restart and bounded post-address listener reconciliation; SSH shell and SFTP independent key-only states; canonical transfer roots and modes; Saves read-only; private roots denied; traversal/symlink/path bounds; same-filesystem atomic publication; authorized-key import; quiesce/health; stale FTP/Telnet/FileBrowser/inherited-Samba/WPS/hotspot flags cannot activate listeners; process-aware listener audit; inherited FTP/Telnet/FileBrowser HTTP and rejected Samba launchers, configurations, and Tweaks exposure absent from newly assembled stable artifacts; WPS progress assets, WPS controls, inherited hotspot controls, and dormant hotspot mutation functions absent while fail-closed entrypoints and older-card process cleanup remain |
| Wi-Fi profile store | JFFS2 private root; stdin-only bounded replacement and `bloomctl network profile upsert ID`; requested/document ID binding; explicit `network forget ID`; inactive-ID deletion with active-profile refusal; active forget only after a separately confirmed fallback connection; schema/unknown-field/count/size enforcement; WPA2-PSK, explicit open, and manual hidden WPA2; WEP and hidden open denial; unique IDs/SSIDs; redacted status; explicit local credential-free UI list with one active marker; sanitized `network connect status` with internal profile-handle validation and public omission; mode-0600 atomic single-profile supplicant projection; symlink, occupied-slot, live-lock, invalid-profile, and prior-byte preservation |
| Wi-Fi scan API | Asynchronous start; redacted status; explicit ID-scoped local results; cancel and terminal discard; one active worker; five-second rate limit; 15-second runtime; 64-KiB raw ceiling; 32 unique printable SSIDs; eight-operation history; open/WPA2-PSK/unsupported classification; duplicate signal reconciliation; backend failure, timeout, oversize, unsafe-path, stale/live-lock, and unsupported-hardware handling |
| Wi-Fi UI | Original Mini hidden; Settings, Shell Quick Settings, and GameSwitcher Quick Settings converge on `--settings-wifi`; direct radio toggle removed; bounded fixed-argv profile-list load outside render; connection state and saved-network detail; selectable Scan/Cancel row and read-only discovered networks; active-only 100-ms SDL wake; deferred GameSwitcher until cancellation discard; single-flight nonblocking fixed-argv scan client; 16-KiB output and five-second deadline; child process-group cleanup; controller-owned start/status/results/discard and cancel-during-start sequencing; 250-ms status opportunities; exact profile/scan/ack schemas; 16 profiles and 32 networks; unique bounded IDs/SSIDs; one active profile; fixed scan phases/errors; terminal history discarded; open/WPA2 profile form with shared keyboard, masked secret, fixed profile-upsert argv, escaped stdin JSON, strict ID/SSID/PSK validation, explicit secret clearing, nonblocking submit/poll, 4-KiB acknowledgement cap, exact upsert response, timeout process-group cleanup, exact saved-ID reuse, 104-bit no-follow random new handles, collision/security/full-store rejection, one editing/submitting controller with 100-ms poll opportunities, exact cancellation, terminal clearing, bounded focus return, supported scan-result selection, explicit saved-not-connected result, and no implicit association; explicit inactive-profile X/confirm forget flow; active-row refusal in Shell and store; fixed nonblocking delete; exact acknowledgement; terminal projection reload; graceful TERM cleanup before bounded KILL; exact guarded-connect status model with fixed phases/operations/failures, lease consistency, 64-byte transaction-ID and 300-second deadline ceilings, and discarded transaction/overlay identity; fixed no-follow `network connect status` loader with less-than-4-KiB output, five-second read ceiling, and stale-state clearing; separately typed single-flight nonblocking status request with process-group timeout and generic-poller refusal; controller-owned single request, 100-ms opportunities, duplicate-begin refusal, typed terminal state, teardown reaping, explicit guarded-state presentation, one-second freshness, fail-closed mutation gating for stale/unavailable/lease-armed/rollback-failed results, read-only refresh without queued intent, and exit teardown; credential and unknown-field rejection; passwords absent from arguments, logs, screenshots, durable Shell state, and evidence |
diff --git a/docs/SECURITY.md b/docs/SECURITY.md
index 77070c3a2..a011f00ab 100644
--- a/docs/SECURITY.md
+++ b/docs/SECURITY.md
@@ -35,6 +35,9 @@ configuration, dormant start logic, share editor, default credentials, and
visible Tweaks controls are no longer shipped or exposed. The signed disable
migration, stale-state cleanup, and exact `smbd` termination remain for
older-card compatibility.
+Stable Tweaks does not expose WPS or the inherited hotspot. The WPS compatibility
+entry point remains only to reject inherited callers, while hotspot state is
+migration-only until NET-08 supplies a separately reviewed transactional owner.
The reviewed key-only Dropbear transport is the only stable remote listener;
SFTP is independently gated and confined through that same transport.
Its chroot is materialized from the versioned canonical transfer policy rather
diff --git a/src/tweaks/appstate.h b/src/tweaks/appstate.h
index cb08e8b52..74cc1c719 100644
--- a/src/tweaks/appstate.h
+++ b/src/tweaks/appstate.h
@@ -16,15 +16,11 @@ bool isMenu(List *target)
}
static List _menu_network;
-static List _menu_wifi;
-static List _menu_wps;
static List _menu_vnc;
void menu_network_free_all(void)
{
list_free(&_menu_network);
- list_free(&_menu_wifi);
- list_free(&_menu_wps);
list_free(&_menu_vnc);
}
diff --git a/src/tweaks/network.h b/src/tweaks/network.h
index 3bc299e85..e6753e8db 100644
--- a/src/tweaks/network.h
+++ b/src/tweaks/network.h
@@ -26,7 +26,6 @@
#define NET_SCRIPT_PATH "/mnt/SDCARD/.tmp_update/script/network"
static struct network_s {
- bool hotspot;
bool ntp;
bool ntp_wait;
bool manual_tz;
@@ -45,7 +44,6 @@ void network_loadState(void)
if (network_state.loaded)
return;
- network_state.hotspot = config_flag_get(".hotspotState");
network_state.ntp = config_flag_get(".ntpState");
network_state.ntp_wait = config_flag_get(".ntpWait");
network_state.force_wifi_on_startup = config_flag_get(".ntpForce");
@@ -79,14 +77,6 @@ void network_execServiceState(const char *service_name, bool background)
printf_debug("network_execServiceState: %s\n", state);
}
-void network_setHotspotState(void *pt)
-{
- network_setState(&network_state.hotspot, ".hotspotState", ((ListItem *)pt)->value);
- network_execServiceState("hotspot", false);
- reset_menus = true;
- all_changed = true;
-}
-
void network_setNtpState(void *pt)
{
network_setState(&network_state.ntp, ".ntpState", ((ListItem *)pt)->value);
@@ -116,11 +106,6 @@ void network_keepServicesAlive(void *pt)
network_setState(&network_state.keep_alive, ".keepServicesAlive", !((ListItem *)pt)->value);
}
-void network_wpsConnect(void *pt)
-{
- system("sh " NET_SCRIPT_PATH "/wpsclient.sh");
-}
-
void network_setTzManualState(void *pt)
{
bool enabled = ((ListItem *)pt)->value;
@@ -203,20 +188,6 @@ void network_setVNCFPS(void *pt)
}
}
-void menu_wps(void *_)
-{
- if (!_menu_wps._created) {
- _menu_wps = list_create(1, LIST_SMALL);
- strcpy(_menu_wps.title, "WPS");
- list_addItem(&_menu_wps,
- (ListItem){
- .label = "WPS connect",
- .action = network_wpsConnect});
- }
- menu_stack[++menu_level] = &_menu_wps;
- header_changed = true;
-}
-
void menu_vnc(void *pt)
{
ListItem *item = (ListItem *)pt;
@@ -246,50 +217,10 @@ void menu_vnc(void *pt)
header_changed = true;
}
-void menu_wifi(void *_)
-{
- if (!_menu_wifi._created) {
- _menu_wifi = list_create(3, LIST_SMALL);
- strcpy(_menu_wifi.title, "WiFi");
- list_addItem(&_menu_wifi,
- (ListItem){
- .label = "IP address: N/A",
- .disabled = true,
- .action = NULL});
- list_addItemWithInfoNote(&_menu_wifi,
- (ListItem){
- .label = "WiFi Hotspot",
- .item_type = TOGGLE,
- .value = (int)network_state.hotspot,
- .action = network_setHotspotState},
- "Use hotspot to host all the network\n"
- "services on the go, no router needed.\n"
- "Stay connected at anytime, anywhere.\n"
- "Compatible with Easy Netplay and\n"
- "regular netplay.");
- list_addItemWithInfoNote(&_menu_wifi,
- (ListItem){
- .label = "WPS connect",
- .action = network_wpsConnect},
- "Use your WiFi router's WPS function\n"
- "to connect your device with a single press.\n"
- " \n"
- "First press the WPS button on your router,\n"
- "then click this option to connect.");
- // list_addItem(&_menu_wifi,
- // (ListItem){
- // .label = "WPS...",
- // .action = menu_wps});
- }
- strcpy(_menu_wifi.items[0].label, ip_address_label);
- menu_stack[++menu_level] = &_menu_wifi;
- header_changed = true;
-}
-
void menu_network(void *_)
{
if (!_menu_network._created) {
- _menu_network = list_create(6, LIST_SMALL);
+ _menu_network = list_create(3, LIST_SMALL);
strcpy(_menu_network.title, "Network");
network_loadState();
@@ -299,10 +230,6 @@ void menu_network(void *_)
.label = "IP address: N/A",
.disabled = true,
.action = NULL});
- list_addItem(&_menu_network,
- (ListItem){
- .label = "WiFi: Hotspot/WPS...",
- .action = menu_wifi});
list_addItemWithInfoNote(&_menu_network,
(ListItem){
.label = "VNC: Screen share...",
diff --git a/src/tweaks/tweaks.c b/src/tweaks/tweaks.c
index e79148561..4edc6b21a 100644
--- a/src/tweaks/tweaks.c
+++ b/src/tweaks/tweaks.c
@@ -217,13 +217,11 @@ int main(int argc, char *argv[])
}
}
}
- if (isMenu(&_menu_network) || isMenu(&_menu_wifi)) {
+ if (isMenu(&_menu_network)) {
network_loadState();
- if (netinfo_getIpAddress(ip_address_label, network_state.hotspot ? "wlan1" : "wlan0")) {
+ if (netinfo_getIpAddress(ip_address_label, "wlan0")) {
if (_menu_network._created)
strcpy(_menu_network.items[0].label, ip_address_label);
- if (_menu_wifi._created)
- strcpy(_menu_wifi.items[0].label, ip_address_label);
list_changed = true;
}
}
diff --git a/static/build/.tmp_update/res/wpsconnected.png b/static/build/.tmp_update/res/wpsconnected.png
deleted file mode 100644
index cf8eb765d..000000000
Binary files a/static/build/.tmp_update/res/wpsconnected.png and /dev/null differ
diff --git a/static/build/.tmp_update/res/wpsfail.png b/static/build/.tmp_update/res/wpsfail.png
deleted file mode 100644
index f9a9260cb..000000000
Binary files a/static/build/.tmp_update/res/wpsfail.png and /dev/null differ
diff --git a/static/build/.tmp_update/res/wpstrying.png b/static/build/.tmp_update/res/wpstrying.png
deleted file mode 100644
index 1da0c95cc..000000000
Binary files a/static/build/.tmp_update/res/wpstrying.png and /dev/null differ
diff --git a/static/build/.tmp_update/script/network/update_networking.sh b/static/build/.tmp_update/script/network/update_networking.sh
index 4743835ed..dc0dc3968 100644
--- a/static/build/.tmp_update/script/network/update_networking.sh
+++ b/static/build/.tmp_update/script/network/update_networking.sh
@@ -44,7 +44,7 @@ main() {
check_${service}state
;;
*)
- echo "Usage: $0 {ntp|hotspot} toggle"
+ echo "Usage: $0 ntp toggle"
exit 1
;;
esac
@@ -211,71 +211,6 @@ check_sshstate() {
disable_flag authsshState
}
-# Starts the hotspot based on the results of check_hotspotstate, called twice saves repeating
-# We have to sleep a bit or sometimes supllicant starts before we can get the hotspot logo
-# Starts AP and DHCP
-
-start_hotspot() {
- ifconfig wlan1 up >> /dev/null 2>&1
- ifconfig wlan0 down >> /dev/null 2>&1
-
- sleep 1
- # IP setup
- hotspot0addr=$(grep -E '^dhcp-range=' "$sysdir/config/dnsmasq.conf" | cut -d',' -f1 | cut -d'=' -f2)
- hotspot0addr=$(echo $hotspot0addr | awk -F'.' -v OFS='.' '{$NF-=1; print}')
- gateway0addr=$(grep -E '^dhcp-option=3' $sysdir/config/dnsmasq.conf | awk -F, '{print $2}')
- subnetmask=$(grep -E '^dhcp-range=.*,[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+,' "$sysdir/config/dnsmasq.conf" | cut -d',' -f3)
-
- # Set IP route / If details
- ifconfig wlan1 $hotspot0addr netmask $subnetmask >> /dev/null 2>&1
-
- # Start
- $sysdir/bin/dnsmasq --conf-file=$sysdir/config/dnsmasq.conf -u root &
- $sysdir/bin/hostapd -i wlan1 $sysdir/config/hostapd.conf >> /dev/null 2>&1 &
-
- ip route add default via $gateway0addr
-
- if is_running hostapd; then
- log "Hotspot: Started with IP of: $hotspot0addr, subnet of: $subnetmask"
- else
- log "Hotspot: Failed to start, please try turning off/on. If this doesn't resolve the issue reboot your device."
- disable_flag hotspotState
- fi
-
-}
-
-# Starts personal hotspot if toggle is set to on
-# Calls start_hotspot from above
-# IF toggle is disabled, shuts down hotspot and bounces wifi.
-check_hotspotstate() {
- sync
- if [ ! -f $sysdir/config/.hotspotState ]; then
- if is_running hostapd; then
- log "Hotspot: Killed"
- pkill -9 hostapd
- pkill -9 dnsmasq
- ifconfig wlan0 up
- ifconfig wlan1 down
- killall -9 udhcpc
- udhcpc -i wlan0 -s /etc/init.d/udhcpc.script &
- check_wifi
- else
- return
- fi
- else
- if is_running hostapd; then
- if wifi_disabled; then
- log "Hotspot: Wifi is turned off, disabling the toggle for hotspot and killing the process"
- rm $sysdir/config/.hotspotState
- pkill -9 hostapd
- pkill -9 dnsmasq
- fi
- else
- start_hotspot &
- fi
- fi
-}
-
# Utility functions
# create/pop the json file for remembering states
diff --git a/test/shell/ci_validation.bats b/test/shell/ci_validation.bats
index 944bde24f..e3164d675 100644
--- a/test/shell/ci_validation.bats
+++ b/test/shell/ci_validation.bats
@@ -118,6 +118,22 @@
[ "$output" = "full" ]
}
+@test "stable WPS asset retirement keeps packaging exhaustive" {
+ scope=/workspace/tools/ci-scope.sh
+ run sh -c "printf '%s\n' static/build/.tmp_update/res/wpstrying.png | '$scope' build"
+ [ "$status" -eq 0 ]
+ [ "$output" = "full" ]
+ run sh -c "printf '%s\n' static/build/.tmp_update/res/wpstrying.png | '$scope' static-analysis"
+ [ "$status" -eq 0 ]
+ [ "$output" = "none" ]
+ run sh -c "printf '%s\n' static/build/.tmp_update/res/wpstrying.png | '$scope' risk"
+ [ "$status" -eq 0 ]
+ [ "$output" = "3" ]
+ run sh -c "printf '%s\n' static/build/.tmp_update/res/wpstrying.png | '$scope' shell-tests"
+ [ "$status" -eq 0 ]
+ [ "$output" = "full" ]
+}
+
@test "stable Telnet launcher removal keeps packaging exhaustive and cppcheck scoped" {
scope=/workspace/tools/ci-scope.sh
run sh -c "printf '%s\n' static/build/.tmp_update/script/network/telnetenv.sh | '$scope' build"
diff --git a/test/shell/legacy_manifest.bats b/test/shell/legacy_manifest.bats
index 67a4bbfd6..935398432 100644
--- a/test/shell/legacy_manifest.bats
+++ b/test/shell/legacy_manifest.bats
@@ -6,7 +6,7 @@
--manifest /workspace/build/legacy-manifest.json
[ "$status" -eq 0 ]
- [[ "$output" == "legacy manifest validate: 331 components" ]]
+ [[ "$output" == "legacy manifest validate: 328 components" ]]
[ ! -e "/workspace/static/packages/RApp/SNK - Neo Geo (GnGeo)" ]
[ ! -e "/workspace/static/packages/RApp/PICO-8 (PICO-8 standalone)" ]
[ ! -e "/workspace/static/packages/RApp/SCUMM (ScummVM standalone)" ]
diff --git a/test/shell/listener_lockdown.bats b/test/shell/listener_lockdown.bats
index 51956a9a6..4fd3d5273 100644
--- a/test/shell/listener_lockdown.bats
+++ b/test/shell/listener_lockdown.bats
@@ -184,3 +184,25 @@ EOF
[ "$status" -eq 3 ]
[[ "$output" == *'"code":"unsupported"'* ]]
}
+
+@test "stable WPS and inherited hotspot controls remain retired" {
+ network=/workspace/static/build/.tmp_update/script/network/update_networking.sh
+ tweaks=/workspace/src/tweaks/network.h
+ hotspot_doc=/workspace/website/docs/07-apps/03-network-features/06-hotspot.md
+
+ [ ! -e /workspace/static/build/.tmp_update/res/wpsconnected.png ]
+ [ ! -e /workspace/static/build/.tmp_update/res/wpsfail.png ]
+ [ ! -e /workspace/static/build/.tmp_update/res/wpstrying.png ]
+ ! grep -F 'start_hotspot()' "$network"
+ ! grep -F 'check_hotspotstate()' "$network"
+ grep -F 'legacy_services="httpState ftpState telnetState smbdState authftpState authhttpState hotspotState"' "$network"
+ grep -F 'ftp | telnet | http | smbd | hotspot)' "$network"
+ ! grep -F 'network_state.hotspot' "$tweaks"
+ ! grep -F 'network_setHotspotState' "$tweaks"
+ ! grep -F 'network_wpsConnect' "$tweaks"
+ ! grep -F 'WiFi: Hotspot/WPS' "$tweaks"
+ grep -F 'BloomOS does not expose the inherited hotspot or WPS controls' "$hotspot_doc"
+ grep -F '/docs/network/ssh' "$hotspot_doc"
+ ! grep -F 'Enabling hotspot' "$hotspot_doc"
+ ! grep -F 'WiFi: Hotspot/WPS' /workspace/website/docs/07-apps/01-included-in-onion/tweaks.mdx
+}
diff --git a/test/test_retroarch_build_identity.py b/test/test_retroarch_build_identity.py
index e38b7dc37..fe1f73084 100644
--- a/test/test_retroarch_build_identity.py
+++ b/test/test_retroarch_build_identity.py
@@ -36,9 +36,9 @@ def test_identity_is_stable_for_unrelated_inventory_totals(self):
self.assertRegex(baseline.stdout.strip(), r"^[0-9a-f]{64}$")
changed = LOCK.read_text(encoding="utf-8")
- changed = changed.replace("component_count = 331", "component_count = 999")
- changed = changed.replace("file_count = 1972", "file_count = 9999")
- changed = changed.replace("byte_count = 260958446", "byte_count = 999999999")
+ changed = changed.replace("component_count = 328", "component_count = 999")
+ changed = changed.replace("file_count = 1969", "file_count = 9999")
+ changed = changed.replace("byte_count = 260922113", "byte_count = 999999999")
self.assertNotEqual(changed, LOCK.read_text(encoding="utf-8"))
with tempfile.TemporaryDirectory() as directory:
candidate = pathlib.Path(directory) / "dependencies.lock"
diff --git a/tools/ci-scope.sh b/tools/ci-scope.sh
index 4163b769c..437df910e 100755
--- a/tools/ci-scope.sh
+++ b/tools/ci-scope.sh
@@ -293,6 +293,13 @@ while IFS= read -r path; do
# stable payload and therefore retains the complete ARM gate.
[ "$mode" != build ] || full=1
;;
+ static/build/.tmp_update/res/wpsconnected.png|\
+ static/build/.tmp_update/res/wpsfail.png|\
+ static/build/.tmp_update/res/wpstrying.png)
+ # Removing inherited WPS UI assets changes stable packaging and
+ # therefore keeps the complete ARM release gate.
+ [ "$mode" != build ] || full=1
+ ;;
static/build/.tmp_update/bin/bftpd|static/build/.tmp_update/bin/bftpdutmp|\
static/configs/.tmp_update/config/bftpd.conf|static/configs/.tmp_update/config/bftpdauth.conf)
# Stable FTP payload deletion requires complete ARM packaging but
@@ -523,6 +530,15 @@ while IFS= read -r path; do
add_shell_target /workspace/test/shell/provenance_policy.bats
add_shell_target /workspace/test/shell/fat_payload.bats
;;
+ static/build/.tmp_update/res/wpsconnected.png|\
+ static/build/.tmp_update/res/wpsfail.png|\
+ static/build/.tmp_update/res/wpstrying.png)
+ raise_risk 3
+ add_shell_target /workspace/test/shell/listener_lockdown.bats
+ add_shell_target /workspace/test/shell/legacy_manifest.bats
+ add_shell_target /workspace/test/shell/provenance_policy.bats
+ add_shell_target /workspace/test/shell/fat_payload.bats
+ ;;
static/build/.tmp_update/bin/bftpd|static/build/.tmp_update/bin/bftpdutmp|\
static/configs/.tmp_update/config/bftpd.conf|static/configs/.tmp_update/config/bftpdauth.conf)
raise_risk 3
diff --git a/website/docs/01-features/index.md b/website/docs/01-features/index.md
index f4cf49604..7753d39ac 100644
--- a/website/docs/01-features/index.md
+++ b/website/docs/01-features/index.md
@@ -210,7 +210,7 @@ For the Miyoo Mini Plus (equipped with wifi) Onion offers many additional possib
- [Automatic date/time synchronisation](apps/tweaks#set-automatically-via-the-internet) (NTP, via Tweaks app)
- [Retroachivements](https://www.retroarch.com/?page=achievements)
- [Scraper](/docs/apps/scraper): import game covers directly from your MMP
-- [Network Services](network-features): [HTTP server (retired)](network/http), [SSH/SFTP](network/ssh), [Hotspot](network/hotspot), [VNC server](network/vnc)...
+- [Network Services](network-features): [HTTP server (retired)](network/http), [SSH/SFTP](network/ssh), [Hotspot/WPS (retired pending transactional replacement)](network/hotspot), [VNC server](network/vnc)...
:::
diff --git a/website/docs/07-apps/01-included-in-onion/tweaks.mdx b/website/docs/07-apps/01-included-in-onion/tweaks.mdx
index ae5f33088..3f741ca4d 100644
--- a/website/docs/07-apps/01-included-in-onion/tweaks.mdx
+++ b/website/docs/07-apps/01-included-in-onion/tweaks.mdx
@@ -155,25 +155,6 @@ It can be MainUI, [GameSwitcher](./game-switcher), [Retroarch](./retroarch) or [
-
-### WiFi: Hotspot/WPS...
-
-
-
-
-#### WiFi Hotspot
-
-
Use hotspot to host all the network services on the go, no router needed. Stay connected at anytime, anywhere. Compatible with Easy Netplay and regular netplay.
-
-
-
-#### WPS connect
-
-
Use your WiFi router's WPS function to connect your device with a single press. First press the WPS button on your router, then click this option to connect.
-
-
-
-
### SSH: Secure shell...
SSH provides a secure command line host for communicating with your device remotely. SFTP provides a secure file transfer protocol.
diff --git a/website/docs/07-apps/03-network-features/06-hotspot.md b/website/docs/07-apps/03-network-features/06-hotspot.md
index da50e7623..c305d4a88 100644
--- a/website/docs/07-apps/03-network-features/06-hotspot.md
+++ b/website/docs/07-apps/03-network-features/06-hotspot.md
@@ -4,32 +4,24 @@ slug: /network/hotspot
# Hotspot
-Your Miyoo Mini Plus just got a lot more portable. Use the reviewed key-only SSH/SFTP service on the go, no extra devices needed. Stay connected with ease, anytime, anywhere.
-Compatible with Netplay (best paired with [Easy Netplay](../multiplayer/easynetplay)
+BloomOS does not expose the inherited hotspot or WPS controls. The inherited
+implementation can replace station configuration, interfaces, routes, and DHCP
+state without Bloom's rollback transaction, so stable Tweaks cannot invoke it.
-There are reports of issues with hotspot on firmware levels below `2023 05 05``.
+Stale `.hotspotState` files are migration input only. Compatibility
+reconciliation disables the marker and stops inherited `hostapd` and `dnsmasq`
+processes instead of starting an access point.
+WPS is also unsupported. Its retained compatibility entry point fails closed
+without changing the active station profile.
-## Features
+## Planned replacement
-- Transfer files on the move
+The v3 work program tracks a separately gated transactional Bloom hotspot as
+NET-08. It must use the same begin/apply/confirm/rollback safeguards as other
+network mutations and requires Mini Plus device evidence before it can appear in
+stable settings.
-- Use Netplay without access to a Wi-Fi router
-
-
-## Enabling hotspot
-
-
-
-You'll find it in **Apps** › **Tweaks** › **Network** › **WiFi: Hotspot/WPS**
-
-Press A or use the arrow keys to enable to the hotspot.
-
-
-## Connecting
-
-On another device you'll now see that your MMP is broadcasting an SSID.
-
-
-
-Connect to this as you would any other Wi-Fi network and use the **password:** `onionos+`
+Until that work is certified, use the reviewed key-only [SSH/SFTP
+service](/docs/network/ssh) while the Mini Plus is connected to a trusted Wi-Fi
+network.