Skip to content
Closed
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ All notable product and architecture changes are recorded here. Releases use imm
## Unreleased

### Added
- Versioned Personality Style presentation mapping (`style_mapping_version_v1`) assigns an original pole-dominance style from already-scored Big Five observations, keeps mixed/adjacent and balanced profiles honest, and never mutates numeric scores.
- Scoring-job cancel and lease-expiry fallback classification lock the current row until the caller transaction ends, so concurrent workers cannot rewrite terminal or unleased evidence.
- PostgreSQL operational-store readiness probe classifies the supported major version and write-readiness, and fails closed when a caller-declared required relation is missing.
- PostgreSQL scoring-job cancellation: queued, leased, or retry-scheduled work becomes cancelled without transferring a fence, exact replay is idempotent, and completed or quarantined evidence cannot be rewritten.
Expand Down
8 changes: 4 additions & 4 deletions docs/TRACEABILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ An active PR, architecture document, conversation decision, or scheduler plan is
| 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 plus `migrations/0011_scoring_request.sql` / `src/postgres_scoring_request.rs` request-identity persistence; live fast-mlsirm integration is 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`; result-serving transport is Target |
| Deterministic narrative fallback | PRD §3.2, §9.5 | TRD §17; Architecture narrative view | ADR-0009, ADR-0010, ADR-0018 | Target |
| 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 |
| Deterministic narrative fallback | PRD §3.2, §9.5 | TRD §17; Architecture narrative view | ADR-0009, ADR-0010, ADR-0018 | Renderer exists on later protected main (`src/deterministic_narrative.rs`); the evaluated baseline in this document remains Target until the documentation rebaseline lands |
| Continuous scores remain source of truth; Personality Style is presentation | PRD §3.2 | Measurement Governance; AI Governance | ADR-0018 | **Active PR** work in `src/style_mapping.rs` assigns a versioned presentation style from already-scored Big Five observations; numeric source remains External fast-mlsirm contract and is not protected-main truth until this head is integrated |
| Immutable instrument release/version lifecycle | PRD §6, §9 | TRD §7; UML publication state | ADR-0005, ADR-0010 | **Implemented** in `src/instrument.rs` plus `migrations/0006_instrument_release.sql` and `src/postgres_instrument_release.rs`: immutable release manifest, exact version/digest/locale/item set, fail-closed Draft/Review/Published/Suspended/Retired lifecycle, idempotent publication events, and new-session eligibility |
| Instrument publication requires intended-use scientific/right/locale evidence | PRD §6, §9, §10 | Measurement Governance; publication evidence gate | ADR-0004, ADR-0013, ADR-0019 | **Implemented** policy gate and immutable evidence provenance in `src/instrument.rs`; each real instrument still requires its own rights/locale/scientific evidence artifacts before publication |
| 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 |
Expand Down Expand Up @@ -128,11 +128,11 @@ migrations/
└── 0012_integration_consumption.sql
```

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

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

**Active PR** #76 data-rights processing-start persistence is not protected-main truth until an unchanged reviewed/check-clean head is integrated. Identity-verified requests persist an immutable operation identity and processing-start time under `FOR UPDATE` so later lifecycle composition cannot race the classified row. Dependent-system execution remains outside this slice.
**Active PR** Personality Style mapping on this head is not protected-main truth until an unchanged reviewed/check-clean head is integrated. `src/style_mapping.rs` assigns a versioned original presentation style from already-scored Big Five observations and does not estimate latent traits or claim MBTI equivalence. Persistence, HTTP result serving, and published interpretation-rule bundles remain outside this slice.

## 5. ADR traceability by concern

