docs: the CA travels in the enrol link, and why not the two alternatives
DECISIONS.md gets the decision with both rejected options and what the longer link measures (89 -> 652 bytes, a 45x23 QR -> 93x47), RUST.md ticks the enrolment queue item and marks the log-upload route superseded rather than editing it, and IRIS.md says what changed for anyone building the Android app. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
d8562d96a3
commit
729098756d
3 files changed
+107
-18
No files matched your search
@@ -534,3 +534,55 @@ marked **DEFERRED** are ones the agent chose not to decide alone.
|
||||
pass," and "The three remaining I5 verifications, closed 2026-09-05,"
|
||||
have the full account. The iris-vs-Masonry choice itself is still
|
||||
Iris's to make.
|
||||
|
||||
## 2026-09-07: the enrolment link carries the CA, so an APK need not be built where its server runs
|
||||
|
||||
**Problem.** Every phone build pinned the CA of the machine that compiled
|
||||
it -- the Compose app from `GeneratePinnedCert`, the iris app from
|
||||
`build.rs` reading `$XDG_CONFIG_HOME/ai-app/certs/ca.pem`. That is fine
|
||||
while the two are the same machine and impossible when they are not, which
|
||||
is exactly the iris client's situation: cross-compiled in this VM,
|
||||
delivered to a phone, run against `ai-server` on the host. Baking the
|
||||
host/port/token as well made it worse -- a token in a built artifact.
|
||||
|
||||
**Decided: the CA rides in the enrolment link**, as `&ca=<base64url of the
|
||||
DER>` (`wg_app_link::enroll::ca_param`), optional and per mint. The app
|
||||
that opens the link pins what the link said, and an APK built anywhere
|
||||
works against whatever server it is pointed at.
|
||||
|
||||
Two alternatives were worked out and rejected.
|
||||
|
||||
- **A CA *fingerprint* in the link, pinned at the TLS handshake.** The
|
||||
smallest link (43 more characters) and the strongest shape, but `ureq`
|
||||
3.4 exposes no hook for a custom `rustls` `ServerCertVerifier`: its
|
||||
`TlsConfig` builds the `ClientConfig` itself, so this needs a hand-written
|
||||
`Connector` on the `unversioned` API and `rustls` as a direct dependency
|
||||
of `client-core`. A lot of machinery in the one crate that must stay
|
||||
light.
|
||||
- **A fingerprint in the link plus an unauthenticated `GET /ca.pem`.**
|
||||
Small code, but it needs a first connection with verification disabled,
|
||||
and it breaks a documented, tested posture -- `auth.rs`'s "gates every
|
||||
route with zero unauthenticated endpoints", which is a load-bearing
|
||||
decision rather than an implementation detail. Not something to change
|
||||
silently for this.
|
||||
|
||||
**What it costs**, measured rather than guessed: on this project's P-256
|
||||
CA the link goes from 89 bytes to 652, and `print_enrollment`'s terminal
|
||||
QR from 45x23 to 93x47 characters. That is why the parameter is the
|
||||
minter's choice per call: `ai-server` passes it (its iris client needs it),
|
||||
`dev-updater` passes `None` (its app is built on the machine it talks to,
|
||||
and its QR stays scannable in an 80-column terminal). The URI printed under
|
||||
the QR is the fallback either way, and is the path Dev Updater's Enroll
|
||||
button already uses -- it opens the link with `ACTION_VIEW`, so Android
|
||||
offers whichever apps registered the scheme, which needed no change here.
|
||||
|
||||
The CA is a public certificate, so putting it in the QR leaks nothing the
|
||||
token did not already: photographing the terminal still costs exactly the
|
||||
token, which is rotatable.
|
||||
|
||||
**The log upload's destination is moot**, so it is not wired to this. On
|
||||
the same day Iris decided Dev Updater will read an APK's runtime log from
|
||||
an on-device ContentProvider instead, which removes `log_upload`,
|
||||
`POST /client-log` and the `AI_APP_LOG_*` baking altogether -- so the
|
||||
enrolment landed without touching any of them, for that change to delete
|
||||
whole.
|
||||
@@ -1160,3 +1160,27 @@ and per-block-row work (RUST.md's "Verification pass over Tasks A and B").
|
||||
measured. With the counter it is a test: one delta into a 100-paragraph
|
||||
reply shapes exactly **1** text layout, the same as into a
|
||||
one-paragraph one.
|
||||
|
||||
## 2026-09-07: the phone app is told which server to talk to, and pins from the link
|
||||
|
||||
Not an iris API change -- a client-facing one, in the crates around it,
|
||||
worth knowing because it changes what a build of the Android app *is*.
|
||||
|
||||
- **An iris APK is no longer tied to the machine that compiled it.** It
|
||||
used to have the server's host, port, token and CA compiled in, which
|
||||
made a build good for exactly one emulator/server pair and put a token
|
||||
in the artifact. Now it registers `aiapp://enroll` like the Compose app:
|
||||
open the link (Dev Updater's Enroll button already offers it, and the
|
||||
phone asks which app should take it) and the app stores where to go and
|
||||
what to trust.
|
||||
- **The CA rides in the link** as `&ca=<base64url DER>`, which is what
|
||||
makes the above possible at all -- a pinned certificate cannot be baked
|
||||
into an APK cross-compiled somewhere else. Optional, so the projects
|
||||
that do build on their own machine keep the short link and the small QR.
|
||||
docs/DECISIONS.md, 2026-09-07, has why not a fingerprint.
|
||||
- **`client_core::config` now holds the storage as well as the parsing**:
|
||||
`EnrolledServer` gained an optional `ca_pem`, and `EnrollmentStore` (the
|
||||
0600 JSON file, moved out of `desktop-app`) is one implementation for
|
||||
both the desktop and the phone -- only the directory differs.
|
||||
`desktop-app --ca` is now the override for a link that carried no CA
|
||||
rather than a required flag.
|
||||
+31
-18
@@ -131,6 +131,13 @@ What exists now:
|
||||
how many lines are held and when the last arrived, and what the uploader
|
||||
last did.
|
||||
|
||||
**Superseded 2026-09-07, and left standing rather than edited**: Dev
|
||||
Updater is growing an on-device runtime-log reader, so this whole route --
|
||||
`log_upload`, `POST /client-log` and the `AI_APP_LOG_*` baking -- is being
|
||||
removed rather than kept current. The app's *server* destination no longer
|
||||
comes from any of it: that is the enrolment link (the queue item above).
|
||||
What follows describes the route as built.
|
||||
|
||||
**How to use it.** Build the APK with the destination in the environment,
|
||||
on the machine `ai-server` runs on:
|
||||
|
||||
@@ -539,24 +546,30 @@ closes it.
|
||||
handing its server token to the app it installed (leaks the token
|
||||
into every managed app); the app posting to ai-server (needs its own
|
||||
enrolment first and puts the phone's logs in the wrong component).
|
||||
- [ ] Iris app enrolment (decided 2026-09-07): the bench APK's log
|
||||
destination is baked from `AI_APP_LOG_*` at build time, which cannot
|
||||
work for Iris's phone -- the APK is built in the VM, whose CA and
|
||||
token are not the host's, and no secret may go in a repo or a
|
||||
delivered artifact. Replace with the mechanism the Compose app and
|
||||
`desktop-app` already use: the iris Android app registers the
|
||||
`aiapp://enroll?host&port&token` VIEW intent, stores the enrolment
|
||||
the way `client_core::config` does, pins the CA the same way (the
|
||||
Compose app reads it at build time from the *building* machine, which
|
||||
is the same problem -- so for the phone the CA has to arrive with the
|
||||
link or the build has to happen on the host; check how Dev Updater's
|
||||
Enroll button and `ai-server --enroll-link` present the link and
|
||||
whether the CA fingerprint can ride in it as a query parameter, which
|
||||
`wg-app-link` may already support). `log_upload` then takes its
|
||||
destination from the enrolment at runtime, `AI_APP_LOG_*` is removed,
|
||||
and Dev Updater's enrol screen offers the link as a tappable link so
|
||||
Android hands it to the iris app. Rejected: building the iris APK on
|
||||
the host (needs cargo-ndk and an NDK there, and still bakes a token).
|
||||
- [x] Iris app enrolment (**done 2026-09-07**): the iris Android app is
|
||||
told which `ai-server` to talk to by an `aiapp://enroll` link, exactly
|
||||
as the Compose app and `desktop-app` are, instead of having it compiled
|
||||
in. `MainActivity` registers the VIEW intent and hands the URI and the
|
||||
app's private files directory to Rust (`src/enrollment.rs` is the intent
|
||||
plumbing and nothing else); the parsing, the file and its 0600 mode are
|
||||
`client_core::config`'s `EnrolledServer`/`EnrollmentStore`, shared with
|
||||
the desktop app. `build.rs`'s `AI_APP_TRANSCRIPT_HOST`/`_PORT`/`_TOKEN`
|
||||
are gone, and with them a token in a built artifact.
|
||||
**The CA travels with the link** (`&ca=`, base64url of the DER) --
|
||||
docs/DECISIONS.md, 2026-09-07, has the decision, the two rejected
|
||||
alternatives and what the longer link costs a QR code. That is what
|
||||
makes an APK cross-compiled here work against the server on the host.
|
||||
Diagnostics says which of three things is true -- `enrolled: host:port`,
|
||||
`not enrolled -- open the enrol link from Dev Updater`, or
|
||||
`enrolment unreadable: ...` -- because "could not find out" wants a
|
||||
different action from "nothing there yet".
|
||||
Dev Updater needed no change: its Enroll button already opens the minted
|
||||
link with `ACTION_VIEW`, and Android offers the chooser between this app
|
||||
and the Compose one.
|
||||
**The log upload was deliberately left out of it**: Dev Updater is
|
||||
growing an on-device runtime-log reader instead (Iris, 2026-09-07), so
|
||||
`log_upload`, `POST /client-log` and the `AI_APP_LOG_*` baking are on
|
||||
their way out whole rather than being rewired first.
|
||||
- [ ] `iris/android-app/build-apk.sh`: clear Gradle's merged-native-libs
|
||||
cache when the ABI changes (the x86_64 trap), and make the debug bench
|
||||
APK installable (648 MB) -- RUST.md's logging section names both.
|
||||
|
||||
Reference in new issue
Block a user