feat(longitudinal): enroll Gyeot programs after consent - #184
feat(longitudinal): enroll Gyeot programs after consent#184cursor[bot] wants to merge 2 commits into
Conversation
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>
There was a problem hiding this comment.
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
can_accept_observations()is state-only. After a valid Seoul enroll, a laterLongitudinalObservationrevoke still returnstrue. ADR-0008 invariant 1 is ongoing participation, not enroll-once. Replace this helper withauthorize_collection(&ConsentSnapshot)that fail-closes unless the enrollment isEnrolled, the snapshot belongs to this participant, andactive_granted_at(LongitudinalObservation)is present.resumecan returnEnrolledwith 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.enrollbindsparticipant_refto the snapshot and stores callertenant_refas an unbound string.ParticipantRecordalready owns tenant/participant identity. Cross-tenant enroll with a matching participant ref succeeds today. Take&ParticipantRecordand reject tenant or participant mismatch.docs/TRACEABILITY.md§2 evaluated-main column marks EMA/ESM Partially implemented for Active PR code. The named baseline is still085ef4b. 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.
Sent by Cursor Automation: Fix Issues
|
|
||
| /// Return whether Gyeot may collect observations for this enrollment. | ||
| #[must_use] | ||
| pub const fn can_accept_observations(&self) -> bool { |
There was a problem hiding this comment.
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.
| /// 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( |
There was a problem hiding this comment.
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.
| 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() { |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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.
| | 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 | |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Sent by Cursor Automation: Fix Issues
|
Closing as a proven superseded predecessor. Fresh compare shows #184 head |
There was a problem hiding this comment.
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.
Sent by Cursor Automation: Fix Issues


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.rsenrolled 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.rsandConsentSnapshot::active_granted_at.Local GREEN:
cargo test --test longitudinal_enrollment_contractcargo test --test documentation_architecture_contract --test consent_identity_safetycargo clippy --all-targets -- -D warningsScope
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.