Skip to content

Commit 97cb530

Browse files
committed
feat(mobile): adopt NLOS instrument interface
1 parent 4a8c8ea commit 97cb530

23 files changed

Lines changed: 2954 additions & 429 deletions

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

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ on:
1111
- 'docs/adr/ADR-32[8-9]*'
1212
- 'docs/adr/ADR-33[0-1]*'
1313
- 'docs/adr/ADR-341-consumer-nlos-beta-tester-delivery-and-diagnostics.md'
14+
- 'docs/adr/ADR-342-cognitum-inspired-mobile-instrument-ui.md'
1415
- 'docs/research/consumer-nlos-acceptance-protocol.md'
1516
- 'docs/research/consumer-nlos-beta-test-protocol.md'
17+
- 'docs/screenshots/consumer-nlos-mobile-ui/**'
1618
- 'docs/schemas/ruview-nlos-*.schema.json'
1719
- 'docs/schemas/ruview-ios-visible-depth-diagnostic-v1.schema.json'
1820
- 'docs/security/consumer-nlos-threat-model.md'
@@ -27,6 +29,8 @@ on:
2729
- 'ui/mobile/**'
2830
- 'harness/ruview/**'
2931
- 'docs/**consumer-nlos*'
32+
- 'docs/adr/ADR-342-cognitum-inspired-mobile-instrument-ui.md'
33+
- 'docs/screenshots/consumer-nlos-mobile-ui/**'
3034
- 'docs/schemas/ruview-ios-visible-depth-diagnostic-v1.schema.json'
3135
- '.github/workflows/consumer-nlos-ci.yml'
3236
- 'v2/Cargo.toml'
@@ -118,6 +122,20 @@ jobs:
118122
- run: npm test -- --runInBand
119123
- run: npx tsc --noEmit
120124
- run: npm run lint
125+
- name: Install pinned Playwright Chromium
126+
run: ./node_modules/.bin/playwright install --with-deps chromium
127+
- name: Run production browser E2E and reproduce mobile screenshots
128+
run: npm run e2e:web
129+
- name: Upload browser E2E review evidence
130+
if: ${{ !cancelled() }}
131+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
132+
with:
133+
name: consumer-nlos-mobile-ui-browser-${{ github.sha }}
134+
path: |
135+
docs/screenshots/consumer-nlos-mobile-ui
136+
ui/mobile/test-results/playwright
137+
if-no-files-found: warn
138+
retention-days: 14
121139
- name: Export browser bundle
122140
run: npx expo export --platform web
123141
- name: Export Expo iOS bundle
@@ -136,6 +154,142 @@ jobs:
136154
console.log(JSON.stringify(report.metadata?.vulnerabilities ?? {}, null, 2));
137155
JS
138156
157+
mobile-ui-contract:
158+
name: Mobile UI ADR, E2E, and screenshot contract
159+
runs-on: ubuntu-24.04
160+
steps:
161+
- name: Checkout
162+
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
163+
164+
- name: Validate UI-only architecture and deterministic review evidence
165+
shell: bash
166+
run: |
167+
set -euo pipefail
168+
python - <<'PY'
169+
import re
170+
import struct
171+
from pathlib import Path
172+
173+
adr_path = Path(
174+
"docs/adr/ADR-342-cognitum-inspired-mobile-instrument-ui.md"
175+
)
176+
flow_path = Path("ui/mobile/e2e/nlos_mobile_ui.yaml")
177+
screenshot_dir = Path("docs/screenshots/consumer-nlos-mobile-ui")
178+
179+
adr = adr_path.read_text(encoding="utf-8")
180+
required_sections = (
181+
"## Context",
182+
"## Specification",
183+
"## Pseudocode and state transitions",
184+
"## Architecture",
185+
"## Decision",
186+
"## Alternatives with quantified tradeoffs",
187+
"## Accessibility",
188+
"## Security and privacy",
189+
"## Performance budgets",
190+
"## Refinement and failure handling",
191+
"## Requirement to evidence mapping",
192+
"## Rollout and rollback",
193+
"## Acceptance test",
194+
)
195+
for section in required_sections:
196+
assert section in adr, f"missing ADR section: {section}"
197+
198+
required_adr_contract = (
199+
"UI-only",
200+
"feat/consumer-nlos-ruview",
201+
"Cognitum Explain",
202+
"No Cognitum name, logo, wordmark",
203+
"SYNTHETIC",
204+
"LIVE VERIFIED",
205+
"LIVE UNVERIFIED",
206+
"STALE",
207+
"DISCONNECTED",
208+
"390 by 844",
209+
"44 by 44 points",
210+
"under 1.5 seconds",
211+
"under 100 milliseconds",
212+
"zero runtime dependency",
213+
"physical iPhone validation pending",
214+
)
215+
for phrase in required_adr_contract:
216+
assert phrase in adr, f"missing mobile UI contract: {phrase}"
217+
218+
flow = flow_path.read_text(encoding="utf-8")
219+
required_flow_contract = (
220+
"appId:",
221+
"---",
222+
"launchApp:",
223+
"tapOn:",
224+
"assertVisible:",
225+
"nlos-evidence-state",
226+
"nlos-synthetic-watermark",
227+
"nlos-beta-setup",
228+
)
229+
for token in required_flow_contract:
230+
assert token in flow, f"missing Maestro UI contract token: {token}"
231+
232+
screen_sources = "\n".join(
233+
path.read_text(encoding="utf-8")
234+
for path in sorted(Path("ui/mobile/src/screens/NLOSScreen").glob("*.tsx"))
235+
)
236+
for test_id in (
237+
"nlos-evidence-state",
238+
"nlos-synthetic-watermark",
239+
"nlos-beta-setup",
240+
):
241+
assert test_id in screen_sources, f"missing screen contract: {test_id}"
242+
for state in (
243+
"SYNTHETIC",
244+
"LIVE VERIFIED",
245+
"LIVE UNVERIFIED",
246+
"STALE",
247+
"DISCONNECTED",
248+
):
249+
assert state in screen_sources, f"missing evidence state: {state}"
250+
251+
state_type = re.search(
252+
r"export type NlosEvidenceState\s*=\s*(.*?);",
253+
screen_sources,
254+
re.DOTALL,
255+
)
256+
assert state_type, "missing NlosEvidenceState type contract"
257+
actual_states = set(re.findall(r"'([^']+)'", state_type.group(1)))
258+
expected_states = {
259+
"SYNTHETIC",
260+
"LIVE VERIFIED",
261+
"LIVE UNVERIFIED",
262+
"STALE",
263+
"DISCONNECTED",
264+
}
265+
assert actual_states == expected_states, (
266+
f"evidence state taxonomy drift: {sorted(actual_states)}"
267+
)
268+
269+
def png_dimensions(path):
270+
data = path.read_bytes()
271+
assert data.startswith(b"\x89PNG\r\n\x1a\n"), f"not a PNG: {path}"
272+
assert data[12:16] == b"IHDR", f"missing PNG IHDR: {path}"
273+
return struct.unpack(">II", data[16:24])
274+
275+
expected_screenshots = (
276+
"overview-390x844.png",
277+
"synthetic-390x844.png",
278+
"setup-390x844.png",
279+
)
280+
for name in expected_screenshots:
281+
path = screenshot_dir / name
282+
assert path.is_file(), f"missing screenshot baseline: {path}"
283+
assert png_dimensions(path) == (390, 844), (
284+
f"wrong screenshot dimensions for {path}: {png_dimensions(path)}"
285+
)
286+
287+
print(
288+
"Validated ADR 342, Maestro UI contract, five evidence states, "
289+
"and three 390x844 PNG baselines"
290+
)
291+
PY
292+
139293
native-ios:
140294
name: Native Swift, simulator, and unsigned archive dry gate
141295
runs-on: macos-26

0 commit comments

Comments
 (0)