Expand Down
20 changes: 16 additions & 4 deletions docs/adr/0018-continuous-scores-and-narrative-separation.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
- Scope: consumer Big Five results, Personality Style mapping, narrative generation, result provenance, client presentation
- Supersedes: none
- Superseded by: none
- Current/as-built status: continuous score/result provenance primitives are implemented on protected main; the consumer Personality Style mapping and narrative runtime are not yet implemented
- Current/as-built status: continuous score/result provenance primitives, canonical style-assignment identity, and deterministic narrative fallback rendering are implemented on protected main; the first versioned Personality Style mapping (`src/style_mapping.rs`, `style_mapping_version_v1`) is Active PR work and is not protected-main truth until that head is integrated
- Target status: deterministic versioned style assignment plus deterministic localized narrative fallback, with optional bounded AI prose rendering
- Migration status: no persisted style-assignment records exist yet; the first implementation must introduce the canonical assignment identity without synthetic backfill claims

Expand Down Expand Up @@ -173,9 +173,9 @@ Costs:

## Follow-up work

- define the first original Personality Style mapping with explicit prototype/rule rationale;
- define the canonical style-assignment serialization schema and test vectors before persistence/API implementation;
- create boundary/mixed-profile fixture bank;
- land the Active PR `style_mapping_version_v1` pole-dominance mapping and keep it segregated from protected-main truth until review/checks pass;
- publish the first approved interpretation-rule bundle and localized deterministic units that this mapping selects;
- persist style-assignment artifacts with the canonical key; do not backfill historical results;
- design Result Explorer explanations showing source dimensions and uncertainty;
- evaluate Barnum susceptibility, perceived usefulness, calibration, and “not like me” feedback separately from score validity.

Expand All @@ -187,6 +187,18 @@ Costs:
- AI policy: `docs/AI_GOVERNANCE.md`.
- Delivery/evidence: `docs/TRACEABILITY.md`, `docs/ROADMAP.md`, `docs/RISK_REGISTER.md`.

## References

American Educational Research Association, American Psychological Association, & National Council on Measurement in Education. (2014). *Standards for educational and psychological testing*. American Educational Research Association.

Costa, P. T., Jr., & McCrae, R. R. (1992). Four ways five factors are basic. *Personality and Individual Differences, 13*(6), 653–665. https://doi.org/10.1016/0191-8869(92)90236-I

Forer, B. R. (1949). The fallacy of personal validation: A classroom demonstration of gullibility. *The Journal of Abnormal and Social Psychology, 44*(1), 118–123. https://doi.org/10.1037/h0059240

John, O. P., & Srivastava, S. (1999). The Big Five trait taxonomy: History, measurement, and theoretical perspectives. In L. A. Pervin & O. P. John (Eds.), *Handbook of personality: Theory and research* (2nd ed., pp. 102–138). Guilford Press.

McCrae, R. R., & Costa, P. T., Jr. (1989). Reinterpreting the Myers-Briggs Type Indicator from the perspective of the five-factor model of personality. *Journal of Personality, 57*(1), 17–40. https://doi.org/10.1111/j.1467-6494.1989.tb00759.x

## Reversal conditions

If empirical evidence shows the narrative mapping is harmful, misleading, or adds no user value, the style layer can be retired while retaining continuous results. A future validated categorical instrument could be added only as its own independently measured construct, not as a silent reinterpretation of Big Five.
111 changes: 111 additions & 0 deletions src/anonymous_authorization.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//! Product authorization for an already-verified anonymous assessment session.
//!
//! An anonymous participant receives a short-lived proof when an assessment session is created.
//! Another part of the application verifies that proof and builds an [`AnonymousSessionContext`].
//! This module does **not** read or verify the raw secret. Instead, it answers a narrower question:
//! "May this verified anonymous session act on this exact assessment-session resource right now?"
//!
//! The answer is deliberately limited. The verified session may act only on the one assessment
//! session named in its context. It cannot be reused to read results, change consent, exercise data
//! rights, administer a tenant, or access another participant's session.

use crate::anonymous_session::AnonymousSessionContext;
use crate::authorization::{ResourceKind, ResourceScope};
use std::error::Error;
use std::fmt::{Display, Formatter};

