Commit graph

14 commits

Author SHA1 Message Date
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).
2026-08-18 10:07:54 +00:00
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.
2026-08-17 12:08:19 +00:00
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.
2026-08-17 09:39:04 +00:00
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.
2026-08-17 05:09:15 +00:00
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).
2026-08-17 04:29:30 +00:00
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).
2026-08-16 22:22:55 +00:00
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.
2026-08-16 21:49:12 +00:00
nigig-ci
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.
2026-08-16 20:30:33 +00:00
nigig-ci
901cddc716 fix(email): abandon_send shipped as dead code; wire it and gate it (B6)
Some checks failed
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
repo hygiene / hygiene (push) Has been cancelled
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.
2026-08-16 20:04:41 +00:00
nigig-ci
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.
2026-08-16 19:51:21 +00:00
nigig-ci
3786e7c1cf docs(email): threat model, and mark Phase A complete (A6)
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
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
New crates/apps/nigig-email/THREAT_MODEL.md. Separate from the root
THREAT_MODEL.md, which is Nigig-Pay's and shares no assets with this
feature.

Nine threats with the control named for each, so a reader can check the
claim rather than take it on trust. The two that matter:

  T-E2 (password leaked by our own code) was LIVE, not hypothetical --
  SmtpConfig derived Serialize over a plaintext String and the whole
  struct was POSTed on wasm.

  T-E7 (DoS via a hostile message) is the SMS A3 bug class. One inbound
  message containing emoji took down the SMS list on every frame until it
  was deleted; email bodies are more hostile, not less.

Sections that exist specifically to avoid overclaiming:

  * "Residual" notes on every mitigation. Secret does not zero on drop.
    We trust the platform root store; no certificate pinning. Header
    injection is handled by lettre, NOT by us -- which means the C1d proxy
    backend, which does not go through lettre, must sanitise or T-E4
    becomes unmitigated.
  * "What has not been tested": no live SMTP server has been contacted,
    the wasm path has never been built, and no IMAP code exists, so
    T-E1/T-E2 cover the SMTP direction only.
  * Four open risks ranked, each tied to a plan item, including two
    (proxy auth, keystore storage) that MUST land with C1d/C1f rather
    than after -- the proxy is only safer than on-device IMAP if its token
    is revocable and scoped.

Marks A1-A6 done in the plan. Phase A is complete.
2026-08-16 19:22:40 +00:00
nigig-ci
cce6889d35 docs(email): C1 decided — both backends, user-selectable; Phase 0 done
Some checks failed
Payment domain, storage, platform and UI / isolated-payment-tests (push) Has been cancelled
Payment domain, storage, platform and UI / payment-ui-tests (push) Has been cancelled
doc-engine / engine (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-map / test (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
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
C1 was the one blocking product decision in this plan. Answer: support
IMAP-on-device AND a server-side proxy, let the user pick, with a form
appropriate to each.

Recorded why that 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 is not new scope, it is scope that was already implied.

What makes it tractable is a trait boundary rather than two parallel UIs:
one `MailBackend` with two impls and a `BackendKind` discriminant on the
account. Everything already built -- grouping, preview_line, the inbox
list, the thread reader, unread handling -- sits ABOVE that line and
consumes `Vec<EmailMessage>` without caring where it came from. That was
deliberate in C2 and it is what keeps two backends contained.

The two forms genuinely differ (IMAP+SMTP wants two servers, two ports,
username and password; the proxy wants an HTTPS base URL and a token), so
this is a backend chooser followed by the matching form, not one form with
rows hidden behind a toggle. Broken into C1a-C1f, with the proxy first:
it is smaller, it is the only option on wasm, and it exercises the trait
boundary end to end.

One thing recorded rather than glossed: offering both DOUBLES the security
surface, and IMAP is the path that keeps a reusable password on the
device. A revocable proxy token is strictly safer than a password that
also unlocks the user's password resets. The setup UI should say which is
which instead of presenting them as equivalent.

Also marks Phase 0 complete -- 0.1 through 0.7, with 0.7 fixed upstream
by 005bed1 (i_tree 1.0.0 -> 0.19.0, exactly the fix predicted here).
2026-08-16 18:35:39 +00:00
nigig-ci
0c14f8d848 docs(email): correct dead commit SHAs; record the SMS parity mechanism
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
Three fixes to the plan, one of them a real defect in the document.

1. Every commit SHA it cited was dead. I wrote them before the final
   rebase, which rewrote them, so the progress log pointed at eleven
   references that `git cat-file -e` cannot resolve. A plan that cites
   nonexistent commits is worse than one that cites none. Remapped:
     28d0608 -> 5a5b817
     1ea9ad6 -> b91f97b
     50760e0 -> 18bbb7b
     169563f -> b701ce5

2. Documented HOW the SMS similarity is achieved, since "like the SMS
   list" is the requirement and prose does not prove it. Added a table
   naming the six shared components both features now consume from
   nigig-uikit/src/shared/conversation/ -- the preview row, its action,
   its props, the stack-navigation view, the message bubbles and the
   bottom-nav actions. Parity is structural, not cosmetic.

   Also recorded the consequence: those widgets are now load-bearing for
   two features, so an email change can regress SMS. The shared kit has
   no tests of its own. New item E6.

   And the one intentional divergence: email rows key on a normalised
   (lowercased) sender address, because Alerts@Bank.co.ke and
   alerts@bank.co.ke are one sender.

3. Updated item 0.7. The makepad `maps` break is fixed by ce0eaae, but
   that commit added `i_tree = "1.0.0"` to crates/apps/map and crates.io
   publishes only up to 0.19.0, so the workspace still does not resolve.
   Verified pre-existing by stashing all email changes and reproducing on
   a pristine tree. This remains the highest-priority blocker: while it
   holds, no crate in the repo can be verified on a runner.
2026-08-16 17:44:59 +00:00
nigig-ci
b701ce5eeb docs(email): assessment and live execution plan
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
Lands in REVIEWS/ rather than the repo root, where 35 markdown files
already compete for attention.

Records the audit (architecture, performance, bugs, design, security,
code quality) with each finding tied to evidence that was executed, not
inferred, and a progress log that marks what has shipped.

Two things worth reading even if you skip the rest:

  - I got the port-465 TLS finding WRONG on the first pass and wrote it
    up as critical credential exposure. Checking lettre 0.11.23's source
    showed relay() is implemented with the same three calls and
    TlsParameters::new already sets accept_invalid_certs: false and a
    TLS 1.2 floor. Downgraded to Medium and the error is recorded rather
    than quietly removed, because a document like this is worthless if
    you cannot tell which claims survived scrutiny.

  - C1 is the single blocking decision: IMAP on device vs a server-side
    proxy. The inbox list, thread reader and grouping are done and work;
    what they display is sample data until that is answered. The plan
    lays out the tradeoff and does not pretend it is a technical call.

Also notes that origin/main does not currently resolve — the makepad
bump in 86c9595 dropped the `maps` feature pageflipnav declares — which
is now item 0.7 and blocks CI verification for every crate, not just
this one.
2026-08-16 17:27:35 +00:00