Skip to content

feat(identity): persist authorized unlink after inspect - #202

Closed
cursor[bot] wants to merge 22 commits into
mainfrom
cursor/bc-08d3bd7b-4151-4d01-992f-2afd21638d37-6992
Closed

cursor[bot] wants to merge 22 commits into
mainfrom
cursor/bc-08d3bd7b-4151-4d01-992f-2afd21638d37-6992

Conversation

@cursor

@cursor cursor Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

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_ref after they asked to unlink.

TDD

RED current_account_proof_unlinks_without_rewriting_participant_identity and authorized_unlink_clears_recovery_and_allows_the_same_account_to_relink called missing unlink_authenticated_account / persist_authorized_account_unlink. GREEN authorizes a still-valid account proof that matches the current issuer-scoped subject, appends the link-end, returns None from recover, then relinks the same participant. Restore drift still blocks new first-links and does not block this disconnect.

Scope

Out of scope

  • HTTP account-link transport / OpenAPI
  • Live Keyverse token verification
  • Store-wide auto-reconcile on unlink

Operator next action

After a returning account asks to disconnect, call persist_authorized_account_unlink with the current Keyverse proof. Recover must then return no participant until a later persist_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.

Open in Web View Automation 

cursoragent and others added 21 commits August 16, 2026 15:25
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>
The Active PR #114 naming commit stored an empty ADR-0020. Restore the
accepted decision, including the #114 persistence status and APA 7
references, so identity-link governance is not silently deleted.

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>
Renumber the identity-link migration so it does not collide with #113 scoring-job health indexes on 0021. Name Active PR #124 as the merge candidate over #114.

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>
Add a composite foreign key so a link-end or current projection cannot
point at another participant's identity-link row. Name Active PR #133
as the landing vehicle over #124 and #114.

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>
Point TRACEABILITY, ADR-0020, ERD, and the as-built schema at the inspect-and-reconcile landing so writers do not reopen #158 or #133.

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>
Point TRACEABILITY, ADR-0020, AS_BUILT, and ERD at #178 so concurrent writers do not treat #173 or #169 as the identity-link landing vehicle.

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>
Record the dual-proof write successor as the identity-link landing
so concurrent writers do not treat #178 or #160 as the persist vehicle.

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>
@cursor
cursor Bot requested a review from seonghobae August 16, 2026 16:30
Prefer #202 over #192 and earlier identity-link predecessors.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 None after 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.

Open in Web View Automation 

Sent by Cursor Automation: Fix Issues

Comment thread src/account_link_write.rs
now_unix_ms: u64,
) -> Result<Option<ParticipantRecord>, AccountLinkWriteError> {
require_recoverable_account(authenticated_control, now_unix_ms)?;
Ok(load_participant_by_current_identity_subject(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

cursor Bot pushed a commit that referenced this pull request Aug 16, 2026
Record #206 as the write/recover/unlink landing over #176. Keep #202 as
the inspect-line unlink vehicle and #158 as restore reconcile.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>

Copy link
Copy Markdown
Contributor

Closing as a proven superseded predecessor. Fresh compare shows #202 head f844ddf5 is the merge base/ancestor of #210 head 7860bc71; #210 retains authorized unlink/relink and adds the post-load current tenant/issuer/subject binding check so concurrent unlink+relink cannot return a rebound participant. Continue exact-head CI/review on #210; do not merge #202 separately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants