Skip to content
Closed
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
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ core: $(CACHE)/.setup shared-libs
@cp $(ROOT_DIR)/build/ra-core-policy.json $(BUILD_DIR)/.tmp_update/config/ra-core-policy.json
@cp $(ROOT_DIR)/build/core-selection-policy.json $(BUILD_DIR)/.tmp_update/config/core-selection-policy.json
@cp $(ROOT_DIR)/build/save-migration-policy.json $(BUILD_DIR)/.tmp_update/config/save-migration-policy.json
@cp $(ROOT_DIR)/build/save-sync-contract.json $(BUILD_DIR)/.tmp_update/config/save-sync-contract.json
@cp $(ROOT_DIR)/build/save-sync-bundle.schema.json $(BUILD_DIR)/.tmp_update/config/save-sync-bundle.schema.json
@cp $(ROOT_DIR)/build/transfer-policy.json $(BUILD_DIR)/.tmp_update/config/transfer-policy.json
@cp $(ROOT_DIR)/build/system-catalog.json $(BUILD_DIR)/.tmp_update/config/system-catalog.json
@cp $(ROOT_DIR)/build/libretro-artwork-policy.json $(BUILD_DIR)/.tmp_update/config/libretro-artwork-policy.json
Expand Down
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 = 326
file_count = 1967
byte_count = 260662316
source_build_components = 232
component_count = 327
file_count = 1968
byte_count = 260706544
source_build_components = 233
unresolved_components = 94
inherited_baseline = "07505ea58c7bba698d6b9220ff43946a43cac76b"

Expand Down
19 changes: 16 additions & 3 deletions build/legacy-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,7 @@
"path": "static/build/.tmp_update/bin/bloom-network-sync",
"file_count": 1,
"byte_count": 2845,
"tree_sha256": "fc3e0cc0ec29b5082344fdb988e4c2e2ceeaccafdbde4015ad0e2f79cde4bb69",
"tree_sha256": "44f59ee23cf0b3f36afbdc981446883782a44a66d0a586ab3131d39a56a75482",
"source": "https://github.com/boburning/BloomOS",
"source_revision": "release-commit",
"license": "GPL-3.0-only",
Expand Down Expand Up @@ -834,6 +834,19 @@
"build_recipe": "Makefile",
"resolution": "source-build"
},
{
"id": "runtime-tmp-update-bin-bloom-sync",
"kind": "runtime",
"path": "static/build/.tmp_update/bin/bloom-sync",
"file_count": 1,
"byte_count": 43429,
"tree_sha256": "0538b57032dc248a0d69c4b7bceebf2271a433878d6378d1cbe028c3872e68ac",
"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-test-runner",
"kind": "runtime",
Expand Down Expand Up @@ -1034,8 +1047,8 @@
"kind": "runtime",
"path": "static/build/.tmp_update/bin/bloomctl",
"file_count": 1,
"byte_count": 40803,
"tree_sha256": "0b9d24be9d02aea7fafb84b48278ff5813e1c3bf86043dfa0d531f1930a9fc88",
"byte_count": 41602,
"tree_sha256": "d7b77598e3c3b402f609996f2e93fbf81c0811322d29420ec630009193868f2b",
"source": "https://github.com/boburning/BloomOS",
"source_revision": "release-commit",
"license": "GPL-3.0-only",
Expand Down
24 changes: 15 additions & 9 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -990,13 +990,13 @@ 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.

`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
`bloom-network-sync` completes the fixed event fan-out without becoming a Save
Sync transport. It validates and acknowledges only its exact sync generation,
returns `engine_ready` when the transport-neutral `bloom-sync` status contract
is healthy, `engine_unavailable` when it is not, and `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.
filesystem `sync` command as Save Sync. This explicit disposition keeps NET-07,
SYNC-02, and the still-conditional SYNC-03 transport gate separate.

Bounded `request enable` and `request disable` operations persist only
`device.wifi_enabled` through the active `bloom-settings` authority before
Expand Down Expand Up @@ -1338,9 +1338,15 @@ idempotent, a contiguous parent-linked generation is a staged fast-forward,
retained verified ancestry is a stale no-op, and incompatible identities,
forks, unrelated lineages, generation gaps, or unverified history preserve both
sides for explicit review. A bundle-ID collision is a security event. No SYNC-01
path exports, imports, transports, or writes Saves. The later `bloom-sync`
transaction is the sole planned publication owner; generic transfer keeps
Saves read-only. The complete normative contract and limitations are in
path exports, imports, transports, or writes Saves. The SYNC-02 `bloom-sync`
runtime is the sole publication owner. It authenticates immutable bundles,
classifies and privately stages or quarantines input, requires explicit review
before promoting a fork, incompatible input, or unrelated lineage, snapshots
the complete current save tree, and publishes a verified candidate through a
same-directory atomic replacement while the game session is inactive. It
cannot override a bundle-ID collision or missing contiguous history. Generic
transfer keeps Saves read-only, and `bloom-sync` itself has no transport or
listener. The complete normative contract and limitations are in
[`SAVE_SYNC.md`](SAVE_SYNC.md).

## Canonical game identity
Expand Down
48 changes: 42 additions & 6 deletions docs/SAVE_SYNC.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Save Sync contract

Status: normative SYNC-01 contract. Save Sync remains unavailable until the
transport-neutral engine, a qualified transport, and the UI pass their separate
SYNC-02 through SYNC-04 gates.
Status: normative SYNC-01 contract with the transport-neutral SYNC-02 engine.
User-visible Save Sync remains unavailable until a qualified transport and the
UI pass their separate SYNC-03 and SYNC-04 gates.

`build/save-sync-contract.json` is the semantic authority and
`build/save-sync-bundle.schema.json` is the closed bundle-envelope schema. This
Expand Down Expand Up @@ -116,9 +116,45 @@ not mean that a transport or UI may replace user data.

Generic SFTP and any future Web Transfer keep Saves read-only. A transport may
move opaque bytes but never owns identity, trust, conflict, or publication.
SYNC-02 must provide one transactional owner that snapshots current data,
publishes only after explicit conflict resolution, preserves rollback material,
and never writes a live save directory directly from transport input.
`bloom-sync` is that transactional owner. Its input and output are immutable
bundle directories containing only `bundle.json`, `payload.bin`, and `complete`.
It has no socket, provider, cloud, discovery, or filesystem-watcher dependency.
The network event consumer may report `engine_ready`, but it starts no transport
and moves no bundle.

The engine keeps its local Ed25519 identity, explicitly authorized peer keys,
exports, candidates, quarantined review material, current lineage records, and
transactions under the private `.bloom/sync` root. The private key is created
with mode `0600`. Normal peer authorization is first-key only: a divergent key
or epoch for an existing device ID fails as clone or rotation evidence. A
deauthorized peer can no longer validate new input and no accepted user data is
deleted.

`export` accepts a closed local manifest draft and one relative path beneath the
matching `saves` or `states` tree. It rejects links and traversal, derives the
exact payload size and digest, adds the local device identity, emits RFC
8785-compatible canonical bytes for the schema's ASCII/integer subset, and
signs those bytes with Ed25519. Empty payloads are valid. Export is not a
transport operation.

`validate` and `import` enforce the fixed SYNC-01 order: bounds, closed schema,
bundle and payload identity, paired device and active epoch, Ed25519 signature,
reviewed core compatibility, then conflict classification. New lineages and
fast-forwards enter private candidate storage. Exact replay and retained
ancestry are no-ops. Collisions, incompatibility, forks, missing history, and
unrelated lineages remain immutable in quarantine. Explicit review may promote
only incompatible, forked, or unrelated-lineage input; a bundle-ID collision or
missing contiguous history cannot be overridden.

`publish` accepts only an exact staged bundle ID, relative local destination,
and matching classification. Review-required input must first carry the
engine's explicit resolution record. Publication is refused while a game
session is active. The engine creates and verifies a full save snapshot, keeps
the replaced file and previous lineage record in a private transaction, copies
the candidate to a same-directory temporary file, verifies its digest, and
atomically renames it into place. A late state-write failure restores the prior
live bytes and current record while retaining the snapshot and candidate.
Transport input is never written directly into the live save tree.

Malformed, oversized, unknown-version, unauthenticated, incompatible, forked,
gapped, or replay-collision input fails closed while preserving user data.
Expand Down
14 changes: 10 additions & 4 deletions docs/SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,9 +388,15 @@ bundle and payload identities match exactly.
Lineage, generation, parent payload hash, and retained verified ancestry own
conflict semantics. Wall time is display-only and never authorizes overwrite.
Forks, unrelated lineages, gaps, unverified history, and incompatible saves are
preserved for explicit review. SYNC-01 has no runtime consumer and cannot read
or write Saves. Future transports move only immutable bundles; only the
transactional `bloom-sync` owner may eventually publish after snapshot and
conflict resolution. Generic remote access continues to expose Saves read-only.
preserved for explicit review. SYNC-01 itself has no runtime consumer and
cannot read or write Saves. SYNC-02's transactional `bloom-sync` owner validates
bounded immutable bundles, an explicitly authorized device/key epoch, the
Ed25519 signature, reviewed compatibility, and ordered conflict outcome before
private staging. It refuses active-session publication, creates a verified
whole-save snapshot, retains prior bytes and lineage state, and uses a
same-directory verified atomic replacement. Late state failure restores the
prior live file. Bundle collisions and missing history cannot be overridden.
Future transports move only opaque immutable bundles and have no publication
authority. Generic remote access continues to expose Saves read-only.

Security-sensitive migrations and repair operations must be bounded, logged, reversible where possible, and validated before activation.
15 changes: 11 additions & 4 deletions docs/program/BloomOS_Work_Program_v3.json
Original file line number Diff line number Diff line change
Expand Up @@ -2088,16 +2088,23 @@
"id": "SYNC-02",
"track": "Sync",
"title": "Transport-neutral bloom-sync engine",
"status": "planned",
"status": "device_gate_pending",
"dependencies": [
"SYNC-01"
],
"github_prs": [],
"github_prs": [651],
"risk_tier": 3,
"deployment_tier": "warm",
"device_gate_required": true,
"last_audited_main": "3a7e78290285806e1ce3eb0c126887644ff665f2",
"evidence": []
"last_audited_main": "a01f2a7fa7cfc3877405b4ba2005d600cbf2ec70",
"evidence": [
"issue #650 tracks the transport-neutral transactional SYNC-02 runtime owner after SYNC-01 merged, with transport selection, synchronization daemons, Save Sync UI, and physical certification explicitly out of scope",
"PR #651 adds the bounded bloom-sync identity, peer trust, immutable export, ordered validation and conflict classification, private staging and quarantine, explicit resolution, verified snapshot, same-directory atomic publication, and rollback owner without adding a cloud provider, socket, listener, or transport dependency",
"the exact runtime commit 8077e94481c202a93f64d512a05bbf7c85384952 passed 595 native tests, 595 AddressSanitizer tests, 849 Shell tests, 47 focused sync/network/provenance/FAT tests, 10 deterministic UI gallery cases with zero changed pixels, full cppcheck, and a clean pinned ARM build ending ci-build READY",
"a strict key-only read-only Mini Plus capability probe found that the shipped OpenSSL required BloomOS-owned runtime libraries; PR #651 now scopes those libraries inside bloom-sync, and the corrected tmp-only probe passed Ed25519 public-key generation, signing, canonical jq ordering, and cleanup without reading or writing Saves or installing a candidate",
"PR #651 exact head 9ce3b5ddb148382f39ac15fc4d9e0b0963248574 passed hosted native, AddressSanitizer, exhaustive Shell, static-analysis, and clean ARM checks after replacing a stale hard-coded inventory fixture with a structural regression test",
"no SYNC-02 candidate has been deployed: the Mini Plus remains healthy on the sole signed UI-12 candidate v0.1.0-dev-e9ce0c30 in testing with its prior version and verified pre-update snapshot retained, no development overlay, an idle network transaction, and a clean key-only listener audit; SYNC-02 remains device-gate pending until the UI-12 physical shutdown observation frees the signed candidate boundary"
]
},
{
"id": "SYNC-03",
Expand Down
12 changes: 8 additions & 4 deletions static/build/.tmp_update/bin/bloom-network-sync
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ 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}
SYNC=${BLOOM_NETWORK_SYNC_ENGINE_BIN:-$SYSTEM_ROOT/bin/bloom-sync}

result() {
printf '{"schema":1,"service":"bloom-network-sync","applied":%s,"state":"%s"}\n' "$1" "$2"
Expand Down Expand Up @@ -35,11 +36,14 @@ reconcile() {
*) 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 ;;
associated)
if [ -x "$SYNC" ] && "$SYNC" status >/dev/null 2>&1; then
disposition=engine_ready
else
disposition=engine_unavailable
fi
;;
offline) disposition=offline ;;
esac

Expand Down
Loading
Loading