Skip to content

fix(identity): recover participant from unterminated history - #133

Closed
cursor[bot] wants to merge 11 commits into
mainfrom
cursor/bc-6fecd5e6-c464-4f7c-8ec5-a17750e81d11-019a
Closed

fix(identity): recover participant from unterminated history#133
cursor[bot] wants to merge 11 commits into
mainfrom
cursor/bc-6fecd5e6-c464-4f7c-8ec5-a17750e81d11-019a

Conversation

@cursor

@cursor cursor Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Superseded

This unterminated-history recovery slice is fully contained in the verified successor chain #147#160. Do not merge this predecessor.

Fresh ancestry evidence:

#160 retains append-only history as the lookup/uniqueness truth and adds the hosted dual-proof persist/recover command boundary. #160 remains Draft and must still satisfy exact-head CI/security/review gates. Closing this PR does not promote any successor behavior to protected-main truth.

cursoragent and others added 9 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>
@cursor
cursor Bot requested a review from seonghobae August 16, 2026 15:46
cursoragent and others added 2 commits August 16, 2026 15:47
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>
@seonghobae
seonghobae marked this pull request as ready for review August 16, 2026 15:48
@cursor

cursor Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@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.

Stale comment

Review

#133 is the persist/reload landing for assessment_participant and append-only identity-link history. Prefer this head over #114 and #124.

load_participant_by_current_identity_subject / current_subject_participant read unterminated participant_identity_link rows (NOT EXISTS end event) and fail closed as CorruptHistory when two unterminated links exist. That matches the projection-loss contract. Persist still locks the participant row with FOR UPDATE and uses the current-subject unique constraint as the concurrent first-link backstop.

Do not merge until exact-head checks and independent last-push approval land. Never self-approve.

Command-authorization honesty is now #144, not #135. Session command-history landing remains #129; that head still counts stored commands after insert without FOR UPDATE on assessment_session, so a READ COMMITTED Activate-only worker can race a later Pause/Resume persist. Do not start HTTP until #144, #133, and a locked #129 successor are integrated.

View PR

Open in Web View Automation 

Sent by Cursor Automation: Fix Issues

@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.

Stale comment

History-as-truth lookup and uniqueness are the right #124 repair. Prefer this head over #114 and #124.

Do not merge yet. Two landing defects remain on this exact head:

  1. Exact replay does not restore or clear current_participant_identity_link. After operator repair deletes the derived row, or leaves a stale row after unlink, the unique enforcer that backs concurrent first-inserts is gone even though history still has the unterminated subject. Successor cursor/bc-3286fd2e-32dd-4741-a21f-ada045db3ab9-1d81 reconciles that projection after persist.
  2. TRACEABILITY still names superseded #124 as the Active PR for this adapter. Operators can merge the wrong vehicle.

Residual, already marked planned in ADR-0020: concurrency arbitration beyond the participant row lock. History FOR SHARE plus the derived unique constraint is the current defense; a later slice should add subject-scoped locking or an equivalent database constraint on unterminated history.

Do not merge #114 or #124. Do not self-approve. Wait for exact-head checks and independent last-push approval. Prefer the successor once it is the named landing head.

Open in Web View Automation 

Sent by Cursor Automation: Fix Issues

Comment thread docs/TRACEABILITY.md
Comment thread src/postgres_participant_identity_link.rs
cursor Bot pushed a commit that referenced this pull request Aug 16, 2026
Point TRACEABILITY, ADR-0020, ERD, and the as-built schema at this
successor so operators do not merge superseded #133, #124, or #114.

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.

Do not merge #133, #124, or #114. History-sourced lookup is the right contract, but two buyer-facing holes remain after projection loss.

  1. Exact replay of an unterminated link returns Duplicate and does not restore current_participant_identity_link. After restore or operator repair, the derived uniqueness safety net stays down.
  2. Unterminated (tenant, issuer, subject) uniqueness lives only on that derived projection. A second history row can be inserted, and the returning login then gets CorruptHistory instead of the original participant_ref. TRACEABILITY still names #124 as the landing vehicle.

