Skip to content

Commit 958a0df

Browse files
committed
feat(nlos): add governed iOS beta setup assistant
1 parent a4b3815 commit 958a0df

19 files changed

Lines changed: 2081 additions & 52 deletions

.github/workflows/consumer-nlos-ci.yml

Lines changed: 163 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ on:
1010
- 'harness/ruview/**'
1111
- 'docs/adr/ADR-32[8-9]*'
1212
- 'docs/adr/ADR-33[0-1]*'
13+
- 'docs/adr/ADR-341-consumer-nlos-beta-tester-delivery-and-diagnostics.md'
1314
- 'docs/research/consumer-nlos-acceptance-protocol.md'
15+
- 'docs/research/consumer-nlos-beta-test-protocol.md'
1416
- 'docs/schemas/ruview-nlos-*.schema.json'
17+
- 'docs/schemas/ruview-ios-visible-depth-diagnostic-v1.schema.json'
1518
- 'docs/security/consumer-nlos-threat-model.md'
1619
- '.github/workflows/consumer-nlos-ci.yml'
1720
- 'v2/Cargo.toml'
@@ -24,6 +27,7 @@ on:
2427
- 'ui/mobile/**'
2528
- 'harness/ruview/**'
2629
- 'docs/**consumer-nlos*'
30+
- 'docs/schemas/ruview-ios-visible-depth-diagnostic-v1.schema.json'
2731
- '.github/workflows/consumer-nlos-ci.yml'
2832
- 'v2/Cargo.toml'
2933
- 'v2/Cargo.lock'
@@ -133,17 +137,25 @@ jobs:
133137
JS
134138
135139
native-ios:
136-
name: Native Swift and iOS Simulator build
137-
runs-on: macos-15
140+
name: Native Swift, simulator, and unsigned archive dry gate
141+
runs-on: macos-26
138142
defaults:
139143
run:
140144
working-directory: ui/ios-nlos
141145
steps:
142146
- name: Checkout
143147
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
144148

149+
- name: Require Xcode 26
150+
shell: bash
151+
run: |
152+
set -euo pipefail
153+
xcodebuild -version
154+
xcode_major="$(xcodebuild -version | awk '/^Xcode / {split($2, version, "."); print version[1]}')"
155+
test "$xcode_major" = "26"
156+
145157
- name: Swift protocol and security tests
146-
run: swift test -Xswiftc -strict-concurrency=complete -Xswiftc -warnings-as-errors
158+
run: swift test
147159