/// Fail-closed authorization error for a verified anonymous assessment session.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum AnonymousResourceAuthorizationError {
/// The caller did not provide a positive time obtained from the trusted server clock.
InvalidTimestamp,
/// The anonymous session had reached or passed its exclusive expiry time.
Expired,
/// The target resource belonged to another tenant.
CrossTenantDenied,
/// Anonymous-session access was requested for a resource other than an assessment session.
ResourceKindMismatch,
/// The target session belonged to another operational participant.
OwnerMismatch,
/// The target assessment-session reference differed from the session named by the proof.
SessionMismatch,
}

impl Display for AnonymousResourceAuthorizationError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_str(match self {
Self::InvalidTimestamp => {
"anonymous resource authorization requires positive server time"
}
Self::Expired => "anonymous session authority is expired",
Self::CrossTenantDenied => {
"anonymous session authority does not match the resource tenant"
}
Self::ResourceKindMismatch => {
"anonymous session authority is limited to its assessment-session resource"
}
Self::OwnerMismatch => {
"anonymous session authority does not match the resource participant"
}
Self::SessionMismatch => {
"anonymous session authority does not match the resource session"
}
})
}
}

impl Error for AnonymousResourceAuthorizationError {}

/// Allow a verified anonymous participant to act on one exact assessment session.
///
/// Callers provide three values:
///
/// - `actor`: an [`AnonymousSessionContext`] created only after the short-lived anonymous proof has
/// already been verified;
/// - `resource`: the [`ResourceScope`] for the assessment session the caller wants to use; and
/// - `now_unix_ms`: the current time from the application's trusted server clock, not a client clock.
///
/// For example, if the verified context names tenant `tenant_alpha`, participant
/// `participant_alpha`, and session `session_alpha`, this function allows access only to the
/// `session_alpha` assessment-session resource owned by that same participant in that same tenant.
/// A result resource or `session_beta` is denied even when the same caller presents the context.
///
/// References in `actor` and `resource` are already in their validated, exact spelling because
/// their constructors reject non-canonical forms. This function therefore compares the exact
/// values instead of trimming, normalizing, or guessing aliases.
///
/// Checks run in a stable fail-closed order: trusted server time, expiry, tenant, resource kind,
/// participant owner, then session identity. This order is part of the error contract used by
/// transports when more than one supplied property is wrong.
///
/// # Errors
///
/// Returns [`AnonymousResourceAuthorizationError`] when the trusted time is invalid, the verified
/// anonymous session has expired, or the requested resource differs from the exact
/// tenant/participant/session binding described above.
pub fn authorize_anonymous_session(
actor: &AnonymousSessionContext,
resource: &ResourceScope,
now_unix_ms: u64,
) -> Result<(), AnonymousResourceAuthorizationError> {
if now_unix_ms == 0 {
return Err(AnonymousResourceAuthorizationError::InvalidTimestamp);
}
if !actor.is_valid_at(now_unix_ms) {
return Err(AnonymousResourceAuthorizationError::Expired);
}
if actor.tenant_ref() != resource.tenant_ref() {
return Err(AnonymousResourceAuthorizationError::CrossTenantDenied);
}
if resource.kind() != ResourceKind::AssessmentSession {
return Err(AnonymousResourceAuthorizationError::ResourceKindMismatch);
}
if resource.owner_participant_ref() != Some(actor.participant_ref()) {
return Err(AnonymousResourceAuthorizationError::OwnerMismatch);
}
if resource.resource_ref() != actor.session_ref() {
return Err(AnonymousResourceAuthorizationError::SessionMismatch);
}
Ok(())
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
//! contracts rather than reimplemented here.

pub mod account_link;
pub mod anonymous_authorization;
pub mod anonymous_session;
pub mod authorization;
pub mod consent;
Expand Down Expand Up @@ -38,3 +39,4 @@ pub mod result;
pub mod scoring;
pub mod scoring_job;
pub mod session;
pub mod style_mapping;
Loading
Loading