Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions build/dependencies.lock
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,10 @@ legacy_channels = ["development"]
[legacy_inventory]
manifest = "build/legacy-manifest.json"
schema = 1
component_count = 334
file_count = 2133
byte_count = 314701761
source_build_components = 230
component_count = 335
file_count = 2134
byte_count = 314704844
source_build_components = 231
unresolved_components = 104
inherited_baseline = "07505ea58c7bba698d6b9220ff43946a43cac76b"

Expand Down
21 changes: 17 additions & 4 deletions build/legacy-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -488,8 +488,8 @@
"kind": "runtime",
"path": "static/build/.tmp_update/bin/bloom-dev-deploy",
"file_count": 1,
"byte_count": 32818,
"tree_sha256": "d07c796d29881fc16ce3674d028d3232fe1079288d71c0311acbcdc45c9511ea",
"byte_count": 32904,
"tree_sha256": "ac0661051a48261c2a585f1dc21ce172c61ed19edb1b2a182660f0202950bc9d",
"source": "https://github.com/boburning/BloomOS",
"source_revision": "release-commit",
"license": "GPL-3.0-only",
Expand Down Expand Up @@ -639,6 +639,19 @@
"build_recipe": "Makefile",
"resolution": "source-build"
},
{
"id": "runtime-tmp-update-bin-bloom-network-sync",
"kind": "runtime",
"path": "static/build/.tmp_update/bin/bloom-network-sync",
"file_count": 1,
"byte_count": 2845,
"tree_sha256": "fc3e0cc0ec29b5082344fdb988e4c2e2ceeaccafdbde4015ad0e2f79cde4bb69",
"source": "https://github.com/boburning/BloomOS",
"source_revision": "release-commit",
"license": "GPL-3.0-only",
"build_recipe": "Makefile",
"resolution": "source-build"
},
{
"id": "runtime-tmp-update-bin-bloom-network-transaction",
"kind": "runtime",
Expand Down Expand Up @@ -1008,8 +1021,8 @@
"kind": "runtime",
"path": "static/build/.tmp_update/bin/bloom-wifi",
"file_count": 1,
"byte_count": 10455,
"tree_sha256": "06534f60dada7c8ccbbdd86e54b99c69a57408c85e01a114f928a779aa3d3d38",
"byte_count": 10607,
"tree_sha256": "4a7baa305d3812dc727118c2ce996a58b569a8b6c61ccd4d2476097c9cb5f836",
"source": "https://github.com/boburning/BloomOS",
"source_revision": "release-commit",
"license": "GPL-3.0-only",
Expand Down
11 changes: 9 additions & 2 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -988,8 +988,15 @@ the explicit `provider_unconfigured` disposition and offline events receive the
explicit `offline` disposition. The compatibility network script no longer
launches the inherited TLS-bypassing check or publishes its legacy availability
marker. Acknowledgement failure leaves the event pending, and consumer failure
remains secondary to Wi-Fi health. Sync consumption remains a separate follow-up
integration step.
remains secondary to Wi-Fi health.

`bloom-network-sync` completes the fixed event fan-out without claiming the
later Save Sync program. It validates and acknowledges only its exact sync
generation, returns `engine_unavailable` while SYNC-01/02 have supplied neither
the save-bundle contract nor `bloom-sync`, and returns `offline` for an offline
observation. It never reads or writes Saves, starts a transport, or treats the
filesystem `sync` command as Save Sync. This explicit disposition lets NET-07
complete while preserving SYNC-01 through SYNC-04 as separately gated work.

Bounded `request enable` and `request disable` operations persist only
`device.wifi_enabled` through the active `bloom-settings` authority before
Expand Down
2 changes: 2 additions & 0 deletions static/build/.tmp_update/bin/bloom-dev-deploy
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ allowed_path() {
network-helpers:bin/bloom-settings|network-helpers:bin/bloom-network|\
network-helpers:bin/bloom-network-metadata|\
network-helpers:bin/bloom-network-update|\
network-helpers:bin/bloom-network-sync|\
network-helpers:bin/bloom-network-services|network-helpers:bin/bloom-network-transaction|\
network-helpers:bin/bloom-platform|network-helpers:bin/bloom-dev-ssh|\
network-helpers:bin/bloom-sftp-gate|network-helpers:bin/bloom-sftp-server|\
Expand Down Expand Up @@ -195,6 +196,7 @@ validate_manifest() {
bin/bloom-network \
bin/bloom-network-metadata \
bin/bloom-network-update \
bin/bloom-network-sync \
bin/bloom-network-services \
bin/bloom-network-transaction \
bin/bloom-platform \
Expand Down
66 changes: 66 additions & 0 deletions static/build/.tmp_update/bin/bloom-network-sync
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/bin/sh

set -u

SYSTEM_ROOT=${BLOOM_NETWORK_SYNC_SYSTEM_ROOT:-/mnt/SDCARD/.tmp_update}
NETWORK=${BLOOM_NETWORK_SYNC_NETWORK_BIN:-$SYSTEM_ROOT/bin/bloom-network}
JQ=${BLOOM_NETWORK_SYNC_JQ_BIN:-$SYSTEM_ROOT/bin/jq}

result() {
printf '{"schema":1,"service":"bloom-network-sync","applied":%s,"state":"%s"}\n' "$1" "$2"
}

reconcile() {
[ -x "$NETWORK" ] && [ -x "$JQ" ] || { result false dependency_unavailable; return 1; }
event=$($NETWORK events status sync 2>/dev/null) || { result false event_unavailable; return 1; }
fields=$(printf '%s\n' "$event" | "$JQ" -er '
if .schema == 1 and .service == "bloom-network-events" and
.operation == "status" and .client == "sync" and
(.connectivity == "unknown" or .connectivity == "associated" or
.connectivity == "offline") and
(.generation | type) == "number" and .generation >= 0 and
.generation <= 2147483647 and (.generation | floor) == .generation and
(.pending | type) == "boolean"
then "\(.connectivity) \(.generation) \(.pending)"
else error("invalid sync network event") end
' 2>/dev/null) || { result false event_invalid; return 1; }
set -- $fields
[ "$#" -eq 3 ] || { result false event_invalid; return 1; }
connectivity=$1
generation=$2
pending=$3
if [ "$pending" = false ]; then result false no_pending_event; return 0; fi
case "$connectivity:$generation" in
associated:[1-9]*|offline:[1-9]*) ;;
*) result false event_invalid; return 1 ;;
esac

# SYNC-01/02 have not yet supplied a save-bundle contract or bloom-sync
# engine. This consumer records that explicit boundary; it never reads or
# writes Saves, starts a transport, or interprets filesystem sync as Save Sync.
case "$connectivity" in
associated) disposition=engine_unavailable ;;
offline) disposition=offline ;;
esac

acknowledgement=$($NETWORK events ack sync "$generation" 2>/dev/null) || {
result false acknowledgement_failed
return 1
}
printf '%s\n' "$acknowledgement" | "$JQ" -e \
--argjson generation "$generation" --arg connectivity "$connectivity" '
.schema == 1 and .service == "bloom-network-events" and
.operation == "ack" and .client == "sync" and
.connectivity == $connectivity and .generation == $generation and
.pending == false
' >/dev/null 2>&1 || { result false acknowledgement_invalid; return 1; }
printf '{"schema":1,"service":"bloom-network-sync","applied":true,'
printf '"state":"acknowledged","connectivity":"%s","generation":%s,' \
"$connectivity" "$generation"
printf '"disposition":"%s"}\n' "$disposition"
}

