nigig-org/.forgejo/workflows/p2p-intel.yml
andodeki 3ef182414e
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
p2p-intel / engine (push) Has been cancelled
p2p-intel / notifications (push) Has been cancelled
p2p-intel / coverage (push) Has been cancelled
p2p-intel / makepad-app (push) Has been cancelled
p2p-intel / exchange-tab (push) Has been cancelled
feat(robius-notification): real macOS and Windows backends, type-checked against the actual frameworks
The previous commit refused to write these two, on the grounds that code no
compiler has ever seen is not an implementation -- it is plausible-looking
text that would sit in the same crate as tested code and be read as equally
finished. That reasoning holds. The premise behind it did not.

`cargo check` needs the *target's standard library*, not a linker or a
platform SDK. `rustup target add x86_64-pc-windows-msvc aarch64-apple-darwin`
puts the real `windows` and `objc2` crates -- genuine WinRT metadata, genuine
Objective-C class definitions -- in front of the type checker on a Linux
host. So both backends are now written and both compile against the
frameworks they call.

Compile-checking earned its place immediately. The Windows backend was
written against `ToastNotificationManager::CreateToastNotifier(&HSTRING)`,
which does not exist: the AUMID overload is `CreateToastNotifierWithId`.
Nothing short of a compiler holding the real metadata would have caught
that, and it would have shipped looking entirely correct.

Because a cfg-gated module can silently compile to nothing -- leaving a
green check that proves only that the module was skipped -- each backend was
verified to be genuinely reachable by injecting a type error and confirming
the target build failed. Both macOS and Windows were checked this way, then
restored.

Two platform decisions worth recording.

macOS uses NSUserNotification, not UNUserNotificationCenter, and that is a
deliberate downgrade to a deprecated API. `UNUserNotificationCenter.current()`
raises an Objective-C exception when the process has no bundle identifier;
that unwinds through Rust frames and aborts. A plain `cargo run` host has no
bundle, so the modern API would crash the caller instead of reporting
unavailable -- which is worse than deprecated. The nil check on
`defaultUserNotificationCenter` is there for the same reason: msg_send on nil
returns zero rather than crashing, so every later call would silently do
nothing, which is precisely the failure this crate exists to remove.

Windows requires the host to supply an AppUserModelID, because a library
cannot invent one. It comes from an MSIX manifest or a Start Menu shortcut,
and a fabricated id produces a notifier that constructs happily and then
fails at Show. `set_app_user_model_id` is therefore public, a no-op off
Windows so portable hosts call it unconditionally, and `is_available()` is
false with a reason naming exactly what is missing until it is called. Toast
payloads are XML, so text is escaped -- the same bug class as the Telegram
MarkdownV2 escaping p2p-intel needed before it dropped Telegram, and a
merchant nickname containing `&` is not hypothetical.

The support table now has two columns, "compiles" and "executed", because
they are different claims. All four backends compile; only Linux has posted
a notification. iOS remains unavailable by design: it needs UN* with a
bundle and an entitlement, which is an app-packaging concern rather than
something this crate can satisfy.

Off Windows the module defines no entry points at all rather than stubs.
Clippy was right to call them dead: sys/mod.rs dispatches elsewhere, so they
existed only to satisfy a symmetry nothing needs.

CI gains a step that clippies all four cross-targets with -D warnings, so a
cfg-gated backend cannot rot unnoticed -- which is exactly how the
CreateToastNotifier mistake would have survived.

51 tests, clippy clean on five targets, p2p-intel still at 225.
2026-09-01 18:54:57 +00:00

183 lines
7 KiB
YAML

