feat(identity): persist authorized unlink after inspect - #202
cursor[bot] wants to merge 22 commits into
Conversation
A buyer who links an anonymous assessment to a Keyverse account must still see that link after process restart. Persist assessment_participant plus append-only link and link-end evidence, reload through the domain lifecycle, and fail closed on conflicting replay or a subject already bound to another participant. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Keep TRACEABILITY, ADR-0020, ERD, and as-built schema pointing at the opened persist/reload vehicle instead of an unnamed Active PR. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Apply each identity link and then its matching ends in one transaction so a complete in-memory unlink+relink aggregate survives restart. Cover one-shot persist, exact replay, and subject reuse after unlink. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Add a tenant-scoped current-subject lookup so a returning Keyverse login can find the same product-owned participant after the anonymous session token is gone. Ended or replaced subjects stay unfindable until they are current again. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Remove the accidentally committed build tree and ignore /target so later local verification cannot leak compiler outputs into the identity-link successor. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
A missing current_participant_identity_link row no longer hides a returning Keyverse login or lets another participant bind the same issuer-scoped subject. Lookup and uniqueness now read append-only link rows that have no matching end. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Name the Active PR vehicle as the successor of #124 so TRACEABILITY, ADR-0020, and the as-built schema do not treat projection-only lookup as the landing contract. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Exact replay of the same identity-link history now reconciles the derived current projection so operator repair cannot hide a returning login behind a missing unique enforcer or leave a stale row after unlink. Name Active PR #133 in TRACEABILITY instead of superseded #124. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
After dump restore the unique enforcer can be missing or stale even though unterminated history is intact. Rebuild every current row from that history, fail closed on two unterminated holders of the same subject, and prefer this successor over #147. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Keep TRACEABILITY, ADR-0020, ERD, and as-built schema pointing at the opened restore-reconcile vehicle instead of an unnamed successor of #147. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Add a read-only inspect so operators can see missing or stale unique-enforcer rows after dump restore, fail closed on two unterminated links for one participant, and run reconcile only when that inspect reports drift. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Fold the #173 buyer path onto the #169 inspect line: a stale current row for an ended Keyverse subject must keep that account occupied until inspect reports drift and restore reconcile rebuilds the unique enforcer. After that path, a later participant can bind the freed subject. Lock ADR-0020 Implementation status to the TRACEABILITY landing PR. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Hosted adapters authorize both current proofs only after restore inspect is clean, persist the append-only history, and recover the same participant from a later valid account. Prefer this write-path head over #160. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
A returning Keyverse account can now disconnect without the expired anonymous session. persist_authorized_account_unlink authorizes the current issuer-scoped proof, appends the link-end, and leaves recover empty so the same subject can relink. Restore drift still blocks new first-links but does not freeze this disconnect. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
There was a problem hiding this comment.
Recover can hand back a rebound participant after unlink+relink
This head is the right inspect-then-write-and-unlink stack. Prefer it over #192, #183, #178, #169, #160, and earlier identity-link persist vehicles for unlink and restore-inspect. Do not merge those predecessors.
The remaining fail-closed hole is recover. recover_participant_for_authenticated_account authorizes the proof, then returns whatever history reconstructs for the looked-up participant. FOR SHARE on the unterminated link row does not block a later link-end insert, so a concurrent unlink+relink under READ COMMITTED can return a participant now bound to another subject, or an unlinked record, to the original account proof.
PR #176 already closed that hole with accept_recovered_participant_for_authenticated_account. This successor must compose that check onto this inspect/unlink head. Until then, do not treat #202 as merge-ready for the identity-link landing.
Keep
- Authorized unlink from unterminated history while restore inspect reports drift.
- Recover returning
Noneafter a sequential unlink, then the same participant relinking. - HTTP and live Keyverse verification out of this slice.
Operator next action
Do not merge #202, #192, #183, or #176 as substitutes for one another. Land recover current-binding on top of this inspect/unlink head, then request independent last-push approval. Never self-approve.
Sent by Cursor Automation: Fix Issues
| now_unix_ms: u64, | ||
| ) -> Result<Option<ParticipantRecord>, AccountLinkWriteError> { | ||
| require_recoverable_account(authenticated_control, now_unix_ms)?; | ||
| Ok(load_participant_by_current_identity_subject( |
There was a problem hiding this comment.
Recover still returns load_participant_by_current_identity_subject without a post-load current-binding check.
current_subject_participant takes FOR SHARE on the unterminated participant_identity_link row. That lock does not block a later participant_identity_link_end insert, so under READ COMMITTED a concurrent unlink+relink can reconstruct a participant now bound to another issuer-scoped subject (or to no current subject) and hand that record back to the original proof.
PR #176 already has accept_recovered_participant_for_authenticated_account for this hole. Compose that keep-only-when-tenant/issuer/subject-still-match check here before treating this head as the identity-link landing. Do not merge #176 instead of this inspect/unlink stack, and do not merge this head until recover rejects a rebound load.
|
Closing as a proven superseded predecessor. Fresh compare shows #202 head |


Why
PR #192 lets a hosted adapter persist a dual-proof account link and recover the same participant after restart. A buyer who later signs in with that Keyverse account still cannot disconnect: there is no authorized unlink command, so recover keeps returning the old
participant_refafter they asked to unlink.TDD
RED
current_account_proof_unlinks_without_rewriting_participant_identityandauthorized_unlink_clears_recovery_and_allows_the_same_account_to_relinkcalled missingunlink_authenticated_account/persist_authorized_account_unlink. GREEN authorizes a still-valid account proof that matches the current issuer-scoped subject, appends the link-end, returnsNonefrom recover, then relinks the same participant. Restore drift still blocks new first-links and does not block this disconnect.Scope
Out of scope
Operator next action
After a returning account asks to disconnect, call
persist_authorized_account_unlinkwith the current Keyverse proof. Recover must then return no participant until a laterpersist_authorized_account_link. Prefer this head over #192. Do not merge #192, #183, or earlier identity-link predecessors. Do not merge this PR until exact-head checks and independent last-push approval are satisfied. Never self-approve.