Compare commits

..
Author SHA1 Message Date
Alex Southwell fe51d29eb4 Merge pull request #329 from saphid/docs/framey-deck-shelves-backend
Document Deck Shelves settings backend evidence
2026-10-26 13:44:49 +00:00
SAPHID 4b47c384e6 Document isolated Deck Shelves settings backend check 2026-10-26 13:32:17 +00:00
Alex Southwell 7baafc9a18 Merge pull request #328 from saphid/fix/framey-audit-module-receipts
Fix receipt-bound modules in Framey browser audit
2026-10-26 13:22:08 +00:00
SAPHID b0d8bf3968 Fix receipt-bound modules in Framey browser audit 2026-10-26 13:08:50 +00:00
Alex Southwell b05a666e50 Merge pull request #327 from saphid/docs/v0446-release-evidence
Document v0.4.46 release verification
2026-10-26 12:56:58 +00:00
SAPHID 8feb19a6d3 Document v0.4.46 release verification 2026-10-26 12:49:18 +00:00
Alex Southwell f84a4da9b1 Merge pull request #326 from saphid/release/v0.4.46
Prepare Frame Control v0.4.46
2026-10-26 12:31:06 +00:00
SAPHID fc978a2afd Record exact v0.4.46 candidate Frame smoke 2026-10-26 12:19:32 +00:00
SAPHID 9aff6adcec Merge remote-tracking branch 'origin/main' into release/v0.4.46 2026-10-26 12:17:07 +00:00
SAPHID 2d8b70d607 Prepare Frame Control v0.4.46 candidate 2026-10-26 12:16:54 +00:00
Alex Southwell 80657b8a8e Merge pull request #325 from saphid/fix/framey-identical-zip-members
Accept identical Decky ZIP members for Free Loader
2026-10-26 12:16:23 +00:00
10 changed files with 201 additions and 16 deletions

No files matched your search

