The app hands its log to Dev Updater on the phone, not through ai-server

Iris's call once the upload route was working: put it in Dev Updater
properly. So the app now exposes its own ring through a ContentProvider
at `<applicationId>.devlog` -- Dev Updater's contract, written down in
that project's README, not something invented here -- and Dev Updater's
phone app reads it on the same device and forwards it to its own build
machine. No tunnel, no token, no second enrolment, and any app that
server delivers can implement the same and get the same Runtime tab.

`DevLogProvider.java` plus `devlog.rs` are the platform glue only: a flat
`String[]` across JNI, a `MatrixCursor` on the Java side, and
`nativeReady` telling Rust the authority the provider actually
registered, so the Diagnostics pane can name somewhere a reader can
query rather than composing a guess. `LogRing::newest_seq()` is the one
addition in `client-core`: an in-memory ring starts again at zero, so it
is what lets a reader notice the process restarted instead of silently
skipping everything since.

Deleted with it, so there is one mechanism: `client_core::log_upload`,
`POST /client-log` on ai-server, the `AI_APP_LOG_*` baking (which left
`build.rs` with nothing to do), and the uploader on both Android
clients. Kept: the ring, `RingLogger`, `install_process_logger`, and the
Diagnostics line -- whose second half is now `devlog provider:
content://<authority>`.

Verified end to end on this checkout's emulator: iris's own
`iris::android::view` startup lines read out of the provider by the
shell, forwarded by Dev Updater's Runtime tab, and served back from
`GET /apps/android-app/components/app/logs?kind=runtime`. A component
whose package has no provider says so in as many words.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Fable 5.1 committed 2026-09-07 20:58:48 -04:00
1 parent e10582a2cd
commit 06b8a1f4b0
15 files changed
+618 -842

No files matched your search

