Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
4.5 KiB
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: https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages, "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).