|
Some checks failed
nigig-map / test (push) Has been cancelled
Payment domain, storage and platform / isolated-payment-tests (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / engine (push) Has been cancelled
Phase 5 of REVIEWS/NIGIG_PAY_CONSOLIDATED_REVIEW.md. Design and the one
item engineering cannot close are in REVIEWS/adr/0007.
## Phase 4 is now verified, not just written
Tranche 5 implemented the draw_walk fixes and said plainly that the
nigig-pay widget edits were unbuilt. That blocker was environmental:
installing the packages tools/makepad-native-libs.sh already lists makes
the UI graph compile. Both commands the status doc listed as required
now pass:
cargo check -p nigig-pay pass
cargo check -p nigig-pay-ui pass
No code changed for this; the claim is now evidence rather than assertion.
## Phase 5: new nigig-pay-platform crate
ADR 0002 reserved this crate and marked it "not yet created".
5.1 One crate owns the seam. It is the only crate in the payment stack
that may name a platform SDK. CI enforces both directions: payment crates
may not import Makepad, and domain/storage may not import jni or the
robius platform crates.
5.3 Correlation is mandatory and single-flight. SessionRegistry admits an
event as Accepted, Duplicate or Ignored; PlatformEvent cannot be built
without a CorrelationId. Closed session ids are retired permanently, so
an abandoned session's confirmation cannot settle the payment that
replaced it. There is a test named after exactly that scenario.
2.8/5.3 Progress decides retry safety, not error kind. classify_failure
takes the failure and the DispatchProgress reached before it, and
progress is the authority. The same TemporarilyUnavailable is safely
retryable before the dial and ambiguous once the menu is being driven —
the distinction the old code could not make, which is defect B3's
mechanism. A property test asserts across the whole failure space that
nothing which may have reached the provider authorises a fresh attempt.
5.4 Fakes cannot ship. MockGateway is cfg-gated, is a compile_error! in a
release build unless allow-mock-in-release is named explicitly, and
stamps every session id with MOCK-. CI asserts the release build fails.
5.5 No unsafe, no PIN. The crate is #![forbid(unsafe_code)] so the JNI
surface stays in robius-ussd. UssdGateway is !Send/!Sync by construction,
making the main-thread requirement a compile error. The adapter leaves
the pin field empty and a test asserts it.
5.6 The web claim is withdrawn. No browser API can drive USSD and a
Daraja credential must never reach a browser, so WebGateway refuses every
call and maps to Fatal — "never sent" — which owes no reconciliation.
7.5 Adversarial SMS corpus. StrictMpesaSms is the payment-boundary
reader, deliberately separate from nigig-core's permissive tracker parser
(ADR 0007 explains why this is not the duplication ADR 0002 forbids). It
requires an exact 10-char code, exact sender-ID match so MPESA-REFUNDS
and FAKE-MPESA are refused, rejects fractional shillings instead of
rounding, and caps body length. Corpus covers spoofing, forged code
shapes, out-of-range amounts, unicode and NUL injection, and replay. The
closing test asserts the honest limit: a well-crafted forgery is still
only evidence, because the output type has no settled state to reach.
## 5.2 is not done and is not closeable here
The AccessibilityService Play-policy review is a business decision. ADR
0007 records it as blocking, states the termination exposure, and names
what must happen before the rail is enabled. USSD dispatch stays behind
the default-off demo feature. If the review fails, ADR 0001's
tracker/launcher position applies and only the dispatch adapter is lost.
## Validation
platform: 56 tests, 64 with --features ussd, fmt, clippy -D warnings
(both feature sets), cargo-deny, mock-in-release guard
asserted to fail pass
domain: 75 tests --locked pass
storage: 36 + 41 tests --locked, incl. sqlcipher pass
nigig-pay, nigig-pay-ui: cargo check pass
cargo-deny reports advisories/bans/licenses/sources ok. The isolated
runner gained a `platform` target and it runs in CI on every push that
touches the crate.
|
||
|---|---|---|
| .. | ||
| src | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
nigig-pay-platform
The seam between the payment domain and the device.
nigig-pay-domain decides what a payment means. nigig-pay-storage decides
how it is remembered. Neither may know that the provider is driven by an
Android AccessibilityService typing digits into a USSD menu. This crate is
where that knowledge is allowed to live, and it is the only crate in the
payment stack permitted to name a platform SDK.
Design rationale and the unresolved AccessibilityService policy question are
in REVIEWS/adr/0007-payment-platform-boundary.md.
What it owns
| Module | Job | Review items |
|---|---|---|
correlation |
Single-flight session registry. Refuses out-of-order, duplicate and foreign callbacks. | 2.7, 5.3, 8.2 |
classify |
Fail-closed error classification driven by dispatch progress. | 2.8, 5.3, B3 |
events |
Typed platform events that cannot exist without a correlation id. | 5.3 |
sms |
Strict M-Pesa reader. Emits untrusted evidence, never settlement. | D1, D2, 0.4, 7.5 |
mock |
Test-only gateway that cannot compile into a release build. | 5.4, 0.5 |
web |
Honest refusal instead of a fake success path. | 5.6, U3 |
ussd |
Android adapter. The only module that names robius_ussd. |
5.1, 5.5 |
The rule that matters most
When a platform call fails there is one question: did the provider see it?
classify_failure(&failure, progress)
DispatchProgress is the authority, not the error kind. The same
TemporarilyUnavailable is safely retryable before the dial and ambiguous
once the menu is being driven, because a session that died after the transfer
was submitted may have moved money.
Adapters must round progress up when unsure. Over-reporting costs a reconciliation entry. Under-reporting costs a duplicate payment.
Features
| Feature | Default | Purpose |
|---|---|---|
ussd |
off | The real Android USSD adapter. Needs robius-ussd. |
mock |
off | Test doubles. Refuses to compile in a release build. |
allow-mock-in-release |
off | Deliberate escape hatch for an integration harness. Named explicitly so nobody enables it by accident. |
Testing
TEST_TARGET=platform ./tools/test-rust-clean.sh
The isolated runner installs the toolchain from rust-toolchain.toml, runs
--locked tests with and without the ussd feature, gates on
clippy -D warnings, asserts the mock gateway cannot be built into a release,
and deletes its temporary environment on any exit.
Invariants worth preserving
#![forbid(unsafe_code)]— the JNI surface stays inrobius-ussd.UssdGatewayis!Send/!Sync, so the main-thread requirement is a compile error rather than a comment.- The adapter never populates a PIN, and a test asserts it.
- Nothing here returns a settled
PaymentOutcomefrom scraped or SMS input. A confirmation may only come from an authorised provider channel viaPaymentCoordinator::record_provider_outcome.