## Summary
`Rules::validate()` was flagging `DuplicateLabel` when the same label
appeared in both a Trash rule and a Delete rule. This is intentional
two-stage processing: trash first (recoverable), delete later
(permanent). The validator should only flag a label appearing in two
rules of the **same action type**.
Fix: key the duplicate-label check on `(label, action)` rather than
`label` alone.
## Test plan
- [x] New test
`test_validate_same_label_different_actions_not_duplicate` — confirms
Trash+Delete on the same label passes validation (was failing before
this fix)
- [x] Existing `test_validate_duplicate_label_reported` still passes —
same label + same action is still flagged
- [x] Full test suite: 125 unit tests pass
- [x] `cargo fmt --check`, `cargo clippy -- -D warnings`, `cargo audit`
clean
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
- New `cull-gmail rules [RULES] validate` subcommand validates a rules
file without executing any actions or requiring Gmail credentials
- `Rules::validate()` checks each rule for: non-empty label set, valid
retention period (parseable as `MessageAge`), valid action
(`Trash`/`Delete`)
- Cross-rule check for duplicate labels (same label in multiple rules)
- Returns all issues collected, not just the first
- Exits 0 if valid, non-zero (with issue list on stderr) if invalid
- Missing rules file fails with an error rather than creating defaults
New public API: `cull_gmail::ValidationIssue` enum with `Display` impl.
## Test plan
- [x] Unit tests: `Rules::validate()` — valid rules, empty labels,
invalid retention, empty retention, invalid action, duplicate labels,
multiple issues collected
- [x] CLI integration tests: valid file → exit 0; invalid file → exit
non-zero + "Rule #1" in output; duplicate labels → exit non-zero + label
name in output; missing file → exit non-zero
- [x] `cargo fmt --check` clean
- [x] `cargo clippy -- -D warnings` clean
- [x] Full test suite: 130+ tests pass
- [x] `cargo audit` clean
## Usage in gmail-culler CI
The validation workflow can now run `cull-gmail rules validate` on the
committed rules file to verify it is syntactically and semantically
correct — without needing Gmail credentials or executing any rules.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
- `cargo release changes` failed because `readme = "README.md"` in
`Cargo.toml` resolves to `crates/cull-gmail/README.md` which did not
exist
- `release-hook.sh` was writing the generated README only to the
workspace root (`../../README.md`), not the crate directory
- `cargo release changes` runs before the pre-release hook, so no
hook-generated file was ever present
Fix:
1. Update `release-hook.sh` to write README to crate directory first,
then copy to workspace root for GitHub display
2. Commit initial `README.md` to crate directory so the file exists
before any hook runs
## Test plan
- [ ] CI validation passes
- [ ] `cargo release changes` succeeds (README.md present in crate dir)
- [ ] Release pipeline completes without "readme does not appear to
exist" error
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
- `release-hook.sh` was writing README only to `../../README.md`
(workspace root)
- `Cargo.toml` has `readme = "README.md"` which cargo-release resolves
relative to the crate directory (`crates/cull-gmail/README.md`)
- cargo-release failed with "readme does not appear to exist"
Fix: write README to `README.md` (crate directory) first, then copy to
`../../README.md` for GitHub display.
## Test plan
- [ ] CI validation passes
- [ ] Release pipeline succeeds (README found at crate level)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [tempfile](https://stebalien.com/projects/tempfile-rs/)
([source](https://redirect.github.com/Stebalien/tempfile)) |
workspace.dependencies | minor | `3.25.0` → `3.27.0` |
---
### Release Notes
<details>
<summary>Stebalien/tempfile (tempfile)</summary>
###
[`v3.27.0`](https://redirect.github.com/Stebalien/tempfile/blob/HEAD/CHANGELOG.md#3270)
[Compare
Source](https://redirect.github.com/Stebalien/tempfile/compare/v3.26.0...v3.27.0)
This release adds `TempPath::try_from_path` and deprecates
`TempPath::from_path`.
Prior to this release, `TempPath::from_path` made no attempts to convert
relative paths into absolute paths. The following code would have
deleted the wrong file:
```rust
let tmp_path = TempPath::from_path("foo")
std::env::set_current_dir("/some/other/path").unwrap();
drop(tmp_path);
```
Now:
1. `TempPath::from_path` will attempt to convert relative paths into
absolute paths. However, this isn't always possible as we need to call
`std::env::current_dir`, which can fail. If we fail to convert the
relative path to an absolute path, we simply keep the relative path.
2. The `TempPath::try_from_path` behaves exactly like
`TempPath::from_path`, except that it returns an error if we fail to
convert a relative path into an absolute path (or if the passed path is
empty).
Neither function attempt to verify the existence of the file in
question.
Thanks to [@​meng-xu-cs](https://redirect.github.com/meng-xu-cs)
for reporting this issue.
###
[`v3.26.0`](https://redirect.github.com/Stebalien/tempfile/blob/HEAD/CHANGELOG.md#3260)
- Support `NamedTempFile::persist` on RedoxOS
([#​393](https://redirect.github.com/Stebalien/tempfile/issues/393))
(thanks to
[@​Andy-Python-Programmer](https://redirect.github.com/Andy-Python-Programmer)).
</details>
---
### Configuration
📅 **Schedule**: Branch creation - Between 12:00 AM and 05:59 AM, on day
24 of the month ( * 0-5 24 * * ) (UTC), Automerge - At any time (no
schedule defined).
🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.
♻ **Rebasing**: Never, or you tick the rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box
---
This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/jerus-org/cull-gmail).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My41OS4wIiwidXBkYXRlZEluVmVyIjoiNDMuNTkuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [toml](https://redirect.github.com/toml-rs/toml) |
workspace.dependencies | patch | `1.0.0` → `1.0.6` |
---
### Configuration
📅 **Schedule**: Branch creation - Between 12:00 AM and 05:59 AM, on day
24 of the month ( * 0-5 24 * * ) (UTC), Automerge - At any time (no
schedule defined).
🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.
♻ **Rebasing**: Never, or you tick the rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box
---
This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/jerus-org/cull-gmail).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNi41IiwidXBkYXRlZEluVmVyIjoiNDMuNTkuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
## Summary
- Migrates flat crate structure to mandatory workspace layout
(`crates/cull-gmail/`)
- Replaces old 4-workflow flag-based CI with the 3-file pipeline model
at toolkit 4.9.6
- Fixes pre-existing broken doctest in `rules.rs`
## Changes
### Workspace migration
- `Cargo.toml` → workspace manifest with `[workspace.package]` and
`[workspace.dependencies]`
- `crates/cull-gmail/Cargo.toml` — crate manifest inheriting from
workspace
- `src/`, `tests/`, `CHANGELOG.md` moved to `crates/cull-gmail/`
- `docs/lib/` moved to `crates/cull-gmail/docs/lib/` (required for
`include_str!` to work with `cargo package`)
- `crates/cull-gmail/release.toml` — crate-specific config, tag format
`cull-gmail-v{{version}}`, **PRLOG replacements removed**
- `crates/cull-gmail/release-hook.sh` — updated paths for workspace
layout
- `release.toml` (workspace) — shared signing/branch settings only
### PRLOG
- Added `## [Unreleased]` section at top
- Updated reference links to use `cull-gmail-v*` tag format
### CI files
- `config.yml` — validation-only at toolkit 4.9.6 (no
`trigger_pipeline`)
- `update_prlog.yml` (new) — pr-merged event trigger
- `release.yml` — standard toolkit jobs: `calculate_versions` →
`release_crate` (with `build_binary: true`) → `release_prlog`
### Anchor tag
- `cull-gmail-v0.1.4` created at `v0.1.4` commit — gives nextsv a
baseline for crate-prefixed version calculation
### Bug fix
- Fixed doctest in `rules::Rules::get_rules_by_label_for_action`: added
missing `EolAction` import and corrected method name
## Post-merge setup required (CircleCI project settings)
- `update_prlog.yml` must be set as the trigger for the "pull_request
merged" event
- `release.yml` must be set as a manual trigger pipeline
🤖 Generated with [Claude Code](https://claude.com/claude-code)