Compare commits

...
Author SHA1 Message Date
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
8 changed files with 110 additions and 13 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,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.
+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 -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}
+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