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). |
|||
|
|
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. |
|||
| 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. |
|||
| 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. |
|||
| 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). |
|||
| 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.
|
||
|
|
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. |