feat(desktop): track catalog provenance on a copied persona

Adding another member's shared agent minted a fresh local persona with a new
UUID and no link back to the publication it came from, so the catalog had no
way to tell an already-added entry from a new one and every click added
another copy.

A copied persona now carries the publication's coordinate — publisher pubkey
plus the persona's d-tag — through create_persona and the unified agent
store, which is enough for the catalog to answer "already added" for a
foreign entry. The coordinate is normalized at the command boundary because
a value that cannot match a publication silently restores the duplicate it
exists to prevent. Absent on every non-catalog path, and skipped when
serializing, so existing records neither break nor gain a null key.

The create path moves to personas/create.rs, mirroring the edit-path split,
and CatalogSource to its own module to keep both files under the size cap.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This commit is contained in:
Will Pfleger
2026-07-27 13:53:34 -04:00
parent b6439c7505
commit 5fffd16a1e
39 changed files with 368 additions and 76 deletions

View File

@@ -94,10 +94,10 @@ const overrides = new Map([
// 3-phase (stage/stop/commit) + commit_cascade_agents injectable helper for
// retry-safety. Load-bearing reviewer-required change; queued to split.
// Consolidation removed the legacy persona-card import/export codecs.
// Retired-in-place ratchet: the edit path moved to personas/update.rs,
// taking mod.rs from 1003 to 779. Kept as a ratchet so the edit-path split
// cannot silently refill.
["src-tauri/src/commands/personas/mod.rs", 779],
// Retired-in-place ratchet: the edit path moved to personas/update.rs and
// the create path to personas/create.rs, taking mod.rs from 1003 to 719.
// Kept as a ratchet so neither split can silently refill.
["src-tauri/src/commands/personas/mod.rs", 719],
// #1418 read-path fix: get_thread_replies' blocker fix (shared TIMELINE_KINDS
// const + build_thread_replies_filter helper, mirroring the channel sibling so
// the two p-gate filters can't drift) plus two guard unit tests. The file was
@@ -381,7 +381,9 @@ const overrides = new Map([
// Available both-present AND adapter-present/CLI-absent — the selectability
// regression guard), bound to an injectable resolver so the tests stay
// PATH-independent.
["src-tauri/src/managed_agents/discovery/tests.rs", 1871],
// +2 (1871 -> 1873): the AgentDefinition and ManagedAgentRecord fixtures each
// set the new mandatory `catalog_source` field.
["src-tauri/src/managed_agents/discovery/tests.rs", 1873],
// identity-import-keyring: the identity resolution state machine's behavioral
// matrix (46 tests over FakeIdentityStore — probe × marker × file cells,
// adoption / read-back-corruption / marker-failure arms, recovery-mode
@@ -576,7 +578,9 @@ const overrides = new Map([
// computing had_* so stale materialized snapshot bytes can never be tagged
// BuzzExplicit and shadow the definition/global fallthrough; the dead
// persona-model re-tag branch replaced; two new regression tests added.
["src-tauri/src/commands/agent_config.rs", 1110],
// +2 (1110 -> 1112): the agent_record and persona_with_model test fixtures
// each set the new mandatory `catalog_source` field.
["src-tauri/src/commands/agent_config.rs", 1112],
// codex-install-auto-restart review-fixes: should_restart_after_install
// takes pid_alive:bool (pure predicate, no OS-dependent call); 3 racy
// cache tests replaced with 6 pure availability_drift predicate tests;