Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ All notable product and architecture changes are recorded here. Releases use imm
## Unreleased

### Added
- Consented longitudinal enrollment: a participant can join a Gyeot-collected EMA/ESM program only after a tenant-owned participant record and an active longitudinal observation grant. Collection re-checks that grant, so a later revoke stops Gyeot even after resume. Work and home membership stay distinct, research refusal does not block personal enrollment, and pause/resume/withdraw keep the enrollment evidence.
- Scoring-job cancel and lease-expiry fallback classification lock the current row until the caller transaction ends, so concurrent workers cannot rewrite terminal or unleased evidence.
- PostgreSQL operational-store readiness probe classifies the supported major version and write-readiness, and fails closed when a caller-declared required relation is missing.
- PostgreSQL scoring-job cancellation: queued, leased, or retry-scheduled work becomes cancelled without transferring a fence, exact replay is idempotent, and completed or quarantined evidence cannot be rewritten.
Expand Down
4 changes: 4 additions & 0 deletions docs/GLOSSARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ Use these terms consistently across PRD, TRD, ADRs, APIs, diagrams, code, UI, an
| **consent form** | Versioned content/policy defining a specific processing purpose and participant decision surface. |
| **consent snapshot** | Immutable evidence of one participant's purpose-specific decision under an exact consent-form/scope version. |
| **research contribution** | Explicit product-domain opt-in record that makes approved data potentially eligible for research processing under a defined scope. Service use alone does not create one. |
| **longitudinal enrollment** | Product-owned record that a participant joined one versioned EMA/ESM program after granting longitudinal observation consent. Gyeot collection still requires a current longitudinal-observation grant at observation time. It is not a TEPP analysis job. |
| **collection system** | External EMA/ESM collection owner referenced by enrollment. Gyeot is the current collection system of record. |
| **Gyeot** | CWL bounded context that owns participant-facing EMA/ESM collection, offline sync, and momentary observation capture. |
| **TEPP** | CWL bounded context that owns temporal, event, multilevel, and multiple-membership analytical artifacts. It does not collect mobile observations. |
| **research participant** | Pseudonymous research-domain identity separated from operational participant identity through a restricted linkage boundary. |
| **restricted linkage** | Highly restricted mapping between operational participant identity and research pseudonym identity. Never part of a public release. |
| **research staging** | Purpose-limited pseudonymized data projection used for privacy/scientific review before a dataset snapshot is approved. |
Expand Down
6 changes: 4 additions & 2 deletions docs/TRACEABILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ An active PR, architecture document, conversation decision, or scheduler plan is
| Operation-scoped capability health | PRD §7, §13 | `docs/OPERABILITY.md` §3–4; Deployment/Operations | ADR-0011, ADR-0017 | **Implemented** domain health/readiness contract in `src/health.rs` plus `src/postgres_health.rs` PostgreSQL major/write-readiness and caller-declared relation presence; HTTP probes, measured thresholds, and deployment evidence remain Target |
| 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 | **Target** on evaluated main. Active PR work adds a consented enrollment domain contract; persistence, observation ingestion, live Gyeot/TEPP adapters, and HTTP remain Target |
| Measurement Workbench | PRD §6 | C4/component view; UML publication-evidence sequence; Measurement Governance | ADR-0001, ADR-0002, ADR-0004, ADR-0019 | Target; fast-mlsirm/Inkspan/RankWeave are External dependencies |
| Headless replaceable clients | PRD §7 | TRD §1, §18; C4 | ADR-0001, ADR-0002 | Architecture established; public transport is Target |
| Community/Hosted/Enterprise profiles | PRD §7, §13 | TRD deployment sections; Deployment/Operations | ADR-0011, ADR-0017 | Target deployment packaging/evidence |
Expand Down Expand Up @@ -128,10 +128,12 @@ migrations/
└── 0012_integration_consumption.sql
```

Still-Target logical modules/adapters include remaining product aggregate persistence/repositories, public/admin HTTP and event transports, live fast-mlsirm/Keyverse/Gyeot/TEPP/semantic-data-portal adapters, research-release staging, deterministic narrative mapping, longitudinal normalized ingestion, participant identity-link history persistence, runtime health transports/metrics, and Measurement Workbench orchestration.
Still-Target logical modules/adapters include remaining product aggregate persistence/repositories, public/admin HTTP and event transports, live fast-mlsirm/Keyverse/Gyeot/TEPP/semantic-data-portal adapters, research-release staging, deterministic narrative mapping, longitudinal observation ingestion, participant identity-link history persistence, runtime health transports/metrics, and Measurement Workbench orchestration.

### Active implementation work that is not protected-main truth

**Active PR** #199 longitudinal enrollment is not protected-main truth until an unchanged reviewed/check-clean head is integrated. `src/longitudinal.rs` binds Gyeot program enrollment to a tenant-owned `ParticipantRecord` and an active longitudinal-observation consent snapshot, re-checks that snapshot before collection, preserves explicit multiple-membership contexts, and keeps pause/resume/withdraw fail-closed. Observation ingestion, persistence, and live Gyeot/TEPP adapters remain outside this slice. Do not merge #184; that head authorized collection from enrollment state alone.

**Active PR** #76 data-rights processing-start persistence is not protected-main truth until an unchanged reviewed/check-clean head is integrated. Identity-verified requests persist an immutable operation identity and processing-start time under `FOR UPDATE` so later lifecycle composition cannot race the classified row. Dependent-system execution remains outside this slice.

## 5. ADR traceability by concern
Expand Down
29 changes: 29 additions & 0 deletions docs/adr/0008-gyeot-and-tepp-longitudinal-boundary.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,35 @@ Sync outages leave bounded local queues and clear user state. Clock anomalies ar
- **TEPP collects mobile observations directly:** couples modeling to client lifecycle.
- **One timestamp and one group per observation:** scientifically invalid for the intended designs.

## As-built versus target

Active PR #199 adds the product enrollment primitive in `src/longitudinal.rs`. It is `IMPLEMENTED_ON_ACTIVE_PR`, not protected-main truth, until the exact reviewed head is merged. Do not treat #184 as the landing vehicle; that head authorized collection from enrollment state alone.

As-built on this PR:

- enrollment requires a tenant-owned `ParticipantRecord` plus an active `ConsentPurpose::LongitudinalObservation` grant and fails closed when that grant is missing or revoked;
- `authorize_collection` re-checks the current consent snapshot, so a later revoke stops Gyeot collection even if the enrollment is still `Enrolled`;
- research refusal does not block personal EMA/ESM enrollment;
- work/home and other membership contexts stay distinct and reject duplicates;
- pause, resume, and withdraw are fail-closed and do not erase enrollment evidence.

Still target:

- PostgreSQL enrollment/observation persistence;
- live Gyeot collection and TEPP analysis adapters;
- HTTP enrollment transport;
- observation-time fields (`observed_at`, `recorded_at`, `received_at`, `available_at`, `valid_from` / `valid_to`) on ingested records.

## References

Bolger, N., & Laurenceau, J.-P. (2013). *Intensive longitudinal methods: An introduction to diary and experience sampling research*. Guilford Press.

Curran, P. J., & Bauer, D. J. (2011). The disaggregation of within-person and between-person effects in longitudinal models of change. *Annual Review of Psychology, 62*, 583–619. https://doi.org/10.1146/annurev.psych.093008.100356

Diez Roux, A. V. (2002). A glossary for multilevel analysis. *Journal of Epidemiology & Community Health, 56*(8), 588–594. https://doi.org/10.1136/jech.56.8.588

Hamaker, E. L., & Wichers, M. (2017). No time like the present: Discovering the hidden dynamics in intensive longitudinal data. *Current Directions in Psychological Science, 26*(1), 10–15. https://doi.org/10.1177/0963721416666518

## Reversal conditions

Collection or analysis implementations may change if their contracts remain stable. Revisit the boundary only if one component ceases independent use and the combined ownership demonstrably reduces rather than increases coupling.
15 changes: 13 additions & 2 deletions docs/architecture/ERD.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ erDiagram
dataset_snapshot ||--o{ dataset_snapshot_member : contains
dataset_snapshot ||--o{ research_release : released_as

tenant_account ||--o{ longitudinal_enrollment : scopes
assessment_participant ||--o{ longitudinal_enrollment : enrolls
longitudinal_enrollment ||--o{ enrollment_membership_context : declares
longitudinal_enrollment ||--o{ longitudinal_observation_record : ingests
longitudinal_enrollment ||--o{ temporal_analysis_submission : submits

Expand Down Expand Up @@ -309,6 +311,7 @@ erDiagram

longitudinal_enrollment {
string enrollment_ref PK
string tenant_ref FK
string participant_ref FK
string program_ref
string consent_snapshot_ref
Expand All @@ -318,6 +321,13 @@ erDiagram
timestamp latest_event_at
}

enrollment_membership_context {
string membership_assignment_ref PK
string enrollment_ref FK
string membership_context_ref
int declaration_order
}

longitudinal_observation_record {
string observation_record_ref PK
string enrollment_ref FK
Expand Down Expand Up @@ -427,7 +437,7 @@ The target ERD deliberately includes several logical entities that are not yet p
- `item_delivery_event` reflects the already-merged `src/item_delivery.rs` domain primitive; durable persistence/API orchestration is still Target.
- `consent_ledger` and `consent_event` persist the already-merged `src/consent.rs` append-only ledger. Physical persistence is carried by Active PR #49 (`migrations/0005_consent_lifecycle.sql`); HTTP consent transport and derived snapshot tables remain Target.
- `participant_identity_link` is the persistence target accepted by ADR-0020. The current `src/participant.rs` `keyverse_subject_ref` field is an application-domain first-link projection, not the future mutable persistence source of truth.
- `longitudinal_enrollment`, `longitudinal_observation_record`, and `temporal_analysis_submission` make the ADR-0008 Commons-owned Gyeot/TEPP orchestration boundary explicit. No TEPP analytical kernel is duplicated here.
- `longitudinal_enrollment`, `enrollment_membership_context`, `longitudinal_observation_record`, and `temporal_analysis_submission` make the ADR-0008 Commons-owned Gyeot/TEPP orchestration boundary explicit. Membership contexts stay in a child table so work and home are not flattened onto the enrollment row. No TEPP analytical kernel is duplicated here.
- `integration_outbox`, `integration_delivery_attempt`, `integration_inbox`, and `integration_consumption` reflect `src/integration.rs` domain semantics. Outbox/inbox/delivery-attempt tables are on protected main; `integration_consumption` pending/processing/completed/quarantined persistence and expire-and-reclaim of a crashed processing claim exist only on this Active PR until merged.

This section is a maturity guard: a logical entity may be architecture-complete without being as-built database evidence.
Expand Down Expand Up @@ -507,7 +517,8 @@ Requirements:

The Commons longitudinal tables are orchestration/evidence records only:

- `longitudinal_enrollment` binds product participant, program, consent, and collection-system references;
- `longitudinal_enrollment` binds tenant, product participant, program, consent, and collection-system references;
- `enrollment_membership_context` stores each declared membership once, in declaration order, so later TEPP analysis is not forced into one primary group;
- `longitudinal_observation_record` stores normalized observation identity/time/construct/version/context references required to reproduce a submission, not a duplicate Gyeot application database;
- `temporal_analysis_submission` records exact observation-set digest, TEPP analysis specification, lifecycle, and returned artifact reference.

Expand Down
14 changes: 12 additions & 2 deletions src/consent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,18 @@ impl ConsentSnapshot {
/// Return whether the latest decision for `purpose` is an active grant.
#[must_use]
pub fn is_granted(&self, purpose: ConsentPurpose) -> bool {
self.latest_event(purpose)
.is_some_and(|event| event.decision == ConsentDecision::Granted)
self.active_granted_at(purpose).is_some()
}

/// Return the server time of the latest active grant for `purpose`.
///
/// A revoked or never-granted purpose returns `None` so enrollment and
/// other purpose-bound commands can fail closed before they start work.
#[must_use]
pub fn active_granted_at(&self, purpose: ConsentPurpose) -> Option<u64> {
self.latest_event(purpose).and_then(|event| {
(event.decision == ConsentDecision::Granted).then_some(event.occurred_at_unix_ms)
})
}

/// Return the consent-form version for an active grant, if present.
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub mod health;
pub mod instrument;
pub mod integration;
pub mod item_delivery;
pub mod longitudinal;
pub mod narrative;
pub mod participant;
pub mod postgres_consent;
Expand Down
Loading
Loading