Compare commits

..
Author SHA1 Message Date
SAPHID 81c2e86f20 Merge remote-tracking branch 'origin/main' into test/framey-deck-shelves-native
# Conflicts:
#	tests/decky-runtime/check.py
2026-10-29 02:27:17 +00:00
Alex Southwell ddbaff8f6a Merge pull request #402 from saphid/fix/framey-radiyo-tls
fix(framey): verify RadiYo HTTPS certificates
2026-10-29 02:26:24 +00:00
SAPHID 250f2b9674 test(framey): verify Deck Shelves settings in native Decky 2026-10-29 02:23:57 +00:00
SAPHID 3495a017d1 fix(framey): verify RadiYo HTTPS certificates 2026-10-29 02:16:19 +00:00
Alex Southwell 2a18f2bda7 Merge pull request #401 from saphid/fix/release-audit-full-pagination
Audit every release-list page before choosing a draft
2026-10-29 01:54:14 +00:00
SAPHID f7ff94aae4 Record live release lookup for audit fix 2026-10-29 01:44:03 +00:00
SAPHID f4c8308f8a Merge remote-tracking branch 'origin/main' into fix/release-audit-full-pagination 2026-10-29 01:41:38 +00:00
Alex Southwell a2e578f6c3 Merge pull request #400 from saphid/fix/release-gate-smoke-proof-20261029
Reject unreadable Frame smoke sensor values
2026-10-29 01:41:02 +00:00
SAPHID e8fb154107 Audit every release-list page before choosing a draft 2026-10-29 01:33:33 +00:00
SAPHID 35b59e62da Document Frame smoke sensor gate evidence 2026-10-29 01:29:48 +00:00
SAPHID be07dbeeeb Reject unreadable Frame smoke sensor values 2026-10-29 01:25:26 +00:00
Alex Southwell b123f5ff34 Merge pull request #399 from saphid/test/framey-gamesync-native
Verify Game Sync configuration in native Decky
2026-10-29 01:07:59 +00:00
Alex Southwell 01e5d8b2db Merge pull request #398 from saphid/test/framey-radiyo-native
Verify RadiYo backend through isolated Decky
2026-10-29 00:49:26 +00:00
20 changed files with 306 additions and 27 deletions

No files matched your search

