Dashboard live polling + audit findings update#63
Merged
Merged
Conversation
added 3 commits
May 29, 2026 12:12
The dashboard demo at /demo/registration was driving the phone-side
endpoints through a simulator. This commit adds the actual Kotlin
client so the same flow runs against the live backend from a real
phone (sideloaded APK).
What lands:
net/RegistrationApi.kt — Retrofit binding for the three
phone-side endpoints (POST
/v1/registrations/{pair-device,
submit-commitment, complete}) plus
request/response DTOs. Same OkHttp +
Json stack as ZeroAuthApi; pulled out
into ApiFactory.createRegistrationApi
so callers that only need the new
surface skip the W3 init cost.
util/RegQrPayload.kt — Parser for the QR deeplinks:
zeroauth://reg?step=<pair|enroll|verify>
&session=<uuid>&code=ZA-XXXX-XXXX
[&challenge=<32-hex>]. Returns
Result.failure with stable string
codes (reg_qr_parse_failed,
reg_qr_missing_field,
reg_qr_bad_code_shape,
reg_qr_bad_challenge_shape) so the UI
routes errors without a try/catch.
util/DeviceFingerprint.kt — Builds the >= 16-char opaque
fingerprint string the server hashes.
Composition: android:<appId>:
<ANDROID_ID>:<install_uuid>; result
SHA-256-hex'd. Per-install UUID lives
in SharedPreferences so the same
install always produces the same
fingerprint (lets the same row in the
devices table re-enroll on
code-regenerate).
ui/reg/RegistrationViewModel.kt — State machine:
Idle → Pairing → AwaitingEnrollScan
→ Committing → AwaitingVerifyScan →
Verifying → Completed | Failed.
onQrText(text) parses, branches by
step, and POSTs. BiometricSecretSource
and ProofGenerator are injection seams
so Phase 1 Sprint 4 can swap the
StubProofGenerator for the real
WebViewMobileProver.
ui/reg/RegistrationHelpers.kt — Default injections:
PerInstallStableSecret persists a
32-byte secret in SharedPreferences so
step 2 and step 3 derive the same
commitment (without that, the verify
step's publicSignals[0] check fails).
DeriveDidAndCommitment computes
Poseidon.hash2(secret, zeroSalt) and
derives the DID suffix. StubProofGenerator
returns a structurally valid Groth16
envelope — the server's verifier will
reject it (intentionally) until the
real prover lands.
ui/reg/RegistrationScreen.kt — Compose UI. Paste-deeplink only for
V1 (the camera scan path mirrors
ui/scan/ScanScreen.kt's ML Kit +
CameraX pipeline and gets wired in
Phase 1 Sprint 4). Step badge,
progress indicators, terminal
Completed / Failed cards.
ui/SplashScreen.kt — Second CTA "Create a new account
(3-QR signup)" routes to the new flow.
Original "Sign in" CTA unchanged.
nav/Nav.kt — New Screen.Registration entry +
composable route.
Tests:
test/util/RegQrPayloadTest.kt — 11 Robolectric tests covering the
happy path for each step + every
stable error code (wrong scheme, wrong
host, unknown step, missing
session/code, malformed code/challenge,
verify-without-challenge).
Verify (CI):
.github/workflows/android.yml runs ./gradlew assembleDebug + test
on every push that touches android/**. The local toolchain in this
environment doesn't have gradle/android SDK installed (the wrapper
jar is gitignored per README), so the validation falls to CI.
How to install on a phone:
cd android
gradle wrapper --gradle-version 8.7
./gradlew :app:installDebug # adb-attached device
The Compose viewModel(factory = ...) builder needs the explicit imports for viewModelFactory and initializer. Without them the qualified path resolves the builder but the lambda-inside-builder fails 'Unresolved reference initializer'. Android CI #62 caught this; local env has no SDK to validate at commit time.
Two small improvements that close the gap between "ceremony built" and "ceremony usable in a demo with a real phone": 1) QrRegistration.tsx polls GET /api/console/registrations/:id every 2 s while the page is in any awaiting_* state. When the server reports a forward transition the dashboard auto-advances without operator intervention — this is what a real phone scanning the QRs would trigger. The simulator panel keeps dispatching state directly so it doesn't wait on the poll. Polling stops at terminal states (completed / abandoned / error) and when the page is idle/creating, so there's no background load when nothing's happening. 2) docs/security/audit-findings.md gains a new "Phase 1 follow-ons landed since the last snapshot" section recording ADR 0022 (device enrollment), ADR 0023 (three-QR signup), and ADR 0024 (qrcode.react dep). The original 21 findings carry forward unchanged. A second new section "Pilot-ready vs production-ready gap" lays out the six known gaps that still block a real BFSI tenant onboarding, so the tracker doesn't claim more than is true. Verify: - npx tsc --noEmit (dashboard) clean - npm test (vitest) 56/56
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two surgical improvements that close the gap between "ceremony built" and "ceremony usable in a demo with a real phone":
Live polling in QrRegistration.tsx — polls `GET /api/console/registrations/:id` every 2 s while the page is in any awaiting_* state. When the server reports a forward transition, the dashboard auto-advances without operator intervention. The simulator panel keeps dispatching state directly so it doesn't wait on the poll. Polling stops at terminal states.
audit-findings.md updates — a new "Phase 1 follow-ons landed since the last snapshot" section records ADR 0022 / 0023 / 0024; a new "Pilot-ready vs production-ready gap" section lists the six known blockers that still gate a real BFSI tenant onboarding (real biometric capture, real Groth16 proof for verify step, camera QR scanning, circuit v1.3 with challenge_nonce, branch protection on main, PII strip).
Test plan