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,"
|
pass," and "The three remaining I5 verifications, closed 2026-09-05,"
|
||||||
have the full account. The iris-vs-Masonry choice itself is still
|
have the full account. The iris-vs-Masonry choice itself is still
|
||||||
Iris's to make.
|
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
|
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
|
reply shapes exactly **1** text layout, the same as into a
|
||||||
one-paragraph one.
|
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
|
how many lines are held and when the last arrived, and what the uploader
|
||||||
last did.
|
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,
|
**How to use it.** Build the APK with the destination in the environment,
|
||||||
on the machine `ai-server` runs on:
|
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
|
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
|
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).
|
enrolment first and puts the phone's logs in the wrong component).
|
||||||
- [ ] Iris app enrolment (decided 2026-09-07): the bench APK's log
|
- [x] Iris app enrolment (**done 2026-09-07**): the iris Android app is
|
||||||
destination is baked from `AI_APP_LOG_*` at build time, which cannot
|
told which `ai-server` to talk to by an `aiapp://enroll` link, exactly
|
||||||
work for Iris's phone -- the APK is built in the VM, whose CA and
|
as the Compose app and `desktop-app` are, instead of having it compiled
|
||||||
token are not the host's, and no secret may go in a repo or a
|
in. `MainActivity` registers the VIEW intent and hands the URI and the
|
||||||
delivered artifact. Replace with the mechanism the Compose app and
|
app's private files directory to Rust (`src/enrollment.rs` is the intent
|
||||||
`desktop-app` already use: the iris Android app registers the
|
plumbing and nothing else); the parsing, the file and its 0600 mode are
|
||||||
`aiapp://enroll?host&port&token` VIEW intent, stores the enrolment
|
`client_core::config`'s `EnrolledServer`/`EnrollmentStore`, shared with
|
||||||
the way `client_core::config` does, pins the CA the same way (the
|
the desktop app. `build.rs`'s `AI_APP_TRANSCRIPT_HOST`/`_PORT`/`_TOKEN`
|
||||||
Compose app reads it at build time from the *building* machine, which
|
are gone, and with them a token in a built artifact.
|
||||||
is the same problem -- so for the phone the CA has to arrive with the
|
**The CA travels with the link** (`&ca=`, base64url of the DER) --
|
||||||
link or the build has to happen on the host; check how Dev Updater's
|
docs/DECISIONS.md, 2026-09-07, has the decision, the two rejected
|
||||||
Enroll button and `ai-server --enroll-link` present the link and
|
alternatives and what the longer link costs a QR code. That is what
|
||||||
whether the CA fingerprint can ride in it as a query parameter, which
|
makes an APK cross-compiled here work against the server on the host.
|
||||||
`wg-app-link` may already support). `log_upload` then takes its
|
Diagnostics says which of three things is true -- `enrolled: host:port`,
|
||||||
destination from the enrolment at runtime, `AI_APP_LOG_*` is removed,
|
`not enrolled -- open the enrol link from Dev Updater`, or
|
||||||
and Dev Updater's enrol screen offers the link as a tappable link so
|
`enrolment unreadable: ...` -- because "could not find out" wants a
|
||||||
Android hands it to the iris app. Rejected: building the iris APK on
|
different action from "nothing there yet".
|
||||||
the host (needs cargo-ndk and an NDK there, and still bakes a token).
|
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
|
- [ ] `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
|
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.
|
APK installable (648 MB) -- RUST.md's logging section names both.
|
||||||
|
|||||||
Reference in new issue
Block a user