case "$#:${1:-}" in
1:reconcile) reconcile ;;
*) result false invalid_arguments; exit 2 ;;
esac
2 changes: 2 additions & 0 deletions static/build/.tmp_update/bin/bloom-wifi
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ SERVICES=${BLOOM_WIFI_SERVICES_BIN:-/mnt/SDCARD/.tmp_update/bin/bloom-network-se
NETWORK=${BLOOM_WIFI_NETWORK_BIN:-/mnt/SDCARD/.tmp_update/bin/bloom-network}
METADATA=${BLOOM_WIFI_METADATA_EVENT_BIN:-/mnt/SDCARD/.tmp_update/bin/bloom-network-metadata}
UPDATE=${BLOOM_WIFI_UPDATE_EVENT_BIN:-/mnt/SDCARD/.tmp_update/bin/bloom-network-update}
SYNC=${BLOOM_WIFI_SYNC_EVENT_BIN:-/mnt/SDCARD/.tmp_update/bin/bloom-network-sync}
NOHUP=${BLOOM_WIFI_NOHUP_BIN:-/usr/bin/nohup}
SELF=${BLOOM_WIFI_SELF:-/mnt/SDCARD/.tmp_update/bin/bloom-wifi}
INTERFACE=${BLOOM_WIFI_INTERFACE:-wlan0}
Expand Down Expand Up @@ -60,6 +61,7 @@ notify_connectivity() {
if "$NETWORK" events publish "$1" >/dev/null 2>&1; then
[ ! -x "$METADATA" ] || "$METADATA" reconcile >/dev/null 2>&1 || true
[ ! -x "$UPDATE" ] || "$UPDATE" reconcile >/dev/null 2>&1 || true
[ ! -x "$SYNC" ] || "$SYNC" reconcile >/dev/null 2>&1 || true
fi
return 0
}
Expand Down
1 change: 1 addition & 0 deletions test/shell/dev_deploy.bats
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ stage_network_bundle() {
bin/bloom-network \
bin/bloom-network-metadata \
bin/bloom-network-update \
bin/bloom-network-sync \
bin/bloom-network-services \
bin/bloom-network-transaction \
bin/bloom-platform \
Expand Down
4 changes: 2 additions & 2 deletions test/shell/legacy_manifest.bats
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
--manifest /workspace/build/legacy-manifest.json

[ "$status" -eq 0 ]
[[ "$output" == "legacy manifest validate: 334 components" ]]
[[ "$output" == "legacy manifest validate: 335 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)" ]
Expand Down Expand Up @@ -104,7 +104,7 @@ import sys
components = json.load(open(sys.argv[1], encoding="utf-8"))["components"]
runtime = [item for item in components if item["id"].startswith("runtime-")]
bloom = [item for item in runtime if item["source"] == "https://github.com/boburning/BloomOS"]
assert len(bloom) == 57
assert len(bloom) == 58
assert all(item["resolution"] == "source-build" for item in bloom)
assert all(item["source_revision"] == "release-commit" for item in bloom)
PY
Expand Down
83 changes: 83 additions & 0 deletions test/shell/network_sync.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env bats

setup() {
export SERVICE=/workspace/static/build/.tmp_update/bin/bloom-network-sync
export BLOOM_NETWORK_SYNC_NETWORK_BIN="$BATS_TEST_TMPDIR/bloom-network"
export BLOOM_NETWORK_SYNC_JQ_BIN=/usr/bin/jq
export EVENT_STATUS="$BATS_TEST_TMPDIR/event.json"
export ACK_STATUS="$BATS_TEST_TMPDIR/ack.json"
export CLIENT_LOG="$BATS_TEST_TMPDIR/client.log"
cat >"$BLOOM_NETWORK_SYNC_NETWORK_BIN" <<'SH'
#!/bin/sh
printf '%s\n' "$*" >>"$CLIENT_LOG"
case "$*" in
'events status sync') cat "$EVENT_STATUS" ;;
'events ack sync '*) [ ! -e "$ACK_STATUS.exit" ] || exit 1; cat "$ACK_STATUS" ;;
*) exit 2 ;;
esac
SH
chmod +x "$BLOOM_NETWORK_SYNC_NETWORK_BIN"
}

