Skip to content

fix(longitudinal): gate collection on current ledger and tenant - #226

Draft
cursor[bot] wants to merge 6 commits into
mainfrom
cursor/bc-6f3c2357-3cd4-4eee-8738-683dcba4a0fb-e8ca
Draft

fix(longitudinal): gate collection on current ledger and tenant#226
cursor[bot] wants to merge 6 commits into
mainfrom
cursor/bc-6f3c2357-3cd4-4eee-8738-683dcba4a0fb-e8ca

Conversation

@cursor

@cursor cursor Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Why

#199 enrolls a Seoul clinic participant after a longitudinal grant and re-checks a caller-supplied ConsentSnapshot. That snapshot is enroll-time evidence. After revoke it stays Granted, so authorize_collection on the enroll-time snapshot still succeeds. Enrolled still documented collection authority. Collection also never re-bound tenant.

TDD

RED tests/longitudinal_enrollment_contract.rs required authorize_collection(&ParticipantRecord, &ConsentLedger) so an enroll-time snapshot that still looks Granted cannot authorize after revoke, and a second clinic's participant record is CrossTenantDenied while consent remains granted.

GREEN replaces the snapshot-only gate with the current ledger head plus the tenant-owned record.

Local GREEN:

  • cargo test --test longitudinal_enrollment_contract
  • cargo test --test documentation_architecture_contract --test consent_identity_safety
  • cargo clippy --all-targets -- -D warnings

Scope

Out of scope

  • PostgreSQL enrollment persistence
  • Observation ingestion time fields
  • Live Gyeot or TEPP adapters
  • HTTP enrollment transport
  • Identity-link persist

Operator next action

Review the enroll-time-snapshot-after-revoke and collection-time cross-tenant contracts. Do not merge #184 or #199. Do not self-approve. Independent last-push approval and exact-head checks are required.

Open in Web View Automation 

cursoragent and others added 5 commits August 16, 2026 16:18
A Seoul clinic participant can join a 14-day mood diary only after
granting longitudinal observation consent. Work and home membership
stay distinct, research refusal does not block personal enrollment,
and pause/resume/withdraw keep the enrollment evidence.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Collection now fails closed after a later longitudinal revoke, even if
the enrollment is still Enrolled or was resumed. Enroll takes the
tenant-owned participant record so a second clinic cannot borrow the
Seoul participant under another tenant.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Enroll-time snapshots stay Granted after a later revoke. Collection now
takes the current consent ledger and the tenant-owned participant record
so a Seoul clinic revoke, an empty ledger, or another clinic fails closed
even when EnrollmentState is still Enrolled.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
@cursor
cursor Bot requested a review from seonghobae August 16, 2026 16:49
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.

Review (8c54fcc)

Collection authorization is no longer enroll-time snapshot or Enrolled membership. authorize_collection(&ParticipantRecord, &ConsentLedger) re-binds the tenant-owned record and the latest LongitudinalObservation event on the supplied ledger. Seoul revoke-after-enroll, resume-after-revoke, empty same-participant ledger, other-participant ledger, and other-clinic record all fail closed. consent_snapshot_ref stays enroll evidence only. TRACEABILITY §2 remains Target on evaluated main.

This is not approval. Independent last-push review by seonghobae is still required. Do not merge #199 or #184. Do not self-approve.

Residual (correctly out of scope here)

The ledger and participant record are still caller-supplied. Persistence/HTTP must load the current participant ledger and the store row under the authenticated caller tenant. Do not “fix” that by routing collection back through consent_snapshot_ref.

enroll can still mint Enrolled from a stale granted snapshot after a later revoke. Collection still fails when it receives the current ledger. Load the current grant at persist/enroll time in the follow-up slice.

Operator next action

Review the enroll-time-snapshot-after-revoke and collection-time cross-tenant contracts on this exact head. Keep #226 as the enrollment landing vehicle. After it lands, persist longitudinal_enrollment + enrollment_membership_context with server-side current-ledger load and caller-tenant bind.

Open in Web View Automation 

Sent by Cursor Automation: Fix Issues

Comment thread src/longitudinal.rs
Comment on lines +399 to +408
fn current_longitudinal_grant(ledger: &ConsentLedger) -> Option<u64> {
ledger
.events()
.iter()
.rev()
.find(|event| event.purpose() == ConsentPurpose::LongitudinalObservation)
.and_then(|event| {
(event.decision() == ConsentDecision::Granted).then_some(event.occurred_at_unix_ms())
})
}

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.

Residual, not a change request on this in-memory slice: current_longitudinal_grant trusts the caller-supplied event list. A prefix that ends on Granted (later revoke omitted) still authorizes while state == Enrolled.

Persistence/HTTP must load the current participant ledger server-side and must not accept a client-built event list. Do not route collection back through consent_snapshot_ref.

Comment thread src/longitudinal.rs
Comment on lines +95 to +97
Self::ParticipantMismatch => {
"use the consent snapshot that belongs to this participant"
}

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.

Wording only: collection now fails on a mismatched ledger or participant record, not only a snapshot. Follow-up copy can say to use the consent evidence that belongs to this participant. Not blocking for this head.

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.

1 participant