name: p2p-intel
# p2p-intel is a nested workspace (crates/apps/p2p-intel/Cargo.toml), so the
# root workspace jobs never touch it. Without this file the crate would have
# no gate at all.
#
# Measured when this job was added:
# total 97.03% of lines (floor 90)
# 223 tests, 0 failures
#
# The coverage script installs the toolchain from rust-toolchain.toml into
# its own temporary directory and deletes it on exit, so there is no
# toolchain step in the `coverage` job.
#
# Two jobs, for the same reason spreadsheet.yml has two: the engine half is
# pure Rust and finishes in about a minute, while the Makepad half has to
# build the Linux windowing backend to link. A single job would hide an
# engine regression behind a slow GPU-stack build.
on:
push:
paths:
- 'crates/apps/p2p-intel/**'
- 'crates/robius-notification/**'
- 'crates/apps/nigig-mpesa/src/pages/exchange/**'
- 'crates/apps/nigig-pay/src/payments_frame/pages/mpesa/exchange/**'
- 'tools/test-p2p-coverage.sh'
- 'tools/test-p2p-app-smoke.sh'
- 'tools/makepad-native-libs.sh'
- 'rust-toolchain.toml'
- '.forgejo/workflows/p2p-intel.yml'
pull_request:
paths:
- 'crates/apps/p2p-intel/**'
- 'crates/robius-notification/**'
- 'crates/apps/nigig-mpesa/src/pages/exchange/**'
- 'crates/apps/nigig-pay/src/payments_frame/pages/mpesa/exchange/**'
- 'tools/test-p2p-coverage.sh'
- 'tools/test-p2p-app-smoke.sh'
- 'tools/makepad-native-libs.sh'
- 'rust-toolchain.toml'
- '.forgejo/workflows/p2p-intel.yml'
jobs:
engine:
runs-on: docker
steps:
- uses: actions/checkout@v4
- name: Test, lint and format the engine crates
working-directory: crates/apps/p2p-intel
run: |
set -Eeuo pipefail
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
- name: Keep the dependency tree lean and the suite hermetic
working-directory: crates/apps/p2p-intel
run: |
set -Eeuo pipefail
# Two properties, one check.
#
# No HTTP client: networking goes through Makepad's Cx::http_request,
# so a reqwest/tokio/rustls here would mean someone reintroduced a
# second networking stack -- and CI would start depending on Binance
# being reachable.
#
# No serde: the stack deserialises with makepad_micro_serde. serde
# and serde_derive are a proc-macro build this crate does not need,
# and `toml` drags them back in through the back door.
tree="$(cargo tree --workspace -e normal 2>/dev/null)"
for forbidden in reqwest tokio rustls hyper serde_json serde_derive '^serde ' '^toml '; do
if printf '%s' "$tree" | sed 's/^[^a-zA-Z]*//' | grep -qE "$forbidden"; then
echo "forbidden dependency matched /$forbidden/ in the default graph" >&2
printf '%s' "$tree" | sed 's/^[^a-zA-Z]*//' | grep -E "$forbidden" >&2
exit 1
fi
done
echo "ok: no HTTP client and no serde in the default build"
notifications:
# robius-notification's Linux backend speaks D-Bus by hand, so its tests
# need a real daemon. ROBIUS_NOTIFICATION_REQUIRE_DBUS makes the suite
# fail rather than skip if one is missing -- without it the integration
# tests could stop running and the build would stay green.
runs-on: docker
steps:
- uses: actions/checkout@v4
- name: Install dbus
run: sudo apt-get install -y -qq dbus
- name: Test the notification crate
env:
ROBIUS_NOTIFICATION_REQUIRE_DBUS: '1'
run: |
set -Eeuo pipefail
cargo fmt -p robius-notification -- --check
cargo clippy -p robius-notification --all-targets -- -D warnings
cargo test -p robius-notification
- name: Type-check every platform backend
# A cfg-gated backend that nothing compiles rots silently: the
# Windows one was written against CreateToastNotifier(&HSTRING),
# which does not exist, and only a compiler holding the real WinRT
# metadata said so. `cargo check` needs the target's std, not an SDK,
# so all four are verifiable from Linux.
run: |
set -Eeuo pipefail
for t in aarch64-linux-android aarch64-apple-darwin aarch64-apple-ios x86_64-pc-windows-msvc; do
rustup target add "$t"
cargo clippy -p robius-notification --target "$t" -- -D warnings
done
coverage:
runs-on: docker
steps:
- uses: actions/checkout@v4
- name: Enforce the coverage floors
run: ./tools/test-p2p-coverage.sh
makepad-app:
runs-on: docker
steps:
- uses: actions/checkout@v4
- name: Install the Makepad native libraries and a virtual display
run: |
set -Eeuo pipefail
sudo bash tools/makepad-native-libs.sh --install
sudo apt-get install -y -qq xvfb
- name: Build and lint the desktop app
working-directory: crates/apps/p2p-intel
env:
# naga is memory-hungry; a parallel build is OOM-killed on a small
# runner, which presents as a SIGKILL with no error message.
CARGO_BUILD_JOBS: '1'
run: |
set -Eeuo pipefail
cargo clippy -p p2p-makepad --features ui --all-targets -- -D warnings
cargo test -p p2p-makepad --features ui
- name: Start the app under a virtual display
# `script_mod!` is parsed at *runtime*, so a malformed widget tree
# compiles perfectly and then logs an error and renders nothing.
# cargo build cannot catch that; this can, and did — it found
# `Row = <View>`, which is not valid in this fork's script language.
env:
CARGO_BUILD_JOBS: '1'
run: ./tools/test-p2p-app-smoke.sh
exchange-tab:
# The spread panel is consumed by nigig-mpesa and nigig-pay, whose
# exchange pages are byte-identical. A change to p2p-analyzer that breaks
# either is a change that ships broken, so both are built here.
runs-on: docker
steps:
- uses: actions/checkout@v4
- name: Install the Makepad native libraries
run: sudo bash tools/makepad-native-libs.sh --install
- name: Build the apps that embed the exchange tab
env:
CARGO_BUILD_JOBS: '1'
run: |
set -Eeuo pipefail
cargo check -p nigig-mpesa -p nigig-pay
- name: The two exchange pages must stay identical
run: |
set -Eeuo pipefail
a=crates/apps/nigig-mpesa/src/pages/exchange/mod.rs
b=crates/apps/nigig-pay/src/payments_frame/pages/mpesa/exchange/mod.rs
if ! diff -q "$a" "$b" >/dev/null; then
echo "the two exchange pages have drifted apart:" >&2
diff "$a" "$b" >&2
exit 1
fi
echo "ok: both apps carry the same exchange page"