Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
e36af18
test(longitudinal): require durable observation persistence
seonghobae Aug 17, 2026
223c402
feat(longitudinal): add immutable observation schema
seonghobae Aug 17, 2026
96d9fd5
feat(longitudinal): persist immutable observation evidence
seonghobae Aug 17, 2026
219ad3f
feat(longitudinal): expose observation persistence adapter
seonghobae Aug 17, 2026
2bbe2ab
test(longitudinal): require tenant-bound persistence
seonghobae Aug 17, 2026
8b2b04a
feat(longitudinal): isolate observation evidence by tenant
seonghobae Aug 17, 2026
4e83c0d
feat(longitudinal): bind persisted observations to tenant
seonghobae Aug 17, 2026
263494e
test(longitudinal): require tenant-scoped restart recovery
seonghobae Aug 17, 2026
3891688
fix(longitudinal): restore durable observations after restart
seonghobae Aug 17, 2026
cf66f3d
fix(longitudinal): preserve checked membership sequence loading
seonghobae Aug 17, 2026
561f999
fix(longitudinal): satisfy strict rustdoc lint
seonghobae Aug 17, 2026
c0ad5d8
Merge branch 'main' into feat/longitudinal-observation-persistence-20…
github-actions[bot] Aug 17, 2026
bf8351d
test(longitudinal): reject membership-less persisted observations
seonghobae Aug 17, 2026
5fb0985
fix(longitudinal): reject observations without memberships
seonghobae Aug 17, 2026
b1ab98b
test(longitudinal): preserve corrupt-restore loader coverage
seonghobae Aug 17, 2026
3b24bce
fix(longitudinal): satisfy doc-markdown gate
seonghobae Aug 17, 2026
456cf12
test(longitudinal): cover immutable replay classification boundaries
seonghobae Aug 17, 2026
bc1ad6c
test(longitudinal): keep replay coverage formatted
seonghobae Aug 17, 2026
65bca82
test(longitudinal): avoid client helper shadowing
seonghobae Aug 17, 2026
7d69fc5
test(longitudinal): structure replay matrix for clippy
seonghobae Aug 17, 2026
27ee781
fix(longitudinal): clarify persistence reference errors
seonghobae Aug 17, 2026
bf4b3ce
test(longitudinal): reject numeric references and clock-code mismatch
seonghobae Aug 17, 2026
a514b66
fix(longitudinal): enforce opaque refs and clock anomaly integrity
seonghobae Aug 17, 2026
052cd95
fix(longitudinal): preserve corruption probes with insert-time clock …
seonghobae Aug 17, 2026
b4b6505
Merge branch 'main' into feat/longitudinal-observation-persistence-20…
opencode-agent[bot] Aug 18, 2026
78892b2
Merge branch 'main' into feat/longitudinal-observation-persistence-20…
opencode-agent[bot] Aug 18, 2026
46816e4
fix(longitudinal): isolate schema integrity tests and rustfmt
cursoragent Aug 18, 2026
73dadc6
fix(longitudinal): cover persist query errors and rustdoc clippy
cursoragent Aug 18, 2026
a436b5a
test(session-http): tolerate peer close during invalid framing
cursoragent Aug 18, 2026
dad81fc
test(longitudinal): reject Unicode numeric separators in DB refs
seonghobae Aug 19, 2026
1f86a81
fix(longitudinal): align DB numeric separator guard
seonghobae Aug 19, 2026
46bf001
test(longitudinal): reject control characters in DB refs
seonghobae Aug 19, 2026
6483b72
fix(longitudinal): reject unsafe DB reference controls
seonghobae Aug 19, 2026
8b0fbe1
test(longitudinal): require anomaly CHECK defense
seonghobae Aug 19, 2026
fb5e161
fix(longitudinal): enforce clock anomaly relation in CHECK
seonghobae Aug 19, 2026
9a080b7
test(longitudinal): preserve corrupt-history recovery probe
seonghobae Aug 19, 2026
b3cb2ea
fix(longitudinal): keep hyphen literal in reference regex
seonghobae Aug 20, 2026
441cea8
test(longitudinal): pin digit-bearing opaque reference contract
seonghobae Aug 20, 2026
b472cea
style(longitudinal): restore schema test trailing newline
seonghobae Aug 20, 2026
377777e
Merge protected main into longitudinal observation reconciliation
seonghobae Aug 20, 2026
8b34b24
test(longitudinal): require Unicode numeric parity in PostgreSQL
seonghobae Aug 20, 2026
c6b0583
fix(longitudinal): align PostgreSQL numeric refs with Rust
seonghobae Aug 20, 2026
dfb18f4
merge(main): reconcile longitudinal persistence with scoring adapter
seonghobae Aug 21, 2026
b9e7b8d
docs(longitudinal): distinguish active persistence from shipped flow
seonghobae Aug 21, 2026
2a2b85d
docs(longitudinal): map active PostgreSQL observation schema
seonghobae Aug 21, 2026
10394d6
docs(longitudinal): record active durable observation slice
seonghobae Aug 21, 2026
b5b1a36
docs(longitudinal): record tenant-scoped source identity
seonghobae Aug 21, 2026
75e4d0a
chore(longitudinal): reconcile observation persistence with current main
seonghobae Aug 21, 2026
ccb0b4b
Merge branch 'main' into feat/longitudinal-observation-persistence-20…
opencode-agent[bot] Aug 24, 2026
49a4b23
Merge branch 'main' into feat/longitudinal-observation-persistence-20…
seonghobae Aug 26, 2026
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
- Active PR #248 adds PostgreSQL 18 durability for immutable normalized longitudinal observation evidence: exact source identity, separate validity/recorded/received/ingested clocks, explicit membership shares whose deferred transaction invariant totals 10,000 basis points, exact replay, tenant isolation, and fail-closed rebinding/immutability. This entry is active-PR evidence only and is not a protected-main or release claim.
- Active PR #287 rejects padded or otherwise noncanonical published narrative, style-mapping, interpretation-unit, and approved-selection references before deterministic rendering. Numeric score authority and the protected-main narrative boundary are unchanged; this is active-PR evidence only and not a release claim.
- Personal result export delivery authorization (`authorize_result_export_read`) reuses the stored-record `ReadOwnResult` check on the product-owned participant and immutable result, then requires the export's result snapshot reference and copied participant reference to match that exact snapshot. A cross-tenant caller fails closed with the ordinary result-authorization denial before export-binding details are evaluated, so a mismatched export is not an existence oracle. No new permission or persistence is introduced; authorized HTTP transport ships through merged `src/result_export_http.rs` and `openapi/result-exports.yaml`. This is the post-#231 export-delivery guard (ADR-0010 provenance; ADR-0003 tenant-bound authorization).
- Personal result export copies one immutable snapshot into JSON and a human-readable report so a purchaser can archive the same Extraversion estimate, standard error, and version provenance they were shown. The owner participant reference stays in both artifacts. Abstained or failed constructs keep their disposition and do not receive an invented score. Approved limitation text is required.
Expand Down
4 changes: 2 additions & 2 deletions docs/TRACEABILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ An active PR, architecture document, conversation decision, or scheduler plan is
| Bounded asynchronous scoring retry/quarantine with stale-worker fencing | PRD §9.4, §10 | TRD §8; ADR-0015 transaction boundary | ADR-0004, ADR-0010, ADR-0015 | **Implemented** product lifecycle plus PostgreSQL enqueue, claim, retry, completion, expiry recovery, and cancellation without transferring a fence; live fast-mlsirm execution remains Target |
| Immutable result provenance | PRD §3.1, §9.4 | TRD §9 | ADR-0004, ADR-0010 | **Implemented** in `src/result.rs`; result-serving transport is Target |
| Personal JSON and human-readable result export | PRD §3.1, §9.4 | TRD §18 `POST /v1/results/{result_ref}/exports`; ADR-0010 export provenance | ADR-0010 | **Implemented** domain copy through merged #231, delivery guard through merged #249 (`src/result_export_authorization.rs`), and authorized HTTP transport through merged #256 (`src/result_export_http.rs`, `openapi/result-exports.yaml`) |
| Deterministic narrative fallback | PRD §3.2, §9.5 | TRD §17; Architecture narrative view | ADR-0009, ADR-0010, ADR-0018 | **Active PR #287** hardens the non-protected narrative slice so published narrative/rule references must already use canonical opaque spelling; protected-main narrative runtime remains Target |
| Deterministic narrative fallback | PRD §3.2, §9.5 | TRD §17; Architecture narrative view | ADR-0009, ADR-0010, ADR-0018 | **Implemented** through merged #287 (`src/deterministic_narrative.rs`, `src/style_mapping.rs`): published narrative/rule references must already use canonical opaque spelling before deterministic rendering; numeric score authority is unchanged |
| Continuous scores remain source of truth; Personality Style is presentation | PRD §3.2 | Measurement Governance; AI Governance | ADR-0018 | Target product narrative mapping; numeric source remains External fast-mlsirm contract |
| Immutable instrument release/version lifecycle | PRD §6, §9 | TRD §7; UML publication state | ADR-0005, ADR-0010 | **Implemented** in `src/instrument.rs` plus `migrations/0006_instrument_release.sql` and `src/postgres_instrument_release.rs`: immutable release manifest, exact version/digest/locale/item set, fail-closed Draft/Review/Published/Suspended/Retired lifecycle, idempotent publication events, and new-session eligibility |
| Instrument publication requires intended-use scientific/right/locale evidence | PRD §6, §9, §10 | Measurement Governance; publication evidence gate | ADR-0004, ADR-0013, ADR-0019 | **Implemented** policy gate and immutable evidence provenance in `src/instrument.rs`; each real instrument still requires its own rights/locale/scientific evidence artifacts before publication |
Expand All @@ -44,7 +44,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`; merged #259 ships protected-main exact `ko-KR`/`en-US` participant report labels that copy immutable scores and provenance; real form content, rights, translation, invariance, HTTP delivery, and accessible reference-client serving remain 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/orchestration adapter; `src/longitudinal_observation.rs` records validity, recorded, received, and ingested clocks with explicit membership shares. Enrollment state, PostgreSQL persistence, HTTP, Gyeot collection, and TEPP kernels remain Target |
| EMA/ESM longitudinal flow | PRD §4 | TRD §16; UML longitudinal sequence; logical ERD extension | ADR-0008 | External Gyeot/TEPP dependencies + Target Commons enrollment/orchestration adapter; `src/longitudinal_observation.rs` records validity, recorded, received, and ingested clocks with explicit membership shares. **Active PR #248 / IMPLEMENTED_ON_ACTIVE_PR** adds PostgreSQL 18 persistence for immutable normalized observation records and membership shares via `migrations/0031_longitudinal_observation.sql` and `src/postgres_longitudinal_observation.rs`; enrollment persistence, HTTP, Gyeot collection, and TEPP kernels 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
11 changes: 11 additions & 0 deletions docs/architecture/AS_BUILT_SCHEMA.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ The protected-main integration identity is source- and tenant-scoped. A physical

PR #218 (`migrations/0014_assessment_session.sql`, `migrations/0016_assessment_session_command.sql`, and `src/postgres_assessment_session.rs`) persist and load one assessment-session identity bound to a published locale-specific release, plus append-only command history. New sessions start only through `created_session_for_start` / `start_created_assessment_session` / `start_created_assessment_session_from_stored_release`. Durable start locks `instrument_release` with `SELECT … FOR UPDATE` so a stale in-memory Published object cannot insert after persist Suspend or Retire. First insert through `persist_assessment_session` takes the same lock, so a reconstituted Created aggregate cannot insert after that later persist. When that lock finds a missing or unpublished release, persist still classifies an exact stored Created row as duplicate so a concurrent retry after the first insert commits cannot turn a later Suspend or Retire into a false unpublished failure. Exact replay of an already stored start or Created row still returns the original session after a later persist Suspend or Retire. The slice is **Active PR**, not protected-main truth. It stores participant, release, version, digest, locale, current state, and creation time. Exact replay is idempotent. Rebinding any stored field or command evidence, or persisting a shorter command history than already stored, fails closed so a stale Activate-only worker cannot rewind Pause/Resume. Command persist locks the `assessment_session` header row with `SELECT … FOR UPDATE` before inserting or counting commands. Load restores created identity without asking whether the release still accepts new sessions, then replays commands so Activate/Pause/Resume survive restart. Isolation is the global opaque `session_ref` primary key; this slice does not add `tenant_ref` because the domain `AssessmentSession` aggregate does not carry tenant. Persist-backed `POST /v1/sessions` / `GET /v1/sessions/{session_ref}` (`src/session_http.rs`, `openapi/sessions.yaml`) sit on this start path. Command HTTP remains outside this slice. #205 is the unlocked-peek first-insert-seal predecessor; #209 is the weaker NotFound-allows-insert competitor; #198 is the exact start-replay predecessor; #180 is the stored-publication lock predecessor; #188 is the in-memory replay predecessor that still lacks the store lock; #153 is the in-memory-start predecessor; #164 is the unlocked stored-load predecessor; #146 is the header-lock predecessor; #129 is the sequential stale-prefix predecessor; #125 is the command-history predecessor that still rewinds on a stale shorter persist; #109 is the persist-and-load predecessor.

## Active PR longitudinal-observation physical schema

PR #248 (`migrations/0031_longitudinal_observation.sql` and `src/postgres_longitudinal_observation.rs`) is **Active PR / IMPLEMENTED_ON_ACTIVE_PR**, not protected-main truth. It maps the logical `longitudinal_observation_record` semantics in `ERD.md` onto two product-owned PostgreSQL 18 relations without moving Gyeot collection or TEPP temporal/multilevel/multiple-membership analysis into this repository.

- `longitudinal_observation` is the immutable parent record. Its opaque observation, tenant, enrollment, source-system, and source-observation references preserve the logical record identity and source provenance; validity, source-recorded, platform-received, and durable-ingestion clocks remain separate; civil-time/UTC-offset and clock-anomaly evidence are retained rather than collapsed.
- `longitudinal_membership_share` is the immutable one-to-many membership relation owned by one observation record. Each opaque membership-context reference carries an integer share, and the migration defers the aggregate invariant that one observation's shares total exactly 10,000 basis points until transaction completion.
- Exact source identity is unique on `(tenant_ref, enrollment_ref, source_system_ref, source_observation_ref)`. Exact replay is idempotent; source rebinding or stored-evidence mutation fails closed. The same source tuple may repeat under a different tenant only with a different observation-record identity. UPDATE, DELETE, and TRUNCATE guards preserve the logical ERD's immutable-evidence semantics.
- Real PostgreSQL tests exercise persistence/reload, tenant isolation, replay/conflict classification, source-identity rebinding rejection, schema integrity, membership totals, immutability, and missing-schema failure. No direct Gyeot or TEPP database access is introduced.

This is an explicit logical-to-physical reconciliation: the physical split preserves one logical observation record with zero-or-more explicit membership-share evidence rows; it does not create a second collection or analysis kernel. Durable longitudinal enrollment, HTTP transport, live Gyeot/TEPP adapters, recovery acceptance, and research-release registration remain Target.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: AS_BUILT prose describes membership rows as 'zero-or-more' but the schema requires at least one

AS_BUILT_SCHEMA.md describes the physical split as preserving "one logical observation record with zero-or-more explicit membership-share evidence rows." The enforced invariant actually requires the shares to total exactly 10,000, i.e. at least one row; a header-only commit fails (verified by postgres_longitudinal_observation_schema_integrity.rs). The 'zero-or-more' phrasing understates the enforced cardinality. Minor documentation imprecision rather than a functional defect.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


## Active PR outbox delivery-lease physical schema

PR #60 (`feat/outbox-delivery-lease-20260814`) extends the protected-main `integration_outbox` relation through `migrations/0013_outbox_delivery_lease.sql` and `src/postgres_integration.rs`. The extension is **Active PR**, not protected-main truth.
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/ERD.md
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ A physical schema must enforce equivalents of the following constraints:
| at most one current Active `participant_identity_link` per participant under the accepted single-account-link policy | unambiguous current account projection |
| unique active `(tenant_ref, identity_issuer, identity_subject_ref)` unless an explicit account-merge ADR permits otherwise | prevent one external subject from silently owning multiple product participants |
| unique `(request_ref, retained_scope_ref)` for retained data-rights completion evidence | prevent duplicate retained-scope evidence while preserving tenant/request binding |
| unique `(enrollment_ref, source_system_ref, source_observation_ref)` | longitudinal ingestion replay safety |
| unique `(tenant_ref, enrollment_ref, source_system_ref, source_observation_ref)` | tenant-scoped longitudinal ingestion replay safety |
| unique analysis-submission idempotency identity per `(enrollment_ref, analysis_spec_ref, observation_set_digest)` | repeatable TEPP dispatch |
| unique `(tenant_ref, source, outbox_event_ref)` or an equivalently stronger globally unique event identity with tenant binding | durable outbound event identity |
| unique `(tenant_ref, consumer_name, source, source_event_ref)` | tenant-bound inbox deduplication |
Expand Down
Loading
Loading