mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-29 04:02:36 +01:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7ff94aae4 | ||
|
|
f4c8308f8a | ||
|
|
a2e578f6c3 | ||
|
|
e8fb154107 | ||
|
|
35b59e62da | ||
|
|
be07dbeeeb | ||
|
|
b123f5ff34 | ||
|
|
01e5d8b2db |
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.
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user