Contacto
Product Brief · v0.3 · 2026

A trustworthy
address book.

Local-first unifier and cleanup for Apple and Google contacts. Safety over surprise.

Apple Contacts Google Contacts macOS · Tauri · Rust
TECHNICAL SOVEREIGN
—— MVP PRODUCT SPEC
Contacto· 02 Problem
The problem

Two address books,
one messy human.

Most people have Apple and Google contacts in parallel — half duplicated, half stale, half junk. Existing tools either ignore it or silently auto-merge and corrupt the record.

APPLE ICLOUD
2,418
contacts
Source A
GOOGLE CONTACTS
3,107
contacts · incl. Other Contacts
Source B
OVERLAP · DUPLICATES · JUNK
~41%
of records need review
UNRESOLVED
02 · PROBLEMCONTACTO
Contacto· 03 Positioning
Positioning

A safe, local-first contact unifier and cleanup tool for macOS.

WHAT IT IS
  • Apple + Google unification
  • Visible provenance
  • Explainable dedupe
  • Dry-run and backup before writeback
WHAT IT IS NOT
  • CRM / pipeline
  • Social graph / enrichment
  • Team / shared directory
  • Silent auto-merge
03 · POSITIONINGCONTACTO
Contacto· 04 Principles
Principles

Four rules the product will not violate.

01
Local-first

Normalization, dedupe, merges, backups — all happen on your machine.

02
Safety over
surprise

No writeback without explicit action, a preview, and a backup.

03
Provenance
is first-class

Every value carries source, lineage, and trust — visible in the UI.

04
Boring
data model

Explicit, inspectable, deterministic. No hidden identity graphs.

04 · PRINCIPLESCONTACTO
Contacto· 05 Core loop
The core loop

Every feature serves one seven-step loop.

01
Connect
Apple + Google sources
02
Ingest
Normalize schema
03
Unify
One card per person
04
Detect
Duplicates + malformed
05
Review
Simulate + dry-run
06
Backup
Default-on snapshot
07
Writeback
Apply approved changes
RETURN TO STEP 01 ON EVERY SYNC · INCREMENTAL DEDUPE ONLY
05 · CORE LOOPCONTACTO
Contacto· 06 Data model
Data model

Person ≠ SourceRecord.

One concrete row from Apple or Google is a SourceRecord. One real human, assembled from many rows, is a Person.

Grouping never mutates the underlying sources. That separation is why merges, unmerges, and restores work cleanly.

sr_0482 Google
sr_0611 Apple
sr_1298 Google
PERSON · p_0091
CN
C. Nolan
3 records
Apple Google
06 · DATA MODELCONTACTO
Contacto· 07 Provenance
Differentiator · 01

Every value knows where it came from.

CONTACT METHODS
WORK EMAIL
c.nolan@syncopy.com
Apple
PRIMARY MOBILE
+44 20 7946 0958
Google
PERSONAL EMAIL
chris.nolan@me.com
Apple Google

For every field value, we track:

· Source type + account
· Raw + normalized value
· First + last seen
· Source last-modified
· Origin: imported / edited / merged
· Trust level
· Sync status
· "Why this value won"
07 · PROVENANCECONTACTO
Contacto· 08 Completeness
Differentiator · 02

Completeness is configurable, per source.

An email-only Google Other Contact is fine. An Apple personal contact with no phone and no email isn't. You write the rule.

Apple Contacts ● COMPLETE
REQUIRED
phone OR email
DEFAULT
min 2 non-empty fields
photo-only flagged
Google · My Contacts ● COMPLETE
REQUIRED
phone OR email
DEFAULT
name required
social-only flagged
Google · Other ● PARTIAL OK
REQUIRED
email only is OK
DEFAULT
no name required
not flagged as junk
OUTPUT CLASSIFICATION · COMPLETE  ·  INCOMPLETE  ·  MALFORMED  ·  INTENTIONALLY PARTIAL  ·  NEEDS REVIEW
08 · COMPLETENESSCONTACTO
Contacto· 09 Dedupe
Dedupe engine

Blocking, not all-pairs.

Each record generates block keys. Only records that share at least one block proceed to scoring. Incremental, explainable, and it scales.

BLOCK KEYS · A SOURCE RECORD MAY BELONG TO MANY
normalized_email_exact normalized_phone_exact phonetic_name_key name_prefix + domain name_prefix + company address_fingerprint photo_hash nickname + phone_last4 given + family_normalized
INCREMENTAL
  1. 01 Normalize changed record
  2. 02 Regenerate its block keys
  3. 03 Fetch candidates from affected blocks
  4. 04 Rescore only impacted pairs
  5. 05 Update queue · archive stale