+76 -71
View File
@@ -89,83 +89,77 @@ Three things this says about the rig, since the rig is new:
claimed of it -- asserted at the end of every draw, and the test
checks both directions.
### Phone logging, 2026-09-07 (built and verified end to end)
### Phone logging, 2026-09-07 (rebuilt on Dev Updater's own tab)
**The problem**: Iris tests these builds on a phone with no `adb`, and
Android forbids one app reading another's `logcat`, so a `log::info!` in
the iris app could not reach her at all. What she asked for was Dev
Updater, which she already reads.
**The route, in one line**: the app keeps its own bounded log ring, posts
it to `ai-server`, and `ai-server` re-emits each line into its own
`tracing` output -- which Dev Updater *already* shows as that component's
**runtime log**, because it runs `ai-server` as a `Managed` service and
that service's script redirects stdout to
`$XDG_DATA_HOME/dev-updater/services/<key>-<component>/<...>.log` and
reports the path. **Nothing in dev-updater changed.** The alternatives and
what each would have cost are in docs/DECISIONS.md.
**The route, in one line**: the app keeps its own bounded log ring and
**exposes it on the device** through a `ContentProvider`; Dev Updater's
phone app -- on the same phone -- reads that while the component's
**Runtime** tab is open and forwards what is new to its own build machine,
into that APK component's runtime log. No tunnel, no token, no second
enrolment.
**It is Dev Updater's contract, not iris's feature.** Written down in
dev-updater's `README.md` under "An app's own log", so any app that server
delivers gets the tab by implementing it. The shape:
| | |
|---|---|
| authority | `<applicationId>.devlog` |
| read permission | `dev.updater.permission.READ_DEVLOG` (`android:readPermission`) |
| `lines?since=<seq>` | held lines with `seq >= since`, ascending: `seq, t_ms, level, target, message` |
| `status` | one row: `held, dropped, newest_seq` |
`newest_seq` is `-1` for an empty ring and is what makes a **restart**
visible: the ring is in memory, so a new process starts again at zero and
a reader holding a cursor would otherwise skip everything since, silently.
`insert`/`update`/`delete` throw. `notifyChange` is **not** implemented --
the ring is filled by a `log::Log` backend on whatever thread logged, and
routing that to a provider means a callback through `client-core` for
every platform, so Dev Updater polls (about a second, only while the tab
is open) and the contract says so.
What exists now:
- `client_core::log_ring` -- `LogRing` (2000 lines / 256 KiB, whichever
bites first, with `dropped` reported rather than inferred), `RingLogger`
(a `log::Log` backend that records *and* forwards to the platform's own
logger), and `install_process_logger`. Reading does not consume, so the
report and the uploader are two readers of one ring.
- `client_core::log_upload` -- `LogUploader::flush_once` (batched at 500
lines, retried from the same cursor on failure) and `LogUpload::spawn`
(a thread flushing every 10s, stopped by dropping the handle).
**Nothing in it calls `log!`**: those lines would land in the ring it is
draining.
- `POST /client-log` on `ai-server` -- `{source, lines:[{seq, at, level,
target, message}]}`, re-emitted at the client's own level under the
target **`ai_server::client_log`**. Under the crate's path deliberately:
a bare `client_log` target is dropped by `RUST_LOG=ai_server=debug`, the
filter AGENTS.md tells people to run with, so every line a phone sent
vanished with nothing saying so. Found by running it.
- `iris/android-app/src/app_log.rs` -- the platform half only:
`android_logger` as the logger to forward to, and the destination baked
in by `build.rs` from `AI_APP_LOG_HOST`/`_PORT`/`_TOKEN` plus the pinned
CA (all three or none). `Copy report` appends the ring to the clipboard
text and flushes the uploader first; `Diagnostics` gains two lines --
how many lines are held and when the last arrived, and what the uploader
last did.
bites first, with `dropped` reported rather than inferred), `since(seq)`,
`newest_seq()`, `RingLogger` (a `log::Log` backend that records *and*
forwards to the platform's own logger), and `install_process_logger`.
Reading does not consume, so the report and the provider are two readers
of one ring.
- `iris/android-app/src/devlog.rs` and
`app/src/main/java/dev/iris/android/demo/DevLogProvider.java` -- the
platform glue only (the sharing rule): a `String[]` across JNI, a
`MatrixCursor` on the Java side, and `nativeReady` telling Rust the
authority the provider actually registered under.
- `iris/android-app/src/app_log.rs` -- `android_logger` as the logger to
forward to, and the Diagnostics pane's two lines: how many lines are
held, and **`devlog provider: content://<authority>`** (or "declared,
not created yet", since Android creates a provider lazily). Named from
what the provider registered rather than composed from the package here,
so a screenshot of that pane is evidence the contract is live.
- On the Dev Updater side (its own repo, commit `013d711`): the tab for
every component rather than only a server's, `DevLog.kt`'s reader and
cursor, `POST /apps/{key}/components/{name}/runtime-log`, and
`$XDG_DATA_HOME/dev-updater/devlogs/<key>-<component>.log` with one
rotation at 4 MiB.
**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.
**Deleted with it, so there is one mechanism**: `client-core`'s
`log_upload`, `POST /client-log` on `ai-server`, the `AI_APP_LOG_*` baking
in `iris/android-app/build.rs` (which left that file with nothing to do,
so it is gone), and the uploader fields on both Android clients.
**How to use it.** Build the APK with the destination in the environment,
on the machine `ai-server` runs on:
**How to use it.** Build and install the bench APK; open Dev Updater ->
the ai-app project -> the **app** component's log button -> the
**Runtime** tab. Nothing to configure: the tab finds the provider from the
package the component installs. A component whose package exposes none
says so in as many words, which is a different sentence from an empty log.
AI_APP_LOG_HOST=10.66.0.1 AI_APP_LOG_PORT=8443 \
AI_APP_LOG_TOKEN=<a token that server accepts> \
./build-apk.sh release
Then read it on the phone: Dev Updater -> the ai-app project -> the
**server** component's log button -> the **Runtime** tab. The app's lines
are the ones tagged `ai_server::client_log`, each carrying `[<source>
<the app's own clock> #<seq>]` before the target and message. A build with
none of those variables set still keeps its ring and still puts it on the
clipboard from `Copy report`; the Diagnostics pane says so in as many
words.
**Verified 2026-09-07, all four hops.** ai-server on 127.0.0.1:8455 with a
scratch config; the bench APK built `--abi x86_64` with
`AI_APP_LOG_HOST=10.0.2.2 AI_APP_LOG_PORT=8455 AI_APP_LOG_TOKEN=...`;
installed and launched on this checkout's emulator. Twenty seconds later
the server's log held
INFO ai_server::client_log: [iris-bench 20:17:02.284 #0]
iris::android::view: iris: new_peer content_scale=2.625
and a scratch `dev-updater` (port 8492, `XDG_DATA_HOME=/tmp/du-test/data`)
with this checkout registered served exactly those lines back from
`GET /apps/ai-app-2/components/server/logs?kind=runtime` -- which is the
JSON the phone's Runtime tab renders.
**Verified 2026-09-07 end to end** -- see "Verified" below.
**Two rig traps this cost an hour to find, both in `build-apk.sh`, both
still there.** Written down rather than fixed because fixing them belongs
@@ -193,7 +187,9 @@ Also: the bench APK's package is `dev.iris.android.demo.bench`, not
`dev.iris.android.demo`. An older non-bench build left installed answers
to the second name, runs, looks right, and reports whatever *it* was built
with -- which is how "log upload: this build has no server configured"
came from a build that had one.
came from a build that had one. The devlog authority carries the
`applicationId` for exactly that reason: the two packages each get their
own and neither can read the other's log.
### `iris::input`/`iris::frame` diagnostics, 2026-09-07
@@ -637,8 +633,16 @@ closes it.
- [ ] Scroll clamped at both ends, and Compose's impulse velocity
estimator with min/max fling velocity (docs/IRIS_TODO.md, 2026-09-07
later). After the culling fix lands (same file).
- [ ] **APK runtime logs in Dev Updater (Iris, 2026-09-07: "please add
android / apk runtime log support to dev updater").** Supersedes the
- [x] **APK runtime logs in Dev Updater (Iris, 2026-09-07: "please add
android / apk runtime log support to dev updater").** **Done
2026-09-07** -- dev-updater `013d711`, and this repo's provider half;
"Phone logging" above is the account, docs/DECISIONS.md the decision.
What was designed and what was built agree except in one place: the
provider does **not** call `notifyChange` (the reason is in both), and
the tab is drawn for every component rather than only where a provider
resolves, since its absence would be the one thing that could not say
which of the several reasons there was nothing to read. The design as
written: Supersedes the
ai-server `POST /client-log` route, which becomes the second mechanism
and is deleted once this works (`log_upload.rs`, `app_log.rs`'s
upload half, the route). Design: Android forbids reading another
@@ -684,10 +688,11 @@ closes it.
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.
**The log upload was deliberately left out of it**: Dev Updater grew an
on-device runtime-log reader instead (Iris, 2026-09-07), so
`log_upload`, `POST /client-log` and the `AI_APP_LOG_*` baking went out
whole rather than being rewired first -- done the same day, "Phone
logging" above. `build.rs` had nothing left to do and is gone with them.
- [ ] `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.