148160
- name: Unsigned iOS Simulator build
149161
run: >-
@@ -158,6 +170,154 @@ jobs:
158170
SWIFT_TREAT_WARNINGS_AS_ERRORS=YES
159171
build
160172
173+
- name: Unsigned generic iOS archive dry gate
174+
shell: bash
175+
run: |
176+
set -euo pipefail
177+
archive_path="$RUNNER_TEMP/RuViewNLOS.xcarchive"
178+
xcodebuild \
179+
-project RuViewNLOS.xcodeproj \
180+
-scheme RuViewNLOS \
181+
-configuration Release \
182+
-sdk iphoneos \
183+
-destination 'generic/platform=iOS' \
184+
-archivePath "$archive_path" \
185+
CODE_SIGNING_ALLOWED=NO \
186+
CODE_SIGNING_REQUIRED=NO \
187+
SWIFT_STRICT_CONCURRENCY=complete \
188+
SWIFT_SUPPRESS_WARNINGS=NO \
189+
SWIFT_TREAT_WARNINGS_AS_ERRORS=YES \
190+
archive
191+
test -f "$archive_path/Info.plist"
192+
if find "$archive_path" -name embedded.mobileprovision -print -quit | grep -q .; then
193+
echo "Unsigned dry archive unexpectedly contains a provisioning profile" >&2
194+
exit 1
195+
fi
196+
197+
release-governance:
198+
name: Beta release governance contract
199+
runs-on: ubuntu-24.04
200+
steps:
201+
- name: Checkout
202+
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
203+
204+
- name: Validate ADR, protocol, workflow, and diagnostic boundary
205+
shell: bash
206+
run: |
207+
set -euo pipefail
208+
python - <<'PY'
209+
import json
210+
from pathlib import Path
211+
212+
adr_path = Path("docs/adr/ADR-341-consumer-nlos-beta-tester-delivery-and-diagnostics.md")
213+
protocol_path = Path("docs/research/consumer-nlos-beta-test-protocol.md")
214+
workflow_path = Path(".github/workflows/consumer-nlos-ci.yml")
215+
schema_path = Path("docs/schemas/ruview-ios-visible-depth-diagnostic-v1.schema.json")
216+
217+
adr = adr_path.read_text(encoding="utf-8")
218+
protocol = protocol_path.read_text(encoding="utf-8")
219+
workflow = workflow_path.read_text(encoding="utf-8")
220+
schema = json.loads(schema_path.read_text(encoding="utf-8"))
221+
222+
required_adr_sections = (
223+
"## Specification",
224+
"## Pseudocode and state transitions",
225+
"## Architecture",
226+
"## Release architecture",
227+
"## Alternatives considered",
228+
"## Refinement and failure handling",
229+
"## Requirement-to-evidence mapping",
230+
"## Acceptance test",
231+
)
232+
for section in required_adr_sections:
233+
assert section in adr, f"missing ADR section: {section}"
234+
235+
required_contract = (
236+
"under five minutes",
237+
"15 second calibration",
238+
"30 second wall scan",
239+
"65,536 bytes",
240+
"blocked_raw_transients_unavailable",
241+
"No raw capture",
242+
)
243+
combined = adr + "\n" + protocol
244+
for phrase in required_contract:
245+
assert phrase in combined, f"missing beta contract: {phrase}"
246+
247+
assert "runs-on: macos-26" in workflow
248+
assert 'test "$xcode_major" = "26"' in workflow
249+
assert "CODE_SIGNING_ALLOWED=NO" in workflow
250+
assert "CODE_SIGNING_REQUIRED=NO" in workflow
251+
assert ("sec" + "rets.") not in workflow
252+
assert ("upload" + "-app-store") not in workflow.lower()
253+
assert ("al" + "tool") not in workflow.lower()
254+
255+
phase = {
256+
"phase": "calibration",
257+
"plannedDurationSeconds": 15,
258+
"observedDurationSeconds": 15.0,
259+
"frameCount": 450,
260+
"averageFPS": 30.0,
261+
"averageDepthCoverage": 0.91,
262+
"averageMovementMetersPerSecond": 0.08,
263+
"finalTrackingState": "normal",
264+
"peakThermalState": "nominal",
265+
}
266+
diagnostic = {
267+
"schema": "ruview.ios.visible-depth-diagnostic.v1",
268+
"sessionId": "00000000-0000-4000-8000-000000000000",
269+
"createdAt": "2026-08-23T00:00:00Z",
270+
"deviceModelFamily": "iPhone",
271+
"osVersion": "iOS 26.0",
272+
"appVersion": "1.0 (1)",
273+
"capabilities": {
274+
"worldTracking": True,
275+
"sceneDepth": True,
276+
"smoothedSceneDepth": True,
277+
"sceneMesh": True,
278+
"rawPhotonHistograms": False,
279+
},
280+
"phases": [phase, {**phase, "phase": "wall_scan", "plannedDurationSeconds": 30}],
281+
"consent": {
282+
"localValidation": True,
283+
"diagnosticExport": True,
284+
"rawSensorExport": False,
285+
},
286+
"evidenceLabel": "direct_depth",
287+
"physicalNLOSStatus": "blocked_raw_transients_unavailable",
288+
"cameraPermission": "granted",
289+
"completionStatus": "completed",
290+
}
291+
encoded = json.dumps(
292+
diagnostic, sort_keys=True, separators=(",", ":"), allow_nan=False
293+
).encode("utf-8")
294+
assert len(encoded) <= 65_536
295+
assert set(diagnostic) == set(schema["required"])
296+
assert diagnostic["consent"]["rawSensorExport"] is False
297+
assert diagnostic["capabilities"]["rawPhotonHistograms"] is False
298+
assert diagnostic["physicalNLOSStatus"] == (
299+
"blocked_raw_transients_unavailable"
300+
)
301+
302+
forbidden = {
303+
"image", "images", "rgb", "depth", "depth_map", "point_cloud",
304+
"csi", "transient", "histogram", "token", "authorization",
305+
"latitude", "longitude", "location", "trajectory", "raw",
306+
}
307+
308+
def walk(value):
309+
if isinstance(value, dict):
310+
for key, child in value.items():
311+
assert key.lower() not in forbidden, f"forbidden diagnostic key: {key}"
312+
walk(child)
313+
elif isinstance(value, list):
314+
for child in value:
315+
walk(child)
316+
317+
walk(diagnostic)
318+
print(f"Validated beta governance contract and {len(encoded)} byte fixture")
319+
PY
320+
161321
metaharness:
162322
name: Advisory MetaHarness
163323
runs-on: ubuntu-latest

0 commit comments

Comments
 (0)