@@ -0,0 +1,20 @@
# Frame smoke sensor validation, 29 October 2026
The locked real Frame smoke reads SteamVR activity, IIO proximity and battery
before installing a test title. The proximity reader converts sensor text to a
Python float. Python accepts `NaN` and infinity as floats, while range
comparisons with `NaN` do not reject it. Before this change, a proximity array
containing `NaN` could pass the standby preflight and the saved-result release
verifier.
Focused local tests supplied `NaN`, positive and negative infinity to both
the live preflight parser and the release-result verifier. They also supplied
boolean, nonfinite, below-threshold and above-100 battery values. Every invalid
sample was rejected. Valid standby, finite proximity and 99–100% integer
battery samples continued to pass. The 25 smoke/cleanup tests, six result-gate
tests and 16 publisher tests passed locally.
This evidence uses simulated sensor responses. It does not record a real Frame
smoke, a worn-headset check, or controller input. Release publication still
requires a complete locked smoke from the exact clean release commit, including
the x86 launch that was skipped in the held v0.4.54 candidate.
@@ -0,0 +1,22 @@
# Deck Shelves 2.0.0 native settings probe, 29 October 2026
The unchanged official archive for store ID 135 has SHA-256
`b566280ad38fd3ecd584b5b46a855f301539eb9a86063834c64b7045f7063f91`.
The earlier [browser evidence](framey-deck-shelves-browser-2026-10-26.md)
proved only local frontend settings rendering and a stubbed Python backend.
`python3 scripts/framey-test-decky-runtime.py --case 135 --output /tmp/framey-deck-shelves-native-20261029.json`
passed with the published Decky 3.3.0 x86_64 loader in a disposable container
with no external network, host devices, session bus or user home. Decky
discovered the official API v1 plugin. Its public `get_settings` returned
disabled defaults; `set_settings` saved one private test shelf; the JSON file
and a fresh `get_settings` agreed. The same shelf ID and title returned after
stopping and restarting the Decky loader. The runner removed its container
and image. No headset lock, real Frame service, Steam library or external
account was used.
This verifies only private backend settings persistence. The plugin's primary
Steam Home shelf injection has no verified target in Framey's isolated panel.
Real Frame ARM64 settings, wearer-visible VR and physical controller input
remain unverified. Deck Shelves stays **partial**; compatibility counts do not
change.
@@ -22,3 +22,25 @@ audio playback, real Frame service, VR visibility or controller was tested.
The published backend sets `check_hostname=False` and `CERT_NONE` for its
outbound HTTPS requests; the loopback HTTP test did not exercise that path.
RadiYo! remains **partial**. The compatibility count does not change.
## Managed TLS repair
The exact official archive's `radiyo-steam-deck/main.py` has SHA-256
`868332dbe3feb1e7a81e35a0c01fdc0a6b68421ec1f84e238564bfad4946e87e`.
The managed installer checks that hash, removes the two certificate and
hostname bypasses from its private copy, and formats certificate errors as
strings for both `fetch` and `getRedirectUrl`. The official ZIP is unchanged;
an already installed managed copy needs Update to receive this repair.
`python3 scripts/framey-test-decky-runtime.py --case 24 --output /tmp/framey-radiyo-tls-runtime-20261029.json`
passed on native x86_64 with the published Decky 3.3.0 loader in a disposable,
network-isolated container. HTTP loopback fetch, custom fetch and redirect
still passed. Both HTTPS methods returned `CERTIFICATE_VERIFY_FAILED` for the
loopback self-signed test certificate. The disposable container and image were
removed. The 40 store transaction tests passed, including managed RadiYo
install, receipt check and removal. No headset lock was taken, and no real
Frame or external station was contacted.
This proves rejection of an untrusted certificate in the isolated backend.
Trusted HTTPS stations, audio playback, live frontend controls, real Frame and
worn VR behavior remain unverified. RadiYo! stays **partial**.
@@ -0,0 +1,20 @@
# Release audit pagination check, 29 October 2026
The independent installer audit previously returned on the first GitHub
release-list page containing a matching tag or draft title. A second matching
draft on a later page was never inspected. The audit could therefore report
one release while the complete list was ambiguous.
Local tests mocked two GitHub pages with the same version draft. The old code
accepted the first draft; the corrected lookup rejects the duplicate after
reading both pages. A second test finds a release on page two, and a full
20-page response fails closed because uniqueness cannot be established. All
13 focused `test_audit_release_assets.py` tests passed locally.
The corrected lookup also read GitHub's live release list without downloading
assets or changing a release. It found the published `v0.4.53` release as ID
`427006429`.
The duplicate and pagination cases use mocked API responses. No release asset
was downloaded, no release was changed, and these results are not a full release
verification or real Frame smoke.
+2 -2
View File
@@ -56,7 +56,7 @@ The API column shows manifest / actual bundled frontend API. Many manifests say
| 21: PowerTools 2.0.3 | legacy / legacy | N/A | Not supported on Frame: Targets AMD Ryzen power controls; Frame uses an ARM64 SoC. | static archive, local browser, fake-Frame |
| 22: Memory Deck 0.1.8 | legacy / legacy | broken | Plugin contains a non-ARM64 Linux binary: memory-deck/bin/libscanmem.so.1.0.0. | static archive, local browser, fake-Frame |
| 23: Animation Changer 1.3.2 | legacy / legacy | partial | Initializer succeeds; forced backend failure prevents loading animations. Boot-video changes untested. | static archive, local browser, fake-Frame |
| 24: RadiYo! 1.3 | legacy / legacy | partial | Initializer succeeds. Exact official legacy backend passed published Decky 3.3.0 x86_64 discovery and loopback-only station fetch, custom-station fetch and redirect in a network-isolated container. Real radio services, HTTPS behavior, audio playback, real Frame backend and worn VR remain unverified. The published backend disables HTTPS certificate verification; no external request was made. | static archive, local browser, fake-Frame, isolated Decky 3.3.0 x86_64 backend |
| 24: RadiYo! 1.3 | legacy / legacy | partial | Initializer succeeds. The exact official legacy backend passes isolated Decky 3.3.0 discovery and loopback HTTP station fetch, custom fetch and redirect. A digest-scoped managed install restores TLS certificate and hostname checks; its fetch and redirect methods reject a loopback self-signed HTTPS endpoint. External radio services, trusted HTTPS, audio playback, real Frame backend and worn VR remain unverified. Older installed copies need an update for the TLS fix. | static archive, local browser, fake-Frame, isolated Decky 3.3.0 x86_64 backend |
| 26: HLTB for Deck 2.0.12 | 1 / 2 | partial | Settings render without the prior IndexedDB SecurityError; Steam library badges have no patch target, and live lookup is unverified. | static archive, local browser, fake-Frame |
| 27: AutoSuspend 2.2.0 | legacy / legacy | broken | Frontend audit failed: Cannot read properties of null (reading '6'). The exact frontend also contains SteamOS suspend and shutdown paths; threshold behavior and safe Frame operation are unverified. | static archive, local browser, fake-Frame |
| 28: Emuchievements 2.0.5 | legacy / legacy | broken | Plugin contains a non-ARM64 Linux binary: Emuchievements/bin/hash. | static archive, local browser, fake-Frame |
@@ -148,7 +148,7 @@ The API column shows manifest / actual bundled frontend API. Many manifests say
| 132: SuggestMe 1.5.3 | 1 / 2 | partial | Initializer succeeds; recommendation backend and Steam library data unverified. | static archive, local browser, fake-Frame |
| 133: Non-Steam Badges 0.1.0 | 1 / 2 | partial | Initializer succeeds; Non-Steam game badges have no supported Steam page injection target. | static archive, local browser, fake-Frame |
| 134: Deck Progress Tracker 1.3.6 | 1 / 2 | partial | Initializer succeeds; Steam game/library statistics and page patches unverified. | static archive, local browser, fake-Frame |
| 135: Deck Shelves 2.0.0 | 1 / 1 | partial | The exact chunked frontend renders settings in opaque local Chromium. The official Python backend saved a shelf and returned it after reopen with a stub Decky module and temporary settings directory. Actual Decky RPC, real Frame settings, Steam Home shelf injection and worn VR remain unverified. | static archive, local browser, fake-Frame, isolated Python backend |
| 135: Deck Shelves 2.0.0 | 1 / 1 | partial | The exact chunked frontend renders settings in opaque local Chromium. The official Python backend now passes published Decky 3.3.0 x86_64 discovery, private shelf settings save/readback and persistence after a loader restart in a network-isolated container. Steam Home shelf injection has no verified target in the isolated panel; real Frame ARM64 settings, worn VR and physical controller use remain unverified. | static archive, local browser, fake-Frame, isolated Decky 3.3.0 x86_64 backend |
| 136: Decky-Clipper 1.0.2 | 1 / 2 | broken | Plugin contains a non-ARM64 Linux binary: Decky-Clipper/bin/gstreamer-1.0/libgstproxy.so. | static archive, local browser, fake-Frame |
| 137: Decky Proton Launch 0.9.0 | 1 / 2 | partial | Initializer succeeds; Proton wrapper installation and launch changes were not run. | static archive, local browser, fake-Frame |
| 138: Docky 1.4.13 | legacy / legacy | broken | This plugin needs root and is not supported by the Frame user service. | static archive, local browser, fake-Frame |
+8 -8
View File
@@ -51,16 +51,16 @@ def find_release(tag):
if not isinstance(batch, list):
raise AuditError("GitHub release list is not an array")
releases.extend(batch)
try:
return CHECKSUMS["find_release"](
(json.dumps(item) for item in releases), tag
)
except ValueError as error:
if "found 0" not in str(error):
raise AuditError(str(error)) from error
if len(batch) < 100:
break
raise AuditError(f"no unique release found for {tag}")
else:
raise AuditError("release list exceeded 20 pages; cannot verify uniqueness")
try:
return CHECKSUMS["find_release"](
(json.dumps(item) for item in releases), tag
)
except ValueError as error:
raise AuditError(str(error)) from error
def validate_assets(release):
+3 -1
View File
@@ -2,6 +2,7 @@
"""Check a locked Frame smoke result against the exact release commit."""
import json
import math
import re
import sys
from pathlib import Path
@@ -40,7 +41,8 @@ def verify(result, commit):
state = result.get('preflight') or {}
if (not isinstance(state, dict) or state.get('activity') != 3 or
not isinstance(state.get('proximity'), list) or not state['proximity'] or
any(type(value) not in (int, float) or value < 0 or value > 10
any(type(value) not in (int, float) or not math.isfinite(value) or
value < 0 or value > 10
for value in state['proximity']) or
not isinstance(state.get('battery'), list) or not state['battery'] or
any(type(value) is not int or value < 20 or value > 100
+6 -1
View File
@@ -16,9 +16,14 @@
},
{
"id": 24,
"shim": null,
"shim": "radiyo-verified-tls",
"probe": "radio-local"
},
{
"id": 135,
"shim": null,
"probe": "shelves-settings"
},
{
"id": 104,
"shim": null,
+47 -5
View File
@@ -289,10 +289,10 @@ def main():
report['result'] = ('discovery, default config and private setting restart persistence passed; '
'rclone, cloud authentication, sync, process signals, real Frame and VR unverified')
elif case['probe'] == 'radio-local':
# The published backend accepts arbitrary URLs and disables
# HTTPS verification. Exercise only a loopback fixture inside
# this network-isolated container; no station or audio opens.
# Exercise HTTP behavior and prove managed RadiYo rejects a
# self-signed HTTPS endpoint. No station or audio opens.
marker = 'Framey private radio station\n'
assert 'ctx.verify_mode = ssl.CERT_NONE' not in (plugin/'main.py').read_text()
class RadioHandler(BaseHTTPRequestHandler):
def do_GET(self):
@@ -329,8 +329,50 @@ def main():
server.shutdown()
server.server_close()
worker.join(timeout=3)
report['result'] = ('actual Decky discovery and local station fetch/redirect passed; '
'external radio, HTTPS, audio playback, Frame and VR unverified')
secure = ThreadingHTTPServer(('127.0.0.1', 0), RadioHandler)
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
fixtures = ROOT/'tests/decky-runtime/fixtures'
context.load_cert_chain(fixtures/'localhost-selfsigned.crt',
fixtures/'localhost-selfsigned.key')
secure.socket = context.wrap_socket(secure.socket, server_side=True)
worker = threading.Thread(target=secure.serve_forever, daemon=True)
worker.start()
try:
base_url = f'https://localhost:{secure.server_port}'
for method, kwargs in (
('fetch', {'url': base_url+'/station', 'customStation': False}),
('getRedirectUrl', {'url': base_url+'/redirect'})):
result = legacy(method, **kwargs)
assert 'error' in result and 'CERTIFICATE_VERIFY_FAILED' in result['error'], result
finally:
secure.shutdown()
secure.server_close()
worker.join(timeout=3)
report['result'] = ('discovery, loopback HTTP fetch/redirect and self-signed HTTPS rejection passed; '
'external stations, trusted HTTPS, playback, real Frame and VR unverified')
elif case['probe'] == 'shelves-settings':
# Private configuration only. Never register a Steam Home
# patch, import a user's settings, or touch their library.
defaults = current('get_settings')
assert defaults['enabled'] is False and defaults['shelves'] == [], defaults
shelf = {'id': 'framey-private-shelf', 'title': 'Private Test',
'source': {'type': 'tab', 'tab': 'all'}, 'limit': 4}
assert current('set_settings', {'enabled': False, 'shelves': [shelf]}) is True
saved = current('get_settings')
assert len(saved['shelves']) == 1 and saved['shelves'][0]['title'] == 'Private Test', saved
state = base/'settings'/record['folder']/'settings.json'
assert json.loads(state.read_text())['state']['shelves'][0]['id'] == shelf['id']
os.killpg(backend.pid, signal.SIGTERM)
backend.wait(timeout=8)
backend = subprocess.Popen([str(loader)], env=env, stdout=log, stderr=log,
start_new_session=True)
processes.append(backend)
until_ready(lambda: rpc('loader/get_plugins'), 'restarted Decky discovery')
restored = until_ready(lambda: current('get_settings')['shelves'],
'private shelf after restart')
assert restored[0]['id'] == shelf['id'] and restored[0]['title'] == shelf['title'], restored
report['result'] = ('published Decky discovery, private shelf settings and restart persistence passed; '
'Steam Home injection, real Frame and VR unverified')
elif case['probe'] == 'toggle-settings':
def call(method, *values):
result = rpc('loader/call_plugin_method', record['name'], method, *values)
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDCzCCAfOgAwIBAgIUei7D5XAp4cxefO1O9TUr1wm52PEwDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTI2MTAyOTAyMDUzOVoYDzIxMjYx
MDA1MDIwNTM5WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDaSOVSxyLrzuOleRA/6RJL+3zXhZPdCjQGC6O2vx7B
EU+8hmvWMpJrUBjE8DncduB9gu8j03vtxX/ZC2lx6GQOFJGPsFEbYwB5InZaCl8E
hV42aYrhukxSJYETy+XrzEa6WT3CBZG8X4vHD+31tO5vBIeUGsWp5Mv2tQY+LAIm
4vgkBfy6dvADyttM7SjN0n/hUVRH+2cO9QiA5GWxmQOinZMmk8er9LpVkjTEdKAx
KxsQP/qZLrY2LOUM0qXpRGaG/1do+wmaid8B1tKrHm5AIMaitDznVSTf/ckcYDPW
BjYVrWdqcwt4v/lrkPEJRQKDJQeLDvQrQD6mGK7PKDsHAgMBAAGjUzBRMB0GA1Ud
DgQWBBSSw7MvTccz05JmuXR7lwnWlrpPejAfBgNVHSMEGDAWgBSSw7MvTccz05Jm
uXR7lwnWlrpPejAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBt
1xwvIf3PU3GWogGHyHxBtnfT1vMSoJ9SknrMD6pjtRfErXwB/ys+G6NRq5PQVXhM
GfpIrDeYFZ6TuWSqPkmNjVc88+rffg34kEuB0h3PPgmGxkvkoar6X72cCJ5DrAC0
P+ufIHLVepoz80/RED9Tf7Xa1YAG8uoVmZXaKBneTfRvRzhtbvksDlOMAOiQfxtq
l8qVGoYctfNl6ka/5AWq7KFvcFaXSSnvsLSWrwYZ+9B21ClxdIGEhYzbKJiPSc00
PbNL5be/QaLpptM/8QSFGjgzwZH7GoZe53XuSgCygvhAgVxDsMyi5qP+6JDGiPci
d4Jd914sn4ONMRq99lZd
-----END CERTIFICATE-----
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDaSOVSxyLrzuOl
eRA/6RJL+3zXhZPdCjQGC6O2vx7BEU+8hmvWMpJrUBjE8DncduB9gu8j03vtxX/Z
C2lx6GQOFJGPsFEbYwB5InZaCl8EhV42aYrhukxSJYETy+XrzEa6WT3CBZG8X4vH
D+31tO5vBIeUGsWp5Mv2tQY+LAIm4vgkBfy6dvADyttM7SjN0n/hUVRH+2cO9QiA
5GWxmQOinZMmk8er9LpVkjTEdKAxKxsQP/qZLrY2LOUM0qXpRGaG/1do+wmaid8B
1tKrHm5AIMaitDznVSTf/ckcYDPWBjYVrWdqcwt4v/lrkPEJRQKDJQeLDvQrQD6m
GK7PKDsHAgMBAAECggEAU3PeFLUkYTHiL4+mykBRqfH2azFa8/1kIVGO4B2WZOq4
6NMdZDFGcDeZmKVdAbADKIkH41MFxMQmmjS2NgHKSuXLNruKbqV9d1JZHBht9j0E
5VmZaq4+YoOLIyXne6xH+BOJPGXDr1h6CHtLFw56v7/kE5ferRLXdbUEs31Av1t+
1Vwj5Ne7QvNTEUTyBFn7fuXmeoUThM8ZdkW5b2fzQ8UZY/UjcOzo2jdg24MYwmBz
Jc1Ysdy6Qiz5SWl3SWTkpdMO6kaHG2cWXOECHtYEXj7HAIiZQJAjsZUHR5rsGrVh
3ecZHA5PIrrUojI2uls1PtFgrAnDN7ZYVJ3Ab2KFkQKBgQD4tl5p3tW1PRAUauWl
e4Brhn3yc9BvIH5aknKX0EBxp9CK1pcRb4c5RvGqmHENG90jeX95vRfCr6okKicX
wpIVqzp9aznA7uzJfAZ2SGsD+rnNfAHCr2lxjhhVCs/WS6Ck5a6i6DolXutunhD3
uad42YGNwYOV7cnZlyauf7GW1wKBgQDgrkjUu/hcn9jCaXHGd3xyelow0S8PZEIB
ogw6BN4eecxnCBExibkpGE+OeCyf/Os2HxObHwd51UNyhQ/FEhdrv4vu+rNb3pIL
8b7wu3zopC01BX62rDMy2abKwD9RyhOJYOflYZ6gJxpWjRTOIcQNBsSmxmXslTk2
iMKskBFnUQKBgHFeJ4EYqs83K0ozQ8R/nBTnNCe89MiCxjWgxY0/xvBHrz5p/Ghd
LwVDukqpu/Zeuhk+e4XRVLBQhDyB3lntPcuUxk9+/c9c2SAqYYOvFOC2e9UXbnIA
pQ62146EJCKpTGt9qnHlawO31ZcRg+h3fsgOVQh5qfJAdp6xUFeAK0H3AoGAeXff
6hsBZ9oHTvvAxgJ9JkrdjbzZy2Fxif54y7kr+J/ykNU8rmJ91yjMggKYZZpP83+T
ku6QlR5mC3jCkNF6FgeFeJrS8tz+ajS0/OLv4bo4soyY99tbBqfC06Rh1y7/purc
FBfVsVHSnG5DjDmXC0/Yay2hlukJ+GCxlqeAsTECgYAXgHIRET8nBkDwF7mCZnm9
IjTVYF/p4OzY9kKHRPUWjdaxEzoZfKWEDohz1x+2ruilzVYEU4b3ISlYzjIqOjaM
w4Kmwd97UlZnOe+13R64951xlN+6MxVUQ0J1Aty1e5QT8FNUD4c2277xvFvDOoep
RmBmgFaX/lBlVfiMlW/Bpg==
-----END PRIVATE KEY-----
+5 -2
View File
@@ -63,8 +63,11 @@ for private settings and a synthetic exclusion list. No process-control method
is called. The official frontend's standalone Toggle component separately
checks saved true/false state and matching settings callbacks; effects are inert.
RadiYo! runs through actual published Decky 3.3.0 legacy RPC in a network-isolated
container. Only loopback station fetch, custom-station fetch and redirect are
tested. External radio, HTTPS and audio playback remain unverified.
container. The managed copy passes loopback station fetch, custom-station fetch
and redirect, and rejects a self-signed loopback HTTPS endpoint from both
methods. The certificate and private key under `tests/decky-runtime/fixtures`
are public test fixtures; they grant no trusted access. External radio, trusted
HTTPS and audio playback remain unverified.
Game Sync runs through actual published Decky 3.3.0 instance RPC in the same
isolation. Only default configuration and a private setting surviving restart
are checked. No rclone, cloud sync, process signal, Frame backend or VR action
+6 -2
View File
@@ -33,6 +33,7 @@ headset isn't reachable. A skipped launch is not a release smoke pass.
"""
import argparse
import json
import math
import os
import platform
import re
@@ -126,10 +127,13 @@ def preflight():
proximity = state.get('proximity')
if (not isinstance(proximity, list) or not proximity or
any(not isinstance(value, (int, float)) or isinstance(value, bool) or
value < 0 or value > 10 for value in proximity)):
not math.isfinite(value) or value < 0 or value > 10
for value in proximity)):
raise RuntimeError('Frame proximity is active or unknown; active session untouched')
battery = state.get('battery')
if not battery or min(battery) < 20:
if (not isinstance(battery, list) or not battery or
any(type(value) is not int or value < 20 or value > 100
for value in battery)):
raise RuntimeError('Frame battery is unavailable or below the smoke threshold')
return state
+32
View File
@@ -34,6 +34,38 @@ def release_assets():
class AssetAudit(unittest.TestCase):
def test_release_lookup_rejects_matching_draft_on_later_page(self):
first = {"id": 42, "tag_name": "untagged-first", "draft": True,
"name": "Frame Control 9.8.7"}
second = {"id": 43, "tag_name": "untagged-second", "draft": True,
"name": "Frame Control 9.8.7"}
first_page = [first] + [
{"id": number, "tag_name": f"v0.0.{number}", "draft": False}
for number in range(1, 100)
]
with mock.patch.object(audit, "github_json", side_effect=[first_page, [second]]) as github:
with self.assertRaisesRegex(audit.AuditError, "expected one release"):
audit.find_release("v9.8.7")
self.assertEqual(github.call_count, 2)
def test_release_lookup_finds_match_after_full_first_page(self):
first_page = [
{"id": number, "tag_name": f"v0.0.{number}", "draft": False}
for number in range(1, 101)
]
matching = {"id": 42, "tag_name": "v9.8.7", "draft": True}
with mock.patch.object(audit, "github_json", side_effect=[first_page, [matching]]) as github:
self.assertEqual(audit.find_release("v9.8.7"), matching)
self.assertEqual(github.call_count, 2)
def test_release_lookup_refuses_unfinished_pagination(self):
page = [{"id": number, "tag_name": "v9.8.7" if number == 1 else f"v0.0.{number}"}
for number in range(1, 101)]
with mock.patch.object(audit, "github_json", return_value=page) as github:
with self.assertRaisesRegex(audit.AuditError, "cannot verify uniqueness"):
audit.find_release("v9.8.7")
self.assertEqual(github.call_count, 20)
def test_published_manifest_matches_release_bytes(self):
assets, _ = release_assets()
release = {"tag_name": "v9.8.7", "body": "Release notes", "assets": assets}
+20
View File
@@ -192,6 +192,26 @@ class StoreTransaction(unittest.TestCase):
self.assertNotEqual(result.returncode, 0)
self.assertTrue(external.exists())
def test_exact_radiyo_managed_copy_restores_tls_verification(self):
from framey_official import PLUGINS, archive as official_archive
plugin = next(p for p in PLUGINS if p['id'] == 24)
data = official_archive(plugin)
self.assertEqual(frame_decky_store.archive_info(data, plugin['sha256'], plugin['name']), plugin['folder'])
result = self.apply('install', data, plugin_id=24,
folder=plugin['folder'], version=plugin['version'])
self.assertEqual(result.returncode, 0, result.stderr)
folder = self.folder.parent/plugin['folder']
source = (folder/'main.py').read_text(encoding='utf-8')
receipt = json.loads((folder/'.frame-control-store.json').read_text())
self.assertEqual(receipt['compatShims'], ['radiyo-verified-tls'])
self.assertEqual(source.count('ctx = ssl.create_default_context()'), 2)
self.assertNotIn('ssl.CERT_NONE', source)
self.assertNotIn('ctx.check_hostname = False', source)
self.assertEqual(source.count("'URL Error: ' + str(error.reason)"), 2)
result = self.apply('remove', plugin_id=24, folder=plugin['folder'])
self.assertEqual(result.returncode, 0, result.stderr)
self.assertFalse(folder.exists())
def test_modified_fileserver_archive_does_not_inherit_exception(self):
from framey_official import PLUGINS, archive as official_archive
record = next(p for p in PLUGINS if p['id'] == 60)
+2 -1
View File
@@ -21,7 +21,8 @@ class RuntimeCoverage(unittest.TestCase):
self.assertIn(case['id'], official)
self.assertIn(case['probe'], ('mpris', 'screenshots', 'system-monitor', 'timer-lifecycle',
'toggle-settings', 'fileserver', 'localsend', 'network-sentinel',
'battery-tracker', 'free-loader', 'radio-local', 'game-sync-config'))
'battery-tracker', 'free-loader', 'radio-local', 'game-sync-config',
'shelves-settings'))
def test_new_shim_without_runtime_case_fails_coverage(self):
import frame_decky_store
+6 -2
View File
@@ -108,7 +108,8 @@ class Safety(unittest.TestCase):
self.assertEqual(frame_smoke.source_revision(), {'commit': None, 'clean': False})
def test_locked_preflight_rejects_active_or_unknown_proximity_before_titles(self):
for proximity in ([], [88.0625], [4.6875, 88.0625], [None]):
for proximity in ([], [88.0625], [4.6875, 88.0625], [None],
[float('nan')], [float('inf')], [float('-inf')]):
with self.subTest(proximity=proximity), mock.patch.object(
frame_smoke, 'remote', return_value=mock.Mock(
returncode=0, stdout=json.dumps({
@@ -261,7 +262,10 @@ class SharedSmokeLock(unittest.TestCase):
for state in ({'activity':1,'proximity':[1],'battery':[100]},
{'activity':None,'proximity':[],'battery':[100]},
{'activity':3,'proximity':[],'battery':[19]},
{'activity':3,'proximity':[],'battery':[]}):
{'activity':3,'proximity':[],'battery':[]},
{'activity':3,'proximity':[0],'battery':[True]},
{'activity':3,'proximity':[0],'battery':[float('nan')]},
{'activity':3,'proximity':[0],'battery':[101]}):
with self.subTest(state=state), mock.patch.object(frame_smoke, 'remote',
return_value=mock.Mock(returncode=0, stdout=json.dumps(state))):
with self.assertRaises(RuntimeError):
+15
View File
@@ -1,6 +1,7 @@
"""The release smoke gate requires a complete result from its exact checkout."""
import importlib.util
import math
import unittest
from pathlib import Path
@@ -51,6 +52,20 @@ class VerifyFrameSmokeResult(unittest.TestCase):
with self.assertRaisesRegex(ValueError, 'preflight'):
gate.verify(data, SHA)
def test_rejects_nonfinite_proximity_and_invalid_battery(self):
for proximity in (math.nan, math.inf, -math.inf):
with self.subTest(proximity=proximity):
data = result()
data['preflight']['proximity'] = [proximity]
with self.assertRaisesRegex(ValueError, 'preflight'):
gate.verify(data, SHA)
for battery in ([True], [math.nan], [101], [19], ['99']):
with self.subTest(battery=battery):
data = result()
data['preflight']['battery'] = battery
with self.assertRaisesRegex(ValueError, 'preflight'):
gate.verify(data, SHA)
def test_rejects_missing_lock_release_record(self):
data = result()
data['lock_released'] = False
+20
View File
@@ -41,6 +41,8 @@ NETWORK_SENTINEL_DIGEST = 'd3e07f40a0c800e72d15594e03c8c61e9bf3092524a578a026052
NETWORK_SENTINEL_MAIN = '2baa390cb66dc267e8f04e61ba1edc43af75115ad5e453d308f6bf26ed70b6b9'
BATTERY_TRACKER_DIGEST = '91c388d3c1b52079314045b79671ad55c767510658b6193f6936ed6cad383b94'
BATTERY_TRACKER_MAIN = '2303ec70c262f1cbfe1c8c138770c6e7c3496827e748e679c94ca43ff9f28725'
RADIYO_DIGEST = '647111578aa7a5ecd16af8d7fe6c068a6d7ea8737c6dce5ac2d52e84a549ec43'
RADIYO_MAIN = '868332dbe3feb1e7a81e35a0c01fdc0a6b68421ec1f84e238564bfad4946e87e'
VERSION = re.compile(r'[A-Za-z0-9][A-Za-z0-9._+\-]{0,39}\Z')
@@ -171,6 +173,9 @@ def archive_info(data, expected_hash, expected_name):
if battery_tracker and (flags != ['_root'] or 'api_version' in manifest
or hashlib.sha256(archive.read(folder+'/main.py')).hexdigest() != BATTERY_TRACKER_MAIN):
raise ValueError('Battery Tracker manifest or source differs from the pinned archive')
if (expected_hash == RADIYO_DIGEST and expected_name == 'RadiYo!' and folder == 'radiyo-steam-deck'
and hashlib.sha256(archive.read(folder+'/main.py')).hexdigest() != RADIYO_MAIN):
raise ValueError('RadiYo source differs from the pinned archive')
return folder
@@ -710,6 +715,21 @@ def split_by_app(data):
source=source.replace(old,old+' env["LD_LIBRARY_PATH"] = env.get("LD_LIBRARY_PATH_ORIG", "")\n')
main.write_text(source,encoding='utf-8')
compat_shims.append('music-control-system-dbus-env')
# RadiYo's exact 1.3 backend turns off certificate and hostname
# checks for both outbound requests. Restore Python's default TLS
# policy only for the pinned official source in this managed copy.
if int(plugin_id)==24 and digest=='647111578aa7a5ecd16af8d7fe6c068a6d7ea8737c6dce5ac2d52e84a549ec43' and folder=='radiyo-steam-deck':
main=unpacked/'main.py'
data=main.read_bytes()
if hashlib.sha256(data).hexdigest()!='868332dbe3feb1e7a81e35a0c01fdc0a6b68421ec1f84e238564bfad4946e87e':
raise RuntimeError('RadiYo source differs from pinned archive')
unsafe=b' ctx.check_hostname = False\n ctx.verify_mode = ssl.CERT_NONE\n'
broken=b"'URL Error: ' + error.reason"
if data.count(unsafe)!=2 or data.count(broken)!=2:
raise RuntimeError('RadiYo TLS source changed')
data=data.replace(unsafe,b'').replace(broken,b"'URL Error: ' + str(error.reason)")
main.write_bytes(data)
compat_shims.append('radiyo-verified-tls')
# Recheck under the transaction lock. Another store operation may
# have installed a colliding name while this archive downloaded.
proposed_name=json.loads((unpacked/'plugin.json').read_text()).get('name')
+3 -3
View File
@@ -1479,7 +1479,7 @@
"https://github.com/isiah-lloyd/radiyo-steam-deck"
],
"status": "partial",
"reason": "Initializer succeeds. Exact official legacy backend passed published Decky 3.3.0 x86_64 discovery and loopback-only station fetch, custom-station fetch and redirect in a network-isolated container. Real radio services, HTTPS behavior, audio playback, real Frame backend and worn VR remain unverified. The published backend disables HTTPS certificate verification; no external request was made.",
"reason": "Initializer succeeds. The exact official legacy backend passes isolated Decky 3.3.0 discovery and loopback HTTP station fetch, custom fetch and redirect. A digest-scoped managed install restores TLS certificate and hostname checks; its fetch and redirect methods reject a loopback self-signed HTTPS endpoint. External radio services, trusted HTTPS, audio playback, real Frame backend and worn VR remain unverified. Older installed copies need an update for the TLS fix.",
"blockInstall": false,
"verification": [
"static archive",
@@ -12196,13 +12196,13 @@
"https://github.com/sponsors/santojon"
],
"status": "partial",
"reason": "The exact chunked frontend renders settings in opaque local Chromium. The official Python backend saved a shelf and returned it after reopen with a stub Decky module and temporary settings directory. Actual Decky RPC, real Frame settings, Steam Home shelf injection and worn VR remain unverified.",
"reason": "The exact chunked frontend renders settings in opaque local Chromium. The official Python backend now passes published Decky 3.3.0 x86_64 discovery, private shelf settings save/readback and persistence after a loader restart in a network-isolated container. Steam Home shelf injection has no verified target in the isolated panel; real Frame ARM64 settings, worn VR and physical controller use remain unverified.",
"blockInstall": false,
"verification": [
"static archive",
"local browser",
"fake-Frame",
"isolated Python backend"
"isolated Decky 3.3.0 x86_64 backend"
],
"browser": {
"id": 135,