Repository navigation
feat(register): flag a pull request that changes a register email address - #25
Conversation
…ch on its evidence pages
…tly and fail on unparsable entries
|
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 30 |
| Duplication | 0 |
AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
Pull Request Overview
✅ No blocking issues noticed
No merge-blocking findings were identified. The workflow’s end-to-end behavior remains unverified because it has not run in the target CI environment; the missing scenarios below should be covered before relying on the new job operationally.
The main implementation file has increased complexity and combines several responsibilities, which presents a maintainability risk but is not a blocker.
About this PR
- The new CI job has not yet been validated in the target GitHub workflow environment. Add or run an end-to-end workflow check covering base-branch checkout, permissions, pull-request event behavior, and warning annotation rendering.
Test suggestions
- Detect an email address added to an existing entry and include the previous addresses in the warning
- Treat domain casing as equivalent but local-part casing as a change
- Flag email addresses on an entry whose ID is new or renamed
- Fail when either register input contains invalid TOML
- Find email addresses in raw HTML markup and decoded main text, case-insensitively
- Report an email missing from all searchable evidence pages
- Treat missing evidence, failed evidence fetches, and non-HTML evidence as inconclusive
- Emit correctly escaped GitHub warning annotations for changed addresses
- Verify the complete CI job, including base-branch checkout and command execution
Prompt proposal for missing tests
Consider implementing these tests if applicable:
1. Emit correctly escaped GitHub warning annotations for changed addresses
2. Verify the complete CI job, including base-branch checkout and command execution
TIP Improve review quality by adding custom instructions
TIP How was this review? Give us feedback
| fn email_changes( | ||
| base: &[SourceFile], | ||
| head: &[SourceFile], | ||
| ) -> Result<Vec<(String, String)>, String> { | ||
| // Domains are case-insensitive; a local part may not be, so only the domain is folded. | ||
| let emails = |entry: &Entry| -> Vec<String> { | ||
| let fold = |address: &str| match address.rsplit_once('@') { | ||
| Some((local, domain)) => format!("{local}@{}", domain.to_lowercase()), | ||
| None => address.to_owned(), | ||
| }; | ||
| entry | ||
| .channels | ||
| .iter() | ||
| .filter(|c| c.kind == ChannelKind::Email) | ||
| .map(|c| fold(&c.address)) | ||
| .collect() | ||
| }; | ||
| for (domain, ids) in mail_domains { | ||
| record( | ||
| format!("email domain {domain}"), | ||
| ids, | ||
| check_mail(&lookup, domain), | ||
| ); | ||
| let parsed = |files: &[SourceFile]| -> Result<Vec<(String, Entry)>, String> { | ||
| let parse = |f: &SourceFile| { | ||
| toml::from_str(&f.text) | ||
| .map(|e| (f.name.clone(), e)) | ||
| .map_err(|e| format!("{} does not parse: {e}", f.name)) | ||
| }; | ||
| files.iter().map(parse).collect() | ||
| }; | ||
| let before: BTreeMap<String, Vec<String>> = parsed(base)? | ||
| .into_iter() | ||
| .map(|(_, entry)| (entry.id.clone(), emails(&entry))) | ||
| .collect(); | ||
| let mut changes = Vec::new(); | ||
| for (file, entry) in parsed(head)? { | ||
| let old = before.get(&entry.id); | ||
| for address in emails(&entry) | ||
| .into_iter() | ||
| .filter(|a| old.is_none_or(|old| !old.contains(a))) | ||
| { | ||
| let message = match old { | ||
| None => format!( | ||
| "new entry `{}` lists email {address}; check it on the entity's own pages", | ||
| entry.id | ||
| ), | ||
| Some(old) => { | ||
| let was = if old.is_empty() { | ||
| "none".to_owned() | ||
| } else { | ||
| old.join(", ") | ||
| }; | ||
| format!( | ||
| "`{}` gains email {address} (before: {was}); check it on the entity's own pages", | ||
| entry.id | ||
| ) | ||
| } | ||
| }; | ||
| changes.push((file.clone(), message)); | ||
| } | ||
| } | ||
| for (url, ids) in forms { | ||
| record( | ||
| format!("web form {url}"), | ||
| ids, | ||
| check_form(agent, &lookup, url), | ||
| ); | ||
| Ok(changes) | ||
| } | ||
|
|
||
| /// Print a GitHub warning annotation for each email address `email_changes` lists. Exits 0 | ||
| /// either way, like a stale `verified_at`; an unreadable register or entry exits 1. | ||
| fn emails_changed(base: &Path, head: &Path) -> ExitCode { | ||
| let load = |dir: &Path| { | ||
| ghostsoap::io::register::load(dir) | ||
| .map_err(|e| eprintln!("error: cannot read {}: {e}", dir.display())) | ||
| }; | ||
| let (Ok(base_files), Ok(head_files)) = (load(base), load(head)) else { | ||
| return ExitCode::FAILURE; | ||
| }; | ||
| let changes = match email_changes(&base_files.entries, &head_files.entries) { | ||
| Ok(changes) => changes, | ||
| Err(e) => { | ||
| eprintln!("error: {e}"); | ||
| return ExitCode::FAILURE; | ||
| } | ||
| }; | ||
| // A workflow command escapes `%`, CR and LF in its message, and `:` and `,` too in a | ||
| // property value. | ||
| let data = |s: &str| { | ||
| s.replace('%', "%25") | ||
| .replace('\r', "%0D") | ||
| .replace('\n', "%0A") | ||
| }; | ||
| for (file, message) in &changes { | ||
| let path = head.join(file).to_string_lossy().replace('\\', "/"); | ||
| let path = data(&path).replace(':', "%3A").replace(',', "%2C"); | ||
| let message = data(message); | ||
| println!("::warning file={path},title=Register email changed::{message}"); | ||
| } | ||
| lines | ||
| println!("{} email address(es) to check", changes.len()); | ||
| ExitCode::SUCCESS | ||
| } |
There was a problem hiding this comment.
🟡 MEDIUM RISK
Suggestion: This file now combines evidence processing, contact validation, and pull-request email comparison. Consider moving the email comparison and GitHub annotation logic into a focused module while preserving the CLI behavior and existing tests.
TIP React 👍 if this review is right, or reply with what's wrong and how it could be better to help improve the reviewer.



Summary
A register pull request can no longer repoint an entry's privacy email without a visible flag, and the register gate's boundary checks are pinned by tests.
register-emailsrunsxtask email-changes, comparing the register with the base branch's and annotating each email address an entry gains and every address of an entry whose id is new, so renaming an entry cannot hide a changed address. Domains compare case-insensitively; local parts do not. It warns and passes, as a staleverified_atdoes: a changed address is often legitimate, so a reviewer checks it on the entity's own pages. An entry that does not parse fails the job.src/core/register.rs.Evidence
src/core/register.rs: 163 caught, 7 missed, 4 unviable. Withemail_changesandemail_on_pagesstubbed, the 2 positive tests fail. The renamed-entry, recased-local-part and unparsable-entry tests fail on the first version ofemail_changes.After: 170 caught, 0 missed, 4 unviable; those tests pass.
cargo fmt --check, clippy-D warnings,cargo test --locked --workspace(131 tests), release build andregistry check(80 entries, 0 errors) pass on Windows 11.email-changeswith one head entry replaced by invalid TOML exits 1 naming the file.Merge danger
Door: two-way. Blast radius: CI
Adds one pull-request job; the drift issue may list emails missing from their evidence pages.
Unverified
The new CI job, including its base-branch checkout, has not run on GitHub before this PR. 14 entries with email channels have no evidence page, so their emails are not checked.