write_event() {
printf '{"schema":1,"service":"bloom-network-events","operation":"status","client":"sync","connectivity":"%s","generation":%s,"changed":false,"pending":%s}\n' "$1" "$2" "$3" >"$EVENT_STATUS"
}

write_ack() {
printf '{"schema":1,"service":"bloom-network-events","operation":"ack","client":"sync","connectivity":"%s","generation":%s,"changed":false,"pending":false}\n' "$1" "$2" >"$ACK_STATUS"
}

@test "sync consumer treats an absent pending generation as a no-op" {
write_event unknown 0 false
run "$SERVICE" reconcile
[ "$status" -eq 0 ]
[[ "$output" == *'"state":"no_pending_event"'* ]]
}

@test "associated sync event records the unavailable engine boundary" {
write_event associated 7 true
write_ack associated 7
run "$SERVICE" reconcile
[ "$status" -eq 0 ]
[[ "$output" == *'"generation":7,"disposition":"engine_unavailable"'* ]]
[ "$(sed -n '2p' "$CLIENT_LOG")" = 'events ack sync 7' ]
}

@test "offline sync event receives an explicit offline disposition" {
write_event offline 8 true
write_ack offline 8
run "$SERVICE" reconcile
[ "$status" -eq 0 ]
[[ "$output" == *'"generation":8,"disposition":"offline"'* ]]
}

