Skip to content

feat(longitudinal): enroll Gyeot programs after consent - #184

Closed
cursor[bot] wants to merge 2 commits into
mainfrom
cursor/bc-8b7eb292-5fb5-48cc-9f20-4e9cc12590f9-00a4
Closed

feat(longitudinal): enroll Gyeot programs after consent#184
cursor[bot] wants to merge 2 commits into
mainfrom
cursor/bc-8b7eb292-5fb5-48cc-9f20-4e9cc12590f9-00a4

Conversation

@cursor

@cursor cursor Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Why

A participant who granted longitudinal observation consent still cannot join an EMA/ESM program. Gyeot cannot collect, and TEPP cannot analyze, until Commons owns enrollment. Collapsing work and home into one group would recreate the atomistic fallacy ADR-0008 forbids.

TDD

RED tests/longitudinal_enrollment_contract.rs enrolled a Seoul clinic participant into a 14-day mood diary with distinct work and home membership after a longitudinal grant. Missing or revoked longitudinal consent failed closed. Research refusal did not block personal enrollment. Pause, resume, and withdraw kept the enrollment evidence.

GREEN adds src/longitudinal.rs and ConsentSnapshot::active_granted_at.

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

  • Consented Gyeot program enrollment only.
  • Explicit multiple-membership contexts; duplicates fail closed.
  • Pause / resume / withdraw without erasing evidence.
  • TRACEABILITY, ADR-0008, glossary, and changelog name this as Active PR work.

Out of scope

Operator next action

Review the Seoul clinic enrollment contract. After this lands, persist enrollment and then accept Gyeot observation identity. Do not merge #52; identity-link landing remains #166. Do not self-approve. Independent last-push approval and exact-head checks are required.

Open in Web View Automation 

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>
@cursor
cursor Bot requested a review from seonghobae August 16, 2026 16:19
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.

Draft head 7bc290d is not merge-ready. Enroll-time longitudinal consent is real. Ongoing collection authorization and tenant binding are not. This run cannot REQUEST_CHANGES on its own PR, so the contract fix lands on a successor rather than this head.

Blocking

  1. can_accept_observations() is state-only. After a valid Seoul enroll, a later LongitudinalObservation revoke still returns true. ADR-0008 invariant 1 is ongoing participation, not enroll-once. Replace this helper with authorize_collection(&ConsentSnapshot) that fail-closes unless the enrollment is Enrolled, the snapshot belongs to this participant, and active_granted_at(LongitudinalObservation) is present.
  2. resume can return Enrolled with no current snapshot. Pause → revoke → resume then makes the state-only gate authorize collection again. Keep resume idempotent for bookkeeping if needed, but collection must re-check the current snapshot.
  3. enroll binds participant_ref to the snapshot and stores caller tenant_ref as an unbound string. ParticipantRecord already owns tenant/participant identity. Cross-tenant enroll with a matching participant ref succeeds today. Take &ParticipantRecord and reject tenant or participant mismatch.
  4. docs/TRACEABILITY.md §2 evaluated-main column marks EMA/ESM Partially implemented for Active PR code. The named baseline is still 085ef4b. Keep §2 as Target on evaluated main and leave the enrollment contract in the Active PR section.

Proven

The Seoul 14-day mood-diary fixture uses a real ConsentLedger. Enroll after grant, research-refusal isolation, missing/revoked grant at enroll time, duplicate membership after trim, and pause/resume/withdraw exact-replay are covered.

Not proven

  • Revoke after enroll stops collection.
  • Resume after revoke stays closed to Gyeot.
  • Cross-tenant enroll fails.
  • Work/home are used as analysis memberships (they are only stored as Vec<String>).

Do not add on this slice

