feat(store): add structured person profile primitives - #565
feat(store): add structured person profile primitives#565salmonumbrella wants to merge 16 commits into
Conversation
roborev: Combined Review (
|
c7af818 to
ac6a574
Compare
roborev: Combined Review (
|
ac6a574 to
2a3221d
Compare
roborev: Combined Review (
|
2a3221d to
58ac8d3
Compare
roborev: Combined Review (
|
58ac8d3 to
b60d539
Compare
roborev: Combined Review (
|
b60d539 to
43ee9d9
Compare
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
|
@wesm all you |
|
Thanks, I'll rebase this and review |
32ba084 to
6f58f7d
Compare
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
- fix(store): clean identity matches on source removal - fix(store): preserve observation provenance - fix(store): retire unsupported observation conflicts - fix(store): preserve complete identity evidence - fix(store): reconcile converged identity conflicts - Merge remote-tracking branch origin/main into structured-profile-primitives-v0191 - fix(store): preserve subset identity review data - fix(store): preserve profile lifecycle integrity - fix(client): accept idempotent service registration - fix(store): reconcile serialized source removal - chore(lint): restore CI baseline - fix(api): align profile fallback contract - fix(api): wire structured profile stores in daemon Generated with Codex
Address three review findings on the structured profile primitives: - A contradictory non-null provider user ID for an existing current observation now supersedes the row and records the new binding as a fresh observation, instead of being silently dropped. Generated conflicts left unsupported by the provider change are cleaned up. - System acceptance of an identity match candidate now requires the candidate to record which stable provider ID matched (non-null normalized_value); the caller-supplied basis label alone no longer suffices. - ValidateServiceScope rejects half-scopes (scope kind without value or the reverse) for optional-scope services and serviceless entries, and scope inputs are trimmed with blanks treated as absent, so malformed scopes cannot fragment identity keys. New ErrServiceScopeIncomplete maps to a 400 in the person profile API. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When a participant merge collapses a user-decided candidate with an observation-generated conflict for the same edge, the conflict state wins but the decision's decided_by, decided_at, and notes were copied only from candidates already in the conflict state — so the review history vanished, and a later conflict cleanup could demote the merged row to an undecided candidate with no trace of the decision. Fall back to the terminal decision's metadata (then any reviewed candidate's) when no conflict-state row carries any. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two identity-candidate hardening changes: - Conflict cleanup restores a collapsed terminal decision instead of demoting to an undecided candidate. A new nullable identity_match_candidates.pre_conflict_state column records the state a conflict should return to when its observation support disappears; participant merges set it when a single accepted or rejected decision loses to a conflict, opposing decisions leave it unset, and a fresh user decision clears it. Legacy databases gain the column via the existing ADD COLUMN migration list on both dialects. - Conflict support predicates now require the observation pair to share one address kind, matching generation (which only ever pairs same-kind observations). Previously a username conflict could stay alive indefinitely because a cross-kind pair (say username vs social) with the same normalized value still counted as support. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Candidate upserts now call ValidateServiceScope after resolving the service, so a required-scope service rejects unscoped candidates and half-scopes are refused, matching observation and contact-point writes. Blank normalized values are stored as absent rather than as empty strings, and the non-user acceptance guard rejects a blank stable provider ID. The shared trim helper is renamed trimmedOrNil now that it covers normalized values as well as scope parts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
roborev: Combined Review (
|
a6a2d45 to
bbb397b
Compare
roborev: Combined Review (
|
roborev: Combined Review (
|
What changed
Why
Msgvault needs one durable person profile with multiple emails, phone numbers, service-scoped usernames, and source evidence. Independent rows preserve per-value history, provenance, normalized lookup, and stable vCard property identity.
Similar names or contact points create review candidates rather than an irreversible merge. This continues roadmap issue #534.
Usage
Use
GET /api/v1/persons/{id}/profileto read the curated profile,PATCH /api/v1/persons/{id}/profilewith the current revision to apply anatomic typed change, and
GET /api/v1/persons/{id}/profile/historyto inspectprior values. Communication services are available under
/api/v1/communication-services.