Prefer the successor that restores the current projection on exact replay and rejects a second unterminated subject in the database. Do not self-approve. Do not merge until exact-head checks and independent last-push approval are satisfied.

Open in Web View Automation 

Sent by Cursor Automation: Fix Issues

&& stored_authenticated == authenticated_proof_ref
&& stored_linked == linked_at_unix_ms
{
Ok(false)

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.

Exact replay of an unterminated link stops at Ok(false) and never rebuilds current_participant_identity_link. After projection loss, a returning persist leaves uniqueness unenforced. Restore the derived current row when this stored link has no matching end.

REFERENCES participant_identity_link (participant_ref, identity_link_ref),
CONSTRAINT current_participant_identity_link_participant_fk FOREIGN KEY (participant_ref)
REFERENCES assessment_participant (participant_ref),
CONSTRAINT current_participant_identity_link_subject_unique UNIQUE (

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.

This unique constraint protects only the derived projection. Unterminated history is the claimed source of truth, but a second open (tenant_ref, identity_issuer, identity_subject_ref) row can still be inserted. That turns a returning-account lookup into CorruptHistory. Enforce one unterminated subject on participant_identity_link itself.

Comment thread docs/TRACEABILITY.md
| Publication event replay is idempotent/conflicting reuse fails closed | TRD §7 | `src/instrument.rs` | durable DB uniqueness/concurrency test |
| Published instrument requires exact-version scientific evidence | Measurement Governance; ADR-0019 | `src/instrument.rs` binds approved evidence status, provenance/scope, mandatory evidence references, validity window, and immutable release identity before publication/reactivation | persistence/API publication integration and real instrument-specific evidence artifacts |
| Optional account linking does not rewrite historical participant/result identity | ADR-0003, ADR-0020 | `src/participant.rs` issuer-scoped first-link primitive preserves stable participant ID | append-only identity-link persistence + unlink/relink/recovery audit tests |
| Optional account linking does not rewrite historical participant/result identity | ADR-0003, ADR-0020 | `src/participant.rs` issuer-scoped first-link primitive preserves stable participant ID; **Active PR** #124 `src/postgres_participant_identity_link.rs` persists and reloads that history without rewriting `participant_ref` | HTTP unlink/relink transport, live Keyverse verification, and backup/restore evidence |

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.

This row still names Active PR #124 while the rest of the document prefers #133. Operators should not treat #124 as the landing vehicle.

cursor Bot pushed a commit that referenced this pull request Aug 16, 2026
Point TRACEABILITY, ADR-0020, ERD, and the as-built schema at this
successor so operators do not merge superseded #147, #133, #124, or #114.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
@seonghobae seonghobae closed this Aug 16, 2026
cursor Bot pushed a commit that referenced this pull request Aug 16, 2026
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>
cursor Bot pushed a commit that referenced this pull request Aug 17, 2026
#135 still said apply_anonymous_session_command ran after a store load
and named superseded #114 as the persist landing. The gate compares
supplied records only; persist/reload remains Active PR #133.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
cursor Bot pushed a commit that referenced this pull request Aug 17, 2026
The command gate still compares supplied records. Docs and the
architecture contract now name Active PR #147 for persist/reload,
forbid the leftover #133 pointer, and stop saying the gate authorized
from loaded records.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
cursor Bot pushed a commit that referenced this pull request Aug 17, 2026
and named superseded #114 as the persist landing. The gate compares
supplied records only; persist/reload remains Active PR #133.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
cursor Bot pushed a commit that referenced this pull request Aug 17, 2026
The command gate still compares supplied records. Docs and the
architecture contract now name Active PR #147 for persist/reload,
forbid the leftover #133 pointer, and stop saying the gate authorized
from loaded records.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
cursor Bot pushed a commit that referenced this pull request Aug 18, 2026
and named superseded #114 as the persist landing. The gate compares
supplied records only; persist/reload remains Active PR #133.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
cursor Bot pushed a commit that referenced this pull request Aug 18, 2026
The command gate still compares supplied records. Docs and the
architecture contract now name Active PR #147 for persist/reload,
forbid the leftover #133 pointer, and stop saying the gate authorized
from loaded records.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
seonghobae added a commit that referenced this pull request Aug 18, 2026
* test(auth): bind anonymous proof to exact session resource

* feat(auth): bind anonymous proof to exact session resource

* feat(auth): expose anonymous session authorization

* style(auth): apply rustfmt to anonymous authorization tests

* test(auth): pin anonymous denial precedence

* docs(auth): explain anonymous session authorization

* feat(auth): authorize anonymous commands from loaded session

Derive the assessment-session resource from the stored participant tenant
and the loaded session so a transport cannot invent a matching scope and
then command a different session.

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

* feat(auth): apply session commands only after anonymous authorization

Keep the loaded session unchanged when the proof is expired or names a
different session, and still fail closed on illegal lifecycle transitions.

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

* fix(auth): classify anonymous commands from loaded records

Compare the verified actor to the loaded participant tenant and session
instead of rebuilding a ResourceScope. Tenant mismatch is reported before
ownership so a foreign-tenant inconsistent pair cannot hide as OwnerMismatch.

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

* feat(participant): persist anonymous assessment identity

Store tenant, participant reference, anonymous status, and creation time
so command authorization can load the participant instead of rebuilding
it from the proof. Exact replay is idempotent; tenant or time rebinding
fails closed; linked participants stay out of this slice.

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

* docs(traceability): name Active PR #118 for participant persist

Record the opened successor so architecture views do not leave the
assessment-participant slice unlabeled.

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

* revert(participant): leave persist/reload on Active PR #114

history on migration 0021. This successor keeps the #104 command-auth
contract fix and does not open a colliding anonymous-only persist slice.

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

* fix(auth): tell the truth about supplied command records

Command authorization compares the verified actor to supplied
participant and session values. It does not accept a ResourceScope and
does not claim those aggregates were store-loaded. Align rustdoc, ADR-0003,
SECURITY_AND_DATA, UML Activate, TRACEABILITY, and CHANGELOG. Use a
session created after publication and before exclusive proof expiry.

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

* fix(auth): satisfy clippy doc-markdown on command timeline

Backtick the publication and exclusive-expiry instants in the command
authorization fixtures, and name honesty successor #135 in TRACEABILITY.

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

* fix(auth): stop claiming command records were store-loaded

and named superseded #114 as the persist landing. The gate compares
supplied records only; persist/reload remains Active PR #133.

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

* docs(traceability): name honesty successor Active PR #144

Point command-authorization honesty at the opened successor so
reviewers do not treat #135 as the landing head.

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

* fix(auth): name persist landing #147 and drop leftover load claims

The command gate still compares supplied records. Docs and the
architecture contract now name Active PR #147 for persist/reload,
forbid the leftover #133 pointer, and stop saying the gate authorized
from loaded records.

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

* docs(traceability): name honesty successor Active PR #159

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

* fix(auth): name persist landing #158 instead of superseded #147

Identity persist/reload landing moved to #158. Keep the command gate
honest about supplied records and stop pinning the leftover #147 pointer.

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

* fix(auth): drop closed #158 persist landing and loaded names

Name persist/reload as Target, forbid the closed pointer, and call
command-test records supplied.

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

* docs(traceability): name honesty successor Active PR #225

Record the opened successor so architecture views do not leave the
command-auth honesty slice labeled as closed #159.

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

* fix(auth): keep anonymous authorization after rebase onto main

Preserve account-link and anonymous-credential modules beside the
session-command authorization entry point so this honesty head stays
mergeable without claiming participant persist on this branch.

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

* docs(traceability): drop merged Active PR leftovers after rebase

After rebasing onto 0c695b9, exclusive outbox leases, observation
clocks/membership, and claim-next scoring-job poll are protected-main
truth. Keep #225 as the Active PR for supplied-record anonymous
command authorization. Persist/reload of assessment_participant
remains Target.

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

---------

Co-authored-by: Seongho Bae <me@seonghobae.me>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
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