PostgreSQL enrollment tables, HTTP transport, live Gyeot/TEPP adapters, observation time fields, or identity-link persist (#166 / #178).

Do not merge this head. Do not self-approve. Independent last-push approval and exact-head checks are still required after the successor contract lands.

Open in Web View Automation 

Sent by Cursor Automation: Fix Issues

Comment thread src/longitudinal.rs

/// Return whether Gyeot may collect observations for this enrollment.
#[must_use]
pub const fn can_accept_observations(&self) -> bool {

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.

can_accept_observations only matches EnrollmentState::Enrolled. After enroll, a later longitudinal revoke still authorizes Gyeot collection. Replace this with authorize_collection(&ConsentSnapshot) that fail-closes unless state is Enrolled, snapshot.participant_ref() matches, and active_granted_at(LongitudinalObservation) is Some.

Comment thread src/longitudinal.rs
/// Returns [`LongitudinalEnrollmentError`] for a blank event reference, a
/// withdrawn enrollment, a resume that is not later than the last event, or
/// a resume attempted while already enrolled with different evidence.
pub fn resume(

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.

resume takes no current snapshot. Pause → revoke → resume returns Enrolled and the state-only collection gate becomes true again. Resume may stay an idempotent lifecycle command, but any path that authorizes collection must re-check the current consent snapshot.

Comment thread src/longitudinal.rs
let participant_ref = required_reference(input.participant_ref)?;
let program_ref = required_reference(input.program_ref)?;
let collection_system_ref = required_reference(input.collection_system_ref)?;
if participant_ref != snapshot.participant_ref() {

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.

Only participant_ref is compared to the snapshot. tenant_ref is copied from the caller. A second clinic can enroll participant_clinic_seoul under tenant_other_clinic. Pass &ParticipantRecord and reject tenant or participant mismatch the same way authorize and account-link already fail closed.

Err(LongitudinalEnrollmentError::EmptyReference)
);

let mut no_membership = seoul_mood_enrollment();

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 suite never revokes after a successful enroll, never resumes after that revoke, and never binds ParticipantRecord. Add those cases before claiming ADR-0008 collection authorization or tenant isolation. Empty memberships can stay legal for person-only EMA; collapsing undeclared work/home is the later observation-weight slice.

Comment thread docs/TRACEABILITY.md
| Korean/English exact locale versions | PRD §3.1, §9.9 | TRD §28; instrument release + locale governance | ADR-0013, ADR-0019 | **Partially implemented**: locale is pinned/validated by `src/instrument.rs`; actual English/Korean form content, rights, translation, invariance and serving are Target |
| WCAG 2.2 AA supported reference client | PRD §9.10 | TRD §27; Quality Attributes | ADR-0002, ADR-0013 | Target; no reference client implementation on evaluated main |
| EMA/ESM longitudinal flow | PRD §4 | TRD §16; UML longitudinal sequence; logical ERD extension | ADR-0008 | External Gyeot/TEPP dependencies + Target Commons enrollment/normalized-ingestion/orchestration adapter |
| EMA/ESM longitudinal flow | PRD §4 | TRD §16; UML longitudinal sequence; logical ERD extension | ADR-0008 | **Partially implemented**: `src/longitudinal.rs` enrolls a participant into a Gyeot-collected program only after an active `LongitudinalObservation` grant, keeps work/home membership contexts distinct, and pauses/resumes/withdraws without erasing evidence. Persistence, observation ingestion, live Gyeot/TEPP adapters, and HTTP remain Target |

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 evaluated-main cell now describes Active PR #184 source. Header baseline is still 085ef4b, which has no src/longitudinal.rs. Keep this row Target on evaluated main and describe the enrollment contract only in the Active PR section.

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

Landing vehicle is now #199 (53180c2+). That head replaces can_accept_observations with authorize_collection(&ConsentSnapshot), binds enroll to &ParticipantRecord, and keeps TRACEABILITY §2 as Target on evaluated main.

Do not merge this #184 head. Do not open another enrollment-domain PR unless #199 is closed or regresses the revoke-after-enroll / cross-tenant contracts.

View PR

Open in Web View Automation 

Sent by Cursor Automation: Fix Issues

Copy link
Copy Markdown
Contributor

Closing as a proven superseded predecessor. Fresh compare shows #184 head 7bc290db is the merge base/ancestor of #199 head 0e77397c; #199 retains the consented longitudinal enrollment/multiple-membership contract and adds current-consent reauthorization for collection plus tenant/participant binding through ParticipantRecord, closing revoke-after-enroll and cross-tenant gaps. #199 explicitly says to prefer it and not merge #184.

@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 this head

#184 authorizes Gyeot collection from EnrollmentState alone. A later revoke, or resume after that revoke, still allows collection. Tenant is an unbound caller string.

Prefer #226 (authorize_collection(&ParticipantRecord, &ConsentLedger)). Do not merge #184 or #199. #199 still trusts a caller-chosen enroll-time snapshot after revoke.

Independent last-push approval and exact-head checks are required on #226. Do not self-approve.

View PR

Open in Web View Automation 

Sent by Cursor Automation: Fix Issues

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