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 = 335
file_count = 2134
byte_count = 314704844
source_build_components = 231
component_count = 336
file_count = 2135
byte_count = 314711917
source_build_components = 232
unresolved_components = 104
inherited_baseline = "07505ea58c7bba698d6b9220ff43946a43cac76b"

Expand Down
25 changes: 19 additions & 6 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": 32904,
"tree_sha256": "ac0661051a48261c2a585f1dc21ce172c61ed19edb1b2a182660f0202950bc9d",
"byte_count": 33011,
"tree_sha256": "4dd16ce6d73174d683d2b6b43161024a1694de4b48c58842ce89e80b15fa394e",
"source": "https://github.com/boburning/BloomOS",
"source_revision": "release-commit",
"license": "GPL-3.0-only",
Expand Down Expand Up @@ -522,6 +522,19 @@
"build_recipe": "Makefile",
"resolution": "source-build"
},
{
"id": "runtime-tmp-update-bin-bloom-display-session-prepare",
"kind": "runtime",
"path": "static/build/.tmp_update/bin/bloom-display-session-prepare",
"file_count": 1,
"byte_count": 3190,
"tree_sha256": "5c4dff01fab14bac6d77ae7d0109d04e6b6eb736038a7d145f8d62db57b8a9bc",
"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-game-smoke",
"kind": "runtime",
Expand Down Expand Up @@ -748,8 +761,8 @@
"kind": "runtime",
"path": "static/build/.tmp_update/bin/bloom-ra-session-prepare",
"file_count": 1,
"byte_count": 12860,
"tree_sha256": "07f87ee2a7b1e88155bf0ff0d4087fdf7861f3dcb9bf02cce3276191a4434de7",
"byte_count": 12798,
"tree_sha256": "dd8a2a3ac3222a7dacf5e0a4546c6107b738f24f420101aaac658e5c841ef471",
"source": "https://github.com/boburning/BloomOS",
"source_revision": "release-commit",
"license": "GPL-3.0-only",
Expand Down Expand Up @@ -813,8 +826,8 @@
"kind": "runtime",
"path": "static/build/.tmp_update/bin/bloom-session",
"file_count": 1,
"byte_count": 25092,
"tree_sha256": "be2f99f54505a7e9081a0603775383cbbc5072f8da1d6063fdf9ef7f2d504f56",
"byte_count": 28930,
"tree_sha256": "3f7d914d334cd99468f9c7713f58c594ba26685f37d0e4117d47dd1255deca24",
"source": "https://github.com/boburning/BloomOS",
"source_revision": "release-commit",
"license": "GPL-3.0-only",
Expand Down
1 change: 1 addition & 0 deletions docs/1.0_TEST_MATRIX.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ observable properties and never substitutes for physical UX review.
| Parsers | Theme, JSON, paths, images, and schema migration fuzz/property tests |
| Display Auto compatibility | Deterministic per-system/core effective visual fixture plus explicit no-selectable-core catalog dispositions; exact policy/catalog/manifest, core binary, core metadata, global, core-override, and content-directory hashes; representative legacy geometry/effect assertions; runtime user and per-game overrides excluded |
| Display profile semantics | Exact Scaling, Screen Effect, and Bezel controls; disjoint geometry/sampling/effect/artwork key ownership; PLAT-01 framebuffer capability; bounded centered Auto/integer/aspect/fill geometry fixtures for 640x480 and 752x560; invalid geometry and stale/coupled/unsafe policy mutations fail without partial config |
| Display settings and session composer | Canonical backward-compatible display selections; exact enum rejection; no Onion projection; observed framebuffer input; independent axis fallback; one private `0600` RetroArch append config shared with achievements; no permanent-config mutation; decorator failure and timeout preserve the immutable base launch |
| Performance | Input/render timings, 30-minute shell soak, 500 open/back cycles |
| Privacy | Allowlisted health/support export contains no private data |
| Developer deployment | Dual-marker/update/session preflight; deterministic allowlisted manifest; exact byte/hash/mode transfer; sealed slots; atomic activation; readiness auto-rollback; active/previous overlay; bounded cleanup; ordinary network-helper activation denied |
Expand Down
49 changes: 49 additions & 0 deletions docs/DISPLAY_SESSION_COMPOSER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Display settings and session composer

DISP-03 stores display selections in the schema-1 `bloom-settings` authority and
applies them only through Bloom's private RetroArch session configuration. It
never edits the global RetroArch configuration or an inherited core/content
override.

The canonical `display` object contains four independent selections:

- `scaling`: `auto`, `integer-pixels`, `correct-aspect`, or `fill`;
- `sampling`: `auto`, `nearest`, or `linear`;
- `screen_effect`: `none`, `auto`, `lcd`, `crt`, or `custom`;
- `bezel`: `none`, `auto`, or `custom`.

Older schema-1 settings without this object read as four `auto` values. The
first display mutation creates the complete object atomically. Display values
are not projected into Onion compatibility state.

At session start, `bloom-display-session-prepare` reads those typed values and
the observed `bloom-platform` framebuffer dimensions. It asks `bloom-launch`
to contribute display-owned keys to the existing trusted composer. The
resulting file is a regular private `0600` file under `/tmp/bloom-session/`.
The session transaction validates and attaches exactly that one file. If
RetroAchievements is enabled, its credential and safety contributions import
the validated display contribution and atomically replace the same file.

`auto` does not rewrite an axis from the DISP-01 baseline. `fill`, nearest or
linear sampling, effect `none`, and bezel `none` have complete typed composer
implementations. Integer Pixels and Correct Aspect remain unavailable until a
trusted source raster/aspect is connected. LCD, CRT, and custom effect or
bezel selections remain unavailable until DISP-04/DISP-05 provide reviewed
identities. An unavailable selection resolves that axis to `auto`, records a
bounded reason, and emits no partial keys for that axis.

The display owner can emit only the allowlisted display keys in
`bloom_retroarch_config.c`; it cannot emit achievement, input-safety, driver,
network, or arbitrary filesystem settings. Custom selections are bounded IDs
in settings and never become raw paths in DISP-03.

Validation:

```sh
make test GTEST_FILTER='BloomSettingsTest.*:BloomRetroarchConfigTest.*:BloomLaunchTest.*'
docker run --rm -v "$PWD:/workspace" -w /workspace \
ghcr.io/boburning/bloomos-shell-test@sha256:36adeca7c265345688ebf47b575c56e350b9016257e0a9ed15c63dc7e0447d63 \
test/shell/display_session_prepare.bats \
test/shell/bloom_session.bats \
test/shell/ra_session_prepare.bats
```
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
117 changes: 117 additions & 0 deletions docs/validation/display/plus-disp03-settings-composer-20260831.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
{
"schema": 1,
"date": "2026-08-31",
"program_slice": "DISP-03",
"pull_request": 608,
"operator": "Codex over maintainer-authorized key-only SSH",
"source_commit": "6050dde80e9b821fd527ef2498ab1a63f1e4d67e",
"base_commit": "5967607dad0067cefb9ba73feb209a2c702b77d6",
"host_validation": {
"focused_native": "64/64 passed",
"full_native": "593/593 passed in 16.11 seconds",
"focused_shell_and_deployment": "115/115 passed",
"full_shell": "792/792 passed across eight shards",
"static_analysis": "focused cppcheck exited 0",
"required_ci": [
"Check build",
"format",
"shell-test",
"Static analysis",
"Test build",
"AddressSanitizer"
],
"required_ci_result": "all passed"
},
"signed_build": {
"workflow_run": "https://github.com/boburning/BloomOS/actions/runs/33345813017",
"version": "0.1.0-dev-6050dde8",
"archive_size": 199862348,
"archive_sha256": "66904930f2c076ab122b4af26b8010403c178002519106afdeaa147089d7da40",
"manifest_sha256": "ad387c51dd5bd393e314c59a272420236dcb3b6bd8fe426abd59735eeaa69e2f",
"manifest_signature_sha256": "3f3ac4f304643033ae0333861cdd44c2e71ce78c81eaf2d2f34c34254f4bdb87",
"payload_manifest_sha256": "4e8309164c62d33c30b6afc6f4d80b18e200695706699a34fbe5067d9e596849",
"build_info_sha256": "c6270dd3c93428f917a3ab9ffe5d999bc7b8f14f9c32fe161b3165726248c389",
"sha256sums_verified": true,
"ed25519_signature_verified": true,
"installed_as_full_update": false
},
"device": {
"model": "miyoo-mini-plus",
"model_id": "354",
"display": "640x480",
"installed_baseline": "v0.1.0-dev-2933f706",
"developer_ssh": "running_key_only",
"physical_certification": false
},
"deployment": {
"tier": "hash-differential Warm overlay",
"candidate_id": "disp03-launch-6050dde8",
"candidate_component": "launch-session",
"candidate_manifest_files": 11,
"candidate_manifest_exact_hashes_verified_before_and_after_activation": true,
"temporary_deployer_size": 33011,
"temporary_deployer_sha256": "c3cbc050c733ed93d60fce93417daa495b6fc83b7912624fdbee062f9c46adaf",
"temporary_deployer_removed": true,
"full_update_performed": false,
"network_component_deployed": false
},
"device_gate": {
"preflight_ready": true,
"session_state_before_and_after": "IDLE",
"retroarch_process_started": false,
"legacy_schema_without_display": {
"status": "applied",
"effective_scaling": "auto",
"effective_screen_effect": "auto",
"effective_bezel": "auto",
"private_config_mode": "0600",
"private_config": [
"video_fullscreen = \"true\""
]
},
"fill_nearest_none_none": {
"status": "applied",
"observed_panel": "640x480",
"private_config_mode": "0600",
"geometry": "bounded full-panel viewport",
"sampling": "nearest",
"effect": "none",
"bezel": "none"
},
"global_retroarch_config_sha256_before": "336ee748ca06bba32582087efc867f811f465558df158056b9a25689bf36672e",
"global_retroarch_config_sha256_after": "336ee748ca06bba32582087efc867f811f465558df158056b9a25689bf36672e",
"canonical_settings_sha256_before": "31fe5ad0969bc8a9e831d10b77752da4712b30d0b96082c8243a6fa495316c9d",
"canonical_settings_sha256_after": "31fe5ad0969bc8a9e831d10b77752da4712b30d0b96082c8243a6fa495316c9d",
"wifi_state_before_during_and_after": "enabled and associated",
"task_temporary_configs_removed": true,
"candidate_helper_removed_on_rollback": true
},
"preliminary_fail_closed_checks": [
"a non-canonical synthetic GameID was rejected before config composition",
"a config path outside /tmp/bloom-session was rejected before file creation",
"an idle-session directory lifecycle race failed before file creation; the final gate recreated the canonical private directory after confirming IDLE"
],
"restoration": {
"candidate_rolled_back": true,
"current_shell_overlay": "disp03-shell-restore-6050dde8",
"current_shell_manifest_exact_hashes_verified": true,
"final_preflight_ready": true,
"final_wifi_state": "enabled and associated",
"final_retroarch_processes": 0
},
"screenshots": {
"shell_restored": {
"path": "docs/validation/display/disp03-20260831/shell-restored.png",
"size": 17029,
"sha256": "26f66de76bfad9d7a04c25b60168c410726e103e1aa34c1e3f1ae342e341d495",
"privacy_safe": true
}
},
"limitations": [
"This is remote SSH, process, config, and framebuffer evidence; it is not physical controls, LCD quality, audio, or gameplay certification.",
"The gate validated composer output without launching a ROM or touching Saves or user data.",
"This evidence certifies the Miyoo Mini Plus only, not Mini V1-V4 or Mini Flip/my285.",
"Integer Pixels and Correct Aspect remain Auto until trusted source raster/aspect is connected.",
"LCD, CRT, and custom effect or bezel selections remain Auto until DISP-04 and DISP-05 provide reviewed identities."
]
}
88 changes: 88 additions & 0 deletions src/bloomLaunch/bloom_launch.c
Original file line number Diff line number Diff line change
Expand Up @@ -968,6 +968,80 @@ int bloom_launch_copy_achievement_state(const char *source_path, const char *des
cJSON_Delete(destination);
return result;
}

int bloom_launch_write_display_config(const char *config_path, const char *scaling,
const char *sampling, const char *screen_effect,
const char *bezel, unsigned panel_width,
unsigned panel_height, char *effective_scaling,
size_t effective_scaling_size, char *effective_effect,
size_t effective_effect_size, char *effective_bezel,
size_t effective_bezel_size, char *reason,
size_t reason_size, char *error, size_t error_size)
{
if (!path_under(config_path, "/tmp/bloom-session/")) {
set_error(error, error_size, "display config path is invalid");
return -1;
}
BloomRetroarchConfig *composer = bloom_retroarch_config_create();
char config[4096];
size_t length = 0;
int composed = composer == NULL ? -1 : bloom_retroarch_config_apply_display(composer, scaling, sampling, screen_effect, bezel, panel_width, panel_height, effective_scaling, effective_scaling_size, effective_effect, effective_effect_size, effective_bezel, effective_bezel_size, reason, reason_size, error, error_size);
if (composer == NULL)
set_error(error, error_size, "cannot allocate RetroArch config composer");
if (composed == 0)
composed = bloom_retroarch_config_render(composer, config, sizeof(config), &length,
error, error_size);
bloom_retroarch_config_destroy(composer);
if (composed != 0 || atomic_write_bytes(config_path, config, length, 0600, error, error_size) != 0 ||
bloom_retroarch_config_validate_file(config_path, error, error_size) != 0) {
memset(config, 0, sizeof(config));
unlink(config_path);
return -1;
}
memset(config, 0, sizeof(config));
return 0;
}

int bloom_launch_attach_session_config(const char *request_path, const char *config_path,
char *error, size_t error_size)
{
if (!path_under(config_path, "/tmp/bloom-session/")) {
set_error(error, error_size, "session config path is invalid");
return -1;
}
cJSON *request = NULL;
if (load_valid(request_path, &request, error, error_size) != 0)
return -1;
int schema = request_schema(request);
int attached = 0;
if (schema == 1) {
cJSON *configs = cJSON_GetObjectItemCaseSensitive(request, "append_configs");
if (cJSON_GetArraySize(configs) == 0)
attached = cJSON_AddItemToArray(configs, cJSON_CreateString(config_path));
else if (cJSON_GetArraySize(configs) == 1) {
cJSON *current = cJSON_GetArrayItem(configs, 0);
attached = cJSON_IsString(current) && strcmp(current->valuestring, config_path) == 0;
}
}
else {
cJSON *current = cJSON_GetObjectItemCaseSensitive(request, "append_config");
if (cJSON_IsNull(current))
attached = cJSON_ReplaceItemInObjectCaseSensitive(request, "append_config",
cJSON_CreateString(config_path));
else
attached = cJSON_IsString(current) && strcmp(current->valuestring, config_path) == 0;
}
if (!attached || !valid_request(request, error, error_size)) {
cJSON_Delete(request);
if (error != NULL && error[0] == '\0')
set_error(error, error_size, "session config could not be attached");
return -1;
}
int result = write_request_json(request_path, request, error, error_size);
cJSON_Delete(request);
return result;
}

int bloom_launch_write_ra_config(const char *request_path, const char *config_path, const char *username,
const char *token, const char *proxy_host, char *error, size_t error_size)
{
Expand Down Expand Up @@ -999,6 +1073,20 @@ int bloom_launch_write_ra_config(const char *request_path, const char *config_pa
int composed = composer != NULL ? 0 : -1;
if (composer == NULL)
set_error(error, error_size, "cannot allocate RetroArch config composer");
if (composed == 0 && access(config_path, F_OK) == 0) {
char import_error[256] = {0};
if (bloom_retroarch_config_import_file(composer, config_path, import_error,
sizeof(import_error)) != 0) {
bloom_retroarch_config_destroy(composer);
composer = bloom_retroarch_config_create();
if (composer == NULL) {
memset(config, 0, sizeof(config));
cJSON_Delete(request);
set_error(error, error_size, "cannot allocate RetroArch config composer");
return -1;
}
}
}
if (composed == 0)
composed = bloom_retroarch_config_set_bool(composer, BLOOM_RA_CONFIG_ACHIEVEMENTS,
"cheevos_enable", 1, error, error_size);
Expand Down
10 changes: 10 additions & 0 deletions src/bloomLaunch/bloom_launch.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,15 @@ int bloom_launch_resolve_achievement_transport(const char *request_path, int ena
const char *core_certification, char *error, size_t error_size);
int bloom_launch_write_ra_config(const char *request_path, const char *config_path, const char *username,
const char *token, const char *proxy_host, char *error, size_t error_size);
int bloom_launch_write_display_config(const char *config_path, const char *scaling,
const char *sampling, const char *screen_effect,
const char *bezel, unsigned panel_width,
unsigned panel_height, char *effective_scaling,
size_t effective_scaling_size, char *effective_effect,
size_t effective_effect_size, char *effective_bezel,
size_t effective_bezel_size, char *reason,
size_t reason_size, char *error, size_t error_size);
int bloom_launch_attach_session_config(const char *request_path, const char *config_path,
char *error, size_t error_size);

#endif
Loading
Loading