diff --git a/README.md b/README.md index 751724c7..93a5089c 100644 --- a/README.md +++ b/README.md @@ -2,65 +2,135 @@ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/ContextualWisdomLab/psychometrics-commons) -CWL Psychometrics Commons is the headless product repository for public psychometric assessment, reflective self-understanding, longitudinal observation, and consent-governed research data contribution. +**Evidence-bound psychometric assessment, longitudinal observation, and consent-governed research workflows.** + +Psychometrics Commons is the hosted product and integration boundary for turning versioned instruments and measurement evidence into participant-facing assessment workflows without duplicating the numerical kernels that belong in specialist measurement libraries. ```text -Measure -> Understand -> Reflect -> Observe Over Time -> Contribute to Science +Measure → Understand → Reflect → Observe Over Time → Contribute to Science +``` + +The product is designed for participants, researchers and instrument developers, product operators/data stewards, and institutional integrators that need explicit provenance, authorization, consent, and release boundaries around psychometric workflows. + +## What this repository owns + +Psychometrics Commons owns product lifecycle and hosting semantics, including: + +- instrument publication and version identity; +- participant and assessment-session lifecycle; +- response and item-delivery evidence; +- consent and data-rights workflows; +- scoring dispatch and immutable result snapshots; +- result authorization, reporting, and export-domain contracts; +- longitudinal observation records; +- product-owned PostgreSQL persistence and recovery boundaries; +- research-contribution handoff and release evidence; +- health, authorization, integration-delivery, and operator-facing runtime primitives. + +It deliberately does **not** become the authority for every adjacent concern: + +| Concern | Authority | +| --- | --- | +| Reusable psychometric numerical kernels and measurement computation | [`fast-mlsirm`](https://github.com/ContextualWisdomLab/fast-mlsirm) | +| Identity and federation | Keyverse | +| Temporal/event and longitudinal-model computation | [`TEPP`](https://github.com/ContextualWisdomLab/TEPP) | +| Research catalog and public release registration | [`semantic-data-portal`](https://github.com/ContextualWisdomLab/semantic-data-portal) | +| Generic provider/LLM orchestration | [`contextual-orchestrator`](https://github.com/ContextualWisdomLab/contextual-orchestrator) | +| Optional replaceable reference-client composition | `g7` | + +Integration happens through explicit, versioned boundaries rather than cross-service application-table access or copied implementation code. + +## Current maturity + +This repository is an **implementation-stage headless runtime**, not a published end-user application release. `Cargo.toml` currently identifies source version `0.1.0` and `publish = false`, and there is currently no GitHub release for this repository. + +Protected `main` is the shipped-source authority. PRDs, ADRs, diagrams, gap ledgers, and open pull requests can describe target or candidate behavior, but they are not release evidence on their own. Use the [traceability map](docs/TRACEABILITY.md), [product/technical gap baseline](docs/product-technical-gap-baseline.md), and [release acceptance contract](docs/RELEASE_ACCEPTANCE.md) when deciding whether a capability is implemented, candidate, or still planned. + +## Evaluate the source + +The crate is a Rust library/runtime foundation rather than a single install-and-run binary. For a repository-level evaluation, use the same toolchain and database boundary exercised by CI. + +### Prerequisites + +- Rust `1.97.1` with `rustfmt` and `clippy`; +- PostgreSQL 18 for the full persistence/integration test suite; +- Python 3 for the repository coverage-contract tests. + +Set a disposable PostgreSQL test connection through `TEST_DATABASE_URL`, then run: + +```bash +cargo fmt --all -- --check +cargo check --locked --all-targets +cargo clippy --all-targets -- -D warnings +cargo test --all-targets +python3 -m unittest discover -s tests -p 'test_*.py' -v +cargo doc --no-deps ``` -This repository owns the hosted product runtime and integration composition. It consumes reusable measurement contracts and numerical capabilities from `ContextualWisdomLab/fast-mlsirm`; identity and federation from Keyverse; temporal/event analysis from TEPP; and research release/catalog capabilities from `semantic-data-portal`. +Do not point test execution at production data. The repository CI uses an ephemeral PostgreSQL database and treats exact-head hosted checks as integration evidence. -## Architecture boundary +## Product principles -Psychometrics Commons owns product APIs, instrument publication, participant/session lifecycle, response events, consent and data-rights workflows, scoring dispatch, immutable result snapshots, product persistence, resource authorization, reference-client composition, deployment profiles, research-contribution handoff, observability, and service integration. +### Continuous measurement stays primary -It does **not** duplicate psychometric numerical kernels, identity credentials, temporal model kernels, public research catalog internals, or generic LLM orchestration. `g7` is an optional replaceable reference client rather than a platform dependency. +Psychometrics Commons is designed around continuous scores, uncertainty, versioned scoring/norm provenance, and explicit interpretation limitations. Presentation narratives must not silently become new psychometric scores or unsupported personality types. -## Personal result export +### Evidence before authority -Protected-main result-export domain evidence is available through `ResultExport::from_snapshot`. Before creating or delivering an export, the server must authorize the authenticated actor against the exact stored result resource, its owning participant, and tenant scope; caller-supplied result, participant, or tenant values are never authority. Only after that authorization succeeds, call `ResultExport::from_snapshot` with an opaque `export_ref`, the exact BCP 47 report locale, and approved limitation text, and deliver the returned JSON or human-readable report to the participant. Before delivery, confirm that every exported construct score, disposition, present standard error, and version provenance match the immutable result snapshot. If authorization or export fails, do not deliver an artifact; repair the authoritative identity/access evidence or the locale, timestamp, or limitation text as appropriate. Do not invent a type score, do not mask the owner `participant_ref`, and do not treat this domain copy as the HTTP `POST /v1/results/{result_ref}/exports` transport; authorized HTTP delivery remains an active slice. +Caller-provided participant, tenant, result, consent, or release identifiers are not authority merely because they appear in a request. Product operations bind decisions to stored identity, authorization, provenance, and lifecycle evidence and fail closed when that evidence is missing or inconsistent. + +### Research contribution is a separate choice + +Using the assessment product does not implicitly enroll a participant in research. Research contribution requires its own consent and pseudonymization/privacy/release path; public research catalog ownership remains outside this repository. + +### Integration does not erase ownership + +External identity, measurement, temporal, catalog, and orchestration systems remain separate authorities. Psychometrics Commons composes them into product workflows without copying their databases or numerical kernels. + +## Architecture at a glance + +The Rust crate exposes product-domain and persistence modules for participant/session state, instruments, responses, consent, data rights, scoring, results, longitudinal observations, authorization, health, and integration delivery. PostgreSQL adapters persist product-owned durable state. External systems connect through explicit contracts and anti-corruption boundaries. + +For the full bounded-context and data model, see [ARCHITECTURE.md](ARCHITECTURE.md), the [architecture view index](docs/architecture/README.md), and the [logical ERD](docs/architecture/ERD.md). + +## Security, privacy, and scientific integrity + +The repository separates several evidence classes that should not be collapsed into a single “ready” claim: + +- **security:** identity, tenancy, authorization, secrets, dependency and supply-chain controls; +- **privacy:** consent, data rights, research separation, retention and restricted linkage evidence; +- **scientific validity:** instrument rights/provenance, scoring policy, calibration, DIF/invariance, norms, uncertainty and interpretation limitations; +- **operability:** migration, backup/restore, failure recovery, degraded modes and observable health; +- **release:** exact source identity, required checks, reproducibility, artifacts, SBOM/provenance, and post-publication verification. + +Start with the [Threat Model](docs/THREAT_MODEL.md), [Measurement Governance](docs/MEASUREMENT_GOVERNANCE.md), [Research Commons Governance](docs/RESEARCH_GOVERNANCE.md), and [Operability and Recovery](docs/OPERABILITY.md). ## Documentation -### Product, technical, and governance baseline - -- [Product and Technical Gap Baseline](docs/product-technical-gap-baseline.md) — exact protected-main snapshot, current open PR/issue inventory, buyer-visible gaps, and the next executable loop. -- [Product Requirements](docs/PRD.md) — users, consumer MVP, longitudinal and research experiences, acceptance criteria, exclusions, and release policy. -- [Technical Requirements](docs/TRD.md) — APIs/events/data contracts, state machines, identity/tenancy, idempotency, failure modes, security/privacy, accessibility, deployment, and release gates. -- [Psychometric Measurement Governance](docs/MEASUREMENT_GOVERNANCE.md) — factor/model selection, scoreability, recovery, DIF/invariance, multilevel/time/facet structure, automated scoring, and governed rubric/item-bank evidence required for publication. -- [Bounded AI Governance](docs/AI_GOVERNANCE.md) — optional AI use, deterministic fallback, provider/privacy boundaries, LLM-as-a-Judge treatment, and prohibited score/decision mutation. -- [Research Commons Governance](docs/RESEARCH_GOVERNANCE.md) — research consent, identity separation, staging/privacy/scientific review, immutable release bundles, access classes, withdrawal/correction, and reproducibility. -- [Threat Model](docs/THREAT_MODEL.md) — security assets, trust boundaries, principal attack/failure scenarios, required controls, and GA evidence rather than architecture-only mitigation claims. -- [Test Strategy](docs/TEST_STRATEGY.md) — TDD, domain/state/persistence/security/scientific/accessibility/recovery evidence classes, exact-head validation, and non-vacuous coverage requirements. -- [Operability and Recovery](docs/OPERABILITY.md) — deployment-profile health, capability degradation, retries, incident model, backup/restore, migrations, runbooks, and evidence-gated SLO/RPO/RTO. -- [Release Acceptance](docs/RELEASE_ACCEPTANCE.md) — exact-head software release, consumer-instrument publication, and Research Commons release gates plus post-release artifact verification. -- [Quality Attribute Scenarios](docs/QUALITY_ATTRIBUTES.md) — measurable scientific, reliability, availability, security, privacy, accessibility, performance, portability, maintainability, observability, and recovery scenarios. -- [Compliance Readiness](docs/COMPLIANCE_READINESS.md) — SOC 2/CSAP readiness evidence model, control/evidence matrix, separation of duties, and explicit certification non-claim. -- [Risk Register](docs/RISK_REGISTER.md) — material scientific, product, security, privacy, operational, integration, and commercial risks with treatment/evidence state. -- [Glossary](docs/GLOSSARY.md) — canonical product, scientific, identity, research, integration, deployment, and evidence terminology. -- [Architecture](ARCHITECTURE.md) — bounded contexts, dependency direction, runtime modules, data domains, integration consistency, and architecture fitness functions. -- [Architecture Decision Records](docs/adr/README.md) — authoritative material decisions and the required ADR quality contract. - -### Architecture viewpoints and evidence - -- [Architecture View Index](docs/architecture/README.md) — context/container/component, UML, ERD, security/data, and deployment/operations views. -- [C4-style Context / Container / Component Views](docs/architecture/C4.md) — stakeholders, external systems, target containers, components, and ownership. -- [UML-Aligned Models](docs/architecture/UML.md) — domain class model, lifecycle state machines, and key interaction sequences. -- [Logical ERD](docs/architecture/ERD.md) — product-owned entities, cardinalities, immutable boundaries, linkage restrictions, and persistence invariants. -- [Security, Privacy, and Data Boundaries](docs/architecture/SECURITY_AND_DATA.md) — trust boundaries, classification, threats, identity, research, and AI data policies. -- [Deployment, Operations, and Recovery](docs/architecture/DEPLOYMENT_AND_OPERATIONS.md) — deployment profiles, degraded modes, observability, backup/restore, migration, and GA recovery evidence. -- [Requirements and Architecture Traceability](docs/TRACEABILITY.md) — PRD/TRD/ADR requirements mapped to current protected-main implementation status and future evidence. -- [Product Delivery Roadmap](docs/ROADMAP.md) — dependency-ordered implementation phases and evidence-based exit criteria. -- [Documentation Completeness Assessment](docs/DOCUMENTATION_ASSESSMENT.md) — what is sufficient as an implementation baseline and what still blocks GA operational evidence. - -### Repository operation - -- [AGENTS.md](AGENTS.md) — repository development and architecture rules for autonomous/agentic work. -- [CLAUDE.md](CLAUDE.md) — concise coding-agent entry point into the same normative contracts. -- [Changelog](CHANGELOG.md) — unreleased and released product/architecture changes. - -## Architecture authority and implementation status - -An accepted ADR must define concrete ownership, interfaces, invariants, failure behavior, security/privacy/tenancy boundaries, migration and rollback, validation evidence, alternatives, and reversal conditions. Material implementation that contradicts an accepted ADR requires an explicit superseding decision rather than silent architectural drift. - -Architecture diagrams may describe **normative target semantics** that are not yet deployed. A diagram, PRD item, ADR, threat mitigation, runbook, or release checklist is not implementation or operational evidence. [`docs/TRACEABILITY.md`](docs/TRACEABILITY.md) identifies what exists on a named protected-main baseline versus what remains a target. When an HTTP API is implemented, its OpenAPI contract becomes a release requirement. When durable event transport is implemented, its AsyncAPI contract becomes a release requirement. When a physical database migration is implemented, its schema and migration evidence become release requirements. Do not fabricate any of these artifacts before the corresponding implementation exists. +| Need | Start here | +| --- | --- | +| Product scope and users | [PRD](docs/PRD.md) | +| Runtime/API/data requirements | [TRD](docs/TRD.md) | +| Product and integration architecture | [ARCHITECTURE.md](ARCHITECTURE.md) | +| Current implementation vs gaps | [Product/Technical Gap Baseline](docs/product-technical-gap-baseline.md) | +| Requirement-to-implementation evidence | [Traceability](docs/TRACEABILITY.md) | +| Measurement publication governance | [Measurement Governance](docs/MEASUREMENT_GOVERNANCE.md) | +| Research contribution/release governance | [Research Governance](docs/RESEARCH_GOVERNANCE.md) | +| Security boundary | [Threat Model](docs/THREAT_MODEL.md) | +| Vulnerability reporting | [ContextualWisdomLab Security Policy](https://github.com/ContextualWisdomLab/.github/blob/main/SECURITY.md) | +| Testing and evidence | [Test Strategy](docs/TEST_STRATEGY.md) | +| Release decision | [Release Acceptance](docs/RELEASE_ACCEPTANCE.md) | +| Architecture decisions | [ADR Index](docs/adr/README.md) | +| Public documentation landing | [docs/index.md](docs/index.md) | + +## Contributing and support + +Before changing a product boundary, read [AGENTS.md](AGENTS.md), [CLAUDE.md](CLAUDE.md), the relevant ADRs, and the current gap/traceability evidence. Keep product lifecycle semantics in this repository and reusable psychometric numerical work in its owning measurement library. + +For defects or product gaps, use this repository's GitHub issues with a reproducible failing case and the affected contract/evidence boundary. For security-sensitive defects, do not place secrets, participant data, private assessment material, or exploit details in a public issue. Follow the [ContextualWisdomLab security policy](https://github.com/ContextualWisdomLab/.github/blob/main/SECURITY.md): use GitHub private vulnerability reporting when it is enabled for this repository, and otherwise contact the maintainers as that policy directs before sending sensitive evidence. + +## License + +ContextualWisdomLab-authored Psychometrics Commons source and documentation are licensed under the [Apache License 2.0](LICENSE). `Cargo.toml` carries the same `Apache-2.0` identifier. + +Third-party dependencies and external services retain their own terms; the repository license does not relicense them. The current direct Rust PostgreSQL dependency comes from the `rust-postgres` project, which is offered under MIT or Apache-2.0 terms. Future dependency, asset, dataset, model, or copied-source changes remain subject to separate commercial-license and provenance review. diff --git a/docs/RISK_REGISTER.md b/docs/RISK_REGISTER.md index 294dad61..12158e4a 100644 --- a/docs/RISK_REGISTER.md +++ b/docs/RISK_REGISTER.md @@ -22,7 +22,7 @@ This register tracks material product, scientific, privacy, security, operationa | Hosted runtime returns fallback/invented score during fast-mlsirm outage/scientific failure | critical | medium | mitigated_by_architecture | typed fail-closed scoring contract, durable pending job; end-to-end failure injection required | | Instrument content changes without version change and historical result becomes unreproducible | critical | low | implementation_in_progress | immutable publication/version contract, content digest, result provenance; persistence constraints pending | | Session/response race creates duplicate or inconsistent scoring evidence | high | medium | implementation_in_progress | idempotent response ledger + immutable snapshot; real DB concurrency/atomic outbox tests pending | -| Cross-tenant object reference exposes another user's session/result/research/data-rights state | critical | medium | evidence_required | tenant/resource authorization architecture; transport/persistence negative tests pending | +| Cross-tenant object reference exposes another user's session/result/research/data-rights state | critical | medium | implementation_in_progress | result-export authorization is protected-main evidence; session reload remediation is Active PR #438 at `67f4508f85ec3483c1358b8c1db99e4c92ba0727`; transport/persistence negative tests and protected-main refetch remain pending | | Anonymous-to-account link permits account takeover/history theft | critical | medium | evidence_required | dual proof-of-control + Keyverse validation; adapter and adversarial tests pending | | Keyverse identity role is confused with product/research authorization | high | medium | mitigated_by_architecture | separate domain authorization and separation-of-duties policy; integration tests pending | | Research release contains operational/Keyverse/linkage identifier | critical | medium | mitigated_by_architecture | restricted linkage + release validation; adversarial release pipeline pending | @@ -42,7 +42,7 @@ This register tracks material product, scientific, privacy, security, operationa | Factor rotation is marketed as globally optimal/universally best | medium | medium | mitigated_by_architecture | best-observed multi-start + stability/recovery policy; no universal criterion claim | | Published research/score artifacts mutate in place after correction | high | low | mitigated_by_architecture | content addressing + supersession; physical DB/object-store constraints pending | | Cross-service direct database access creates hidden coupling/privacy blast radius | high | medium | mitigated_by_architecture | ADR-0001/0015; credential/dependency fitness tests pending | -| Outbox/inbox replay duplicates external release/deletion/scoring side effects | high | medium | evidence_required | transactional outbox/inbox design plus Active PR #264 exact-event publisher-to-fenced-persistence handoff; live worker, crash/recovery, and external side-effect idempotency evidence pending | +| Outbox/inbox replay duplicates external release/deletion/scoring side effects | high | medium | evidence_required | transactional outbox/inbox design plus protected-main exact-event publisher-to-fenced-persistence handoff from merged #264; live worker, crash/recovery, and external side-effect idempotency evidence remain pending | | Optional dependency outage is reported as total product outage or blocks personal results | medium | medium | mitigated_by_architecture | capability-scoped readiness/degradation; deployment failure tests pending | | Community profile silently depends on g7/AI/TEPP/portal | high | low | mitigated_by_architecture | ADR-0002/0011 + deployment profile contract; install/end-to-end proof pending | | OpenAPI/AsyncAPI target docs are published before implementation and mislead integrators | medium | medium | mitigated_by_architecture | ADR-0014 as-built-only contract rule; CI gate pending | diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index 2985a8ec..ed75a5a2 100644 --- a/docs/TRACEABILITY.md +++ b/docs/TRACEABILITY.md @@ -1,8 +1,8 @@ # Requirements and Architecture Traceability - Status: Normative traceability index -- Date: 2026-08-21 -- Evaluated protected-main implementation baseline: `4499d9c0889c082487ddbd7fd8d0d5d18257995d` +- Date: 2026-08-28 +- Evaluated protected-main implementation baseline: `09534ef52c9307ce0dc559e9d908ebd715c641a1` This document prevents product requirements, architecture decisions, governance, code, and release evidence from drifting independently. It is intentionally explicit about what is **implemented on the evaluated protected-main baseline**, what exists only on an **active PR**, and what remains **target architecture**. @@ -27,15 +27,13 @@ An active PR, architecture document, conversation decision, or scheduler plan is | Immutable response snapshot before scoring | PRD §9.3 | TRD §5–8 | ADR-0005, ADR-0010 | **Implemented** domain semantics in `src/response.rs` | | Version-pinned scoring | PRD §9.4, §10 | TRD §8 | ADR-0004, ADR-0010 | **Implemented** reusable product-side scoring dispatch contract in `src/scoring.rs` with canonical SHA-256 engine-artifact digest provenance, `migrations/0011_scoring_request.sql` / `src/postgres_scoring_request.rs` request-identity persistence, and protected-main request-bound external adapter `src/scoring_engine.rs`; live fast-mlsirm execution remains Target | | 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`; authorized result-read transport is active PR #257 and not protected-main truth | -| Authorized immutable personal result read | PRD §3.1, §9.4 | TRD §18 `GET /v1/results/{result_ref}`; result authorization boundary | ADR-0003, ADR-0010 | **Active PR** #257 exposes an authorized immutable result read that checks server-owned participant/result bindings before route identity; it is not protected-main truth | -| 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 | -| 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`) | +| Immutable result provenance | PRD §3.1, §9.4 | TRD §9 | ADR-0004, ADR-0010 | **Implemented** in `src/result.rs`; authorized result-read transport is protected-main evidence through merged #257 | +| Authorized immutable personal result read | PRD §3.1, §9.4 | TRD §18 `GET /v1/results/{result_ref}`; `openapi/results.yaml`; result authorization boundary | ADR-0003, ADR-0010 | **Implemented** through merged #257: the authorized immutable result read checks server-owned participant/result bindings before route identity | +| 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 | **Partially implemented** domain copy through merged #231 and delivery guard through merged #249 (`src/result_export_authorization.rs`); authorized HTTP export remains Target because historical PR #256 is not an ancestor of the evaluated protected-main baseline | | 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 | -| Quick and Deep assessment paths | PRD §3.1, §9 | TRD §5–7; immutable release/item-delivery boundary | ADR-0005, ADR-0010 | **Active PR #261** binds ordered Quick/Deep item subsets to one immutable release and copies release locale/provenance; path persistence, item-delivery transport, conversion, and scoring integration remain Target | +| Quick and Deep assessment paths | PRD §3.1, §9 | TRD §5–7; immutable release/item-delivery boundary | ADR-0005, ADR-0010 | **Implemented** through merged #261: ordered Quick/Deep item subsets bind to one immutable release and copy release locale/provenance; item-delivery transport, conversion, and scoring integration remain Target | | 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 | | Optional Keyverse account linking | PRD §3.1, §9.7 | TRD §10; UML identity-link lifecycle | ADR-0003, ADR-0020 | **Partially implemented**: issuer-scoped first-link fail-closed domain primitive in `src/participant.rs`; append-only unlink/relink/recovery history, persistence, audit, and transport remain Target | | Cross-cutting tenant/task authorization | PRD §7, §9 | TRD §11; Security/Data | ADR-0001, ADR-0003 | **Implemented** fail-closed domain gate in `src/authorization.rs` binds consent operations to participant-owned `ConsentLedger` / `ManageOwnConsent`; persistence/policy-adapter/public-transport integration remains Target | @@ -48,7 +46,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. **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 | +| 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. **Implemented persistence through merged #248** adds PostgreSQL 18 storage 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 | @@ -57,20 +55,19 @@ An active PR, architecture document, conversation decision, or scheduler plan is | Invariant | Source | Enforcement/evidence on evaluated main | Missing evidence before GA | |---|---|---|---| -| Server-authoritative session state | TRD §5 | `src/session.rs` + session contract tests, including published-release/locale binding at creation and protected-main persist-backed `POST /v1/sessions` / `GET /v1/sessions/{session_ref}` | Command HTTP, tenant isolation, and complete response/item flow remain missing | +| Server-authoritative session state | TRD §5 | `src/session.rs` + session contract tests, including published-release/locale binding at creation and protected-main persist-backed `POST /v1/sessions`; `openapi/sessions.yaml` is the as-built contract for the protected session HTTP family; the protected-main session GET still needs a transport authorization boundary | Command HTTP, authorized session reload, tenant isolation, and complete response/item flow remain missing | +| Authorized session reload | TRD §5, §11; Security/Data | Active PR #438 at `67f4508f85ec3483c1358b8c1db99e4c92ba0727` binds reload to a server-owned participant and uses an owner-bound PostgreSQL query; this is not protected-main evidence | Exact reviewed/check-clean merge, hosted PostgreSQL evidence, and protected-main refetch | | Only Active accepts responses | TRD §5–6 | `SessionState::accepts_responses` + response tests | transport-level rejection test | | Item delivery sequence is positive and evidence-safe | TRD §5–7 | `src/item_delivery.rs` + item-delivery domain tests | durable uniqueness/order/API integration | | Conflicting idempotency replay fails closed | TRD §6 | `src/response.rs` | DB uniqueness/concurrency test | | Snapshot requires Completed state | TRD §5–6 | `src/response.rs` | transaction atomicity test with persistence | -| Scoring uses durable snapshot identity | TRD §8 | `src/scoring.rs` requires a canonical SHA-256 engine-artifact digest and protected-main `src/scoring_engine.rs` rejects mismatched request/result provenance | live fast-mlsirm adapter + retry/outbox integration | | Scoring uses durable snapshot identity | TRD §8 | `src/scoring.rs` requires a canonical SHA-256 engine-artifact digest and `src/scoring_engine.rs` rejects a result that does not match the complete dispatched request | live fast-mlsirm adapter + retry/outbox integration | | Stale scoring worker cannot complete a newer attempt | TRD §8; ADR-0015 | `src/scoring_job.rs` uses monotonically increasing fencing tokens and rejects stale/expired completion or failure evidence; `src/postgres_scoring_job.rs` persists enqueue, named claim, claim-next poll, retry, terminal outcomes, expired-lease recovery, and cancellation without transferring a fence | live adapter evidence | | Scientific failure is typed, no invented score | TRD §8; Measurement Governance | scoring contract tests plus `src/scoring_engine.rs` typed engine/request-mismatch errors | cross-process failure injection and live provider evidence | | Historical result does not mutate | TRD §9 | `src/result.rs` snapshot semantics | persistence and API supersession tests | -| Authorized result read does not expose cross-tenant existence | TRD §11, §18; Security/Data | Active PR #257 `src/result_http.rs` authorizes server-owned participant/result records before comparing the requested reference and uses safe RFC 9457-style problem responses | protected-main transport, PostgreSQL resource loading, and cross-tenant HTTP E2E | -| Result export includes machine-readable provenance and the same scores | ADR-0010; PRD §3.1 | Protected-main `src/result_export.rs` copies snapshot scores, standard errors, dispositions, owner identity, and version provenance into JSON and a human-readable report; authorized HTTP transport shipped through merged #256 | contract tests on `tests/result_export_http.rs` | -| Result export includes machine-readable provenance and the same scores | ADR-0010; PRD §3.1 | Protected-main `src/result_export.rs` copies snapshot scores, standard errors, dispositions, owner identity, and version provenance into JSON and a human-readable report | authorized HTTP transport shipped through merged #256 | -| Personal export delivery is authorized from stored participant/result records | ADR-0010; ADR-0003; TRD §11 | Protected-main `src/result_export_authorization.rs` (merged #249) reuses stored-record `ReadOwnResult` and fail-closes cross-tenant before export-binding details | HTTP export contract tests on merged `tests/result_export_http.rs` | +| Authorized result read does not expose cross-tenant existence | TRD §11, §18; Security/Data | Protected-main `src/result_http.rs` through merged #257 authorizes server-owned participant/result records before comparing the requested reference and uses safe RFC 9457-style problem responses; `openapi/results.yaml` is the matching as-built HTTP contract | PostgreSQL resource loading and cross-tenant HTTP E2E | +| Result export includes machine-readable provenance and the same scores | ADR-0010; PRD §3.1 | Protected-main `src/result_export.rs` copies snapshot scores, standard errors, dispositions, owner identity, and version provenance into JSON and a human-readable report | authorized HTTP transport and its contract tests remain Target; historical PR #256 is not an ancestor of the evaluated baseline | +| Personal export delivery is authorized from stored participant/result records | ADR-0010; ADR-0003; TRD §11 | Protected-main `src/result_export_authorization.rs` (merged #249) reuses stored-record `ReadOwnResult` and fail-closes cross-tenant before export-binding details | HTTP export transport and contract tests remain Target | | Narrative cannot mutate score / deterministic fallback exists | AI Governance; ADR-0018 | architecture policy | mapping implementation + canonical style-assignment key + fallback/no-score-mutation tests | | Instrument release bytes/version/item order are immutable | TRD §7 | `src/instrument.rs` + publication contract tests; `src/postgres_instrument_release.rs` persists immutable manifest columns | API publication integration | | Only Published release accepts new sessions | TRD §7 | `PublicationState::accepts_new_sessions` in `src/instrument.rs`; protected-main `start_created_assessment_session_from_stored_release` locks publication evidence and persists HTTP create/reload; load still restores created identity without re-checking current eligibility | Command HTTP and the rest of the assessment transport remain missing | @@ -83,7 +80,7 @@ An active PR, architecture document, conversation decision, or scheduler plan is | Export/deletion requires request-specific identity verification | TRD §13 | `src/data_rights.rs`; `src/postgres_data_rights.rs` persists the requested identity and local propagation events | Keyverse/account/anonymous transport integration | | Legal retention represented explicitly | TRD §13 | `src/data_rights.rs` partial completion; protected-main merged #77 persists terminal `completion_evidence_ref` / `completed_at_unix_ms` and immutable retained-scope evidence | dependency execution/restore tests after local propagation | | No cross-service DB access | TRD §1–2; ADR-0015 | architecture policy only | deployment credential/fitness-function test | -| Initial physical persistence target is upstream PostgreSQL 18.x | ADR-0015; Deployment/Operations | **Implemented subset** in `migrations/0001_integration_delivery.sql`, `migrations/0002_scoring_job_state.sql`, `migrations/0003_data_rights_propagation.sql`, `migrations/0005_consent_lifecycle.sql`, `migrations/0006_instrument_release.sql`, `migrations/0011_scoring_request.sql`, `migrations/0012_integration_consumption.sql`, matching adapters, and PostgreSQL operational-store readiness | remaining product aggregates, crash/restart restore acceptance | +| Initial physical persistence target is upstream PostgreSQL 18.x | ADR-0015; Deployment/Operations | **Implemented subset** in `migrations/0001_integration_delivery.sql`, `migrations/0002_scoring_job_state.sql`, `migrations/0003_data_rights_propagation.sql`, `migrations/0005_consent_lifecycle.sql`, `migrations/0006_instrument_release.sql`, `migrations/0011_scoring_request.sql`, `migrations/0012_integration_consumption.sql`, `migrations/0014_assessment_session.sql`, `migrations/0016_assessment_session_command.sql`, `migrations/0024_data_rights_completion.sql`, `migrations/0031_longitudinal_observation.sql`, matching adapters, and PostgreSQL operational-store readiness | remaining product aggregates, crash/restart restore acceptance | | No default tenant for writes | TRD §11; Security/Data | authorization-domain primitive exists; persistence remains Target | persistence/API tenant negative tests | | Tenant-bound transactional outbox/inbox | TRD §19–20; ADR-0014/0015 | `src/integration.rs` domain envelope/inbox/retry contracts plus PostgreSQL tenant/source-scoped integration evidence, delivery-attempt persistence, and inbox consumption; protected-main merged #264 binds the verified publisher receipt to the exact source/tenant/event fence before persistence | durable side-effect processing completion, poison-message/crash recovery, broader aggregate transaction integration | | Inbox receipt is not side-effect completion | ADR-0014/0015; UML integration sequence | `src/integration.rs` states/retry semantics; PostgreSQL inbox consumption persists pending/processing/completed and expire-and-reclaim | live adapter crash/retry tests | @@ -99,7 +96,7 @@ An active PR, architecture document, conversation decision, or scheduler plan is ## 4. Source module map -Current protected-main Rust module surface on `4499d9c0889c082487ddbd7fd8d0d5d18257995d`: +Current protected-main Rust module surface on `09534ef52c9307ce0dc559e9d908ebd715c641a1`: ```text src/lib.rs @@ -114,7 +111,7 @@ src/lib.rs ├── deterministic_narrative.rs # deterministic AI-independent approved style narrative fallback ├── health.rs # operation-scoped liveness/readiness and capability-state contract ├── instrument.rs # immutable release manifest + scientific publication-evidence gate -├── assessment_path.rs # Active PR #261 release-bound Quick/Deep item subset contract +├── assessment_path.rs # merged #261 release-bound Quick/Deep item subset contract ├── api_problem.rs # safe RFC 9457 problem-details primitive ├── localized_result_report.rs # exact-locale report presentation over immutable exports (merged #259) ├── integration.rs # outbox/inbox/retry/quarantine domain contracts @@ -126,6 +123,7 @@ src/lib.rs ├── participant.rs # stable participant identity + issuer-scoped optional Keyverse account link ├── postgres_consent.rs # PostgreSQL purpose-specific consent ledger persistence ├── postgres_data_rights.rs # PostgreSQL data-rights request and local propagation persistence +├── postgres_data_rights_completion.rs # PostgreSQL terminal data-rights completion persistence ├── postgres_data_rights_processing.rs # PostgreSQL identity-verified data-rights operation persistence ├── postgres_health.rs # PostgreSQL major/write-readiness and relation-integrity probe ├── postgres_inbox_consumption.rs # PostgreSQL inbox consumption distinct from receipt @@ -137,8 +135,10 @@ src/lib.rs ├── postgres_response_snapshot.rs # PostgreSQL immutable response-snapshot persistence ├── postgres_result_snapshot.rs # PostgreSQL immutable result-snapshot persistence ├── postgres_assessment_session.rs # PostgreSQL session/reload/command persistence +├── postgres_longitudinal_observation.rs # PostgreSQL immutable longitudinal observation persistence ├── result_authorization.rs # personal result resource authorization ├── result_export.rs # immutable personal result export domain copy +├── result_http.rs # authorized immutable result-read transport ├── session_http.rs # persist-backed session HTTP transport ├── reference.rs # internal opaque-reference normalization ├── research_release.rs # product-side Research Commons release-evidence gate @@ -147,37 +147,36 @@ src/lib.rs ├── result_export_authorization.rs # post-#231 export-delivery guard (merged #249) ├── scoring_engine.rs # request-bound external scoring-engine adapter boundary ├── scoring.rs # version-pinned scoring dispatch contract -├── scoring_engine.rs # request-bound external scoring-engine adapter boundary ├── scoring_job.rs # bounded retry/quarantine lifecycle with lease fencing -└── session.rs # server-authoritative assessment-session transitions bound to a published locale release +├── session.rs # server-authoritative assessment-session transitions bound to a published locale release +├── session_http_boundary.rs # hardened session HTTP request-framing boundary +└── style_mapping.rs # deterministic Personality Style presentation mapping migrations/ ├── 0001_integration_delivery.sql through 0007_result_snapshot.sql ├── 0010_response_snapshot.sql through 0016_assessment_session_command.sql ├── 0018_data_rights_processing_start.sql -└── 0019_inbox_claim_expiry_guard.sql +├── 0019_inbox_claim_expiry_guard.sql +├── 0024_data_rights_completion.sql +└── 0031_longitudinal_observation.sql ``` -Still-Target logical modules/adapters include remaining product aggregate persistence/repositories, remaining public/admin HTTP and event transports, live fast-mlsirm/Keyverse/Gyeot/TEPP/semantic-data-portal adapters, research-release staging, deterministic narrative mapping, longitudinal enrollment persistence, participant identity-link history persistence, runtime health transports/metrics, and Measurement Workbench orchestration. - -### Active implementation work that is not protected-main truth +Still-Target logical modules/adapters include remaining product aggregate persistence/repositories, remaining public/admin HTTP and event transports, live fast-mlsirm/Keyverse/Gyeot/TEPP/semantic-data-portal adapters, research-release staging, longitudinal enrollment persistence, participant identity-link history persistence, runtime health transports/metrics, and Measurement Workbench orchestration. -Merged #249 `authorize_result_export_read` is protected-main delivery-guard evidence in `src/result_export_authorization.rs`: it authorizes the stored participant/result with existing `ReadOwnResult` (ADR-0010 export provenance; ADR-0003 tenant-bound authorization) and then requires the export's `result_snapshot_ref` and copied `participant_ref` to match that exact immutable snapshot. Cross-tenant callers fail closed with the ordinary result-authorization denial before export-binding details are evaluated. No new permission or persistence was introduced; authorized HTTP transport ships through merged #256. -Merged #231 personal result export is protected-main domain evidence. `ResultExport::from_snapshot` copies the stored construct scores, standard errors, dispositions, owner `participant_ref`, and version provenance into a JSON document and a human-readable report. Approved limitation text is required so the report cannot imply diagnosis, employment fitness, or a type score. Padded export aliases are rejected at this boundary without rewriting shared reference trimming used by consent and other domains. The snapshot is not mutated. Do not fold unrelated persistence into this domain slice. -**Active PR** #257 authorized immutable personal result reads are not protected-main truth until an unchanged reviewed/check-clean head is integrated. `src/result_http.rs` accepts only exact `GET /v1/results/{result_ref}` targets, checks server-owned participant/result authorization before identity comparison, returns no result existence oracle to an unauthorized actor, and serializes the stored immutable provenance without recomputation. PostgreSQL result loading and cross-tenant HTTP E2E remain required before this becomes a complete product path. -**Active PR** #284 durable accepted response-event persistence is not protected-main truth until an unchanged reviewed/check-clean head is integrated. `migrations/0020_response_event.sql` and `src/postgres_response_event.rs` keep the accepted mid-session ledger prefix durable across process restart with exact replay classification, contiguous sequence recovery, immutable provenance, and fail-closed migration/reference contracts. Public response HTTP transport and completed snapshot reload remain separate slices. -**Active PR** #301 consolidated public-release identity privacy gate is not protected-main truth until an unchanged reviewed/check-clean head is integrated. It consolidates the identity-column denylist, cell-value scanner, separator/prefix hardening, structured-value fail-closed behavior, and the `IdentityInventoryUnavailable` fail-closed inventory contract required by issue #260. A missing or blank effective restricted-identity inventory must fail closed before public fixture approval rather than being read as "nothing to match". -Merged #249 `authorize_result_export_read` is protected-main delivery-guard evidence in `src/result_export_authorization.rs`: it authorizes the stored participant/result with existing `ReadOwnResult` (ADR-0010 export provenance; ADR-0003 tenant-bound authorization) and then requires the export's `result_snapshot_ref` and copied `participant_ref` to match that exact immutable snapshot. Cross-tenant callers fail closed with the ordinary result-authorization denial before export-binding details are evaluated. No new permission or persistence was introduced. - -Merged #256 authorized personal result export HTTP is protected-main transport evidence: `src/result_export_http.rs` and `openapi/result-exports.yaml` bind the stored result to the authenticated participant/resource scope and preserve the immutable export provenance from `src/result_export.rs`. +### Protected-main implementation evidence referenced by this view +Merged #249 `authorize_result_export_read` is protected-main delivery-guard evidence in `src/result_export_authorization.rs`: it authorizes the stored participant/result with existing `ReadOwnResult` (ADR-0010 export provenance; ADR-0003 tenant-bound authorization) and then requires the export's `result_snapshot_ref` and copied `participant_ref` to match that exact immutable snapshot. Cross-tenant callers fail closed with the ordinary result-authorization denial before export-binding details are evaluated. No new permission or persistence was introduced; authorized HTTP transport remains Target on the evaluated protected-main baseline. +Merged #231 personal result export is protected-main domain evidence. `ResultExport::from_snapshot` copies the stored construct scores, standard errors, dispositions, owner `participant_ref`, and version provenance into a JSON document and a human-readable report. Approved limitation text is required so the report cannot imply diagnosis, employment fitness, or a type score. Padded export aliases are rejected at this boundary without rewriting shared reference trimming used by consent and other domains. The snapshot is not mutated. HTTP `POST /v1/results/{result_ref}/exports` remains Target. Do not fold unrelated persistence into this domain slice. +Merged #257 authorized immutable personal result reads are protected-main transport evidence. `src/result_http.rs` accepts only exact `GET /v1/results/{result_ref}` targets, checks server-owned participant/result authorization before identity comparison, returns no result existence oracle to an unauthorized actor, and serializes the stored immutable provenance without recomputation. `openapi/results.yaml` is the protected-main as-built contract for this HTTP family. PostgreSQL resource loading and cross-tenant HTTP E2E remain required before this becomes a complete product path. Merged #77 terminal data-rights completion persistence is protected-main evidence: `src/postgres_data_rights_completion.rs` and `migrations/0024_data_rights_completion.sql` persist request-bound completion evidence/time and immutable tenant-bound `data_rights_retained_scope_evidence` rows for deletion scopes that remain legally retained. Exact replay is idempotent; operation, completion identity/time, tenant, request kind/state, and retained-scope rebinding fail closed. This slice does not claim dependent-system execution has completed merely because local terminal evidence exists. +Merged #225 anonymous-session resource authorization compares the verified actor to the supplied participant tenant/owner and session and applies a lifecycle command only after that check. The command entry point does not accept a caller-built `ResourceScope` and does not claim the aggregates were store-loaded. Persist/reload of `assessment_participant` remains Target. Append-only identity-link history persistence remains a later slice. HTTP transport remains outside this slice. Persist-backed session HTTP with `openapi/sessions.yaml`, exclusive outbox delivery leases, longitudinal observation clocks/membership, and claim-next scoring-job poll are already on protected main. -Closed-unmerged #220 public research-fixture identity-column rejection is no longer an active lane; its fail-closed goal survives as open issue #260, which requires a fresh independently reviewed slice that fails closed when the authoritative restricted-identity inventory is absent or blank. - -Merged #231 personal result export is protected-main domain evidence. `ResultExport::from_snapshot` copies the stored construct scores, standard errors, dispositions, owner `participant_ref`, and version provenance into a JSON document and a human-readable report. Approved limitation text is required so the report cannot imply diagnosis, employment fitness, or a type score. Padded export aliases are rejected at this boundary without rewriting shared reference trimming used by consent and other domains. The snapshot is not mutated. HTTP `POST /v1/results/{result_ref}/exports` remains Target/active #256. Do not fold unrelated persistence into this domain slice. +### Active implementation work that is not protected-main truth -Merged #225 anonymous-session resource authorization compares the verified actor to the supplied participant tenant/owner and session and applies a lifecycle command only after that check. The command entry point does not accept a caller-built `ResourceScope` and does not claim the aggregates were store-loaded. Persist/reload of `assessment_participant` remains Target. Append-only identity-link history persistence remains a later slice. HTTP transport remains outside this slice. Persist-backed session HTTP, exclusive outbox delivery leases, longitudinal observation clocks/membership, and claim-next scoring-job poll are already on protected main. +Closed-unmerged #284 durable accepted response-event persistence is no longer an active lane; response-event persistence, public response HTTP transport, and completed snapshot reload remain separate Target slices until a fresh exact-head implementation is integrated. +**Active PR** #409 at exact head `558e7e560ef2e814932a97ce9ed48e32606bd76a` carries the current-main public-release identity privacy gate and is not protected-main truth until an unchanged reviewed/check-clean head is integrated. It consolidates the identity-column denylist, cell-value scanner, separator/prefix hardening, structured-value fail-closed behavior, and the `IdentityInventoryUnavailable` fail-closed inventory contract required by issue #260. A missing or blank effective restricted-identity inventory must fail closed before public fixture approval rather than being read as "nothing to match". +Historical PR #256 authorized personal result export HTTP is not part of the evaluated protected-main baseline; `src/result_export_http.rs` and `openapi/result-exports.yaml` are therefore not protected-main transport evidence here, and HTTP export remains Target. +Closed-unmerged #220 public research-fixture identity-column rejection is no longer an active lane; its fail-closed goal survives as open issue #260, which requires a fresh independently reviewed slice that fails closed when the authoritative restricted-identity inventory is absent or blank. ## 5. ADR traceability by concern @@ -236,7 +235,9 @@ Whenever a durable conversation decision changes one of those boundaries, the ap The prose API/event families in TRD are architecture requirements, not evidence of an implemented transport. -When the first HTTP API is implemented, the same PR or a prerequisite PR must add and validate an OpenAPI 3.2.x document whose operations and problem responses match the actual implementation. HTTP errors use RFC 9457 problem details unless a documented domain representation is more appropriate. +Protected main already binds its implemented HTTP families to exact as-built OpenAPI artifacts: `openapi/sessions.yaml` covers the persist-backed session `POST /v1/sessions` and `GET /v1/sessions/{session_ref}` family, while `openapi/results.yaml` covers the authorized immutable `GET /v1/results/{result_ref}` family. Those contracts are implementation evidence only for the operations they actually describe; result-export HTTP and the remaining public/admin families stay Target. + +Every additional HTTP API must add or update and validate an OpenAPI 3.2.x document in the same PR or an accepted prerequisite PR so its operations and problem responses match the actual implementation. HTTP errors use RFC 9457 problem details unless a documented domain representation is more appropriate. When durable message transport is implemented, the same PR or a prerequisite PR must add and validate an AsyncAPI 3.1.x document for actually produced/consumed event channels and message schemas. It must encode/reference ADR-0014 canonical UTF-8 payload hashing, SHA-256 payload digest semantics, tenant/resource binding, deduplication identity, pending/processing/completed consumption, replay retention, and quarantine behavior. @@ -270,4 +271,4 @@ Nottingham, M., Wilde, E., & Dalal, S. (2023). *Problem Details for HTTP APIs* ( OpenAPI Initiative. (2025). *OpenAPI Specification, Version 3.2.0*. -AsyncAPI Initiative. (2026). *AsyncAPI Specification, Version 3.1.0*. +AsyncAPI Initiative. (2026). *AsyncAPI Specification, Version 3.1.0*. \ No newline at end of file diff --git a/docs/adr/0014-api-and-event-contract-representation.md b/docs/adr/0014-api-and-event-contract-representation.md index 2fcf3de1..f799ba76 100644 --- a/docs/adr/0014-api-and-event-contract-representation.md +++ b/docs/adr/0014-api-and-event-contract-representation.md @@ -6,9 +6,9 @@ - Scope: Psychometrics Commons public/admin HTTP APIs, product-owned durable domain events, errors, schema/version negotiation - Supersedes: none - Superseded by: none -- Current/as-built status: persist-backed session create/reload HTTP (`POST /v1/sessions`, `GET /v1/sessions/{session_ref}`, `openapi/sessions.yaml`) exists on Active PR #232 and is not protected-main truth; remaining public/admin families and durable external event transport are still unimplemented on protected main +- Current/as-built status: persist-backed session create/reload HTTP (`POST /v1/sessions`, `GET /v1/sessions/{session_ref}`, `openapi/sessions.yaml`) is protected-main implementation evidence through merged #232; remaining public/admin families and durable external event transport are still incomplete on the evaluated protected-main baseline - Target status: every implemented HTTP/event surface has an exact versioned machine-readable as-built contract and deterministic integrity/idempotency semantics -- Migration status: no deployed HTTP/event transport requires migration yet; the first implementation must introduce the contract in the same or prerequisite PR +- Migration status: the protected-main session HTTP family ships with its OpenAPI contract and existing session persistence migrations; each additional HTTP/event family must introduce its machine-readable contract in the same or a prerequisite change and add persistence migration only when that family owns new durable state ## Context @@ -103,7 +103,7 @@ When durable event transport is implemented, the AsyncAPI/schema artifact must e ## Data and persistence impact -No transport persistence exists yet on protected main. The target logical model requires outbox event identity, tenant/subject binding, schema/canonicalization version, payload digest, delivery attempts, inbox deduplication identity, processing state, side-effect evidence, and quarantine/reconciliation evidence. `docs/architecture/ERD.md` defines the logical target; physical migrations must preserve these semantics when introduced. +Protected main already contains product-owned persistence used by the implemented session HTTP family. Durable external event transport remains incomplete: its target logical model requires outbox event identity, tenant/subject binding, schema/canonicalization version, payload digest, delivery attempts, inbox deduplication identity, processing state, side-effect evidence, and quarantine/reconciliation evidence. `docs/architecture/ERD.md` defines the logical target; physical migrations must preserve these semantics when introduced. ## Invariants @@ -153,7 +153,7 @@ Transport/broker choice is deployment-specific, but health and reconciliation mu ## Migration and rollout -The first implemented HTTP transport must introduce its OpenAPI document in the same PR or an accepted prerequisite PR. The first durable event transport must do the same for AsyncAPI plus the canonicalization/digest implementation and persistence constraints. +The session HTTP family satisfied the HTTP side of this ADR by landing its implementation and `openapi/sessions.yaml` together through merged #232. Each later HTTP family must introduce or update its exact OpenAPI contract in the same PR or an accepted prerequisite PR. The first durable event transport must do the same for AsyncAPI plus the canonicalization/digest implementation and persistence constraints. Contract changes are validated before deployment. During compatibility windows, old and new versions may be served/consumed concurrently only when the implementation has explicit routing/adapter tests. @@ -165,7 +165,7 @@ Rollback must restore an application version that still understands any messages - `docs/architecture/UML.md` must not model receipt as equivalent to externally visible side-effect completion. - `docs/architecture/SECURITY_AND_DATA.md` must preserve tenant/purpose boundaries for event payloads and quarantine. - `docs/architecture/DEPLOYMENT_AND_OPERATIONS.md` must include replay/quarantine/recovery evidence when event transport is implemented. -- `docs/TRACEABILITY.md` remains target until as-built OpenAPI/AsyncAPI and transport tests exist. +- `docs/TRACEABILITY.md` must distinguish the protected-main session HTTP family from target or active-PR transport families and bind each implemented family to its exact machine-readable contract. ## Validation and release evidence @@ -186,7 +186,7 @@ Release gates for an implemented transport include: - client/consumer compatibility tests for the supported window; - security tests that verify examples/errors/quarantine evidence do not disclose prohibited data. -Until the transport exists, these are explicit target acceptance requirements rather than fabricated passing evidence. +These are release requirements for the transport families to which they apply; absence of a not-yet-implemented family is not converted into fabricated passing evidence. ## Alternatives considered @@ -233,7 +233,7 @@ Costs: ## Follow-up work -- when the first HTTP transport lands, add the exact OpenAPI document and route/problem contract tests; +- keep each implemented HTTP family synchronized with its exact OpenAPI route/problem contract tests; - when the first durable event transport lands, add AsyncAPI plus canonicalization/digest test vectors and tenant-bound outbox/inbox migrations; - add consumer crash/replay/quarantine integration tests against the selected persistence/broker adapters; - link deployment-specific deduplication-retention policy to backup/restore and broker retention evidence. @@ -244,7 +244,7 @@ Costs: - Technical requirements: `docs/TRD.md` API, event, transactional integration, version compatibility, security, and validation sections. - Architecture: `ARCHITECTURE.md`, `docs/architecture/ERD.md`, `docs/architecture/UML.md`, `docs/architecture/DEPLOYMENT_AND_OPERATIONS.md`. - Decisions: ADR-0015 for persistence/transaction boundaries. -- Delivery evidence: `docs/TRACEABILITY.md`, `docs/ROADMAP.md`, `tests/documentation_architecture_contract.rs` until as-built transport tests replace documentation-only fitness evidence. +- Delivery evidence: `docs/TRACEABILITY.md`, `docs/ROADMAP.md`, tests for implemented transport contracts, and `tests/documentation_architecture_contract.rs` for documentation fitness. ## Reversal conditions diff --git a/docs/architecture/AS_BUILT_SCHEMA.md b/docs/architecture/AS_BUILT_SCHEMA.md index a6b8290e..e5741a7a 100644 --- a/docs/architecture/AS_BUILT_SCHEMA.md +++ b/docs/architecture/AS_BUILT_SCHEMA.md @@ -1,34 +1,36 @@ # As-Built PostgreSQL Schema Map - Status: Normative evidence map -- Date: 2026-08-14 -- Protected-main baseline: `cc5850a0d1eacbbf16d03075534fce460a8286e6` +- Date: 2026-08-28 +- Protected-main baseline: `09534ef52c9307ce0dc559e9d908ebd715c641a1` This document records which portions of the logical ERD have executable PostgreSQL migrations and adapters. It does **not** promote active-PR DDL or target entities to protected-main truth. `ERD.md` remains the normative logical model; this file is the physical/as-built maturity companion required once migrations exist. Status terms follow `docs/TRACEABILITY.md`: **Implemented** means evidence exists on the named protected-main baseline, **Active PR** means evidence exists only on an open PR, and **Target** means required behavior not yet implemented on that baseline. ## Protected-main physical schema -Protected main contains executable PostgreSQL 18 persistence subsets for integration delivery, scoring-job state, and instrument releases. Each listed subset has an owning adapter and real PostgreSQL contract evidence on or before the named protected-main baseline. These are bounded persistence slices, not claims that the complete product lifecycle is deployed or GA-ready. +Protected main contains executable PostgreSQL 18 persistence subsets for integration delivery, scoring-job state, instrument releases, assessment sessions, and longitudinal observations. Each listed subset has an owning adapter and real PostgreSQL contract evidence on or before the named protected-main baseline. These are bounded persistence slices, not claims that the complete product lifecycle is deployed or GA-ready. | Physical object | Logical ownership | Protected-main maturity | |---|---|---| -| `integration_outbox` | integration | Implemented subset; exclusive delivery-lease extension is **Active PR** #60 | +| `integration_outbox` | integration | Implemented subset; exclusive delivery leases; merged #60 | | `integration_delivery_attempt` | integration | Implemented subset | | `integration_inbox` | integration | Implemented subset | | `scoring_job_state` | scoring | Implemented subset | | `instrument_release` | instrument publication | Implemented subset | | `integration_consumption` | integration | Implemented subset | -| `assessment_session` | session | **Active PR** #218 (not protected-main truth) | +| `assessment_session` | session | Implemented subset through merged #232 | +| `longitudinal_observation` | longitudinal collection | Implemented subset through merged #248 | +| `longitudinal_membership_share` | longitudinal collection | Implemented subset through merged #248 | The protected-main integration identity is source- and tenant-scoped. A physical implementation must continue to preserve the stronger logical tenant/resource, replay, and crash-safety invariants in ADR-0014 and ADR-0015. -## Active PR assessment-session physical schema +## Protected-main assessment-session physical schema -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. +Merged #232 (`migrations/0014_assessment_session.sql`, `migrations/0016_assessment_session_command.sql`, and `src/postgres_assessment_session.rs`) persists and loads 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 an **Implemented subset on protected main**. 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` uses this start path. The historical protected-main GET is not an authorization boundary; Active PR #438 at `67f4508f85ec3483c1358b8c1db99e4c92ba0727` adds host-verified authority and participant-bound reload, but remains unmerged. 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 +## Protected-main 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. +Merged #248 (`migrations/0031_longitudinal_observation.sql` and `src/postgres_longitudinal_observation.rs`) is **Implemented on protected main**. 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. @@ -37,11 +39,11 @@ PR #248 (`migrations/0031_longitudinal_observation.sql` and `src/postgres_longit 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. -## Active PR outbox delivery-lease physical schema +## Protected-main 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. +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 an **Implemented subset on protected main**. -The active slice adds: +The protected-main slice adds: - nullable `lease_worker_ref` and `lease_ref` opaque ownership references; - nullable positive `lease_fencing_token` and `lease_expires_at_unix_ms` values that are either all present or all absent for the current lease; @@ -51,7 +53,7 @@ The active slice adds: - database-clock authority for both worker-side lease-expiry classification and exclusive-lease recovery, while caller-supplied attempt timestamps remain immutable delivery-attempt evidence and a future caller observation cannot steal a still-live lease; - fail-closed stale fencing before replay classification whenever a current lease exists, while exact replay after a completed attempt has cleared its lease remains idempotent. -Real PostgreSQL evidence on the active PR is carried by `tests/postgres_outbox_delivery_lease.rs`, `tests/postgres_outbox_delivery_lease_fencing_integrity.rs`, `tests/postgres_outbox_delivery_lease_authority.rs`, `tests/postgres_outbox_delivery_lease_concurrency.rs`, `tests/postgres_outbox_delivery_lease_coverage_edges.rs`, and `tests/postgres_outbox_delivery_lease_migration_isolation.rs`. These tests cover exclusive claim/recovery, monotonic fencing, invalid physical state rejection, database-authoritative expiry, rejection of a future caller timestamp against a still-live lease, stale-fence replay precedence, blocking-proven concurrent claims, schema isolation, and persistence failure paths. The slice must remain **Active PR** until the exact reviewed/check-clean head is merged and protected main is refetched. +Real PostgreSQL evidence on protected main is carried by `tests/postgres_outbox_delivery_lease.rs`, `tests/postgres_outbox_delivery_lease_fencing_integrity.rs`, `tests/postgres_outbox_delivery_lease_authority.rs`, `tests/postgres_outbox_delivery_lease_concurrency.rs`, `tests/postgres_outbox_delivery_lease_coverage_edges.rs`, and `tests/postgres_outbox_delivery_lease_migration_isolation.rs`. These tests cover exclusive claim/recovery, monotonic fencing, invalid physical state rejection, database-authoritative expiry, rejection of a future caller timestamp against a still-live lease, stale-fence replay precedence, blocking-proven concurrent claims, schema isolation, and persistence failure paths. These tests are protected-main evidence for merged #60. ## Protected-main inbox-consumption physical schema diff --git a/docs/architecture/ERD.md b/docs/architecture/ERD.md index bf825bae..a6be32dc 100644 --- a/docs/architecture/ERD.md +++ b/docs/architecture/ERD.md @@ -448,13 +448,13 @@ erDiagram The target ERD deliberately includes several logical entities that are not yet physical tables: - `instrument_release` is the locale-specific publication identity already owned by `src/instrument.rs`. Physical `migrations/0006_instrument_release.sql` persists that one-row aggregate (immutable manifest columns plus `publication_state`); HTTP publication transport remains Target. -- `data_rights_request` and `data_rights_propagation_state` are the first durable export/deletion slice. Protected main persists requested-state identity plus local propagation and processing evidence. **Active PR #77** adds terminal `completion_evidence_ref` / `completed_at_unix_ms` fields and immutable `data_rights_retained_scope_evidence` child rows for deletion scopes that must remain retained; this is not protected-main truth until #77 is integrated. Dependent-system execution remains Target. -- Physical `assessment_session` exists only on Active PR #218 (`migrations/0014_assessment_session.sql`): Created identity (participant, release, version, digest, locale, creation time) plus a current-state projection. New sessions start only from a stored published release locked in the same transaction; first insert through `persist_assessment_session` takes the same lock; when that lock finds a missing or unpublished release, persist still classifies an exact stored Created row as duplicate; exact replay of an already stored start or Created row still returns the original session after a later persist Suspend or Retire; reconstitution is load, not start. Physical `assessment_session_command` (`migrations/0016_assessment_session_command.sql`) stores append-only command history so later states reload by replaying Activate/Pause/Resume. A shorter persist than already stored fails closed and does not rewind that projection. Command persist locks the header row with `SELECT … FOR UPDATE` before inserting or counting commands. Load reconstitutes created identity without re-checking current publication eligibility. Persist-backed HTTP create/reload sits on this start path. Protected main still has the `src/session.rs` aggregate only. +- `data_rights_request` and `data_rights_propagation_state` are the first durable export/deletion slice. Protected main persists requested-state identity plus local propagation and processing evidence. **Implemented on protected main through merged #77** adds terminal `completion_evidence_ref` / `completed_at_unix_ms` fields and immutable `data_rights_retained_scope_evidence` child rows for deletion scopes that must remain retained. Dependent-system execution remains Target. +- Physical `assessment_session` is implemented on protected main through merged #232 (`migrations/0014_assessment_session.sql`): Created identity (participant, release, version, digest, locale, creation time) plus a current-state projection. New sessions start only from a stored published release locked in the same transaction; first insert through `persist_assessment_session` takes the same lock; when that lock finds a missing or unpublished release, persist still classifies an exact stored Created row as duplicate; exact replay of an already stored start or Created row still returns the original session after a later persist Suspend or Retire; reconstitution is load, not start. Physical `assessment_session_command` (`migrations/0016_assessment_session_command.sql`) stores append-only command history so later states reload by replaying Activate/Pause/Resume. A shorter persist than already stored fails closed and does not rewind that projection. Command persist locks the header row with `SELECT … FOR UPDATE` before inserting or counting commands. Load reconstitutes created identity without re-checking current publication eligibility. Persist-backed HTTP create/reload sits on this start path. - `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. Persist/reload of `assessment_participant` remains Target. Append-only identity-link history persist remains Active PR #52; it is not protected-main truth until integrated. Do not name closed #158, #147, #133, #114, or #124 as the current persist landing. - `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. -- `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. Exclusive outbox delivery-lease columns (`lease_worker_ref`, `lease_ref`, `lease_fencing_token`, `lease_expires_at`, `delivery_lease_generation`) and database-clock expiry recovery exist only on Active PR #60 until merged. `integration_consumption` pending/processing/completed/quarantined persistence is already on protected main. +- `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. Exclusive outbox delivery-lease columns (`lease_worker_ref`, `lease_ref`, `lease_fencing_token`, `lease_expires_at`, `delivery_lease_generation`) and database-clock expiry recovery are protected-main evidence from merged #60. `integration_consumption` pending/processing/completed/quarantined persistence is already on protected main. This section is a maturity guard: a logical entity may be architecture-complete without being as-built database evidence. diff --git a/docs/architecture/UML.md b/docs/architecture/UML.md index c308b054..70dad909 100644 --- a/docs/architecture/UML.md +++ b/docs/architecture/UML.md @@ -510,9 +510,9 @@ For effects fully owned by the same PostgreSQL transaction, the domain side effe - External systems are accessed through versioned adapters; direct database joins across bounded contexts are forbidden. - Failure paths shown in the TRD remain normative even if omitted from a simplified happy-path diagram. - Target-only sequence actors/containers remain target architecture until `docs/TRACEABILITY.md` links protected-main implementation evidence. -- `src/item_delivery.rs`, `src/participant.rs`, `src/authorization.rs`, and `src/integration.rs` are protected-main domain evidence; the API/persistence sequences around them remain target until their adapters land. Active PR #264 adds `src/integration_delivery.rs`, which verifies the publisher receipt against the exact source/tenant/event identity before a fresh transaction persists the fenced attempt; it does not yet prove a live external publisher worker. -- Session start from a stored published release plus persist-backed `POST /v1/sessions` / `GET /v1/sessions/{session_ref}` exists on Active PR #232; command HTTP remains target. +- `src/item_delivery.rs`, `src/participant.rs`, `src/authorization.rs`, and `src/integration.rs` are protected-main domain evidence. `src/integration_delivery.rs` is also protected-main evidence through merged #264: it verifies the publisher receipt against the exact source/tenant/event identity before a fresh transaction persists the fenced attempt, but it still does not prove a live external publisher worker. +- Session start from a stored published release plus persist-backed `POST /v1/sessions` / `GET /v1/sessions/{session_ref}` is protected-main evidence through merged #232; command HTTP remains target. ## 14. Reference -Object Management Group. (2017). *OMG Unified Modeling Language (OMG UML), Version 2.5.1*. +Object Management Group. (2017). *OMG Unified Modeling Language (OMG UML), Version 2.5.1*. \ No newline at end of file diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 9f50e176..3da98aac 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -1,8 +1,8 @@ # Product and Technical Gap Baseline - Status: current delivery snapshot, not a release or certification claim -- Snapshot date: 2026-08-25 (Asia/Seoul) -- Evaluated protected-main head: `70c9344a022c602062b7d6a43cfde7adc9d476cc` (`Merge pull request #343 from ContextualWisdomLab/automation/session-http-fixture-lock-20260824`) +- Snapshot date: 2026-08-28 (Asia/Seoul) +- Evaluated protected-main head: `09534ef52c9307ce0dc559e9d908ebd715c641a1` (`Merge pull request #248 from ContextualWisdomLab/feat/longitudinal-observation-persistence-20260818`) - Repository: `ContextualWisdomLab/psychometrics-commons` - Scope: participant-visible product journey, product/runtime implementation, architecture evidence, open PRs/issues, and next executable loop @@ -19,52 +19,56 @@ Evidence status uses these meanings: The authority order is accepted/superseding ADR → PRD/TRD → measurement, AI, and research governance → quality/security/compliance/risk constraints → architecture views → machine-readable contracts → code, migrations, tests, and operational evidence. A lower layer cannot promote a target into shipped behavior. +Abbreviations used here are defined for first-time readers: P0/P1/P2 are priority levels (blocking, high, and planned); MAE is mean absolute error; RMSE is root mean square error; WCAG is the Web Content Accessibility Guidelines; 3NF is third normal form; ERD is entity-relationship diagram; ADR is architecture decision record; CWL is ContextualWisdomLab; CEFR is the Common European Framework of Reference for Languages; DIF is differential item functioning; IPIP is the International Personality Item Pool; LCOV is a line-coverage report format; SBOM is software bill of materials; SLO/RPO/RTO are service-level objective, recovery-point objective, and recovery-time objective; `MERGEABLE` and `BLOCKED` are GitHub merge-state values; OpenCode is the required hosted code-review gate; and Strix is the hosted security-scan gate. + ## Observed protected-main baseline -The evaluated head contains a Rust product-runtime library with PostgreSQL adapters and three as-built HTTP families (session, result read/export): +The evaluated head contains a Rust product-runtime library with PostgreSQL adapters and two as-built HTTP families (session and result read): | Area | Observed evidence | Participant meaning | |---|---|---| | Assessment lifecycle | `src/session.rs`, response ledger/snapshot, scoring job, result snapshot, and instrument publication contracts | The core lifecycle is modeled with fail-closed replay and provenance rules. | | Session transport | `src/session_http.rs`, `openapi/sessions.yaml`, `POST /v1/sessions`, and `GET /v1/sessions/{session_ref}` | A participant can start/reload a created session contract, but item delivery/response submission HTTP is not yet on protected main. | -| Persistence | Migrations `0001`–`0007`, `0010`–`0016`, `0018`–`0019`, `0024`, plus PostgreSQL adapters for integration (including merged #264 fenced delivery handoff), consent, data rights completion (#77), instruments, responses, results, scoring, sessions, and health | Durable slices exist, but remaining aggregates, recovery drills, and deployment evidence are not closed. | +| Persistence | Migrations `0001`–`0007`, `0010`–`0016`, `0018`–`0019`, `0024`, `0031`, plus PostgreSQL adapters for integration, consent, data rights completion (#77), instruments, responses, results, scoring, sessions, health, and longitudinal observations (#248) | Durable slices exist, but remaining aggregates, recovery drills, and deployment evidence are not closed. | | Scoring boundary | Version-pinned scoring request/result contracts in `src/scoring.rs`, `src/scoring_job.rs`, PostgreSQL adapters, and the protected-main request-bound adapter in `src/scoring_engine.rs` | Numeric kernels remain correctly outside this repository; a live `fast-mlsirm` execution and instrument evidence proof is still absent from protected main. | -| Result export | `src/result_export.rs` domain copy (#231), `src/result_export_authorization.rs` delivery guard (#249), `src/result_export_http.rs` + `openapi/result-exports.yaml` authorized transport (#256), and `src/localized_result_report.rs` exact ko-KR/en-US reports (#259) are all protected main at this head | The personal archive lane — immutable copy, tenant-fail-closed authorization, HTTP download, localized report — is one protected-main flow; a browser-level journey across all families remains unproven. | +| Result export | `src/result_export.rs` domain copy (#231), `src/result_export_authorization.rs` delivery guard (#249), and `src/localized_result_report.rs` exact ko-KR/en-US reports (#259) are protected main at this head; authorized HTTP export (#256) is not an ancestor of protected main | The personal archive domain and delivery guard are shipped, but HTTP download remains a target; a browser-level journey across all families remains unproven. | | Authorization and identity | Domain authorization, anonymous/session identity contracts, account-link HTTP primitives exist | Durable identity-link history persistence and complete Keyverse integration remain incomplete. | -| Longitudinal evidence | `src/longitudinal_observation.rs` preserves validity, recorded, received, ingested, and membership evidence | The temporal contract is present; enrollment persistence, HTTP collection, and Gyeot/TEPP orchestration remain incomplete. | -| Research release | `src/research_release.rs` contains release-gate domain contracts; #220 closed unmerged, so the fail-closed inventory outcome lives in open issue #260 with no active PR | Publication must still prove privacy inventory availability, release persistence, scientific review, and semantic-data-portal registration. | -| Narrative and locale | Deterministic narrative/style primitives plus merged #259 exact Korean/English report labels exist | Personality Style mapping v1 reconciliation (#314) and canonical published rule references (#287) remain active PR lanes. | +| Longitudinal evidence | `src/longitudinal_observation.rs`, `migrations/0031_longitudinal_observation.sql`, and `src/postgres_longitudinal_observation.rs` preserve validity, recorded, received, ingested, and membership evidence on protected main through merged #248 | The temporal contract and normalized persistence are present; enrollment, HTTP collection, and Gyeot/TEPP orchestration remain incomplete. | +| Research release | `src/research_release.rs` contains release-gate domain contracts; #220 closed unmerged, while active current-main PR #409 carries the fail-closed inventory outcome for open issue #260 | Publication must still prove privacy inventory availability, release persistence, scientific review, and semantic-data-portal registration. | +| Narrative and locale | Deterministic narrative/style primitives, merged #314 Personality Style mapping v1, merged #287 canonical published rule references, and merged #259 exact Korean/English report labels exist | A rendered accessible client and browser-level narrative journey remain unproven. | | Reference client/design system | No frontend or Storybook inventory is present in this repository snapshot | The product is not yet a demonstrable accessible client. No Figma file ID is invented; an actual design artifact is required before an ADR can record one. | ## Priority gaps | Priority | Gap and participant-visible consequence | Current evidence | Smallest next executable slice | Completion evidence | |---|---|---|---|---| -| P0 | **Complete public assessment journey.** A participant cannot yet rely on one protected-main path from anonymous start through item delivery, response submission, completion, scoring, result read, and authorized export. | Session HTTP, result read/export HTTP, and locale reports are protected main; durable accepted response-event recovery (#284), item-delivery/reference-parity lanes, and Quick/Deep release binding (#261) remain open PRs or Target. | Merge the response/delivery persistence and reference-parity slices in dependency order, then add item-delivery and response-command HTTP with one OpenAPI contract per family and RFC 9457 errors. | Real HTTP E2E on exact protected head; persisted restart/replay; no invented score; cross-tenant negatives; OpenAPI contract validation. | +| P0 | **Complete public assessment journey.** A participant cannot yet rely on one protected-main path from anonymous start through item delivery, response submission, completion, scoring, result read, and authorized export. | Session HTTP, Quick/Deep release binding (#261), result-read HTTP (#257), and locale reports are protected main; authorized export HTTP (#256), response-event persistence PR (#427), session-command HTTP (#414), item-delivery HTTP (#197), response HTTP (#415), and reference-parity lanes remain open PRs or Target. | Merge the response/delivery persistence and reference-parity slices in dependency order, then complete item-delivery and response-command HTTP with one OpenAPI contract per family and RFC 9457 errors. | Real HTTP E2E on exact protected head; persisted restart/replay; no invented score; cross-tenant negatives; OpenAPI contract validation. | | P0 | **Live scientific scoring and instrument evidence.** The runtime has scoring contracts and a request-bound external-engine adapter, but no shipped, rights-cleared IPIP Big Five instrument release with real `fast-mlsirm` execution evidence. | `src/scoring_engine.rs` rejects mismatched external results without recomputing numerics; live execution and instrument-specific evidence remain Target. | Add one rights/locale/scientific evidence bundle and a versioned `fast-mlsirm` adapter execution proof without copying upstream kernels. | True-parameter recovery, bias/MAE/RMSE, uncertainty coverage, boundary/parity evidence as applicable, exact artifact digest, and end-to-end result provenance. | -| P0 | **Research public-release fail-closed inventory.** An absent identity inventory currently risks being treated as “nothing to match.” | #220 closed unmerged; issue #260 stays open and requires a distinct `inventory unavailable` outcome from a fresh slice (#301 carries related privacy-gate hardening). | Deliver #260 against current main without resurrecting #220 wholesale; never query another service database. | RED absent/blank inventory; GREEN non-empty no-match; forbidden operational/Keyverse/linkage match; pseudonym allowed; raw identifiers absent from errors. | -| P1 | **Durable identity and authorization journey.** Anonymous participation, account linking, recovery, and personal resource access are not one proven transport/persistence boundary. | Domain primitives and link HTTP primitives exist; participant base persistence (#250) and credential-bound session authority (#253/#302) remain open. | Consolidate the narrowest append-only participant/link persistence slice after exact dependency review. | Proof separation, issuer/subject/tenant binding, unlink/relink recovery, historical identity immutability, and cross-tenant HTTP tests. | -| P1 | **Longitudinal product loop.** Participants cannot yet enroll, persist, reload, and submit observations into an owned Commons→Gyeot/TEPP handoff. | Observation semantics are protected main; #248 persistence and identity correction #289 are active lanes; enrollment/HTTP are Target. | Merge the observation persistence and identity correction first, then add enrollment/observation HTTP with explicit source and membership keys. | Restart/replay, duplicate identity rejection, temporal ordering, multiple-membership preservation, tenant/consent gates, and bounded handoff evidence. | +| P0 | **Research public-release fail-closed inventory.** An absent identity inventory currently risks being treated as “nothing to match.” | #220 and #301 are closed unmerged; issue #260 stays open while active current-main PR #409 carries the distinct `inventory unavailable` outcome and related privacy-gate hardening. | Complete review and protected-main integration of #409 for #260; never resurrect predecessor implementations or query another service database. | RED absent/blank inventory; GREEN non-empty no-match; forbidden operational/Keyverse/linkage match; pseudonym allowed; raw identifiers absent from errors. | +| P1 | **Durable identity and authorization journey.** Anonymous participation, account linking, recovery, and personal resource access are not one proven transport/persistence boundary. | Domain primitives, credential-bound session authority (#253), and link HTTP primitives exist; participant base persistence (#250) and append-only identity-link history remain incomplete. | Consolidate the narrowest append-only participant/link persistence slice after exact dependency review. | Proof separation, issuer/subject/tenant binding, unlink/relink recovery, historical identity immutability, and cross-tenant HTTP tests. | +| P1 | **Longitudinal product loop.** Participants cannot yet enroll, persist, reload, and submit observations into an owned Commons→Gyeot/TEPP handoff. | Observation semantics and normalized PostgreSQL persistence are protected main through #248; the identity-collision repair lane (#289) is closed without merge, and enrollment/HTTP are Target. | Complete the identity-collision repair as a prerequisite before enrollment/HTTP, then add enrollment/observation HTTP with explicit source and membership keys against the current protected-main baseline. | Restart/replay, duplicate identity rejection, temporal ordering, multiple-membership preservation, tenant/consent gates, and bounded handoff evidence. | | P1 | **Accessible end-to-end result experience.** Exact-locale report labels ship as a library contract, but no rendered client proves accessibility, keyboard paths, and non-color equivalents. | Merged #259 provides exact ko-KR/en-US deterministic reports over immutable exports; no reference client exists. | Build the smallest accessible reference flow using native HTML/CSS and a token inventory in a separate reference-client boundary; record a real Figma File ID in its ADR before any design claim. | WCAG 2.2 AA automated plus keyboard/assistive-technology checks, locale no-fallback tests, text/table equivalents, screenshot-audited UX, and snapshot provenance parity. | | P1 | **Operational confidence.** The code has health/retry/recovery contracts, but no measured deployment-profile restore, SLO/RPO/RTO, or incident exercise evidence. | `docs/OPERABILITY.md`, ADR-0017, and release acceptance explicitly keep these as evidence gates. | Run one Compose/PostgreSQL bootstrap, upgrade, backup/restore, and crash/replay drill for the supported profile. | Exact artifact manifest, measured recovery results, deduplication/provenance reconciliation, and current runbook evidence. | -| P2 | **Architecture/documentation freshness.** Protected-main and active-PR references drift after every merge or concurrent push. | This refresh names protected-main `70c9344a…`, resolves TRACEABILITY conflicts for merged #249/#256/#259/#264/#77, and re-baselines this document; historical inventory rows remain explicitly historical. | Re-run the same exact-ref refresh after each material merge or branch movement; do not promote queued checks or a PR head into protected-main evidence. | Documentation fitness tests pass and every current status names an exact current head, protected-main SHA, or explicit unverified gate state. | +| P2 | **Architecture/documentation freshness.** Protected-main and active-PR references drift after every merge or concurrent push. | This refresh names protected-main `09534ef5…`; it reconciles the result-transport and migration-inventory status in `docs/TRACEABILITY.md` while open PRs remain explicitly outside protected-main truth. | Reconcile the next affected traceability/as-built entries after each protected-main merge; do not promote queued checks or a PR head into protected-main evidence. | Documentation fitness tests pass and every current status names an exact current head, protected-main SHA, or explicit unverified gate state. | | P2 | **Database scale and naming proof.** Existing migrations use descriptive multi-word table names, but there is no single automated proof for all object names, 3NF/cardinality reconciliation, or hot-partition behavior. | Current migration inventory is observable; ERD is logical and several future aggregates have no physical migration. | Add a bounded migration/schema contract check when the next physical slice lands; document partition keys only where measured workload requires them. | Clean/upgrade migration tests, ERD reconciliation, naming check, tenant uniqueness checks, and measured contention/partition evidence. | | P2 | **Design-system and buyer onboarding evidence.** No Figma/Storybook artifacts exist in this backend-focused repository, so visual consistency and onboarding are not reviewable. | No frontend or Storybook files were observed in the protected-main tree. | Use a separate reference-client/design-system boundary when the first client slice is authorized; record the real Figma File ID in its ADR. | Storybook inventory, design-token tests, keyboard/interaction/i18n checks, and a published usable client; do not fabricate a Figma ID. | -## Current PR and gate refresh (2026-08-25) +## Current PR and gate refresh (2026-08-28) -After fetching `origin`, protected `main` is `70c9344a022c602062b7d6a43cfde7adc9d476cc`. GitHub lists 60 open PRs at this snapshot. Merged since the 2026-08-21 refresh: #249 (export delivery guard), #256 (authorized export HTTP), #259 (exact-locale reports), #264 (fenced publisher handoff), #77 (terminal data-rights completion), #319/#320 (integration reference parity). #220 closed unmerged; #87 closed superseded. +After fetching `origin`, protected `main` is `09534ef52c9307ce0dc559e9d908ebd715c641a1`. GitHub lists 61 open PRs at this snapshot. Since the previous baseline, protected main merged #248 (longitudinal observation persistence), #253 (credential-bound session authority), #257 (authorized result reads), #261 (Quick/Deep release binding), #287 (canonical narrative rule references), #314 (Personality Style mapping v1), and #413 (response-ledger session-signature test reconciliation). #284 and #289 are closed without merge. The complete live inventory remains [GitHub's open-PR view](https://github.com/ContextualWisdomLab/psychometrics-commons/pulls?q=is%3Aopen+is%3Apr+base%3Amain). -Today's merge-loop classification, refreshed from exact PR heads: +The current buyer-impact queue is intentionally bounded; every action still requires refetching the exact PR head, base, checks, reviews, security results, and unresolved threads: -| Lane | PRs | Gate state and required action | +| Lane | PRs | Current gap and next action | |---|---|---| -| Fixture serialization (fresh heads) | #372–#378, #369, #344 | Based on current main; org checks queued/running. The org `strix` smoke contract was repaired on `ContextualWisdomLab/.github@8fd471a3` (workflow fallback `openai-direct/gpt-5.4` aligned with the smoke assertion); runs started after 2026-08-25T01:42Z are unaffected. Merge per PR as current-head checks plus CodeRabbit/Devin commit statuses succeed. | -| Padded/exact-reference hardening | #306–#317, #321–#322, #325, #329–#331 | Older heads with pre-fix strix failures; branches are being updated onto current main so the fixed org workflow re-evaluates them. Coverage failures on stale heads must be re-proven against the new head before any merge decision. | -| Feature/reconcile lanes | #250, #253, #257, #261, #273–#279, #284, #287–#294, #301–#304, #310, #312–#314, #318 | Conflicting or behind; each requires update-branch, conflict resolution, and fresh exact-head evidence. Superseded content must be closed with evidence instead of force-pushed. | -| Documentation baseline | #263 | This document: conflict-resolved against merged-lane truth and refreshed to the 2026-08-25 head. | +| Public assessment transport | #197 (Draft), #414, #415 | Session-command HTTP (#414 current head `c4bbef22f4f58e651eeb8cf35453b8d4f9f4ea2a`), response-recording HTTP (#415 `0bae8219eda535af0360f8dbd3a080f9d83472c8`), and item-delivery remain outside protected main; #414's Runtime CI, line/branch coverage, package, and security checks pass, but its required `opencode-review` check fails externally. #415's functional, line/branch coverage, package, and security checks pass. Neither lane has independent approval. The coverage gate enforces merged LCOV records while retaining JSON instantiation diagnostics; each lane must still reconcile against the persisted session/ledger boundary before adding browser E2E. | +| Durable response ledger | #427 | Current head `03fe0ddafc2fef075db96d226cd6b73ebda791ab` is open, non-Draft, and `MERGEABLE` but `BLOCKED`; its read-isolation regression and formatting failures were repaired without weakening the write-only `READ COMMITTED` guard, and its functional, line/branch coverage, package, and security checks pass. `opencode-review` fails because no current-head OpenCode verdict was posted, while Strix fails closed after provider/backend internal-server errors; Devin Review is informational, independent approval is absent, and the coverage gate enforces merged LCOV records while retaining JSON instantiation diagnostics. | +| Durable parity and fail-closed hardening | #279, #292, #302, #318, #331, #398, #400–#402, #409, #411, #419, #420, #421, #427, #428–#433 | These are active or reconciliation lanes, not protected-main truth; current active heads include #409 `558e7e560ef2e814932a97ce9ed48e32606bd76a`, #427 `03fe0ddafc2fef075db96d226cd6b73ebda791ab`, #432 `bd53dd2394d4f30bcc583a764c4d36404bdb9c92`, and #433 `a542687df6671c9b1250e375c82631a15cabff96`. #409's local privacy/documentation contracts and hosted functional, coverage, package, security, and Devin checks pass; OpenCode has no current-head verdict and Strix fails on provider/backend errors. #432 and #433 have core Runtime coverage checks passing but remain blocked by external security/review gates and missing independent approval. Preserve process dependency order and current-head evidence before merge decisions. | +| Identity, longitudinal, and instrument gaps | #165, #226, #250, #417, #422 | Protected main now has normalized longitudinal persistence, but participant persistence, enrollment/HTTP, startable catalog transport, and identity/recovery completion remain incomplete. #417 is the current identity-collision repair lane at head `20d8ec369908fe844e2940ffeb467579fee97951`; its Runtime format/test, line coverage, branch coverage, package, and security checks pass, while OpenCode has no current-head verdict and Strix failed closed on provider/backend errors. It remains blocked without independent approval and cannot satisfy the prerequisite above yet. | +| CEFR and documentation targets | #426, #435; issues #424 and #425 | #426 is a Draft design lane; #435 is Ready for review at current head `2ebe6a140d3f2113556d474645ce355ed59d1176`, but its new hosted checks and independent review must be revalidated; scientific/right-cleared English A1–B2 publication and released-contract consumption remain product targets until evidence and ownership are implemented. | +| Governance, operations, and release evidence | #103, #247, #403, #408, #418; issue #326 | Current checks and toolchain/release lanes require exact-head hosted evidence; branch protection restoration remains an open governance issue. | -No row above is merge evidence by itself; each PR still requires its own current-head check completion, CodeRabbit/Devin commit-status success, and thread resolution at merge time. +No row above is merge evidence by itself; a PR is mergeable only after its unchanged current head has terminal required checks, independent non-author approval, resolved threads, and the protected repository policy permits merge. ## Historical open PR inventory (2026-08-20) @@ -172,7 +176,7 @@ These exact-head rows are a bounded operational view, not merge evidence. A PR i ## Current issue inventory -The open repository issues observed in this snapshot are [#260](https://github.com/ContextualWisdomLab/psychometrics-commons/issues/260), **Fail closed when public-release identity inventory is unavailable**, and [#326](https://github.com/ContextualWisdomLab/psychometrics-commons/issues/326), **governance: restore enforceable main branch protection**. Issue #260 no longer depends on an open PR (#220 closed unmerged); its required distinction is: +The five open repository issues observed in this snapshot are [#260](https://github.com/ContextualWisdomLab/psychometrics-commons/issues/260) (**Fail closed when public-release identity inventory is unavailable**), [#326](https://github.com/ContextualWisdomLab/psychometrics-commons/issues/326) (**governance: restore enforceable main branch protection**), [#405](https://github.com/ContextualWisdomLab/psychometrics-commons/issues/405) (**align persisted snapshot reference validation with exact domain spelling**), [#424](https://github.com/ContextualWisdomLab/psychometrics-commons/issues/424) (**publish an English A1–B2 placement instrument family and immutable profile result**), and [#425](https://github.com/ContextualWisdomLab/psychometrics-commons/issues/425) (**consume the CWL CEFR language-assessment profile v1**). Issue #260 no longer depends on an open PR (#220 closed unmerged); its required distinction is: - `inventory unavailable` when the effective restricted-identity inventory is absent or blank; - `forbidden identity` when a supplied authoritative inventory matches a fixture cell; diff --git a/tests/session_http_openapi_contract.rs b/tests/session_http_openapi_contract.rs new file mode 100644 index 00000000..00ff9312 --- /dev/null +++ b/tests/session_http_openapi_contract.rs @@ -0,0 +1,115 @@ +//! Machine-readable contract gate for the persist-backed session HTTP boundary. +//! +//! ADR-0014 requires every implemented HTTP operation to carry an exact +//! OpenAPI 3.2.x as-built contract. This test includes the session contract at +//! compile time and checks the implemented collection path, methods, response +//! families, and durable session representation so contract drift fails CI. + +use psychometrics_commons_runtime::session_http::SESSION_COLLECTION_PATH; + +const SESSION_OPENAPI: &str = include_str!("../openapi/sessions.yaml"); + +fn section<'a>(document: &'a str, start: &str, end: Option<&str>) -> &'a str { + let (_, remainder) = document + .split_once(start) + .unwrap_or_else(|| panic!("missing OpenAPI section: {start:?}")); + match end { + Some(end) => remainder + .split_once(end) + .map_or(remainder, |(body, _)| body), + None => remainder, + } +} + +#[test] +fn session_openapi_is_pinned_to_the_implemented_operations() { + assert_eq!(SESSION_COLLECTION_PATH, "/v1/sessions"); + assert!(SESSION_OPENAPI.starts_with("openapi: 3.2.0\n")); + assert_eq!( + SESSION_OPENAPI.matches("\n /v1/").count(), + 2, + "session OpenAPI must describe only the two implemented session paths" + ); + + let create = section( + SESSION_OPENAPI, + " /v1/sessions:\n", + Some(" /v1/sessions/{session_ref}:\n"), + ); + for required_fragment in [ + " post:\n", + " operationId: startAssessmentSession\n", + " - name: Idempotency-Key\n", + " required: true\n", + " \"200\":\n", + " \"201\":\n", + " \"400\":\n", + " \"405\":\n", + " \"409\":\n", + " \"500\":\n", + "#/components/schemas/SessionCreate", + "#/components/schemas/CreatedSession", + ] { + assert!( + create.contains(required_fragment), + "missing as-built session-create contract fragment: {required_fragment:?}" + ); + } + + let reload = section( + SESSION_OPENAPI, + " /v1/sessions/{session_ref}:\n", + Some("components:\n"), + ); + for required_fragment in [ + " get:\n", + " operationId: loadAssessmentSession\n", + " - name: session_ref\n", + " in: path\n", + " required: true\n", + " \"200\":\n", + " \"400\":\n", + " \"404\":\n", + " \"405\":\n", + " \"500\":\n", + "#/components/schemas/CreatedSession", + ] { + assert!( + reload.contains(required_fragment), + "missing as-built session-reload contract fragment: {required_fragment:?}" + ); + } +} + +#[test] +fn session_openapi_preserves_durable_session_identity_and_problem_shape() { + let components = section(SESSION_OPENAPI, "components:\n", None); + for required_property in [ + " participant_ref:\n", + " instrument_release_ref:\n", + " locale:\n", + " session_ref:\n", + " instrument_version_ref:\n", + " instrument_release_content_digest:\n", + " state:\n", + " created_at_unix_ms:\n", + ] { + assert!( + components.contains(required_property), + "missing session identity/provenance property from OpenAPI: {required_property:?}" + ); + } + + for required_fragment in [ + " application/problem+json:\n", + " - type\n", + " - title\n", + " - status\n", + " - detail\n", + ] { + assert!( + components.contains(required_fragment), + "missing RFC 9457 problem contract fragment: {required_fragment:?}" + ); + } +}