09 · DEDUPECONTACTO
Contacto· 10 Scoring
Scoring, explained

No score without an explanation path.

DEFAULT WEIGHTS · RAW ADDITIVE, CLAMPED TO [0, 1]
exact external linked ID +1.00
exact email match +0.55
exact phone match +0.50
exact photo hash +0.35
strong name similarity +0.20
conflicting emails −0.35
conflicting phones −0.30
explicit never-merge −1.00
OUTPUT BANDS
Exact / High-certainty
Auto-suggest, still reviewed
0.95 – 1.00
Strong candidate
Surfaced prominently
0.80 – 0.94
Review candidate
Needs user judgment
0.60 – 0.79
Not surfaced by default
Available on demand
< 0.60
10 · SCORINGCONTACTO
Contacto· 11 Merge
Merge preview

Nothing commits until you say so.

Before a merge applies, you see the records involved, the pair score, the field conflicts, the winning values we'd pick, and the alternates we'd keep.

  • · Accept recommended values
  • · Override field-by-field
  • · Preserve both where supported
  • · Lock a field, or "never merge"
MERGE PREVIEW · p_0091
Christopher Nolan
Score 0.92 ● STRONG
DISPLAY NAME Christopher Nolan C. Nolan APPLE WINS
WORK EMAIL c.nolan@syncopy.com APPLE
MOBILE +44 20 7946 0958 GOOGLE
ORG Syncopy Inc. Syncopy KEEP BOTH
BACKUP REQUIRED · AWAITING REVIEW
Defer Simulate
11 · MERGE PREVIEWCONTACTO
Contacto· 12 Safety
Safety

Three gates before any change touches a source.

01
Dry-run

Simulate the full operation. See creates, updates, deletes, conflicts, and blocks — without touching the source.

mutates_source: false
artifact: storable
02
Backup

On by default. Snapshot the exact records and values about to change, with manifest, ruleset, and app version.

opt-out, not opt-in
failure → blocks apply
03
Restore

Single contact, one operation's batch, or the full snapshot. Rehydrate review state too.

writes new history
never erases past

Apply is blocked if dry-run fails, backup fails, or required permissions are missing.

12 · SAFETYCONTACTO
Contacto· 13 History
History

A complete, filterable audit trail.

Every operation writes an event: actor, type, affected contacts, result, backup ID. Filter it, jump from event to contact, restore from the snapshot attached.

14:32:08 writeback applied · 14 contacts → Apple bk_0043
14:31:55 backup operation_scoped · 28 records bk_0043
14:30:12 merge approved · Nolan · score 0.92
14:22:00 sync_run google · +47 · ∆12 · −3
14:20:11 rule_change Google · Other · email-only OK
14:18:03 restore reverted batch · bk_0041 · 6 contacts
13 · HISTORYCONTACTO
Contacto· 14 Architecture
Architecture

Rust core owns truth. Svelte renders and asks.

FRONTEND
Svelte 5 UI
· Nano Stores (domain)
· Svelte runes (local UI)
· Vite · TypeScript
sourceStore · contactStore
dedupeStore · writebackStore
backupStore · historyStore
IPC BOUNDARY
Tauri commands + events
STORAGE
SQLite · JSON blobs
BACKEND
Rust core
· Apple Contacts bridge
· Google People API
· Normalization + matching
dedupe · writeback-planner
backup · restore
policy-eval · history
14 · ARCHITECTURECONTACTO
Contacto· 15 MVP scope
MVP scope

Ten things a user can do on day one.

01 Connect Apple + Google
02 Import and normalize all records
03 See where each value came from
04 Configure completeness per source
05 Review duplicates with explainable scores
06 Simulate merges, cleanup, writeback
07 Automatic backup before apply
08 Write changes back to Apple safely
09 Restore from backup
10 Audit what happened, after
15 · MVP SCOPE · EXPLICITLY OUT: FACEBOOK · CRM · ENRICHMENT · TEAMS · AUTO-MERGECONTACTO
Contacto· 16 What's next
Closing

Contacts are the beachhead.

The architecture — local-first, provenance, explainable scoring, backup-by-default — generalizes to any messy personal data.

NEAR-TERM
Exchange & CardDAV connectors. Richer policy rules. Cluster-level review.
MID-TERM
Apple-specific backup fidelity. Shared review artifacts. Read-only delegate mode.
LONG-TERM
A general-purpose safety layer for personal data: calendars, files, messages.
16 · WHAT'S NEXTCONTACTO · v0.3