# Pi server image (linux/arm64) and the FCM `fid` target Date: 2026-10-18. Branch `agent-pi-image`. Machine: the owner's M4 MacBook, Podman 6-CPU `applehv` machine (arm64 Linux VM). Nothing was pushed to any registry; no Gradle build ran at the same time (the `/tmp/kvitt-gradle.lock` lock was free and no Gradle daemon was busy when each build started). ## 1. FCM HTTP v1 `fid` field (D-083 follow-up) Source: , "Last updated 2026-09-08 UTC", read 2026-10-18. The `Message` union field `target` lists `token`, `fid`, `topic` and `condition` as mutually exclusive. The descriptions: * `token` (deprecated): "Deprecated: Use fid instead. During the transition period, this field also accepts a Firebase Installation ID (FID). Registration token to send a message to." * `fid`: "Firebase Installation ID (FID) to send a message to." Change: `push::fcm::message_body` now puts the stored `devices.fcm_token` value (the phone's installation id, D-083) in `message.fid` instead of `message.token`. No database or API change; the column and the E-17 field keep the name `fcm_token`. `contracts/examples/push/*.json` (8 files) use `fid` with an example installation id, and the `push_fcm` tests check `fid` and that `token` is absent. ## 2. Image build Command, from the worktree root (as `infra/dev/build.sh` does; `infra/build-images.sh` does not exist yet, it belongs to M2c-pi): ``` podman build --platform linux/arm64 --no-cache -f server/Dockerfile -t localhost/kvitt-server:pi-check server/ ``` | Check | Result | |---|---| | Build stage C toolchain | `rust:1.99.1-bookworm` carries gcc and cc 12.2.0 (Debian 12.2.0-14+deb12u1); no `cmake` binary, and none is needed | | Cold compile (new target cache id, registry cache warm) | rc 0, 48 s wall, 232 crates compiled, `aws-lc-sys v0.46.0` and `aws-lc-rs v1.18.2` built, no warnings, `Finished release` in 44.7 s | | Warm rebuild (shared target cache) | rc 0, 14 s | | Image | arm64/linux, 63.8 MB, user `10001:10001`, entrypoint `kvitt-server`, cmd `serve` | | CA roots | `/etc/ssl/certs/ca-certificates.crt` present (224,449 bytes, 150 certificates) from distroless `ca-certificates` | | Binaries | `/usr/local/bin/kvitt-server` (19.9 MB), `/usr/local/bin/kvitt-import` (8.2 MB) | | `kvitt-server --version` / `--help` | `kvitt-server 0.1.0`; help lists the six subcommands | | `kvitt-import --version` | `kvitt-import 0.1.0` | The arm64 binaries ran natively in the arm64 Podman VM; qemu is not involved (the VM registers qemu only for x86 and big-endian arm). The binaries target generic Armv8-A (no `target-cpu=native`), so they suit the Pi 3's Cortex-A53; running on the Pi itself is still M2c-pi's. No Dockerfile change was needed. Two comments were added: where the C toolchain comes from, and where the CA bundle is. ## 3. Live probe from the image Script: a throwaway Podman volume at `/data`, with the container hardened as on the dev stack (uid 10001, read-only root, no capabilities, `no-new-privileges`). `infra/secrets/` was mounted read-only at `/secrets` and `KVITT_FCM_PROJECT_ID=kvitt-1337` was set. The steps: `bootstrap`, then `serve`. Over the API: admin accepts (E-3), partner invite (E-12), member accepts (E-3), member E-17 with a made-up 22-character installation id that belongs to no installation, account (E-18), admin pushes one expense (E-32). The volume and container were removed afterwards. | Step | Result | |---|---| | Start-up log | `push enabled`, `project = kvitt-1337` | | `/health` | 200, `schema_version` 3, `db_ok` true | | API calls | E-3 200, E-12 201, E-3 200, E-17 204, E-18 201, E-32 200 `accepted` | | Outbound TLS + OAuth | succeeded: the sender exchanged the service-account JWT at `https://oauth2.googleapis.com/token` and then called `fcm.googleapis.com` `messages:send`. A failed token fetch would end as "push failed" with a token error, never as an unregistered answer | | FCM answer | `FCM token unregistered: cleared` (HTTP 404 or `UNREGISTERED`) about 0.5 s after start: FCM took the `fid` body and reported the made-up id as unknown. A field FCM did not know would have produced 400 `INVALID_ARGUMENT` | | Container memory after the run | about 3.7 MB | The send call went to an installation id that does not exist, so no device received anything. No real device token was used. ## Results (owner's phone) Still to come: an end-to-end push to a real phone through `fid`, as part of the planned end-to-end pairing, offline and push test (HANDOFF ยง3), and the image running on the Pi (M2c-pi).