+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "frame-control",
"version": "0.4.45",
"version": "0.4.46",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "frame-control",
"version": "0.4.45",
"version": "0.4.46",
"license": "MIT",
"devDependencies": {
"electron": "^44.4.5",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "frame-control",
"productName": "Frame Control",
"version": "0.4.45",
"version": "0.4.46",
"description": "Desktop app for managing a Valve Steam Frame over SSH",
"private": true,
"main": "main.js",
@@ -0,0 +1,32 @@
# Chunked Framey frontend audit route, 26 October 2026
The local 116-plugin Chromium audit served chunked modules at a route that
omitted the store receipt, while the production panel requested
`/framey/module/<id>/<receipt>/<file>`. The audit parsed that receipt as part
of the file path and returned 404. This made its result for chunked frontends
weaker than the separate targeted panel checks.
The audit now accepts the production URL shape only when the plugin ID,
receipt and JavaScript path are valid, the receipt equals the catalog SHA-256,
and the cached archive bytes match that SHA-256. Invalid IDs, receipts, paths
and changed archive bytes fail before the module is served. The production
Framey panel and installer are unchanged.
## Exact archived browser check
Pinned official Deck Shelves 2.0.0 (store ID 135, SHA-256
`b566280ad38fd3ecd584b5b46a855f301539eb9a86063834c64b7045f7063f91`)
ran through the repaired audit in an opaque Chromium iframe. Its entry module
and all eight JavaScript chunks returned HTTP 200 at receipt-bound URLs. The
frontend reported ready and called `get_settings`; the audit denied that
backend call and a Steam library read. Plugin network access remained blocked
by the audit policy. The previous
catalog browser observation had failed to resolve `./chunks/...` from a blob
URL. This check verifies the current audit route and frontend initializer,
not the official backend or a live shelf.
The focused route regression covers entry and chunk reads plus wrong receipt,
path traversal, malformed ID and changed archive bytes. The catalog's
**4 works / 50 partial / 51 broken / 11 N/A** statuses are unchanged. Deck
Shelves remains partial; installed settings, home-shelf behavior, worn VR and
physical controller input remain unverified.
@@ -4,7 +4,17 @@ The exact official Deck Shelves 2.0.0 archive (store ID 135, SHA-256 `b566280ad3
Framey now serves JavaScript files from the managed plugin's `dist` directory using hierarchical module URLs. Its opaque `allow-scripts` iframe still has no Frame UI key. Local Chromium received HTTP 200 for the entry and eight chunks, reported the initializer ready, and rendered the settings panel with “Enable shelves on Home,” “Create default shelves,” “Behavior,” and “Shelves.” The browser audit denied the attempted `get_settings` backend call, Steam library read and plugin network access. No backend method executed.
The catalog moves this exact version from broken to **partial** for visible local settings only. The plugin's main feature injects shelves into Steam Home; Framey has no verified Steam Home patch target. Settings writes, official backend behavior, real Frame module serving, wearer-visible VR, and physical controller input remain unverified. The [compatibility table](../framey-compatibility.md) and store views retain these limits.
The catalog moves this exact version from broken to **partial** for visible local settings. The plugin's main feature injects shelves into Steam Home; Framey has no verified Steam Home patch target. Actual Decky RPC, real Frame settings, wearer-visible VR, and physical controller input remain unverified. The [compatibility table](../framey-compatibility.md) and store views retain these limits.
## Isolated official backend settings check
I verified the same archive digest before loading its unchanged `Deck-Shelves/main.py` in local Python. A stub `decky` module supplied only a logger; `DECKY_PLUGIN_SETTINGS_DIR` and `DECKY_HOME` pointed inside a temporary directory. No Decky service, headset, Steam API, or network call was involved. Calling `_main`, `get_settings`, and `set_settings` returned the default disabled state, saved one shelf named `Local Test`, and returned that shelf from a fresh `Plugin` instance. The temporary directory was removed after the check.
```json
{"archiveSha256":"b566280ad38fd3ecd584b5b46a855f301539eb9a86063834c64b7045f7063f91","defaultEnabled":false,"defaultShelves":0,"saveResult":true,"reopenedEnabled":true,"reopenedShelfTitle":"Local Test","settingsFileExists":true}
```
This proves only the archive's Python settings logic under those local conditions. It does not prove that Decky 3.3.0 can dispatch these methods on the ARM64 Frame, that the opaque panel can save settings through the production bridge, or that a shelf appears on Steam Home or in VR.
## Exact source real Frame gates
@@ -0,0 +1,44 @@
# v0.4.46 candidate gate, 26 October 2026
This candidate includes merged Free Loader PR #325, the Decky user-tool PATH
fix (#323), and the interactive worn-check proximity gate (#324).
The official 116-plugin catalog is **4 works / 50 partial / 51 broken /
11 N/A**. Free Loader is partial for its verified package, local frontend
and isolated settings/empty-deals scope. No live feed or worn-VR claim follows.
## Locked real Frame smoke before the version bump
At 12:12 UTC, source commit `21b85ba99dfd742a56e2c20259f56f35d83590fc`
ran `scripts/frame-smoke.sh` on the real Frame under a unique owner of
`/tmp/frame-test.lock`. Locked preflight found SteamVR activity **3**
(standby), proximity raw **3.0** and battery **100%** on Frame OS 0.5.6 build
`20261022.6153909`. Test-owned ARM64 and Windows/Proton titles installed,
launched and were removed. The optional x86_64 title installed and was
removed; launch was **skipped** because Steam Linux Runtime 4 is absent.
The result was **12 passed / 1 skipped / 0 failed**. Cleanup passed and the
command exited 0, which includes release of its owned lock. The detailed
result remains private at
`tests/smoke/results/2026-10-26T121228Z-20261022.6153909.json` in the
candidate checkout.
This smoke checks release plumbing and temporary titles, not an installed
official Decky plugin. No wearer was confirmed. The interactive worn-check
script was not run, and no headset wake or controller input was sent. Free
Loader's actual ARM64 Decky backend, live feeds, panel visibility to a wearer,
physical controllers, keyboard and optical comfort remain unverified. The
hourly worn-headset check stays enabled.
## Publication boundary
At 12:17 UTC, exact version-bumped candidate commit
`9aff6adcec71852ce9bedf0ae37364bd32bb6627` ran the same locked
`scripts/frame-smoke.sh`. Preflight again found activity **3** standby,
proximity raw **3.0** and battery **100%**. The result was **12 passed /
1 optional x86 launch skipped / 0 failed**. Owned title/process cleanup
passed and the command exited 0 after releasing its lock. Its private result
is `tests/smoke/results/2026-10-26T121721Z-20261022.6153909.json` in the
candidate checkout. This paragraph is the only change after that smoke.
Green release-PR and post-merge CI, tagged installer build, all ten installer
digests with `SHA256SUMS`, independent package checks, publisher dry run and
public `update.json` readback remain required before publication.
+58
View File
@@ -0,0 +1,58 @@
# v0.4.46 tagged release, 26 October 2026
[PR #326](https://github.com/saphid/frame-control/pull/326) passed every required
check on exact head `fc978a2afdfa47e60477fcb0bb573e7b6181fa89` and merged
at 12:31:06 UTC. Main became `f84a4da9b1ae938d15b658af17903c0e10a56e94`.
All seven jobs in the [post-merge main run](https://github.com/saphid/frame-control/actions/runs/40323149126)
passed, including iOS and fake-Frame e2e. The app and lockfile both say
`0.4.46`. Annotated tag `v0.4.46` dereferences to that main commit. The
[tagged release workflow](https://github.com/saphid/frame-control/actions/runs/40324176903)
passed its draft, Linux, Windows, Apple Silicon Mac, Intel Mac and checksum jobs.
## Frame and package gates
The [candidate evidence](v0446-candidate-2026-10-26.md) records a locked
exact-version real Frame smoke: **12 passed / 1 optional x86 launch skipped /
0 failed**, with owned cleanup and lock release. The x86 launch needs Steam
Linux Runtime 4, which was absent. A separate 12:25 UTC read-only check acquired
and released its own `/tmp/frame-test.lock`; SteamVR activity was **3**
(standby), proximity raw **3.0**, and battery **100%**. No wearer was confirmed,
so no wake, controller input or interactive worn-check script ran.
The draft contained exactly ten installers plus `SHA256SUMS`, each with a
GitHub SHA-256 digest. A fresh downloaded checksum file matched the ten digest
lines regenerated from GitHub metadata byte for byte; its digest was
`365e7f6d9549ff993162964920a6560480069ed04ce1eca5551696ce7bbc4ae8`.
`scripts/publish-release.sh --dry-run v0.4.46` passed the exact tag, installer,
checksum and update-manifest gates after release notes were attached.
Independent downloads passed these checks:
| Package | Result |
| --- | --- |
| Linux x86_64 AppImage | SHA-256 `65f66f1bbb5908d49c179307950f93b643c2b8da8ac4da48a778c5c9bac5d41b`; launched under isolated Xvfb, bundled server returned HTTP 200 with `FrameControl/1`, then stopped. |
| Mac arm64 ZIP | SHA-256 `9af88f75db7936472590022ca03a03da9de4f91de7b35c5d4d130b6dc78ab4b5`; ZIP CRC valid, bundle version 0.4.46, main Mach-O CPU arm64. |
| Mac x64 ZIP | SHA-256 `28d9ac00c9c918a7cb9f2caac54505faa8a953285bb40f82e02d9233b775d37a`; ZIP CRC valid, bundle version 0.4.46, main Mach-O CPU x86_64. |
| Windows x64 Setup | SHA-256 `68e303dcabb2c82ba5b9a5a58bace2ca8154f9645f1da2214ca4c6dbb05f157d`; PE/Nullsoft installer format. Tagged Windows CI checked packaged files and Python libraries. |
| Linux arm64 DEB | SHA-256 `da9b00bd44ca7a91c542df26d8c5711ad564d618add34f89aef946785c0bd538`; package version 0.4.46 and architecture arm64. |
Tagged Mac and Windows packages were not launched on physical machines during
this release gate. Earlier physical Intel Mac acceptance covers older bytes.
## Publication and limits
`scripts/publish-release.sh v0.4.46` published
[v0.4.46](https://github.com/saphid/frame-control/releases/tag/v0.4.46) at
12:48:03 UTC. GitHub's latest-release API returned the expected tag,
`draft=false`, `prerelease=false`, ten installers, `SHA256SUMS` and
`update.json`. A fresh public latest-download of `update.json` reported
version 0.4.46, the stable release page, ten hashed installers and the
worn-VR limit in its notes.
The exact-version official catalog is **4 works / 50 partial / 51 broken /
11 N/A** out of 116. Free Loader is partial for package, local frontend and
isolated settings/empty-deals evidence. Its actual ARM64 Decky load, live
feeds and notifications remain unverified. The real Frame was in standby;
official-plugin wearer visibility, physical controller usability, keyboard
and optical comfort remain unverified. The hourly shared-lock worn check stays
enabled.
+1 -1
View File
@@ -142,7 +142,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 now initializes and renders Deck Shelves settings in an opaque local Chromium panel. Backend calls were denied; Home-screen shelf injection has no verified Steam UI target in Framey. Settings writes, real backend behavior and worn VR are 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 |
| 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 |
+23 -9
View File
@@ -11,6 +11,7 @@ import io
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import json
from pathlib import Path
import re
import sys
from unittest.mock import patch
import zipfile
@@ -41,6 +42,27 @@ def render_panel(record, data):
return framey_panel.panel(record['id']).replace(
b'<script id="framey-bundle"', PRELUDE.encode()+b'<script id="framey-bundle"')
def read_module_asset(path, records, cache):
"""Serve the same receipt-bound module URL shape as the installed panel."""
prefix = '/framey/module/'
if not path.startswith(prefix):
raise ValueError('Invalid module path')
parts = path[len(prefix):].split('/', 2)
if len(parts) != 3 or not re.fullmatch(r'[1-9][0-9]*', parts[0]):
raise ValueError('Invalid plugin ID')
plugin_id, receipt, name = parts
if not framey_panel.RECEIPT.fullmatch(receipt) or not framey_panel.MODULE_PATH.fullmatch(name):
raise ValueError('Invalid module receipt or path')
record = records[int(plugin_id)]
if receipt != record['sha256']:
raise ValueError('Plugin receipt changed')
data = (cache/(receipt+'.zip')).read_bytes()
if hashlib.sha256(data).hexdigest() != receipt:
raise ValueError('Archive hash mismatch')
with zipfile.ZipFile(io.BytesIO(data)) as archive:
return archive.read(record['folder']+'/dist/'+name)
PAGE = '''<!doctype html><meta charset=utf-8><title>Framey catalog frontend audit</title>
<h1>Framey catalog frontend audit</h1>
<p>Exact archived frontend code runs in an opaque iframe. All backend calls and network requests are denied.
@@ -134,15 +156,7 @@ def main():
return
elif path.startswith('/framey/module/'):
try:
plugin_id, name = path.removeprefix('/framey/module/').split('/', 1)
if not framey_panel.MODULE_PATH.fullmatch(name):
raise ValueError('Invalid module path')
record = records[int(plugin_id)]
data = (args.cache/(record['sha256']+'.zip')).read_bytes()
if hashlib.sha256(data).hexdigest() != record['sha256']:
raise ValueError('Archive hash mismatch')
with zipfile.ZipFile(io.BytesIO(data)) as archive:
body = archive.read(record['folder']+'/dist/'+name)
body = read_module_asset(path, records, args.cache)
content_type = 'text/javascript; charset=utf-8'
module_asset = True
except (KeyError, ValueError, OSError, zipfile.BadZipFile):
+19
View File
@@ -5,6 +5,7 @@ import hashlib
import importlib.util
import io
from pathlib import Path
import tempfile
import unittest
import zipfile
@@ -21,6 +22,7 @@ class ReceiptBoundAudit(unittest.TestCase):
stream = io.BytesIO()
with zipfile.ZipFile(stream, 'w') as archive:
archive.writestr('Example/dist/index.js', bundle)
archive.writestr('Example/dist/chunks/frontend.js', b'export const ready = true;')
data = stream.getvalue()
digest = hashlib.sha256(data).hexdigest()
record = {'id':42, 'folder':'Example', 'sha256':digest}
@@ -30,3 +32,20 @@ class ReceiptBoundAudit(unittest.TestCase):
self.assertIn(b'const report=(kind,message)', page)
with self.assertRaisesRegex(ValueError, 'hash mismatch'):
audit.render_panel({**record, 'sha256':'0'*64}, data)
with tempfile.TemporaryDirectory() as directory:
cache = Path(directory)
(cache/(digest+'.zip')).write_bytes(data)
records = {42:record}
route = f'/framey/module/42/{digest}/'
self.assertEqual(audit.read_module_asset(route+'index.js', records, cache), bundle)
self.assertEqual(audit.read_module_asset(route+'chunks/frontend.js', records, cache),
b'export const ready = true;')
for bad in (f'/framey/module/42/{"0"*64}/index.js',
route+'chunks/../frontend.js', route+'other.json',
f'/framey/module/042/{digest}/index.js'):
with self.subTest(bad=bad), self.assertRaises((ValueError, KeyError)):
audit.read_module_asset(bad, records, cache)
(cache/(digest+'.zip')).write_bytes(data+b'changed')
with self.assertRaisesRegex(ValueError, 'hash mismatch'):
audit.read_module_asset(route+'index.js', records, cache)
+10 -2
View File
@@ -12061,12 +12061,13 @@
"https://github.com/sponsors/santojon"
],
"status": "partial",
"reason": "The exact chunked frontend now initializes and renders Deck Shelves settings in an opaque local Chromium panel. Backend calls were denied; Home-screen shelf injection has no verified Steam UI target in Framey. Settings writes, real backend behavior and worn VR are unverified.",
"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.",
"blockInstall": false,
"verification": [
"static archive",
"local browser",
"fake-Frame"
"fake-Frame",
"isolated Python backend"
],
"browser": {
"id": 135,
@@ -12101,6 +12102,13 @@
"mode": "exact archive in opaque local Chromium; backend and network denied",
"frontendReady": true,
"settingsRendered": true
},
"backendFollowUp": {
"evidence": "docs/evidence/framey-deck-shelves-browser-2026-10-26.md",
"mode": "exact archive Python with stub Decky module and temporary settings directory",
"defaultSettingsRead": true,
"shelfSaveReopen": true,
"actualDeckyRpc": false
}
},
{