@test "sync consumer rejects malformed event authority output" {
printf '%s\n' '{"schema":1,"client":"sync","connectivity":"associated","generation":1,"pending":true}' >"$EVENT_STATUS"
run "$SERVICE" reconcile
[ "$status" -eq 1 ]
[[ "$output" == *'"state":"event_invalid"'* ]]
}

@test "sync event acknowledgement failure retains the pending generation" {
write_event associated 9 true
write_ack associated 9
: >"$ACK_STATUS.exit"
run "$SERVICE" reconcile
[ "$status" -eq 1 ]
[[ "$output" == *'"state":"acknowledgement_failed"'* ]]
}

@test "sync consumer rejects mismatched acknowledgement output" {
write_event associated 10 true
write_ack offline 10
run "$SERVICE" reconcile
[ "$status" -eq 1 ]
[[ "$output" == *'"state":"acknowledgement_invalid"'* ]]
}

@test "sync event disposition has no transport or save mutation surface" {
! grep -E 'curl|wget|rsync|syncthing|/Saves|^[[:space:]]*sync([[:space:]]|$)' "$SERVICE"
run "$SERVICE" arbitrary
[ "$status" -eq 2 ]
[[ "$output" == *'"state":"invalid_arguments"'* ]]
}
3 changes: 2 additions & 1 deletion test/shell/transfer_policy.bats
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ teardown() {
bin/bloom-network \
bin/bloom-network-metadata \
bin/bloom-network-update \
bin/bloom-network-sync \
bin/bloom-network-services \
bin/bloom-network-transaction \
bin/bloom-platform \
Expand Down Expand Up @@ -131,7 +132,7 @@ teardown() {
run python3 /workspace/tools/dev-deploy-manifest.py network-helpers "$payload" \
net02-fixture 0123456789abcdef0123456789abcdef01234567 "$manifest"
[ "$status" -eq 0 ] || printf '%s\n' "$output" >&3
[ "$(jq '.files | length' "$manifest")" -eq 19 ]
[ "$(jq '.files | length' "$manifest")" -eq 20 ]

rm "$payload/bin/bloom-platform"
run python3 /workspace/tools/dev-deploy-manifest.py network-helpers "$payload" \
Expand Down
27 changes: 27 additions & 0 deletions test/shell/wifi_backend.bats
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ setup() {
export BLOOM_WIFI_NETWORK_BIN="$BATS_TEST_TMPDIR/bloom-network"
export BLOOM_WIFI_METADATA_EVENT_BIN="$BATS_TEST_TMPDIR/bloom-network-metadata"
export BLOOM_WIFI_UPDATE_EVENT_BIN="$BATS_TEST_TMPDIR/bloom-network-update"
export BLOOM_WIFI_SYNC_EVENT_BIN="$BATS_TEST_TMPDIR/bloom-network-sync"
export BLOOM_WIFI_NOHUP_BIN=/usr/bin/nohup
export BLOOM_WIFI_SELF="$SERVICE"
export BLOOM_WIFI_WATCH_DIR="$BATS_TEST_TMPDIR/watcher"
Expand All @@ -28,6 +29,7 @@ setup() {
export EVENT_LOG="$BATS_TEST_TMPDIR/events.log"
export METADATA_LOG="$BATS_TEST_TMPDIR/metadata.log"
export UPDATE_LOG="$BATS_TEST_TMPDIR/update.log"
export SYNC_LOG="$BATS_TEST_TMPDIR/sync.log"
mkdir -p "$BLOOM_WIFI_ROOT/sys/class/net/wlan0"
printf down >"$BLOOM_WIFI_ROOT/sys/class/net/wlan0/operstate"
printf 0 >"$BLOOM_WIFI_ROOT/sys/class/net/wlan0/carrier"
Expand Down Expand Up @@ -72,6 +74,10 @@ SH
cat >"$BLOOM_WIFI_UPDATE_EVENT_BIN" <<'SH'
#!/bin/sh
printf '%s\n' "$*" >>"$UPDATE_LOG"
SH
cat >"$BLOOM_WIFI_SYNC_EVENT_BIN" <<'SH'
#!/bin/sh
printf '%s\n' "$*" >>"$SYNC_LOG"
SH
cat >"$BLOOM_WIFI_IP_BIN" <<'SH'
#!/bin/sh
Expand All @@ -81,6 +87,7 @@ SH
chmod +x "$BLOOM_WIFI_IP_BIN" "$BLOOM_WIFI_SLEEP_BIN" "$BLOOM_WIFI_SERVICES_BIN" \
"$BLOOM_WIFI_NETWORK_BIN" "$BLOOM_WIFI_METADATA_EVENT_BIN"
chmod +x "$BLOOM_WIFI_UPDATE_EVENT_BIN"
chmod +x "$BLOOM_WIFI_SYNC_EVENT_BIN"
chmod +x "$BLOOM_PLATFORM_BIN" "$BLOOM_WIFI_JSONVAL_BIN"
}

Expand All @@ -95,6 +102,7 @@ SH
[ "$(cat "$EVENT_LOG")" = 'events publish associated' ]
[ "$(cat "$METADATA_LOG")" = reconcile ]
[ "$(cat "$UPDATE_LOG")" = reconcile ]
[ "$(cat "$SYNC_LOG")" = reconcile ]
}

@test "associated Plus without an address restarts canonical DHCP" {
Expand Down Expand Up @@ -262,6 +270,7 @@ SH
[ ! -s "$WIFI_LOG" ]
[ ! -e "$METADATA_LOG" ]
[ ! -e "$UPDATE_LOG" ]
[ ! -e "$SYNC_LOG" ]
}

@test "metadata observation failure never restarts or rolls back healthy Wi-Fi" {
Expand All @@ -277,6 +286,7 @@ SH
[ ! -s "$WIFI_LOG" ]
[ "$(cat "$EVENT_LOG")" = 'events publish associated' ]
[ "$(cat "$UPDATE_LOG")" = reconcile ]
[ "$(cat "$SYNC_LOG")" = reconcile ]
}

@test "update disposition failure never restarts or rolls back healthy Wi-Fi" {
Expand All @@ -292,6 +302,23 @@ SH
[ ! -s "$WIFI_LOG" ]
[ "$(cat "$EVENT_LOG")" = 'events publish associated' ]
[ "$(cat "$METADATA_LOG")" = reconcile ]
[ "$(cat "$SYNC_LOG")" = reconcile ]
}

@test "sync disposition failure never restarts or rolls back healthy Wi-Fi" {
printf up >"$BLOOM_WIFI_ROOT/sys/class/net/wlan0/operstate"
printf 1 >"$BLOOM_WIFI_ROOT/sys/class/net/wlan0/carrier"
: >"$BLOOM_WIFI_ROOT/sys/class/net/wlan0/ipv4"
printf '#!/bin/sh\nexit 1\n' >"$BLOOM_WIFI_SYNC_EVENT_BIN"
chmod +x "$BLOOM_WIFI_SYNC_EVENT_BIN"

run "$SERVICE" reconcile
[ "$status" -eq 0 ]
[[ "$output" == *'"applied":false,"state":"preserved"'* ]]
[ ! -s "$WIFI_LOG" ]
[ "$(cat "$EVENT_LOG")" = 'events publish associated' ]
[ "$(cat "$METADATA_LOG")" = reconcile ]
[ "$(cat "$UPDATE_LOG")" = reconcile ]
}

@test "disabling Wi-Fi stops an in-flight address recovery watcher before radio teardown" {
Expand Down
1 change: 1 addition & 0 deletions tools/dev-deploy-manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"bin/bloom-network",
"bin/bloom-network-metadata",
"bin/bloom-network-update",
"bin/bloom-network-sync",
"bin/bloom-network-services",
"bin/bloom-network-transaction",
"bin/bloom-platform",
Expand Down
1 change: 1 addition & 0 deletions tools/legacy_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"runtime-tmp-update-bin-bloom-network",
"runtime-tmp-update-bin-bloom-network-metadata",
"runtime-tmp-update-bin-bloom-network-update",
"runtime-tmp-update-bin-bloom-network-sync",
"runtime-tmp-update-bin-bloom-network-services",
"runtime-tmp-update-bin-bloom-network-transaction",
"runtime-tmp-update-bin-bloom-remote-keys",
Expand Down
Loading