git.lucas.co / cce-secrets
secrets manager
git clone https://git.lucas.co/cce-secrets.git

commitdfd47e0c6ff9f5e42acc854706900c38ffeb0558
parent28c73892a1
authorLucas Galante <[email protected]>
date2026-09-21 12:15
KEYRING-SYNC.md: phase 1 results — 168 of 171 adopted, what phase 2 inherits

The hidden-dialog gotcha (main window stacked over the Authorize
dialog), the 1Password Account item that must never be mirrored, the
cce-mail keyring items that look like logins, and why the installed
binary had to precede the state flip.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

 KEYRING-SYNC.md | 75 +++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 65 insertions(+), 10 deletions(-)

diff --git a/KEYRING-SYNC.md b/KEYRING-SYNC.md
index 2679e3a..eaefaba 100644
--- a/KEYRING-SYNC.md
+++ b/KEYRING-SYNC.md
@@ -173,9 +173,10 @@ that is irrelevant, and there is no resident unlock to keep warm.
 
 # Scoping: 1Password as the interchange (option 1, 2026-09-21)
 
-Status: **phase 0 measured (2026-09-21), nothing built.** App and `op`
-installed, integrated, signed in. The oneshot timer does not survive the
-measurements; see "Phase 0 results" at the end.
+Status: **phase 1 shipped (2026-09-21).** `adopt` paired 168 of 171 keyring
+logins with 1Password and the base is keyed by item id; the kdbx timer is
+stopped and `sync` refuses until phase 2's daemon. See "Phase 0 results"
+and "Phase 1 results" at the end.
 
 ## Goal
 
@@ -299,7 +300,10 @@ several vaults can be mirrored, each entry keeping its `op-vault`.
 `adopt` is the migration step the kdbx never needed: the keyring already
 holds the 168 entries, and after the CSV import so does 1Password, so the
 first run must **pair, not copy**. Match on (title, username), exact and
-case-sensitive; stamp `op-item`/`op-vault` on the match, drop `kdbx-*`; print
+case-sensitive, with the url as the tiebreaker when that alone is
+ambiguous; stamp `op-item`/`op-vault` on the match (the `kdbx-*`
+attributes stay until the kdbx backend retires, so an accidental kdbx run
+still pairs by uuid instead of re-creating everything); print
 every entry unmatched on either side and stop there. Duplicated
 (title, username) pairs on either side are refused, listed, and left to the
 person — a wrong pairing here silently cross-links two accounts, which is
@@ -387,10 +391,10 @@ unacceptable however native the window is. Measure it before anything else
    from a `systemd-run --user` unit; time how long the authorization lasts
    and whether the prompt appears in `cce-authenticator`.~~ **Done 2026-09-21,
    results below.** The timer is dead; the daemon replaces it.
-1. **`Interchange` trait + `OnePassword` backend + `adopt --dry-run`.** The
-   kdbx backend moves behind the trait unchanged; `cargo test` on the
-   pairing logic against a fixture list. `adopt` for real once the dry run
-   pairs 168/168 (or the leftovers are explained).
+1. ~~**`Interchange` trait + `OnePassword` backend + `adopt --dry-run`.**~~
+   **Done 2026-09-21** (fab49c8, 28c7389); results below. The kdbx backend
+   did *not* move behind the trait yet — an unexercised impl is dead code;
+   it joins when phase 2 rewires `sync`.
 2. **`sync` on the new backend.** Exercise the full table in an isolated
    `dbus-run-session` keyring against a throwaway vault, as phase 2 above
    did against a fixture kdbx: edit both ways, delete both ways, conflict,
@@ -405,8 +409,9 @@ unacceptable however native the window is. Measure it before anything else
 
 1. Which vault(s) to mirror — Personal only, or everything `op item list`
    can read?
-2. `adopt` match key: (title, username) exact, or also fall back to URL
-   host + username for retitled entries?
+2. ~~`adopt` match key: (title, username) exact, or also fall back to URL
+   host + username for retitled entries?~~ Answered: url as the tiebreaker
+   for duplicate (title, username) only — two Microsoft tenants needed it.
 3. Does the CSV round trip carry TOTP seeds, and should
    `cce-authenticator` then read them from 1Password directly (its own
    decision, as before)?
@@ -512,3 +517,53 @@ app's approval entirely.
    present as one, the authorization might be remembered across restarts
    the way it is for a terminal window. Not needed for the design; nice
    if cheap.
+
+## Phase 1 results (2026-09-21)
+
+`cce-keyring-sync adopt --vault Personal` against the live keyring and the
+freshly imported vault:
+
+| | |
+| --- | --- |
+| keyring logins | 171 |
+| 1Password logins after cleanup | 172 |
+| paired and stamped | **168** |
+| 1Password only | 4: 1Password's own account item, `secure.bankofamerica.com` (was in the kdbx Recycle Bin — the CSV export carries the bin), and two test entries |
+| keyring only | 3: two `…@cce-mail:default` items cce-mail writes with a `UserName` attribute, and one test entry |
+| field drift on a pair | 1 (notes) — base timestamp left unknown so the first sync takes 1Password's value |
+
+The 168 all pair on exact (title, username); four needed the url tiebreaker
+(two Microsoft tenants, two xferrecords accounts). The first dry run
+refused on 23 import artefacts — Recycle Bin entries the CSV export had
+resurrected, eight copies of one test login among them — which were
+archived in 1Password by hand; the per-copy report (url, timestamp, id)
+is what made that a five-minute job.
+
+Things learned that phase 2 has to carry:
+
+- **The Authorize dialog can be hidden.** It is a 400×370 floating window
+  the app raises via xdg-activation at the same origin as its main window;
+  when the main window was opened later it sat on top, and three dialogs
+  in a row timed out unseen while the app log showed each one shown. Focus
+  went to the dialog; the compositor did not raise it. Filed against
+  cce-compositor. Until fixed: keep the main 1Password window closed or
+  moved when a sync is expected to prompt.
+- **`1Password Account (…)` must never be mirrored.** It is the item
+  1Password creates for the account itself — Secret Key and account
+  password inside. The sync's "1Password only → keyring" rule would copy
+  it into gnome-keyring. Phase 2 skips it (title prefix `1Password
+  Account`; better, its `category` if the list ever distinguishes it).
+- **cce-mail's keyring items look like logins** (they carry `UserName`),
+  so the sync will create them in 1Password at the first run, as the kdbx
+  sync adopted them before (that is where the `…@cce-email:default`
+  debris in the CSV came from). Either accept that — mail passwords in
+  1Password are not wrong — or have phase 2 exclude items whose label
+  matches `*@cce-mail:*`. Decide before the first real sync.
+- **The old binary must never see the new state.** The version-2 base is
+  keyed by item id; the kdbx `sync` reading it would treat every kdbx
+  entry as never seen and re-create all of them in the keyring. Hence the
+  install-before-adopt order, the `backend` guard in every kdbx path, and
+  the backup `state.json.kdbx-<ts>` for a rollback (restore it and
+  re-enable the timer; the stamps are harmless to the kdbx path).
+- The cce-secrets Sync button now runs a `sync` that refuses; its status
+  line shows the refusal text until the daemon lands.