93 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5d2d890f70 |
feat(nigig-site): enforce SITE-01 fail-closed containment
Some checks failed
nigig-build (CAD) / full-crate-check (push) Waiting to run
nigig-build (CAD) / doc-workspace-coverage (push) Waiting to run
nigig-build (CAD) / cad-widget-coverage (push) Waiting to run
nigig-build (CAD) / cad-engine-coverage (push) Waiting to run
nigig-map / test (push) Waiting to run
sms / android (push) Waiting to run
sms / nigig-sms (push) Waiting to run
sms / supply-chain (push) Waiting to run
sms / gates (push) Waiting to run
sms / robius-sms (push) Waiting to run
spreadsheet / engine-coverage (push) Waiting to run
spreadsheet / ui-controller-coverage (push) Waiting to run
traffic / gates (push) Waiting to run
traffic / nigig-traffic (push) Waiting to run
traffic / supply-chain (push) Waiting to run
nigig-site / Owned paths and honest test contracts (push) Has been cancelled
nigig-site / Cargo check-all-targets (push) Has been cancelled
nigig-site / Cargo clippy-site-owned (push) Has been cancelled
nigig-site / Cargo contained-media-export-fixtures (push) Has been cancelled
nigig-site / Cargo containment-storage-crypto (push) Has been cancelled
nigig-site / Cargo integration-non-live (push) Has been cancelled
nigig-site / Cargo production-dependency-containment (push) Has been cancelled
nigig-site / Cargo unit (push) Has been cancelled
nigig-site / Runtime UI (explicitly skipped until enabled) (push) Has been cancelled
nigig-site / Migration and recovery (explicitly skipped until enabled) (push) Has been cancelled
nigig-site / Media limits (explicitly skipped until enabled) (push) Has been cancelled
nigig-site / Real server interoperability (explicitly skipped until enabled) (push) Has been cancelled
nigig-site / Security and supply-chain baseline (push) Has been cancelled
nigig-site / Release capability gate (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
Contain production capabilities, remove the production sync surface, and keep legacy media/export/transport implementations test-only. Require authenticated existing-key storage with preservation-first recovery and sticky write disablement, backed by deterministic fault and concurrency tests plus dependency and workflow contracts. |
||
|
|
f49d8b16ac |
ci(nigig-site): establish truthful SITE-00 gates
Some checks failed
nigig-site / Owned paths and honest test contracts (push) Has been cancelled
nigig-site / Cargo check-all-targets (push) Has been cancelled
nigig-site / Cargo clippy-site-owned (push) Has been cancelled
nigig-site / Cargo integration-non-live (push) Has been cancelled
nigig-site / Cargo media-export-baseline (push) Has been cancelled
nigig-site / Cargo storage-crypto-baseline (push) Has been cancelled
nigig-site / Cargo unit (push) Has been cancelled
nigig-site / Runtime UI (explicitly skipped until enabled) (push) Has been cancelled
nigig-site / Migration and recovery (explicitly skipped until enabled) (push) Has been cancelled
nigig-site / Media limits (explicitly skipped until enabled) (push) Has been cancelled
nigig-site / Real server interoperability (explicitly skipped until enabled) (push) Has been cancelled
nigig-site / Security and supply-chain baseline (push) Has been cancelled
nigig-site / Release capability gate (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
|
||
| ac8f8aa002 |
chore: sync full working tree to gitdab
Some checks failed
Payment domain, storage, platform and UI / isolated-payment-tests (push) Waiting to run
Payment domain, storage, platform and UI / payment-ui-tests (push) Waiting to run
repo hygiene / hygiene (push) Has been cancelled
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (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
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
traffic / gates (push) Has been cancelled
traffic / nigig-traffic (push) Has been cancelled
traffic / supply-chain (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
doc-engine / coverage (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
email / gates (push) Has been cancelled
email / email-domain (push) Has been cancelled
email / nigig-email (push) Has been cancelled
email / supply-chain (push) Has been cancelled
nigig-map / test (push) Has been cancelled
sms / gates (push) Has been cancelled
sms / robius-sms (push) Has been cancelled
sms / android (push) Has been cancelled
sms / nigig-sms (push) Has been cancelled
sms / supply-chain (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
Whole-tree sync: cad-core/cad-ui split sources, nigig-build construction_frame migration, pdf port progress, mpesa/pay/uikit/doc updates, workspace members/profiles/lock, CI workflows and reviews. See individual file history for details. |
|||
| 3ef182414e |
feat(robius-notification): real macOS and Windows backends, type-checked against the actual frameworks
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
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. |
|||
| e16d1d1d44 |
feat(robius-notification): a zero-dependency D-Bus notifier, and p2p-intel's last limit closes on Linux
Some checks failed
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
repo hygiene / hygiene (push) Has been cancelled
sms / gates (push) Has been cancelled
sms / robius-sms (push) Has been cancelled
sms / android (push) Has been cancelled
sms / nigig-sms (push) Has been cancelled
sms / supply-chain (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
doc-engine / coverage (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
email / gates (push) Has been cancelled
email / email-domain (push) Has been cancelled
email / nigig-email (push) Has been cancelled
email / supply-chain (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
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
nigig-map / test (push) Has been cancelled
ADR 0035 left one of p2p-intel's four limits open: no OS notification backend, so alerts stopped when the window closed. The seam existed with one implementation that truthfully did nothing. This is the crate that fills it. Posting a notification on Linux is one D-Bus method call. Three ways to make it were measured rather than assumed: notify-rust with libdbus is twelve crates but a C library, which needs pkg-config and breaks the Android and iOS cross-compile this crate family keeps clean; notify-rust with zbus is pure Rust and 169 crates including an async executor; writing the wire format out is about 250 lines and nothing at all. robius-sms deleted polkit and gio for exactly this reason -- its E9 note records they were the sole source of two RUSTSEC advisories and an LGPL question for every consumer -- so pulling a 169-crate tree back into the same family for one method call would reverse that decision for a worse reason. The cost of hand-rolling is that the protocol has to be exactly right, and a mistake makes the daemon disconnect with no diagnostic. That cost was paid in tests: the suite starts a private dbus-daemon per test and talks to it. This matters more than it sounds. The marshaller and the parser were written from the same reading of the specification, so them agreeing with each other proves only that I was consistently wrong or consistently right; only a third party can say which. It found three bugs no unit test would have. The first is the one worth dwelling on. Every error reply parsed as success. The header-field walk assumed all fields were strings, but REPLY_SERIAL is a u32, and reading its four bytes as a string length desynchronised the cursor so ERROR_NAME was never reached. `post` returned Ok against a bus with no notification service running. That is precisely the bug this crate was written to eliminate -- a notifier that reports success and delivers nothing -- reintroduced by accident inside its own parser. I cannot think of a stronger argument for testing against something you did not write. Second, is_available() was true on a bare bus, because NameHasOwner *succeeds* and answers false in its body; checking only for an error reported a working notifier on a machine with no notification daemon. Third, replies were not correlated. The bus sends NameAcquired unprompted right after Hello, so "read the next message" consumed a signal and treated it as the answer. Replies are now matched on REPLY_SERIAL, and a single read carrying several messages is walked rather than truncated. The suite also serialises every test that mutates DBUS_SESSION_BUS_ADDRESS behind a mutex. The variable is process-wide and cargo runs tests in parallel threads; three consecutive parallel runs are now green. --test-threads=1 would have made the failures go away too, and would have hidden a real hazard from whoever reads the file next. On the four platforms, honestly. Linux is implemented and tested. Android is implemented and *compiles* -- cargo check and clippy both pass for aarch64-linux-android -- but has never run on a device, and the module says so in its first paragraph. It handles the two things Android drops silently, missing POST_NOTIFICATIONS on API 33+ and a missing channel on API 26+, because both are the same accepted-and-discarded failure this crate exists to remove. Apple and Windows are deliberately not written. Neither could be compiled here -- no macOS or Windows toolchain and no way to add one -- and objc2 message sends or WinRT calls that no compiler has ever seen are not an implementation. They are plausible-looking text that would sit in the same crate as tested code and be read as equally finished. Both return PermanentlyUnavailable with a reason naming ADR 0036, and their module docs record the call sequence so the next person starts from a design rather than a blank file. The support table says "written" and "verified" in separate columns for the same reason. p2p-intel's dashboard now uses SystemNotifications instead of UnavailableNotifications. The latter stays: on a platform with no backend it is still the truthful answer, and a test needs something that reliably cannot deliver. Alerts are tagged per fiat so a market replaces its own previous notification rather than stacking -- a 30-second poll would otherwise fill the shade, and a full shade is what makes someone turn notifications off for the app entirely, which costs more than the feature is worth. Two new tests pin the invariant that a sink must never report delivery it did not achieve. CI gains a notifications job that installs dbus and sets ROBIUS_NOTIFICATION_REQUIRE_DBUS=1. The bus-backed tests skip when dbus-daemon is absent so the suite stays green on a bare machine, but a silent skip in CI would mean the integration tests quietly stopped running while the build stayed green. I verified the guard fails by hiding dbus-daemon behind a stub that exits 127. 50 tests in the new crate, 225 in p2p-intel, clippy clean on host and Android, and the app still starts under Xvfb. |
|||
| 8bf62e2644 |
feat(p2p-intel): the exchange tab becomes spread intelligence, with rails, drift capture, and three of four limits closed
Some checks failed
doc-engine / engine (push) Has been cancelled
doc-engine / coverage (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
email / gates (push) Has been cancelled
email / email-domain (push) Has been cancelled
email / nigig-email (push) Has been cancelled
email / supply-chain (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
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
nigig-map / test (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
sms / gates (push) Has been cancelled
sms / robius-sms (push) Has been cancelled
sms / android (push) Has been cancelled
sms / nigig-sms (push) Has been cancelled
sms / supply-chain (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
p2p-intel / engine (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
The exchange tab in nigig-mpesa and nigig-pay was a swap mock-up -- Sell/Buy
cards showing hardcoded ETH and AAVE at $38,409.24, wired to nothing -- above
six rate labels, each built from the *first* cached advert for its exchange:
Binance P2P: USDT BUY @ 129.92 KES min 1000 max 200000 via BANK
That is a price, not an opportunity. It never compared the two sides of the
book, said nothing about whether the counterparty could be dealt with, and
ignored what it costs to move the money. Against the live KES book it would
have shown a 134.60 advert from a merchant with three completed trades.
Both pages now run the p2p-intel analyzer over the adverts the app already
caches. No new endpoint, no new traffic, api.rs untouched -- the data was
always there, nothing was being asked of it. The two files are
byte-identical and CI now fails if they drift apart, which they already had
once: data.rs differs between the apps by 188 lines of tests one copy has
and the other does not.
**Rails, because a spread alone is a lie.** 29 bps on a 10,000 KES trade is
29 KES of gross margin; M-Pesa Send Money costs 55 KES a leg, 110 round
trip. The trade is deeply negative and the spread says nothing about it.
rail.rs prices every route and ranks by what survives. M-Pesa fees come from
the published Safaricom bands in robius_ussd::mpesa_bands -- real tariff
rows, not estimates, and flat rather than percentage, which is exactly why
the same spread is ruinous at 500 KES and fine at 200,000. Both legs are
charged. An amount outside the tariff reports OutOfRange and an
unconfigured bank reports Unknown; neither reports zero, because zero is a
claim and it is the wrong one. Bank tariffs ship unconfigured for the same
reason: every bank differs and there is no table to default to.
**API drift capture.** These endpoints are internal and undocumented. When
one changes the symptom is an empty panel -- indistinguishable from a quiet
market -- and the response that broke it is gone by the time anyone looks.
Every parse failure is now recorded with the payload excerpt that caused it
and copyable as a plain-text report. Deduplicated, because a 30-second poll
against a changed endpoint fails 120 times an hour and 120 identical rows is
a log nobody reads; the excerpt is excluded from the equality check, since
two responses differing only in advert ids are the same drift. The exported
header states that it carries response excerpts only and never a request,
credential or account number -- the type can only be constructed from a
response body, so that is enforced rather than promised. Cutting the excerpt
is done on character boundaries: Binance really returns names like
BennyBoss and a byte slice would panic mid-character.
The repo's other clipboard code (nigig-build's crdt_widget.rs) answers a
Hit::TextCopy, which is the query-driven path the platform uses for Ctrl+C
on a focused widget. Right for a text editor, wrong for a button exporting a
report the user never selected, so this uses cx.copy_to_clipboard and
confirms in the UI -- a copy button with no feedback is one people press
three times.
On the four limits, the honest scoreboard is two closed, one usefully
sidestepped, one open. ADR 0035 records why, because rounding all four up to
"addressed" would have been the easy write-up and the wrong one.
The poll timer and the host clock are closed outright. cx.start_interval
delivers the tick as an ordinary UI-thread event, so there is no runtime and
nothing to join at shutdown, and the interval reads through
effective_poll_seconds so the fifteen-second floor still applies -- a config
file cannot be used to hammer a rate-limited endpoint, which a test pins by
name. Staleness is now shown, because a price from four minutes ago is not a
price. One ambiguity is recorded rather than hidden: last_scan_ms == 0 is the
"never scanned" sentinel, so a scan whose timestamp genuinely is 0 reads as
never. That only happens when the host clock is broken, which is exactly
when the UI should not claim the data is current.
The headless-backend limit is sidestepped, and the distinction matters
enough to write down. Xvfb is a real X11 server that draws into memory, so
the app gets the display it insists on and tools/test-p2p-app-smoke.sh runs
the real binary end to end. That earned its place immediately: it caught
`Row = <View> { ... }`, which is not valid in this fork's script language
and which cargo build is entirely silent about, because script_mod! is
parsed at *runtime* -- a broken widget tree compiles perfectly and then
renders nothing. The gate greps for [E] in the log for that reason; the
process exits 0. I verified it fails by reintroducing the bad syntax. What
it still cannot do is drive widgets: makepad_test's Selector::id(..).click()
needs the harness to own the event loop, so the six #[ignore]d interaction
tests stay ignored.
OS notifications stay open. There is no robius-notification crate beside
robius-sms and robius-ussd, and Makepad exposes none on any target; building
one means NotificationCompat on Android, UNUserNotification on iOS and a
D-Bus call on Linux. What shipped is the seam plus one implementation named
UnavailableNotifications -- named for what it is, because a DefaultNotifier
that silently discarded every alert would read like a working feature at
every call site. is_delivering() returns false and the UI shows the reason,
so the user is told that alerts stop with the window instead of assuming
they are covered. A no-op reporting success is the exact "declared versus
delivered" failure ADR 0017 exists to prevent.
223 tests, coverage 97.03% with fifteen per-file floors -- seven of them new
and all measured, after I noticed an earlier edit had silently failed to
apply and the new files were being counted in the total but gated by
nothing. CI gains the Xvfb smoke job and an exchange-tab job that builds
both host apps and diffs their pages.
|
|||
| fefde1ecca |
refactor(p2p-intel): Makepad's HTTP stack, in-app alerts, and micro_serde -- serde and reqwest are gone
Three changes that turn out to be one change: the app now uses the platform's own facilities instead of carrying its own. Networking moves to Cx::http_request, alerting moves into the UI, and deserialisation moves to makepad_micro_serde. The default dependency graph drops from serde + serde_derive + serde_json + toml + reqwest + rustls + tokio + hyper to **twenty-five crates total**, none of which is any of those. Networking follows nigig-mpesa/src/pages/exchange/api.rs: build an HttpRequest, hand it to Cx::http_request keyed by a LiveId, match the reply in handle_network_responses. There is no HTTP client, no TLS stack and no async runtime in this workspace any more. That is not only leanness -- on Android and iOS the platform stack is the only one that works without shipping a second TLS implementation, so the `live` feature that gated reqwest has been deleted rather than made default. Correlating replies is where the real trap was. A scan of five markets puts ten requests in flight and the replies come back in whatever order the network gives them, so the LiveId has to say which market and which side. RequestKey encodes both plus a generation counter and round-trips through a u64 with the high bit set, so a LiveId Makepad derived from a name is never decoded as a scan reply, and a late reply from a previous round is dropped instead of folded into fresh data. Six tests cover the codec, including the one that matters most: the two sides of one market must not share an id, or the second reply overwrites the first and every spread is measured against itself. The transport allowlist is lifted from the nigig-mpesa review, which reached the same conclusion I would have: Makepad exposes no certificate pinning -- its only TLS control is set_ignore_ssl_cert, which weakens verification -- so what is enforceable at this layer is that only HTTPS to p2p.binance.com can be dialled at all. The tests cover the two ways a naive check leaks: the lookalike host p2p.binance.com.evil.example, which passes any starts_with test, and the userinfo smuggle https://p2p.binance.com@evil.example/, which resolves to evil.example while reading as Binance. Telegram is gone, as asked, and the app alerts itself: a banner, an unread badge on the status line, an Alerts tab holding the history, and a chime. Removing it removes a bot token from the threat model entirely -- a token in a config file is a bot anyone who reads the file can drive -- and removes a second network dependency from a tool already gated on one endpoint. One test now records the *absence* of a bug rather than its fix: Twin_traders00 is a real merchant from the live capture, and its underscore previously had to be escaped or Telegram rejected the whole message with a 400 and delivered nothing. Rendering in our own UI deletes that failure mode, and the test asserts the name appears unescaped. The trade-off is stated in the README rather than glossed: an in-app alert only reaches you while the app is running. No OS notification is raised, so a minimised window is a missed alert. The chime is synthesised rather than bundled -- a two-note rising blip generated at the device's sample rate, which is a few dozen lines instead of an audio asset shipped on three platforms, and which can therefore be tested. It is, and the tests found the bugs you would expect from writing audio: a freshly rendered chime starts *finished* so opening the output does not announce itself at startup, both note edges fade so neither clicks, the tail pads with silence rather than replaying whatever the buffer last held, and a nonsense sample rate falls back instead of panicking. Rising rather than falling because a falling interval reads as a dismissal and this is an invitation to act. The micro_serde migration surfaced two behaviours that differ from serde and both bit before they were understood. **micro_serde is strict by default.** deserialize_json errors on the first key it does not model. Binance sends about forty fields per advert and we model eight, so the strict parser cannot read the response at all -- and for a config file it means an older build cannot open a file written by a newer one. Everything uses deserialize_json_lenient, and a test pins that the strict call *would* have failed, because the two differ by one word and the strict one looks more correct. **There is no #[serde(default)].** Optional config entries are modelled as Option<T> on a Raw* struct and resolved into the real Config by hand. A few more lines in exchange for two fewer dependency trees. config.toml became config.json for the same arithmetic: micro_serde has no TOML reader, and toml depends on serde, so a single config file would have dragged all of serde back in through the back door. Also worth recording: DeJsonErr implements Debug but not Display, and Debug is the variant carrying line and column, so every error path formats it with {e:?} deliberately rather than by accident. CI gains a check that fails the build if reqwest, tokio, rustls, hyper, serde, serde_derive, serde_json or toml reappears in the default graph. I verified it fails by adding serde back to p2p-core and watching it match serde_derive, then reverted. A gate that cannot fail is decoration. 136 tests in the default feature set and 36 more with --features ui, including the RequestKey codec, which cannot be measured by the coverage job because it lives behind the feature that needs Makepad. Coverage 96.92% with ten per-file floors, up from eight -- chime.rs at 100% and client.rs at 95.69% are new. The desktop binary was built and linked to prove the app half is real. |
|||
| eaeebd3910 |
feat(p2p-intel): Binance P2P spread intelligence, shaped by what the live book actually contains
Some checks failed
p2p-intel / engine (push) Has been cancelled
p2p-intel / coverage (push) Has been cancelled
p2p-intel / makepad-app (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
doc-engine / coverage (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
email / gates (push) Has been cancelled
email / email-domain (push) Has been cancelled
email / nigig-email (push) Has been cancelled
email / supply-chain (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
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
nigig-map / test (push) Has been cancelled
sms / gates (push) Has been cancelled
sms / robius-sms (push) Has been cancelled
sms / android (push) Has been cancelled
sms / nigig-sms (push) Has been cancelled
sms / supply-chain (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
A new nested workspace under crates/apps/p2p-intel: six engine crates, a CLI, and a Makepad dashboard that builds for desktop, Android and iOS. It reads public P2P adverts, measures the spread that is actually fillable, alerts when one is worth acting on, and tracks what the float really cost. It never places an order. Binance publishes no P2P trading API, and automating an escrow release is how a merchant loses their float to chargeback fraud. This is the intelligence layer; execution stays manual. The design came from a live capture rather than a sketch, and the capture contradicted the sketch three times. All three are now pinned by tests against checked-in real payloads. **The best price is routinely the least fillable one.** In the KES book the top sell advert was 134.60 from a merchant with three completed trades, implying a 3.6% spread; the next was 130.30. Another advert showed a 0% completion rate. A best-price scan with no quality floor does not find opportunities, it finds outliers, and outliers on a P2P book are bait or a merchant about to run dry. QualityFilter defaults to 95% completion and 50 orders, and analyse() reports every exclusion with its reason rather than dropping it silently. The honest consequence is recorded in the integration suite: at those defaults **not one sell-side advert in the captured KES book qualified**. There was no fillable arbitrage. A tool that reported the raw best-price number would have sent its user after a trade that does not exist, so the test asserts best_sell is None and net_bps is None rather than asserting a comfortable number. **tradeType is inverted between request and response.** Asking the endpoint for tradeType "BUY" returns adverts whose own adv.tradeType reads "SELL". Both are correct: the request parameter is what you want to do, the response field is what the advertiser is doing. Conflating them inverts every spread and the result still looks plausible, which makes it the most expensive mistake available here. Side keeps the two apart with request_trade_type()/advert_trade_type(), and a test asserts they are never equal. **An empty market answers HTTP 200 with success: true.** NGN returned zero adverts. "No ads" and "no answer" need opposite responses, so is_empty_market() is a named predicate and ScanError separates Malformed (Binance changed the payload; retrying makes it worse) from Network (transient). basis_points_above returns None against a zero base rather than an infinity, so an empty book cannot read as an infinite opportunity at 3am. Money is never a float, following the rule in nigig-pay-domain/src/money.rs. IEEE 754 cannot represent 0.1 and a spread is a difference of two nearly equal numbers, which is exactly where binary floating point loses the digits that matter. Binance sends prices as decimal strings, so Price parses them straight into scaled i128 integers and never passes through f64. i128 rather than i64 because the intermediate in a bps calculation overflows, not the result. Excess precision is refused rather than rounded and a thousands separator is refused rather than dropped: "1,299.92" read as 129992 is a 1000x error that still looks like a price. Tests pin 0.1 + 0.2 == 0.3 and rotate 100 round trips at one price asserting exactly zero P&L. Alerting is mostly restraint. At a 30-second poll one wide spread would fire 120 identical messages an hour, and a channel that cries wolf gets muted, at which point the tool has negative value because the user believes they are covered. AlertGate suppresses repeats inside a cooldown and re-alerts early only when the spread improves materially -- a collapsing spread is not worth waking someone for. Telegram MarkdownV2 escaping is tested against a real merchant name from the capture, Twin_traders00, whose underscore would otherwise make Telegram reject the message with a 400 and deliver nothing. Writing the dashboard view model found a bug in my own comparator: sorting descending by swapping the tuple to (b, a) also silently swaps the meaning of the None arms, which put dead markets at the top of the opportunity list. The test that caught it was written first and named for the behaviour, not the implementation. Networking is behind a non-default `live` feature, so an ordinary cargo test cannot make a request and CI never depends on Binance being reachable. A CI step asserts reqwest is absent from the default dependency graph so this cannot regress quietly. A live scan was run once to confirm the fixtures match reality; it reported a negative spread for KES and an empty NGN book, which is the tool working correctly. Conventions follow the repo rather than the generic layout in the request: .forgejo/workflows/p2p-intel.yml rather than .github, and no Dockerfile, since the stack is pure Rust and nothing else here is containerised. error_set is used instead of anyhow, matching nigig-core. The root Cargo.toml excludes the nested workspace by name, as it already does for makepad_table, so the isolation is intentional rather than dependent on a table inside someone else's manifest. 106 tests, coverage 96.86% with per-file floors enforced by tools/test-p2p-coverage.sh. Both the total and per-file gates were verified to actually fail by running them with impossible floors; a gate that cannot fail is decoration. Two files are excluded and only because they were first emptied of decisions: the Makepad widget, which needs a GPU and a windowing backend this repo has no headless backend for, and the CLI main, which is argument parsing and println. Every rule the widget renders lives in view_model.rs, measured at 97%. That split is deliberate -- spreadsheet-ui/grid.rs once hid 36 pure functions behind a file-level exclusion, and excluding a file you have not emptied of logic is how that happens. The Makepad desktop binary was built and linked in the sandbox to prove the app half is real and not just a compiling stub. |
|||
| a5ba719d8d |
perf(cad): add measured sub-pixel LOD to the 2D path -- Phase 5
Some checks failed
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
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
Phase 4 reduced the number of tessellation calls, but it left the geometry volume unchanged: a zoomed-out 2,000-part plan still queued a full outline for every visible part. At the measured 200 m site view a 1 m part is only about 2.7 logical pixels across, so the remaining geometry was not resolvable detail. Add a pure lod policy that projects plane extents into logical pixels, keeps selected and hovered parts full detail, and conservatively falls back to a full outline for malformed state. The renderer and CadViewport::frame_budget call the same policy. Ordinary sub-pixel parts use one bounded 2x2 filled marker, while FrameBudget reports full outlines, markers, strokes and fills separately. Also fold the remaining 2D 1.2 margins into render_budget::VIEW_MARGIN, extend the structural benchmark and coverage harness, correct the phase documentation, and explicitly leave 3D mesh LOD deferred until a real GPU/window measurement justifies a second geometry policy. |
|||
| a2b05c56c9 |
feat(makepad-table): opt-in capabilities feature, and raise the matrix_client defect
The two caveats from the dependency investigation. ## The capabilities feature Camera and location attachments are now available behind `features = ["capabilities"]`, which pulls `nigig-uikit` and supplies `UikitAttachmentProvider`. Measured: 89 crates by default, 275 with the feature on. That cost is real and it is inherent, not packaging waste. `camera_widget` imports `send_geocode_request` and `request_map_tile` from `nigig-core`, both of which call `spawn_async` — the shared Tokio runtime — and the first makes an HTTPS call to Nominatim. A camera that geocodes needs an async runtime and an HTTP client; there is no lighter honest version. It is affordable because it is opt-in, and because any app enabling it already depends on `nigig-core`, so that app's own tree grows by nothing. Everything touching `nigig-uikit` is in one module, so the boundary is a file rather than `#[cfg]` scattered through the widget. The provider holds no widgets of its own: the host owns the `CameraWidget` already in its tree and this asks it to open, because a provider that instantiated a second camera would fight the first for the device. A second request while one is outstanding is refused rather than overwriting. The table turns that refusal into `AttachmentUnavailable`, so the user is told the camera is busy instead of watching their first request vanish. File picking is deliberately declined here — `robius-file-picker` already ships unconditionally and costs nothing, and two paths for one job is one too many. Two CI gates, both verified to fail when they should: the opt-in build must keep compiling, and the default build must pull none of `tokio`, `reqwest`, `hyper`, `clap`, `csv`, `image`, `nigig-uikit` or `nigig-core`. The second checks the resolved `cargo tree` rather than the manifest, because feature unification can switch an optional dependency on from a sibling crate. Tests 99 default, 105 with the feature. Both clippy-clean. ## The matrix_client defect Raised in REVIEWS/MATRIX_CLIENT_FEATURE_GATE.md rather than fixed. It is not my crate, nothing depends on the broken combination, and a blind fix could change behaviour someone relies on. `matrix_client` declares `native = ["dep:tokio", "dep:reqwest", "dep:rusqlite"]` but its source gates on `#[cfg(not(target_arch = "wasm32"))]`. Two switches for the same modules, so on a native target with the feature off the modules compile and their dependencies do not — 19 errors, 26 ungated uses across 7 files. There is no CI job for the crate, which is why it rotted unnoticed. The note corrects an overstatement I made while arguing for the trait hook. I said fixing this would unblock wasm. It would not: `matrix_client` already builds clean for wasm32 with `--no-default-features`, and `nigig-core` has 8 wasm errors of its own (`crate::platform::spawn` missing) that have nothing to do with it. The only broken combination is native-target-with-feature-off, which nothing builds. I also said earlier that `matrix_client` was heavy — it is a 7-dependency local crate, not matrix-sdk. That was wrong and it inflated the case for the trait hook; the note records the measured numbers instead. |
|||
| 9989043a37 |
ci: gate the coverage that was already measured and unenforced (Phase 0)
Some checks failed
repo hygiene / hygiene (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
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
Phase 0 of REVIEWS/REPO_COVERAGE_100_PLAN.md, and the reason it is Phase 0: no new tests, no new measurement, just ratchets on numbers that were already good and already decaying-capable. **spreadsheet** — `tools/test-spreadsheet-coverage.sh` has had a 96 floor for the engine and another for the UI controllers, and no CI job has ever run it. New `.forgejo/workflows/spreadsheet.yml`, two jobs: engine-coverage 98.83% of lines (floor 96) ui-controller-coverage 98.85% of lines (floor 96) Split in two because the halves cost very differently. The engine is pure Rust and finishes in about three minutes; the UI half has to build Makepad's Linux backend to link a test binary. One job would hide an engine regression behind a ten-minute build. **CAD widget layer** — `cad-widget-coverage` in nigig-build.yml, deliberately REPORT-ONLY. It sits at 13.25% of 10,637 lines with six files at exactly zero, and a floor there would read as a blessing rather than a debt. What the job buys is that the number is printed on every push instead of being rediscovered in six months. The first real input test should set a floor behind it. Also corrects the plan. It claimed the doc workspace module was ungated; it is not — nigig-build.yml has run doc-workspace-coverage since before the plan was written. I had surveyed by grepping workflow files for the word "coverage" and attributed nigig-build's coverage jobs to CAD alone. I nearly committed a duplicate workflow on the strength of it. The census table was right; the prose under it was not, and the correction is in the file. One thing checked and deliberately NOT changed: the spreadsheet script appears to skip its UI half when the native packages are absent. It does not. `makepad-native-libs.sh --check` returns 1, the script runs under `set -e`, and it aborts. What misled me was reading `$?` after piping the script into `tail` — which reports tail's status, not the script's. The same class of mistake this repository's CI comments warn about; no fix was needed and none was made. Verified by running each job's exact command line: COVERAGE_TARGET=engine ./tools/test-spreadsheet-coverage.sh rc=0 COVERAGE_TARGET=ui ./tools/test-spreadsheet-coverage.sh floors met ./tools/test-cad-widget-coverage.sh 13.25%, rc=0 |
|||
| 3928063392 |
ci(email): raise the domain floor; record the finance-email path
Some checks failed
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
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
email / gates (push) Has been cancelled
email / email-domain (push) Has been cancelled
email / nigig-email (push) Has been cancelled
email / supply-chain (push) Has been cancelled
email.yml: FLOOR 225 -> 230. The review doc records the email-to-finance sharing and notes the chat/Matrix path remains unbuilt (matrix_client has login+sync only). |
|||
|
|
6e784fffee |
fix(ci): the sample_thread gate was inverted under pipefail
The 'Development sample data must not reach the UI' gate ran count=$(grep ... | wc -l) under set -euo pipefail. When sample_thread is correctly ABSENT from the UI, grep returns exit 1 (no matches), pipefail propagates it, and set -e kills the script -- so the gate reported FAIL in the GOOD state and would have passed in the BAD state. Add || true so a zero-match result is counted as 0 and the gate passes, as intended. Verified: all 11 gates now pass, and sample_thread is confirmed gone from the UI crate (only in email_store.rs, definition + tests). |
||
|
|
632479c964 |
fix(ci): email.yml has been invalid YAML for six commits
`python3 -c "yaml.safe_load(open('.forgejo/workflows/email.yml'))"` fails:
mapping values are not allowed here
in ".forgejo/workflows/email.yml", line 455, column 35
A workflow that does not parse does not fail -- it does not RUN. So every
gate in this file has been silently absent: the S2 password checks, the
multi-recipient regression check, the TLS check, the coverage floors. All
of them. The file has looked like protection while providing none.
Cause: the "Coverage floors" step was rewritten to call
tools/test-email-coverage.sh, and ten lines of the previous inline
implementation were left behind underneath the new `run:` scalar. YAML
reads the first `echo "$out" | grep -E '^test result:'` as a new mapping
key and gives up.
Broken by
|
||
| 6bf138d027 |
ci(email): cover the trip-report modules
Some checks failed
email.yml / ci(email): cover the trip-report modules (push) Failing after 0s
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
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
doc-engine / engine (push) Successful in 18s
doc-engine / coverage (push) Successful in 30s
doc-engine / consumer (push) Successful in 4m58s
nigig-map / test (push) Failing after 2m18s
sms / gates (push) Successful in 3s
sms / robius-sms (push) Failing after 11m46s
sms / android (push) Successful in 1m48s
sms / nigig-sms (push) Successful in 5m42s
sms / supply-chain (push) Successful in 7s
The domain test filter and floor (225) now include finance_report and email_receipts, and test-email-coverage.sh instruments both new files. Domain tests 216 -> 234; coverage 90.6% over 15 files. The review doc records the new feature. |
|||
|
|
b478945c34 |
ci(doc): gate the doc-workspace coverage floor on every push
Some checks failed
email.yml / ci(doc): gate the doc-workspace coverage floor on every push (push) Failing after 0s
repo hygiene / hygiene (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
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
Adds the doc-workspace-coverage job to the nigig-build workflow, mirroring the CAD gate: checkout, then ./tools/test-doc-workspace-coverage.sh, which installs its own instrumented toolchain into a shell-trap-cleaned temp dir and fails if the total floor (92%) or any per-file floor is not met. The script joins the workflow's push/PR path filters next to tools/test-cad-coverage.sh so edits to the harness itself re-run the gate. The doc README gains the milestone section recording the 28.55% -> 96.76% line measurement, the honest exclusions (widget layer, persistence write-path wrappers, defensive traversal guards) and the behavior pins and defect fixes the drive surfaced. |
||
| b83e7122c4 |
feat(makepad-table): file picker, search, recents, New/Delete (Invoicer UI Phase 3)
Some checks failed
email.yml / feat(makepad-table): file picker, search, recents, New/Delete (Invoicer UI Phase 3) (push) Failing after 0s
repo hygiene / hygiene (push) Has been cancelled
makepad-table / model (push) Has been cancelled
makepad-table / widget (push) Has been cancelled
makepad-table / hygiene (push) Has been cancelled
The last open phase. The sidebar gains a search box, a filtered document list and a recents list; the toolbar gains New Invoice/Quote/Receipt, Open, Save As and Delete. **The file picker is robius, not makepad's.** Makepad has an `open_system_openfile_dialog`, and it is implemented on macOS only — the Linux and Android backends never handle `CxOsOp::SelectFileDialog`, so the op is queued and dropped. It compiles, it runs, the dialog never appears. That is the worst kind of broken, so this uses `robius-file-picker`, the same crate `nigig-build`, `nigig-pay-ui` and `nigig-sms` already depend on at the same pinned revision, which goes through `rfd` on desktop and the platform picker on Android. CI gates against the macOS-only call returning. The picker's callback runs off the UI thread with no `Cx`, so it parks its outcome in a mutex and signals; `drain_file_picker` applies it on the next `Event::Signal`. Same shape as the SMS bulk CSV import. Model additions, in `makepad-doc-model` so they are testable without a window: `DocKind` with `blank()` constructors, `DocumentLibrary::create`, `remove`, and `selection_after_remove`. Decisions worth naming, because each has a wrong answer that looks fine: - **A new document is empty**, not seeded from the samples. A blank invoice arriving with "Acme Studio LLC" on it invites someone to export it without noticing whose name is there. `issue_date` is blank too — there is no clock in that crate and a guessed date is worse than none. - **Generated numbers cannot collide**, including with documents loaded from disk, and they reuse gaps left by deletions. The number becomes the filename: two documents called INV-1 save over each other and one is lost silently. - **Delete removes the row, not the file.** Removing an entry from a list is not consent to delete a document off disk, and there is no undo here. The status line says the file is untouched. - **Save reports "Choose where to save…", not "Saved."** The dialog being open is not the file being written. - **Search filters on every keystroke**, unlike the header fields, which commit on Return. Every prefix of a query is a valid narrower search; there is no such thing as a half-typed one. - **Searching does not move the selection.** Filtering is a view change, and switching the open document because a letter was typed loses the user's place. - **`selection_after_remove` is separate and exhaustively tested.** Deleting before the selection shifts it, deleting the selection keeps the index unless it was last, deleting after it changes nothing, and emptying the library selects nothing. Every wrong answer silently shows a different document; one of them indexes out of range. The document list is a fixed pool of 12 button slots rather than a `PortalList`, because this app opens documents one at a time. The pool is honest about its limit: anything past it renders as "+n more — narrow the search to reach them" rather than being dropped. Tests 79 -> 90. Six of them are the invoicer's first: `App` derives `Script` and cannot be built outside a live `Cx`, so the sidebar's presentation logic was extracted into four pure functions and tested there. Verified by reintroducing six defects across the two crates — silent overflow, a selection marker that shifts the indent, whitespace counting as a search, colliding numbers, a selection that ignores the shift, and a `blank()` that pre-fills. Also fixed, all pre-existing and all now blocking the `-D warnings` gate that has been running on these crates since the workflow was added: `std::io::Error::new(ErrorKind::Other, _)` in two crates, a manual `RangeInclusive::contains`, a manual `is_multiple_of`, a single-arm `match`, and a duplicated `#[test]` attribute that was annotating one function twice — which is why the count reads 36 rather than 37 here; no test was lost. The sample data keeps its `12_000_00` money literals, where the last group is the minor units and the number reads as "12,000.00" at a glance. `inconsistent_digit_grouping` is allowed at the crate root with that reasoning, rather than regrouping every amount into thousands and making each one need arithmetic to check against its comment. |
|||
| 189377a3a3 |
ci(email): build the wasm path; document the closed §8 gaps
Some checks failed
email.yml / ci(email): build the wasm path; document the closed §8 gaps (push) Failing after 0s
repo hygiene / hygiene (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
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
doc-engine / coverage (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
nigig-map / test (push) Has been cancelled
sms / gates (push) Has been cancelled
sms / robius-sms (push) Has been cancelled
sms / android (push) Has been cancelled
sms / nigig-sms (push) Has been cancelled
sms / supply-chain (push) Has been cancelled
email.yml: install the wasm32-unknown-unknown target and check the email domain's credential-bearing wasm half (call_email_api, WasmFetchTransport, set_email_api_url) so a browser-only breakage cannot reach main unseen. The domain test floor ratchets 205 -> 210. The review doc's §8 is rewritten: the TLS handshake, the wasm build, B1 and the test/clippy baselines are now executed/measured; the only entries left are the ones that genuinely cannot run in CI (a live relay's cert, a browser's fetch), stated with their exact reasons. |
|||
| 89ca5186c6 |
docs(pdf): Phase 4 is not 100% — audit it, and verify the half that is
Some checks failed
email.yml / docs(pdf): Phase 4 is not 100% — audit it, and verify the half that is (push) Failing after 0s
repo hygiene / hygiene (push) Has been cancelled
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
Asked whether Phase 4 was complete, I checked the tree instead of my own
commit message, and the commit message was wrong.
Three items named in the Phase 4 spec are **not** implemented, and the
status line said "complete" over them:
- **Field-value reconciliation** (`form_reconcile_test.dart`). Setting a
value writes /V, marks the field dirty and regenerates /AP — that all
works. What is missing is the reconciliation case: a file opened with
/V and /AP *already disagreeing*, where the right answer depends on
/NeedAppearances. Nothing decides that today.
- **Type1/CFF embedding.** The spec hedges with "if feasible", so this is
a legitimate deferral rather than an oversight — but "complete" did not
say so. `sfnt.rs` detects CFF outlines and `font.rs` reads an existing
/FontFile3; nothing writes one. Creation is TrueType-only.
- **`repair-cmap`.** No equivalent exists.
`text_box_appearance_test.dart` *is* covered, by appearance.rs:235 — it
just does not carry that filename, which is why a grep for the dart test
names is a starting point and not an answer.
The other half of the exit criterion — "generated PDFs open cleanly in
external viewers" — had never been checked at all. The sample generator's
own doc comment admits no test in this repository can assert it. So I
ran it through implementations we share no code with, and **it passes**:
qpdf --check no syntax or stream encoding errors
pdfinfo title, author, subject, keywords, 2 pages,
Form: AcroForm
pdftotext all text, including the embedded DejaVu subset
and its em-dash
qpdf --list-attachments readme.txt, extracted by name with description
catalogue /Outlines /Names /EmbeddedFiles /PageLabels
/Dests /PageMode /ViewerPreferences /AcroForm
`tools/check-pdf-external-readers.sh` makes that repeatable, and pdf.yml
runs it. It treats a qpdf *warning* as failure, not just an error: qpdf
warns where it had to reconstruct, and reconstructing is exactly what a
stricter viewer will refuse to do. Negative-tested twice — removing the
attachment fails 3 checks, and corrupting the startxref offset makes
qpdf report "file is damaged".
Two defects that audit found:
- **The sample never exercised XMP**, so the Phase 4 feature most likely
to be silently missing was also the one nothing looked at. Probed
separately: `set_xmp_metadata` works, pdfinfo reports
`Metadata Stream: yes`.
- **A `Banner` naming an unregistered font produces a structurally valid
PDF that renders no text.** qpdf --check passes; poppler says
`Unknown font tag 'F1'` and draws nothing. `stamp.rs` cannot register
the font itself — fonts belong to the document, and a banner does not
know which document it will be drawn into — so this is now documented
on `Banner` with a worked example, and pinned by
`a_banner_font_must_be_registered_or_the_page_lacks_the_resource`,
which asserts on the page's /Font resources because that is the thing
actually missing and the thing a caller can check.
The plan now records that it was wrong once, rather than quietly
correcting itself. A status line that has been overstated should show its
working.
Engine suite 952 -> 953. Phase 4's engine half is verified end to end
against third-party readers; the ui.rs interaction half is written and
still blocked on the Makepad headless backend.
|
|||
| fc0b1f287f |
ci(email): run the conversation-kit tests; mark Phase E complete
Some checks failed
email.yml / ci(email): run the conversation-kit tests; mark Phase E complete (push) Failing after 0s
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
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
sms / robius-sms (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
doc-engine / coverage (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
nigig-map / test (push) Has been cancelled
sms / gates (push) Has been cancelled
sms / android (push) Has been cancelled
sms / nigig-sms (push) Has been cancelled
sms / supply-chain (push) Has been cancelled
email.yml: the domain test floor ratchets 190 -> 205, and the nigig-email job runs cargo test -p nigig-uikit --lib -- conversation so an email-driven regression in the shared kit cannot silently surface in SMS. The review doc marks E1-E6 done and records the honest correction E5 surfaced: lettre's timeout bounds only the TCP connect, not the greeting/command reads — the send path now bounds the whole operation. |
|||
|
|
228bc2c81f |
ci(doc-engine): gate the engine coverage, and note it in the doc README
Some checks failed
email.yml / ci(doc-engine): gate the engine coverage, and note it in the doc README (push) Failing after 0s
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
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
doc-engine / coverage (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
New coverage job runs tools/test-doc-engine-coverage.sh on changes to crates/apps/doc/**, the script itself, or the workflow. A coverage number nobody gates goes down; the floors (total plus per-file) are the enforcement. The doc workspace README records the milestone and the two CRDT-tolerance behaviors the new tests pin. |
||
| 2a74c6cac4 |
ci(email): gate the keystore feature, cover email_bulk
Some checks failed
email.yml / ci(email): gate the keystore feature, cover email_bulk (push) Failing after 0s
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
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
nigig-map / test (push) Has been cancelled
sms / gates (push) Has been cancelled
sms / robius-sms (push) Has been cancelled
sms / android (push) Has been cancelled
sms / nigig-sms (push) Has been cancelled
sms / supply-chain (push) Has been cancelled
email.yml: the feature-compile check now covers imap,keystore together. test-email-coverage.sh instruments email_bulk.rs (91.9% line) alongside the rest of the domain; total 89.84%, floors enforced. The review doc records C6/C7/C1f as fully closed, with the honest caveats unchanged (network sockets and the OS vault are compile-checked, not runtime-verified). |
|||
| ab17c72c55 |
feat(makepad-table): drag-reorder columns, and the first tests this crate has
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
email.yml / feat(makepad-table): drag-reorder columns, and the first tests this crate has (push) Failing after 0s
makepad-table / model (push) Has been cancelled
makepad-table / widget (push) Has been cancelled
makepad-table / hygiene (push) Has been cancelled
Phase 4 of the README's table, plus the test infrastructure Phases 1-3 never
had. The crate had zero tests before this; it now has 21.
Drag-reorder:
- A press on a column header no longer commits to an action. It arms a drag
and resolves on release: travel more than 8px and it reorders, release
without travelling and it opens the column menu as before. Without that
ambiguity resolved, every menu open would jitter into a one-pixel drag.
The threshold matches `TouchTracker::MOVE_THRESHOLD` so a mouse and a
finger agree on what a drag is.
- While dragging, the carried column is tinted full-height and a 2px bar
marks the boundary it would land on. The bar is suppressed when the drop
is a no-op, so no bar means nothing will happen rather than a bar sitting
misleadingly at the source edge.
- `TableAction::ColumnMoved { from, to }` fires only when the index actually
changed, so a host persisting column order is not asked to write on every
wobble. An open cell editor is cancelled, because it addresses a cell by
index and the indices just moved underneath it.
`draw_drag: DrawVector` — declared, never used anywhere — is replaced by two
`DrawColor` layers. `DrawVector` is a full tessellator with path, vertex,
index and paint state; a translucent rectangle and a vertical bar do not
need any of it.
Testability, which needed a structural change rather than a test file:
`Table` derives `Script` and `Widget`, so it has no `Default` and cannot be
constructed without a live `Cx`. Nothing about it was unit-testable. The
logic worth testing does not need a widget, so it moved off it —
`ColumnGeometry` owns boundary and drop-position arithmetic, and a free
`reorder_columns` owns the move. `Table` forwards to both, and
`compute_layout` now goes through `ColumnGeometry` too, so there is one
implementation rather than two that can drift.
The 21 tests cover column geometry at even and uneven widths and at a
non-zero origin, drop-position resolution including the exact-midpoint case
and clamping outside the table, the index shift in both directions, no-op
drops, out-of-range refusal, cells travelling with their header, ragged
rows, a permutation property over repeated drags, and the Phase 3 menu's
geometry and hit-testing.
Verified by reintroducing three defects separately: removing the shift for
the removed source column fails 7 tests, dropping the no-op guard fails 1,
and moving headers without their cells fails 3.
Phase 3 was marked "scaffolds only" in the README and was in fact
substantially complete — menu state, open, hit-test, apply, and drawing all
present, with 15 row and column actions wired. Corrected to done, with its
geometry now under test.
Also adds `.forgejo/workflows/makepad-table.yml`, the first CI this tree has
had. Every step passes `--manifest-path` explicitly: the crate is excluded
from the root workspace, so `-p` from the repo root cannot reach it and
`--workspace` skips it — omitting the flag does not fail loudly, it silently
tests nothing. The workflow gates tests, clippy at `-D warnings` and fmt,
and asserts three invariants that would otherwise regress quietly: that the
exclusion still holds from both sides, that no manifest tracks a git branch
instead of pinning a revision, and that monetary fields stay integer.
Each gate was checked by breaking what it protects. The exclusion check
caught a defect in itself while being tested: a bare grep for the path also
matched the explanatory comment above the exclude list, so deleting the
entry and keeping the comment passed. It now anchors on the quoted entry.
Two pre-existing clippy warnings fixed so the new `-D warnings` gate starts
from zero.
|
|||
| 1c91d6b398 |
ci(cad): gate the engine coverage, with per-file floors
Some checks failed
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
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
email.yml / ci(cad): gate the engine coverage, with per-file floors (push) Failing after 0s
repo hygiene / hygiene (push) Has been cancelled
The harness measured; nothing enforced. A coverage number nobody gates
goes down.
tools/test-cad-coverage.sh now exports llvm-cov JSON and fails when the
total drops below 85% or any of the fourteen engine files drops below
its own floor. The per-file floors are the point: deleting every test in
persistence.rs moves the total by under two points, so a single number
would wave that through. Each floor sits a couple of points under
today's measurement, so refactoring does not trip it and a real loss
does.
The low floors are the honest ones. arch_pdf (72) and arch_gltf (72)
have gaps in byte-layout paths that only a real PDF or GLB consumer
reaches; arch_svg (79) and cad_scene (78) have gaps in widget-facing
helpers and defensive arms on invariants SceneBuilder already enforces;
exporters (88) cannot reach the save-dialog branch without a windowing
system. Raising those needs work, not a bigger number here.
Also in this commit, from running the script the way CI will rather than
with a warm local checkout:
- the Makepad fetch is sparse + blobless + depth 1 over the actual
path-dependency closure (math, csg and its six siblings,
micro_serde, its derive, micro_proc_macro, live_id, id_macros).
29 MB and two seconds instead of a 319 MB checkout of a repository
that is mostly shaders, fonts and demos. Two of those crates were
found by the run failing at manifest-read time, which is why the
script now verifies all thirteen manifests exist before building
instead of trusting the sparse pattern.
The new cad-engine-coverage job needs no native packages and no GPU --
makepad-math and makepad-csg are dependency-free Rust, which is the
whole reason the engine can be measured at all. It installs its own
toolchain into a temp dir and deletes everything through a shell trap:
nothing cached between runs, nothing left in the workspace.
Verified end to end with a cold run: fresh toolchain, fresh sparse
fetch, 466 tests green, total 88.75%, all floors met, environment
cleaned.
|
|||
| b87d8b0762 |
test(email): coverage over the full domain; IMAP feature gate in CI
Some checks failed
sms / gates (push) Has been cancelled
sms / robius-sms (push) Has been cancelled
sms / android (push) Has been cancelled
sms / nigig-sms (push) Has been cancelled
sms / supply-chain (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
email.yml / test(email): coverage over the full domain; IMAP feature gate in CI (push) Failing after 0s
nigig-map / test (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
repo hygiene / hygiene (push) Has been cancelled
tools/test-email-coverage.sh now instruments all twelve email files (the new pacing, credential-store, cache, session and imap modules) and enforces per-file floors; measured 90.7% line coverage over the domain. email.yml: the domain test filter gains imap_client::/credential_store::, the test floor ratchets 150 -> 190, the sample-data gate is now a hard zero (sample_thread is test-only), and a new step checks the feature-gated IMAP transport still compiles. The review doc marks Phase C and Phase D complete with the honest caveats (sockets/keystore/pool-reuse are not host-verified). |
|||
| 3dab4a1fd5 |
test(email): coverage floors for the email domain
Some checks failed
email.yml / test(email): coverage floors for the email domain (push) Failing after 0s
repo hygiene / hygiene (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
tools/test-email-coverage.sh instruments the nigig-core email domain and enforces a whole-domain floor (90%) plus per-file floors on the files that harboured the bugs. It runs in an isolated temp dir and reports over only the seven email source files, excluding Makepad's generated code. Wired into email.yml, which also now runs mail_proxy tests and ratchets the domain test floor to 150. Measured 93.4% line coverage across the domain. |
|||
|
|
c0b27d0586 |
feat(email): MailBackend trait and BackendKind — both backends (C1a/C1b)
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
email / gates (push) Has been cancelled
email / email-domain (push) Has been cancelled
email / nigig-email (push) Has been cancelled
email / supply-chain (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
You chose to support IMAP-on-device AND a server-side proxy, user
selectable. This is the seam that makes that contained rather than two
parallel apps.
Why it is cheaper than it sounds: wasm cannot open a raw TCP socket, so a
proxy always had to exist for the browser target. The second backend was
never optional -- it was implied scope nobody had named.
C1a, mail_backend.rs:
BackendKind { ImapSmtp, ProxyApi } with three predicates that exist so
the UI cannot get them wrong:
is_available_on_wasm() IMAP is raw TCP; a browser cannot open one,
so the chooser must not offer a dead option
stores_reusable_password() IMAP keeps a REUSABLE mailbox password on
the device. For most people that is the
password-reset channel for every other
account they own. A revocable proxy token
is strictly safer, and the chooser must say
so rather than presenting a free choice
summary() the honest one-liner, asserted by test to
actually mention "password" / "revoke"
BackendSettings is the PERSISTABLE half and carries no secret, exactly
as EmailAccount does for the password (S2). BackendDraft::validate
returns (settings, Secret) and reports every problem in one pass.
The trait is deliberately synchronous and tiny -- kind(), is_configured(),
describe(). Anything computable above the line (grouping, previews,
threading) is NOT a backend concern, which is why email_store did not
change at all. I/O stays in the free functions that already own the async
context, so this file is host-testable with no runtime.
ImapSmtpBackend exists with validation but no protocol client yet; that
is C1e and nothing here claims a connection works.
C1b: EmailAccount gained `backend: BackendSettings`, #[serde(default)] so
existing persisted accounts still load. A test asserts the serialised
account -- including the backend section -- contains neither the token nor
a field named password/token.
Provider defaults now fill IMAP too, so a Gmail user still fills one
field. Outlook is special-cased: its IMAP host is outlook.office365.com,
not imap.outlook.com, so the naive smtp->imap rewrite would produce a name
that does not resolve.
New gate, negative-tested both ways: stores_reusable_password() and
is_available_on_wasm() must exist, and the persisted settings structs must
not declare password/token/secret fields.
Domain tests 99 -> 126. Test floor 95 -> 120.
|
||
|
|
901cddc716 |
fix(email): abandon_send shipped as dead code; wire it and gate it (B6)
Auditing Phase B against the tree rather than against my own notes found that abandon_send() existed in nigig-core and NOTHING called it. The user had no way to stop waiting on a hung send. I had marked B6 "partial" for the right reason -- lettre cannot cancel mid-transaction -- and missed that the part I did implement was unreachable. A control the user cannot reach is not a control. It is dead code wearing a safety label, which is worse than an acknowledged gap because it reads as done. Now wired: while a send is in flight the Send button becomes "Stop waiting". The label is deliberately not "Cancel" -- this does not stop delivery, because once DATA is accepted the message is sent whether we wait for the reply or not. It frees the UI and suppresses a result the user has stopped caring about. The 20s timeout from A6 bounds the window. New gate: abandon_send() must exist in nigig-core AND be called from the UI. The wiring is the thing checked, not the function. That gate was ALSO broken when first written -- it grepped for `abandon_send()` across src/, and the comment block explaining why the control exists mentions it by name, so unwiring the call left the gate green. Same flaw as the B5 gate in the previous commit, found the same way: delete the fix, watch the gate. Now excludes comment lines. Twice in two commits I have written a gate that its own explanatory text satisfied. Worth stating rather than quietly fixing: a gate is only evidence if you have watched it fail. Phase B verified closed: B1-B6 all done, 11 gates pass, 99 domain tests, check --all-targets clean on both crates, fmt clean. |
||
|
|
d889cbecd4 |
ci(email): gate multi-recipient send, and a gate that did not work
Some checks failed
repo hygiene / hygiene (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
email / gates (push) Has been cancelled
email / email-domain (push) Has been cancelled
email / nigig-email (push) Has been cancelled
email / supply-chain (push) Has been cancelled
Two new gates, and one of them was broken when I first wrote it. B1 gate: the send path must call email_send::parse_recipients, must NOT contain a single-Mailbox parse of the whole To field, and must add every accepted recipient. Three checks rather than one, because each failure mode is separately reachable. B5 gate: spawn_send_email must keep the SEND_IN_FLIGHT swap. THE B5 GATE DID NOT WORK AS FIRST WRITTEN. It grepped the whole file for `SEND_IN_FLIGHT.swap(true`, and the unit TESTS for the guard contain that same string -- so deleting the guard from production code left the gate green. I found it by negative-testing, which is the only reason I know. Now scoped to the text before `#[cfg(test)]`. That is worth recording rather than quietly fixing: a gate whose own test fixtures satisfy it is indistinguishable from a gate that works, and the only way to tell them apart is to break the thing on purpose. Negative tests, all confirmed firing: remove the list parse -> fires reintroduce `let to_mbox: Mailbox = ..` -> fires delete the in-flight guard -> fires (after the fix) and all 10 gates pass on the clean tree. Test floor 60 -> 95 (actual 99). Bulk page: builds through EmailSendRequest, so a partly-invalid list reports what was dropped instead of refusing everything, and requires a second tap before sending. The prompt quotes the recipient count and any duplicates or rejections, so the user knows what they are confirming. Editing the message after arming re-prompts rather than sending the old confirmation. |
||
|
|
b3d562f4e2 |
ci(email): gate the Phase A security properties, and surface the warning
Three new gates in email.yml, each negative-tested by reverting the fix
and confirming the gate fires:
1. SmtpConfig.password must be a Secret, AND SmtpConfig must not derive
Serialize/Deserialize. Two separate checks, because either alone
re-opens S2: a Secret that gets serialised is still exposed, and a
String that never gets serialised still Debug-prints.
2. set_email_api_url must call email_api_url_is_safe. Checks both that
the validator exists and that the setter uses it -- a validator
nobody calls is decoration.
3. tls_mode_for_port must exist, and Tls::None / Tls::Opportunistic must
not appear. Opportunistic is the dangerous one: it silently accepts a
downgrade, which is exactly the attack starttls_relay's Tls::Required
prevents.
Negative tests, all confirmed firing:
password: Secret -> String gate fires
re-add #[derive(.., Serialize)] gate fires
remove the validator call gate fires
introduce Tls::None gate fires
and all 8 gates pass on the clean tree.
Domain test floor raised 38 -> 60 (actual: 68) and the filter widened to
include the secret:: module, so the new tests are actually covered by the
floor rather than sitting outside it.
Also surfaces config_warning() in the setup flow, so a from/username
mismatch is shown while the user can still fix it, rather than becoming a
silent provider rejection later.
One YAML trap worth recording: the test filter ends in `secret::`, and a
bare trailing colon makes YAML parse the line as a mapping. The run string
has to be quoted. Caught by validating the workflow before committing,
which is the only reason this is not a broken pipeline.
|
||
| cf73ef4c1d |
test(pdf): assert what a file declares is delivered, and floor the coverage
Every serious bug in this stack has had one shape: a valid, well-typed,
empty-or-default value where the file plainly declared content. xobjects
empty for every document; acroform() dropping every field behind an
indirect reference; DCTDecode returning its own compressed bytes; a JPEG
decoder that was a stub returning black. None errored, none panicked, and
the tests asserted Ok, which they got.
Coverage would not have caught any of them. Measured when each shipped:
page.rs 92.4%, form.rs 93.6%, content.rs 89.2%, xref.rs 95.2%. The buggy
lines ran; nobody checked what they produced.
So: a property test that walks the raw object graph of every corpus
fixture, counts what the file declares, and requires the API to deliver
it - fonts, xobjects, graphics states, colour spaces, form fields,
filters, MediaBox. It reimplements the resolution rule independently of
page.rs on purpose; a test that asks the code under test what to expect
agrees with the bug.
It failed the day it was written, on a shape the corpus had never
contained. Every fixture wrote /Resources inline, and all six extractors
read it with dict.get_dict("Resources") - which returns None for an
indirect reference and never consulted /Parent. A page with
"/Resources 5 0 R", the commonest shape in real PDFs, reported no fonts,
no xobjects, no graphics states and no colour spaces. Same for a page
inheriting resources from its /Pages node. Empty, not wrong, so nothing
failed.
Fixed by resolving /Resources once in PdfPage::from_obj through a helper
implementing the full inheritance rule (32000-1 Table 30), and passing
the resolved dictionary down. Indirect /MediaBox entries resolve too.
Six resources/ fixtures cover the shapes that were missing.
Mutation-checked: reverting inheritance kills 5 tests, the sub-dict
reference 3, indirect MediaBox 2, and removing the depth bound hangs.
One mutation survived - a visited-set guarding a /Parent cycle, which
the depth bound already handles - so it was deleted rather than left as
untested defence with a reassuring comment.
tools/test-pdf-coverage.sh enforces a floor instead of printing a number,
with per-file floors as well as a total: image.rs could fall from 33% to
5% and move the total by under a point. All three failure modes verified
to fail. It caught a bug in itself first - its ignore regex matched its
own work directory and reported a confident TOTAL 0.00%.
.gitattributes marks *.pdf binary. An xref entry must be exactly 20 bytes
(7.5.4), so with a one-digit generation field it ends in a space, and
git diff --check was reporting unfixable "trailing whitespace" on every
fixture in the corpus.
TEST_TARGET=pdf: 695 passed, 0 failed (was 680). Coverage 83.42%.
ADR 0017 records the four mutations so they can be repeated by hand.
|
|||
|
|
7751e96c54 |
ci(email): give nigig-email a CI workflow, and fix two bugs it caught
(Phase 0.3, 0.6) nigig-email had no CI of any kind. That is how a binary with unbalanced braces reached main and stayed there -- `cargo check -p nigig-email` failed while `--lib` passed, so the library was fine and the BINARY had never compiled once. It is also how four unused dependencies survived. Four jobs: gates 4 source scans, no toolchain, fail fast email-domain the 38 pure tests in nigig-core + a floor nigig-email check --all-targets, test, fmt, clippy ratchet supply-chain unused deps, lockfile, whitespace `--all-targets` is deliberate in the check step: `--lib` alone passed for the entire time main.rs was syntactically invalid, which is precisely the failure this job exists to prevent. Phase 0.6: fmt is a HARD gate here, not report-only. The crate already formats clean so there is no pre-existing drift to grandfather in -- unlike sms.yml and nigig-map.yml, which inherited hundreds of diffs and had to settle for reporting. WRITING THE GATES FOUND TWO REAL BUGS, both in bulk.rs: B3 -- `port_t.parse().unwrap_or(587)` was still live. A typo'd port like "465x" silently became 587, and because the port selects the transport (465 implicit TLS vs 587 STARTTLS) that silently changed the security posture with no message. Now routed through AccountDraft::validate, which is unit tested in nigig-core and returns AccountError::PortInvalid. B2 -- the handler read five TextInputs and built an SmtpConfig on EVERY action event: ten heap allocations per keystroke, per scroll, per timer tick from any widget in the app, for a struct only read on click. It also captured whatever the fields happened to hold when an unrelated action fired. Now read on click. I also got a baseline wrong and corrected it. I set the clippy ratchet to 2, having seen two `unexpected_cfgs` warnings for native_activity from the app_main! macro. Measuring with the same dedupe the script uses gives 0 -- those two attribute to the bin target and are filtered by the package_id check. A baseline above the real count is not a harmless margin: the script fails when n < BASELINE precisely so slack cannot hide a regression. Every gate negative-tested: password field on EmailAccount -> fails unwrap_or(587) in non-comment code -> fails a new clippy warning -> fails (0 -> 2) test floor raised above actual -> fails (38 < 99) and all pass on the clean tree. Two of my own regexes were too strict on the first run and are fixed here: the port gate matched the comments that document the old behaviour, and the sample-data gate counted the `use` import as a call site. A gate that trips on its own rationale is a gate nobody keeps. Verified: check --all-targets clean; 41 tests pass; fmt clean; clippy 0 at baseline 0. |
||
|
|
964fd5d4ef |
build(email): drop three unused dependencies, and gate the platform one
(Phase 0.5)
nigig-email declared four dependencies its source never mentions:
serde 0 references in src/
serde_json 0
robius-location 0
chrono 1 <- KEPT, see below
robius-location is the same defect SMS Phase B removed from nigig-build,
nigig-core and nigig-uikit: it drags polkit/gio/glib into the dependency
graph, which is where RUSTSEC-2024-0370, RUSTSEC-2024-0429 and an
LGPL-2.1 distribution question come from -- for code that is never
called.
A CI gate already exists to stop that regressing ("The removed platform
deps must not come back"), but its manifest list covered only three
crates and nigig-email was not one of them. Added it, so this cannot come
back the way it did here.
Correction to the assessment: it listed chrono as unused. That was true
when written and is no longer -- inbox.rs::format_thread_time uses it for
list-row timestamps. Kept, with a comment saying why, so the next person
auditing this file does not delete it and break the build.
Gate negative-tested: appending robius-location back to the manifest
produces
ERROR: crates/apps/nigig-email/Cargo.toml declares robius-location
but never uses it
and removing it passes again.
Verified: cargo check -p nigig-email --all-targets -> 0 errors;
41 tests still pass (38 nigig-core email_*, 3 nigig-email).
|
||
|
|
422a0e3388 |
docs(ci): all jobs green; note that this is a starting line
All checks were successful
repo hygiene / hygiene (push) Successful in 3s
cad-module was the last red job and now passes. Board updated. Also records that pdf.yml/fuzz reporting "skipped" is correct -- it is gated on schedule || workflow_dispatch -- so nobody spends time investigating it as a failure. The caveat stays prominent: several jobs are green because their gate is deliberately loose (the nigig-map unit-test ratchet sits at 9 real failures, and its fmt/clippy steps are report-only). Those are listed under Known-not-gated so a full green board is not mistaken for a healthy codebase. |
||
|
|
e89dea347a |
ci: name the nigig-build formatting gate for what it actually checks
The step was called "Formatting (CAD module)" but runs `cargo fmt -p nigig-build`, which is the entire crate. Of the 1,559 diffs it reported on its first real run, the three worst files were doc/widgets/doc_widget.rs, doc/tests.rs and project_management/mod.rs -- none of them CAD. Anyone debugging the red job was pointed at the wrong directory. |
||
|
|
afe7c518b3 |
docs(ci): nigig-map is green; record what is deliberately not gated
All checks were successful
repo hygiene / hygiene (push) Successful in 3s
16 of 17 jobs now pass. Updates the board and adds two sections: - Known-not-gated: the nigig-map unit-test ratchet (9 real logic failures), the three test/bench targets that do not compile, and the report-only fmt/clippy steps. Written down so nobody reads a green tick as "this crate is healthy". - Writing a ratchet step: the `bash -e` trap that made this workflow fail at exactly its own baseline, and the `|| status=$?` fix. Cheap to record, expensive to rediscover. Also corrects the cad-module note: `cargo fmt -p nigig-build` is 1,559 diffs across 89 files spanning doc, project_management and cost_estimator, not just CAD. |
||
|
|
833181faec |
ci(map): fix the ratchet aborting before it could evaluate anything
First real run of nigig-map.yml reported failure at 530 passed /
9 failed -- exactly the baseline it was supposed to allow.
The step ran `out="$(cargo test ...)"` under the runner's `-e` shell.
cargo test exits 101 while any test fails, and a failing command
substitution in a plain assignment aborts the step immediately, so
neither the parse nor the comparison ever executed. The `set -o
pipefail` I had added made it worse, not better.
`|| status=$?` puts the assignment inside a tested compound command,
which -e exempts, so the script keeps control and decides for itself.
Verified against the same `bash -e` the runner uses:
at baseline 530 passed / 9 failed -> exit 0, "OK"
regressed 527 passed / 12 failed -> exit 1, "12 failing ...
baseline is 9"
restored 530 passed / 9 failed -> exit 0
My bug, introduced in
|
||
|
|
de698b1a64 |
ci(map): make the workflow runnable, and cover the code it now guards
nigig-map.yml has never executed a single step. It used
actions/setup-rust@v1, which does not exist on data.forgejo.org, so
every run died in "Set up job" with "repository not found" and
cancelled all seven steps -- the same class of defect as
android-actions/setup-android in sms.yml. Replaced with the inline
rustup install already used by pay-domain.yml.
That action also requested `toolchain: stable`, contradicting the
1.97.1 pin in rust-toolchain.toml. The replacement reads the channel
out of rust-toolchain.toml, so CI and developers use one compiler.
Added the native GL/wayland dependencies; Makepad does not build
without them.
Gates, scoped to what is honestly true today now that the crate
compiles:
- Build is a hard gate. This is the regression that matters: until
the previous commit the crate did not compile at all.
- Unit tests are a RATCHET at 9, not a hard gate. 535 unit tests
existed and had never run; 526 pass and 9 fail on real logic
(4 mvt_parser, 1 overpass_parser, 4 sprite classification). Failing
the build on those would mean a permanently red job that everyone
learns to ignore. The ratchet fails the moment a tenth appears.
- `cargo test` with no filter is NOT used: two of the four test
targets and the criterion bench do not compile (tests/ui.rs imports
makepad_widgets::makepad_test; tests/makepad_visual_tests.rs and
benches/tile_decode_bench.rs import pub(crate) modules, and
criterion is not a declared dev-dependency). Separate defects.
- fmt and clippy report without gating, matching doc-engine.yml and
sms.yml. rustfmt could not parse view.rs while the crate was broken
so it skipped all of src/; there are now 392 visible pre-existing
diffs and 132 clippy warnings. A step that always fails is worse
than no step.
Also added four unit tests for center_lat() and meters_per_pixel().
Both were introduced in the compile fix and had zero coverage: I
verified that by regressing center_lat() by +1.0 degree and watching
the ratchet stay green at 9. It now fails at 12. The tests round-trip
the projection across eight latitudes, pin the equator to zero, check
hemisphere sign, and assert the ground scale ratio between 0 and 60
degrees is cos(60) = 0.5 -- the position puck's accuracy circle is
sized from that, so an inversion would be wrong by 2x at Nordic
latitudes.
Ratchet negative-tested both ways: perturbing lon_lat_to_normalized
takes it 9 -> 12 and fails; at HEAD it reports 530 passed, 9 failed
and passes.
|
||
|
|
ea7788673d |
docs(ci): record the repo-hygiene scheduling bug and the current board
All checks were successful
repo hygiene / hygiene (push) Successful in 4s
Adds the fourth defect the first real runs exposed -- the mapping form of `on:` not being scheduled on this instance -- and a table of the latest result for all 17 jobs, so "is CI green" has an answer that is not someone's memory. 14 pass. The two failures, cad-module formatting and nigig-map, are pre-existing source problems rather than CI plumbing. |
||
|
|
4d627496d2 |
ci: use the list form of on: so repo-hygiene actually runs
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
repo-hygiene.yml is the one workflow with no path filter. Its whole
purpose is to run on every commit, because the other six are scoped
with `paths:` and a commit touching only unfiltered files otherwise
gets no checks at all. The file's own header comment explains this,
citing commit
|
||
|
|
89f7c1df2a |
docs(ci): how to register a runner, and what the first runs found
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
Not in the repo root -- this lives next to the workflows it describes. Covers registering a forgejo-runner against gitdab (Gitea 1.22), the ubuntu-latest label every job depends on, the docker-vs-host tradeoff, and how to read job logs given that this instance's REST API 404s on the logs endpoint. The important section is action resolution: Forgejo resolves `uses:` against data.forgejo.org with no github.com fallback, and a missing action fails the job in "Set up job" before any step runs -- which reads like an infrastructure blip rather than a config error. Records which actions currently resolve and which do not. Also records the four defects the first real runs exposed, three now fixed, so the next person understands why these workflows look the way they do. |
||
|
|
bda0124992 |
ci(pay): pass --config to cargo-deny's check subcommand, not the binary
The dependency audit step has never audited anything. cargo-deny 0.18.6
exits 2 immediately:
error: unexpected argument '--config' found
tip: 'check --config' exists
--config belongs to the `check` subcommand, and its path resolves
relative to the manifest rather than the working directory, so it also
has to be absolute. nigig-build.yml already gets both right; this
invocation predates that fix.
This step is the last one that runs in isolated-payment-tests, so its
failure also skipped the three gates behind it:
- Payment crates must not depend on Makepad
- Domain and storage must not reach the platform SDK
- Mock gateway must not compile into a release build
Verified with cargo-deny 0.18.6 against all three crates:
nigig-pay-domain advisories ok, bans ok, licenses ok, sources ok
nigig-pay-storage advisories ok, bans ok, licenses ok, sources ok
nigig-pay-platform advisories ok, bans ok, licenses ok, sources ok
Found by running the workflow on a real runner for the first time.
|
||
|
|
892471c73f |
ci: commit tools/*.sh executable, and gate the mode
Five of the six scripts under tools/ were committed mode 100644. Every
one of them is invoked with a leading ./ from pay-domain.yml or
pdf.yml, so those steps could only ever fail:
./tools/makepad-native-libs.sh: Permission denied
./tools/test-mpesa-store-clean.sh: Permission denied
Both are real failures from run 349, the first time a runner existed to
execute pay-domain.yml at all. They fail at the job's first substantive
step, so payment-ui-tests did no work whatsoever and
isolated-payment-tests skipped its last seven gates -- including the
dependency audit, the "payment crates must not depend on Makepad"
check, and the mock-gateway-in-release guard.
The mode is a property of the index, so a local chmod that is never
staged does not fix it. Marked all five executable with
`git update-index --chmod=+x` and added a hygiene gate that fails if any
tracked tools/*.sh is not 100755.
repo-hygiene.yml is the right home: it has no path filter, needs no
toolchain, and already exists to validate CI configuration itself.
Gate negative-tested: reverting one script to 100644 fails it with
"tools/makepad-native-libs.sh is mode 100644, expected 100755";
restoring the bit passes.
|
||
| 1d3e6ab72a |
fix(pay): correlate USSD callbacks to the payment that asked for them
Some checks failed
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
repo hygiene / hygiene (push) Has been cancelled
doc-engine / consumer (push) Successful in 3m56s
Payment domain, storage, platform and UI / payment-ui-tests (push) Failing after 4s
doc-engine / engine (push) Successful in 15s
nigig-map / test (push) Failing after 2s
Payment domain, storage, platform and UI / isolated-payment-tests (push) Failing after 1m58s
sms / gates (push) Successful in 3s
sms / robius-sms (push) Successful in 22s
sms / android (push) Successful in 21s
sms / nigig-sms (push) Successful in 4m6s
sms / supply-chain (push) Successful in 4s
R2.1. Review items 2.7 and 5.3.
## SessionRegistry was built in Phase 5 and never wired
The pump still read:
while let Some(ev) = robius_ussd::next_event() {
... if let Some(id) = h.current.take() { ... }
}
next_event() drains a process-wide queue and its entries carry no session
id, so every event was applied to whatever `current` happened to be.
Reproduced before changing anything: payment A is dispatched then abandoned
with events still queued; payment B starts; the pump drains A's ResultText
and SessionEnded and applies both to B. An abandoned payment settles the one
that replaced it.
## Now
- Dispatch claims the single in-flight slot. The USSD backend returns no
session handle, so the intent id is the correlation id — enough, because
the registry only has to tell this payment from the previous one.
- Every event is admitted against the live operation before it can touch an
intent. Foreign and stale events are logged and dropped.
- Terminal events are de-duplicated; progress chatter still repeats freely.
ussd_duplicate_key mirrors ProviderSignal::duplicate_key in the platform
crate, and a test pins the two together.
- All six terminal and teardown paths retire the session id, so a late
duplicate cannot revive a closed operation.
6 tests, including the abandoned-payment scenario by name. Verified by
removing the close call: that test goes red. CI gate asserts the pump still
admits, dispatch still claims, and at least six paths still close — matching
the method rather than a receiver literal, because rustfmt wraps the call.
## What this does not do
The pump still lives in PayFlowHandler, which still owns the pending-store
writes and the bulk queue. Moving *ownership* to PaymentCoordinator changes
who cancels on teardown and who observes an out-of-order callback, which is
what ADR 0007's device matrix exists to check. That is now tracked as R2.1b.
The correlation defect — the one that could settle the wrong payment — is
closed, and it did not need a device. I had previously filed the whole of
R2.1 as device-blocked; that was too coarse.
## Validation
nigig-pay-ui 78 (was 72) / nigig-mpesa 20 pass
domain 148 / storage 41 / platform 64 / mpesa 29 pass
clippy -p nigig-pay-ui --no-deps -D warnings 0 errors
builds: pay-ui, pay, mpesa, core; default and --no-default pass
correlation injection: abandoned-session test fails without it pass
pin-capture guard pass
|
|||
|
|
015cf44422 |
fix(cad): remove the reachable panics; gate unwrap/expect at 5
Phase 6, scoped to what is provable rather than a blanket -D warnings.
Measured the CAD module first: ~200 clippy warnings, but the panic
family -- the part the plan actually cared about, copying the pay
crates' ratchet -- was only 8: 2 unwrap, 6 expect, 0 panic!. Three were
real, five are genuine constructor invariants.
Fixed:
- code_editor.rs x2. `lazy_init_session(); self.session.as_mut().unwrap()`
in both draw_walk and handle_event. Correct today, but the guarantee
lived across a function boundary the compiler cannot see, so an
unwrap sat on a widget draw path waiting for a third caller to forget
the prologue -- and a panic there kills the editor with unsaved work
in it. Added `editor_and_session()`, which splits the borrow and
returns Option, so both sites take an early return instead.
I first tried folding init into `get_or_insert_with`. That silently
dropped the `keep_cursor_in_view = Once` side effect, which only
happens on the create path. Caught it by grepping for the field rather
than trusting the refactor; reverted.
- cad_scene.rs x1. MeshCache::get_or_build did
`.write().expect("mesh cache poisoned")` while every other method on
the type already degraded with `if let Ok(..)`. Reachable: the export
path calls get_or_build on a spawned thread, so one panicking worker
poisoned the lock and the next draw took the UI thread down with it.
The cache is pure derived data -- every entry rebuilds from its node
-- so a poisoned lock now costs memoisation, not correctness. The mesh
is built before the lock is taken, and the double-check still prefers
a racing thread's entry so Arc::ptr_eq comparisons stay consistent.
Left alone, with reasons: 4 x cad_scene "default material/layer always
exists" (SceneBuilder::new inserts both; verified) and 1 x arch_gltf
serde_json::to_vec over a Value built in that file.
New gate: "No new unwrap/expect in CAD production code", allowlist of 5.
A bare count drifts upward quietly and a blanket ban just gets
#[allow]-ed, so the count is pinned and each exemption is named in the
comment.
The gate skips #[cfg(test)] by BRACE DEPTH rather than stopping at the
first one. That matters: arch_gltf.rs has production code after two test
modules, so the existing panicking-macro gate's "stop at first
#[cfg(test)]" awk cannot see line 850 at all. My first attempt used the
same awk idiom and reported 4 of 5 -- I only noticed because the number
disagreed with clippy. Verified the older macro gate is not currently
hiding anything, but it is hiding it by luck.
Both negative tests pass: an unwrap added to viewport.rs is caught, and
one added to arch_gltf.rs *after* its test modules -- the exact blind
spot -- is also caught, named with file and line.
13 gates now, all green. 761 lib + 154 integration tests pass.
|
||
|
|
015462b386 |
ci(sms): install the Android SDK inline instead of a nonexistent action
Some checks failed
A runner was registered against this repo for the first time, so the
workflows in .forgejo/ finally executed instead of only ever being run
by hand. The android job failed immediately:
Unable to clone https://data.forgejo.org/android-actions/setup-android
refs/heads/v3: repository not found: Not found.
android-actions/setup-android does not exist on data.forgejo.org, and
Forgejo does not fall back to github.com for action resolution. The
failure happens in "Set up job", before any step runs, which cancels
all seven remaining steps. The job reported failure without compiling a
single line -- so the Android gate, the only job in this file that sees
the ~600 lines of JNI under #[cfg(target_os = "android")], has never
checked anything.
Replaced with an inline cmdline-tools install, which is the same
sequence used to verify these crates by hand and depends only on
actions/checkout and actions/setup-java -- both of which do resolve.
Verified on the same runner in this run: gates, robius-sms (48 tests),
nigig-sms (46 tests, floor gate, clippy ratchet) and supply-chain all
pass. nigig-map.yml has the identical defect with actions/setup-rust@v1
and is left alone here.
|
||
| 5800beb552 |
fix(pay): close the R1 gaps against the completion standard
You are right that my first pass at R1 fell short. It deferred an item on a judgement call, and it fixed three defects without regression tests naming them. Four gaps, all closed here. ## 1. S8 was deferred; it is now done as far as the platform allows I skipped certificate pinning as "wasted work if the endpoints get dropped". That was my call to make about effort, not an external blocker. Investigated properly: Makepad's HttpRequest exposes no pinning API. Its only TLS control is set_ignore_ssl_cert, which weakens verification. Pinning is not implementable at this layer without patching the platform crate. What *is* enforceable is the property pinning mostly buys — that a mistyped, injected or attacker-supplied URL cannot be dialled. check_transport gates every request on HTTPS plus a four-host allowlist, at all three dial sites in both copies of the client. 7 tests: lookalike hosts (api.coingecko.com.evil.example), embedded credentials (https://evil@real/), explicit ports, plain HTTP, malformed URLs, and an assertion that TLS is never disabled. Verified by disabling the allowlist: 3 tests fail. ## 2. The 13-digit phone defect had no test naming it I fixed it and moved on. It now has a regression test quoting the original duplicated branches, plus a property test that normalisation output is either empty or exactly a valid 10-digit 07/01 number — no third outcome. ## 3. The fee-policy UI wiring was untested The domain guard had 11 tests; the wiring that connects it to the pay sheet had none, so nothing proved the sheet actually consults it. Four tests now cover the shipped policy: it identifies the bundled tariff, refuses once stale, still quotes while current, and keeps "unknown band" distinct from "stale table". ## 4. The exchange client had no tests at all It does now, via the transport module above. ## A test that failed against itself tls_verification_is_never_disabled_in_this_module asserts the module never calls set_ignore_ssl_cert — and the literal in the assertion put the string in the file, so it failed on first run. The needle is now assembled at runtime. Recorded because it is exactly the kind of thing that gets "fixed" by deleting the test. ## Completion standard, now written into the plan A phase is done when: no item is deferred on a judgement call; no capability is removed to satisfy a review item; defects found while implementing are fixed in the same phase even if absent from the review; every fix carries a test that fails without it; and CI enforces it. ## Validation domain 148 / storage 41 / platform 64 / mpesa 29 pass nigig-pay-ui 72 (was 66) / nigig-mpesa 20 pass clippy -p nigig-pay-ui --no-deps -D warnings 0 errors builds: pay-ui, pay, mpesa, core pass allowlist injection: 3 tests fail when disabled pass pin-capture guard pass Pre-existing and untouched: `cargo test -p nigig-pay --lib` fails to build on clean HEAD (ClassifiedTransaction not in scope in transact.rs). Verified by stashing. The transport tests are exercised through the nigig-mpesa copy. |
|||
|
|
25f32f7870 |
test(sms): build a real test suite (Phase G)
Some checks failed
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
repo hygiene / hygiene (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
nigig-map / test (push) Failing after 1s
sms / gates (push) Successful in 3s
sms / robius-sms (push) Successful in 23s
sms / android (push) Failing after 54s
sms / nigig-sms (push) Successful in 4m12s
sms / supply-chain (push) Successful in 6s
50 tests -> 102, and the two that were there at the start of this work
are deleted.
Where this started: robius-sms had ZERO tests, and nigig-sms had two --
bulk_sub_tab_default_is_contacts and bulk_sub_tab_variants_distinct.
Both asserted a derived Default and a derived PartialEq. Neither
mentioned SMS. Neither could fail short of the compiler breaking. That
is the defect that produced every other defect in this plan: nothing
could prove a change was safe, so nothing was ever deleted and every
bug survived contact with review.
Property tests (proptest, new dev-dependency)
Seven over truncate_preview, format_timestamp, badge_text, and five
more over segment_count, the rate limiter and ScheduleRequest.
These are the ones that matter, because the hand-written cases in this
repo all encode a bug someone had ALREADY found. proptest searches the
space instead. I verified that by reinstating the original byte-slicing
truncate_preview and confirming
prop_truncate_preview_survives_mixed_scripts and
prop_truncate_preview_respects_the_char_limit both fail against it --
they would have caught A3 before it shipped.
prop_rate_limiter_respects_capacity models the window independently
and asserts the invariant across random clock sequences, rather than
re-implementing the limiter's own arithmetic in the assertion.
Integration tests (2 new files, public API only)
robius-sms/tests/sms_pipeline.rs and nigig-core/tests/sms_store.rs go
through the public surface the application actually uses. The unit
tests inside src/ can see private helpers; these cannot, which is the
point -- they catch a refactor that keeps every unit test green while
breaking the caller-visible contract.
Two of them are privacy canaries. e1_message_bodies_are_never_persisted
and e1_no_body_text_reaches_the_serialised_store fail if anyone removes
#[serde(skip)] from OfflineSmsMessage.body. Verified by removing it:
both fail, the other six pass. Nothing else in the tree would have
noticed the inbox silently going back to plaintext on disk.
Named regression tests
One per defect, named for it -- c1_*, d1_*, d3_*, e1_*, e7_*, a4_*,
c3_*, c7_* -- so a future reader goes from a failing test straight to
the bug it guards rather than to a git archaeology session.
New coverage for logic that had none
- build_timeline_items / build_filtered_timeline_items: date-divider
placement and the message indices the draw loop uses to index
conv_data.messages. An off-by-one there renders the wrong body in
the wrong bubble; it had no test at all.
- kind_to_offline / kind_from_offline round-trip: the only thing
stopping a cached Sent message reappearing as Inbox after a restart,
which would flip the bubble to the wrong side of the screen.
- normalize_number: what C1 groups on, across five formatting variants
plus short codes and alphanumeric senders.
MessageKind::from_android_type / to_android_type were hoisted out of
sys/android/inbox.rs onto the type, the same way ScheduleRequest::validate
was in A4, so the provider mapping is testable off-device. An
unrecognised TYPE value is preserved verbatim in Unknown rather than
defaulted, and there is a property test asserting the round trip is
total over every i32.
CI: a test-count FLOOR at 100. A floor rather than a ratchet -- unlike
the clippy count, there is no reason to ever want this number to fall.
Deliberately NOT faked: the JNI cursor loop, the keystore round-trip and
broadcast delivery still need an emulator. A mock returning what I expect
would test my expectations, not Android. Those remain called out in the
Phase A and E commit messages.
Verified: 11/11 checks. 48 robius-sms + 46 nigig-sms + 8 sms_store = 102.
clippy -D warnings clean on host and aarch64-linux-android; nigig-sms
ratchet holds at 32 (my first draft added an orphaned `use super::*`,
caught by the ratchet and removed rather than baselined).
|
||
| a264f53eb7 |
feat(pay): complete phase R1 of the remaining-work plan
All four R1 items. Two of them uncovered defects that were not in the review, and R1.4's corpus found a live bug. ## R1.1 versioned fee policy (U9) The band table is a static "effective Jan 2024" snapshot. When Safaricom revises a tariff, nothing notices: the old number is quoted and the user authorises a total they are not charged. FeePolicy attaches provenance and a 400-day trust horizon. Past it, fee_for returns FeeError::PolicyOutOfDate rather than a number, and the sheet refuses to quote exactly as it already does for an unknown band — "no band for this amount" and "our table is old" stay distinguishable because they need different messages. Verified by disabling the check: 4 tests fail. Domain tests 137 -> 148. ## R1.2 quality gate for nigig-pay-ui (Q2) Correcting my own earlier count: 9 of the 10 unwraps were in tests. The one production case, on the dispatch path inside the biometric branch, is now a fail-closed path — no request, no prompt, no dispatch. nigig-pay-ui now denies unwrap_used/expect_used outside tests and CI runs clippy --no-deps -D warnings. Scoped with --no-deps because matrix_client and robius-ussd carry pre-existing warnings that are not this crate's to fix, and a gate that fails on someone else's code gets disabled. Turning the lint on surfaced 13 more issues, one a real defect: normalise_phone had two identical branches, and the 13-digit "254…" arm produced an 11-digit result — not a valid MSISDN, but non-empty, so it flowed on as a recipient. The duplication was hiding it. ## R1.3 exchange API (S7/S8/S10) The client forged origin/referer for api2.bybit.com and p2p.binance.com, impersonating those exchanges' own web clients against internal endpoints. Removed from both copies (nigig-pay and nigig-mpesa — item A5 again), along with the framework-identifying User-Agent. CI rejects either regrowing. Requests are still made, now honestly identified. If those endpoints reject an honest client the P2P panes fall back to their offline cache, which is the true state of the integration rather than a disguised one. Not done, deliberately: certificate pinning. Pinning an endpoint the product may drop is wasted work, and whether to keep these endpoints is a product call recorded in the plan. ## R1.4 adversarial CSV corpus (7.5) parse_csv turns an untrusted file into a payment list. Corpus covers empty input, injection-shaped fields, overflow, NUL, RTL override, full-width digits, a 5,000-row file and malformed numbers. The bar is not "parses correctly" but "never silently produces a payment nobody intended". Verified it can fail. UI tests 61 -> 66. ## Validation domain 148 / storage 41 / platform 64 / mpesa 29 / pay-ui 66 pass clippy -p nigig-pay-ui --no-deps -D warnings 0 errors builds: pay-ui, pay, mpesa, core; default and --no-default pass pin-capture guard pass fee-policy injection: 4 tests fail with the check removed pass corpus injection: catches a fabricating normaliser pass |
|||
|
|
1670ddf49c |
refactor(sms): delete the dead code and the duplication (Phase F)
Some checks failed
doc-engine / engine (push) Has been cancelled
doc-engine / consumer (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
repo hygiene / hygiene (push) Has been cancelled
nigig-map / test (push) Has been cancelled
sms / gates (push) Has been cancelled
sms / robius-sms (push) Has been cancelled
sms / android (push) Has been cancelled
sms / nigig-sms (push) Has been cancelled
sms / supply-chain (push) Has been cancelled
Net -596 lines. No behaviour change except F10, which replaces a label that was lying. F2 -- four copies of one stub backend. apple.rs, linux.rs and windows.rs were BYTE-IDENTICAL 66-line files, and unsupported.rs was the same again. That duplication is what let them drift: Phase C3 had to fix `Error::Unknown` in exactly one of the four, because only one had it wrong. Collapsed into sys/stub.rs, which each platform module invokes. 268 lines become 35 plus one shared definition. The module is cfg'd out on Android, which has a real implementation and would otherwise report the macro as unused under -D warnings. F3 -- TWO dead compose implementations. SmsComposePage (189 lines) was registered in the VM and instantiated nowhere. Separately, the FAB and its compose overlay were left in the DSL as `visible: false` with a comment saying "FAB removed: SMS compose/inbox navigation now lives in SmsActionBar" -- but 102 lines of DSL and 53 lines of handler stayed behind, wired to a button no user can reach. Deleted both, and send_reply() with them: it existed only to serve the unreachable overlay. Compose navigation is SmsActionBar's, as the comment already said. F4 -- a whole second contact subsystem, unreachable. sms_screen.rs carried its own CONTACTS_CACHE, contacts_loaded(), load_contacts_into_cache(), display_name_for_number(), normalize_number(), try_load_contacts() and a contacts_load_attempted field. Nothing called any of it -- the live implementation is in conversations_list.rs. Worth noting the dead copy was also the WRONG one: its display_name_for_number did an O(n) linear scan of the whole phone book per lookup, where the live version is O(1) because cache_contact_number inserts under both the raw and normalised key. F5 -- the page tree was written out twice. sms_bulk_page, sms_schedule_page and sms_more_page were each declared under Desktop AND under Mobile, byte-identical apart from indentation. Any change to a page header had to be made in both places or the layouts silently diverged. Now three named widgets plus a shared SmsPageHeader, referenced from both variants. F8 -- serde, serde_json and robius-location were declared by nigig-sms and referenced nowhere in its sources. F9 -- was_scrolling was read twice per frame from the same portal list; the copy in handle_event was bound and never used. F10 -- the character counter was a hardcoded lie. The old compose page rendered "0 / 160 characters" and never updated it. It died with F3, but the bulk composer -- where the money actually goes -- had no cost indication at all. It now shows live segment count as you type, using segment_count() from Phase A5, because segments are the billing unit and "160" is only right for GSM-7: one emoji forces UCS-2 and drops the limit to 70. This is the only user-visible change in the commit. F1 and F7 were already done, in Phase A (shared cursor.rs) and Phase D1 (I/O out of draw_walk). The deletions orphaned eight imports, which are also removed. Together that takes the nigig-sms clippy ratchet from 49 to 32 -- these were not suppressed, the code they reported on is gone. Verified: 10/10 checks. clippy -D warnings clean on host AND aarch64-linux-android, 28 robius-sms tests, 22 nigig-sms tests, nigig-build still builds, metadata --locked clean. |