feat(domain): add shared/ids.rs entity identifier newtypes (task T-108)#24
Conversation
Introduce TaskId, RunId, WorktreeId (UUID v4) plus ProfileId, PrId
(i64 row IDs) per ARCHI §4.1. Each is `#[serde(transparent)]` so the
JSON wire shape stays identical to the inner primitive — TypeScript
bindings will see `string`/`number`, not `{0:"…"}`. Two private
declarative macros (`uuid_newtype!`, `i64_newtype!`) collapse the
duplicate impl blocks at compile-time without runtime cost.
Type-system distinction is the whole point: `let _: RunId = TaskId::new()`
fails at compile-time with `expected RunId, found TaskId`, killing
"passed wrong identifier" bugs across bounded contexts. UUID newtypes
ship `new()` (v4), `from_uuid()`, `Default` (clippy `new_without_default`)
and Display delegates to the inner UUID/i64 for grep-friendly logs.
Domain stays import-pure: only `serde` + `uuid` from the libs already
permitted in `domain/mod.rs` — `tests/architecture.rs` still passes.
19 unit tests cover v4 emission, from_uuid roundtrip, equality + Hash,
Display matching, serde transparent roundtrip (UUIDs as bare quoted
strings, i64s as bare integers), Default v4, Send + Sync, and two
new() calls producing distinct UUIDs. Coverage 100% lines / 100%
regions / 100% functions on `ids.rs` (domain target ≥90%).
Blocks T-109 `Task` model wiring.
Hoist `#[must_use]` from each ctor (`new`, `from_uuid`) onto the struct declaration itself so the lint covers `Default::default()` plus any future fallible constructor uniformly — net `-2` attributes per UUID newtype, identical clippy verdict, stronger contract. Drop `distinct_uuid_newtypes_do_not_compare_equal_by_construction`: its body was a runtime-only `assert_eq!(task.0, run.0)` already covered by the three `*_from_uuid_preserves_value` tests, and the comment claimed a "compile-error guarantee" the test never actually exercised (no `compile_fail` doctest). Trim redundant inline comment in `task_id_serde_roundtrip_is_transparent` that restated what the assertion already says. Coverage `ids.rs` stays at 100% lines / 100% regions / 100% functions; `cargo test --workspace` 90 → 89 (1 redundant test removed); clippy + fmt clean.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds five new entity identifier newtypes ( ChangesEntity Identifier Types
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.md`:
- Line 15: Within the [Unreleased] block there is a duplicate "### Changed"
heading; remove the extra "### Changed" header and merge its bullet points under
the existing "### Changed" section so there's only one such heading in that
release block (also check for and consolidate any duplicate "### Added" headings
similarly).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: c6ba5501-50f2-425c-a3be-3a68e889051b
📒 Files selected for processing (2)
CHANGELOG.mdsrc-tauri/src/domain/shared/ids.rs
…sed] blocks Address CodeRabbit MD024 finding on PR #24: the two new T-108 headings (`### Changed` for the /simplify follow-up, `### Added` for the feat commit) were stacked above the pre-existing PR #23 / T-107 blocks, creating two same-name headings inside the same `[Unreleased]` section. Move both T-108 bullets to the top of their respective existing blocks instead so the new content no longer adds duplicate headings. Pre-existing duplicate headings further down the section (`### Changed` at the old T-105/T-104 simplify block, `### Added` at the older T-104/ T-103 block) were already in `main` before this branch and are out of scope for this PR.
Summary
Introduce five validated newtype identifiers for entity IDs across bounded contexts —
TaskId,RunId,WorktreeId(UUID v4) andProfileId,PrId(i64 row IDs) per ARCHI §4.1. Type-system enforcesRunId != TaskIdeven though both wrap the same primitive, killing the "passed wrong identifier" bug class.Implements Task #108 of Sprint 1 (foundational domain layer). Blocks T-109 (
Taskentity) and unblocks T-131 (architecture test for forbidden imports).Why
Hexagonal architecture + DDD requires validated newtypes to distinguish across contexts. Raw
Uuid/i64allows confusinglet task_id: Uuid = run_id; // oops. Newtype wrappers + compile-time type distinction are Rust's idiomatic answer.#[serde(transparent)]keeps JSON wire shape identical ("uuid-string", not{0:"uuid-string"}) so frontend TypeScript bindings remain simple.Changes
src-tauri/src/domain/shared/ids.rs: Five newtypes with declarative macros (uuid_newtype!,i64_newtype!) to avoid duplication. Each derivesDebug,Clone,Copy,PartialEq,Eq,Hash,serde::Serialize,serde::Deserialize. UUID types havenew() -> Self(v4),from_uuid(),Default(satisfies clippynew_without_default); both types haveDisplayfor logging.[Unreleased].HashSet, Display, serde transparent roundtrip (UUID → bare"string", i64 → bare123),Send + Syncbounds,Defaultconsistency.tests/architecture.rs(T-131) pre-exists and remains green — domainmod.rsalready forbids tokio/libsql/reqwest/tauri/git2/keyring/portable_pty/notify, and this file imports only std/serde/uuid (whitelist-permitted).Testing
All checks green: 89 tests passed,
ids.rs100% line/region/function coverage (domain target ≥90%).Related Issues
Notes for Reviewer
uuid_newtype!,i64_newtype!) expand inline at L56-61. Not exported (no#[macro_export]), so scope is tight. Considered proc macro (overkill for 2 shapes, 5 uses) and trait-based blanket impl (impossible without HKT — each type is distinct, and you cannot blanket-implDefault/Displayover arbitrary newtypes).pub struct TaskId(pub Uuid)exposes the inner field as per T-108 task spec technical-details. Future encapsulation viaas_uuid()accessors is deferred (belong to an ADR if strictness is desired).#[serde(transparent)]rationale: JSON shape stays identical to the inner type — UUID serializes as bare quoted string, i64 as bare integer. This is critical for TypeScript binding simplicity at the IPC boundary; codegen (T-117/T-121) will seetype TaskId = stringvia ts-rs export, not a wrapped object.80a2d68): hoisted#[must_use]from method-level to struct-level so the lint covers all constructors uniformly (includingDefault::default()). Dropped a misleading test (distinct_uuid_newtypes_*) whose comment claimed a "compile-error guarantee" but only did runtime checks.Checklist
ids.rs)Summary by CodeRabbit
New Features
Improvements
Tests & Documentation