Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
154c644
test(docs): pin Claude runtime support contract
seonghobae Aug 14, 2026
2de7ef1
docs: align Claude guidance with fail-closed runtime
seonghobae Aug 14, 2026
65f2f0c
test(docs): normalize Markdown wrapping in runtime contract
seonghobae Aug 14, 2026
2640398
test(docs): reject marginal fallback claim
seonghobae Aug 14, 2026
1193778
revert: keep runtime guidance lane scoped
seonghobae Aug 14, 2026
bad0e9f
test(docs): require machine-readable runtime contract
seonghobae Aug 16, 2026
09dcd09
docs: publish machine-readable runtime contract
seonghobae Aug 16, 2026
9ea1b6c
test(config): expose integer callback trust gap
seonghobae Aug 15, 2026
67ecdf3
fix(config): reject caller-controlled integer coercion
seonghobae Aug 15, 2026
a005092
test(config): cover bounded integer trust controls
seonghobae Aug 15, 2026
3ea7c09
docs(changelog): record config integer boundary hardening
seonghobae Aug 15, 2026
2faa822
docs(doctoring): record config integer trust boundary
seonghobae Aug 15, 2026
805df46
fix(config): validate simulation and fit controls at construction
cursoragent Aug 16, 2026
0814458
fix(config): store trusted integers and harden seed controls
cursoragent Aug 16, 2026
5c349bc
Merge branch 'main' into fix/claude-runtime-contract-621
opencode-agent[bot] Aug 16, 2026
1640719
docs: lock buyer-facing auto backend to fail-closed Rust
cursoragent Aug 16, 2026
ccc46ce
fix: lock leftover auto-backend surfaces to fail-closed Rust
cursoragent Aug 16, 2026
cf670a3
test: tolerate wrapped fit --help and reference/parity wording
cursoragent Aug 16, 2026
495f956
fix: lock leftover demo and acceptance auto surfaces to Rust
cursoragent Aug 16, 2026
4276bcb
test(sales): expose automatic backend authority gap
seonghobae Aug 16, 2026
03e174a
test(sales): defer backend authority to owning PR
seonghobae Aug 16, 2026
155ebb3
test(backend): cover unloadable Rust core
seonghobae Aug 17, 2026
8a77789
fix(backend): fail closed before untrusted controls and loader errors
seonghobae Aug 17, 2026
00863db
test(backend): enforce callback-safe controls
seonghobae Aug 17, 2026
f90c74f
docs(backend): record loader and control hardening
seonghobae Aug 17, 2026
c210d6d
docs(backend): unify loader and selector trust boundary
seonghobae Aug 17, 2026
9bde12e
fix(config): reconcile integer safety with current main
seonghobae Aug 17, 2026
df7f215
test(config): align resource-limit regressions with constructor valid…
seonghobae Aug 17, 2026
6bfadea
test(config): keep bifactor objective fixture valid
seonghobae Aug 17, 2026
b73d459
merge origin/main and resolve auto-backend buyer-surface conflicts
cursoragent Aug 17, 2026
4646d1a
fix(test): match auto-fit Rust authority error text
cursoragent Aug 17, 2026
be50b5e
fix(diagnostics): trust fold counts before budget products
cursoragent Aug 18, 2026
e43f934
test(config): use raw regex literals in compatibility checks
seonghobae Aug 18, 2026
d82c239
fix(diagnostics): trust fit-diagnostic integer controls
cursoragent Aug 18, 2026
1f9055b
chore(stack): reconcile config hardening into runtime-contract branch…
seonghobae Aug 19, 2026
286bd2d
test(backend): document import failure fixture
seonghobae Aug 21, 2026
f8facd5
merge main and harden backend authority
seonghobae Aug 24, 2026
5d3db09
merge: sync with main (pull in #1324 CI-contract-drift fixes)
claude Aug 24, 2026
1eb6216
test(backend): lock reference guidance contract
seonghobae Aug 24, 2026
635a01d
fix(adr): preserve accepted backend decision
seonghobae Aug 24, 2026
4f4b6c2
fix(docs): distinguish CLI and Python reference paths
seonghobae Aug 24, 2026
c63744b
fix(cli): point NumPy parity users to --reference
seonghobae Aug 24, 2026
033e10d
test(runtime): align fail-closed reference contract
seonghobae Aug 24, 2026
7e16889
merge: sync with main
claude Aug 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,21 @@ fully before making changes. In particular it defines:
deployment concerns. The hosted runtime must not be recreated under
`services/assessment_runtime` in this repository.

## Runtime Contract

The following block is machine-checked against package metadata and the shipped
backend-ownership policy. Keep it as one exact TOML block rather than replacing
it with an equivalent prose-only claim.

<!-- BEGIN fast-mlsirm-runtime-contract -->
```toml
[runtime_contract]
requires_python = ">=3.12"
auto_backend = "rust_required"
numpy_role = "reference_parity_only"
```
<!-- END fast-mlsirm-runtime-contract -->

## Common Commands

### Setup
Expand All @@ -36,9 +51,9 @@ If cargo is absent, maturin may try to provision a temporary Rust toolchain via
`puccinialin`; set `MATURIN_NO_INSTALL_RUST=1` when you need a fail-fast
offline/proxy-safe build. A proxy or certificate error in that fallback is not
proof of a Python/PyO3 incompatibility. `pyproject.toml` declares
`requires-python = ">=3.12"`, matching the required CI matrix on CPython 3.12
and 3.14. Do not advertise a lower floor than the hashed CI dependency lock can
install.
`requires-python = ">=3.12"`, matching the required CPython 3.12 and 3.14 CI
matrix. Broader interpreter claims need matching build/import/full-suite CI
evidence.

```bash
python -m pip install -e . # builds fast_mlsirm._core via maturin
Expand Down Expand Up @@ -146,9 +161,11 @@ examples/enterprise_demo/ Synthetic procurement evidence manifests
PyO3/numpy. maturin (configured in `pyproject.toml`) compiles the extension
into `fast_mlsirm._core` during install.
- The **backend axis is `{numpy, rust, auto}`** (default `auto`), resolved in
`python/fast_mlsirm/backend.py`: Rust when `_core` imports, otherwise the
numerically-identical NumPy reference in `python/fast_mlsirm/objective.py`
and `math.py`, which is kept for parity testing and fallback.
`python/fast_mlsirm/backend.py`: explicit `numpy` is the reference/parity
choice, explicit `rust` requires the compiled core, and production-convenience
`auto` resolves to Rust when `_core` imports. `auto` fails closed when the
compiled Rust core is unavailable; automatic resolution never silently
changes the numerical owner to NumPy.
- **GPU is a device sub-option of the Rust backend**, not a separate backend:
`FitConfig(backend="rust", rust_device={auto,cpu,gpu})`. The wgpu kernels in
`crates/mlsirm-core/src/gpu.rs` run in f32 and fall back to the f64 scalar
Expand Down
32 changes: 17 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,9 @@ print(fixed_item_calibration.best)
diligence, and PR queue governance evidence while Code Connect stays disabled.
- CLI commands for simulation and fitting.
- Rust-backed fitting objective (neg-loglik, gradients, and distance kernels)
via PyO3/maturin as the production numeric path, with an explicitly named
NumPy reference API retained only for parity testing and research inspection.
via PyO3/maturin as the primary numeric path, with a numerically-identical
NumPy reference backend kept for parity testing. `auto` fails closed when
the compiled Rust core is unavailable.

## Install

Expand All @@ -187,12 +188,12 @@ For local development:
python -m pip install -e .
```

The default runtime backend is `"auto"`, which requires the compiled Rust core
(`fast_mlsirm._core`) and fails closed when the extension is unavailable. Source
and editable installs use maturin to build the extension, so they require a
working Rust toolchain; installed wheels ship the compiled core. Use the
explicit `fast_mlsirm.fit_reference` API or `fit --reference` only for
non-production NumPy parity work.
The default runtime backend is `"auto"`. It uses the compiled Rust core
(`fast_mlsirm._core`) and fails closed when that extension is unavailable.
Automatic resolution never silently selects NumPy. Source and editable installs
use maturin to build the extension, so they require a working Rust toolchain;
installed wheels ship the compiled core. Pass `backend="numpy"` only when you
want the explicit pure-Python reference used for parity testing.
The core Rust workspace can be tested with:

```bash
Expand Down Expand Up @@ -428,12 +429,13 @@ kaefa-style item-fit penalty metrics. `--fixed-items` accepts a `.npy` boolean
mask or item-index vector; when omitted, all items are treated as the fixed
calibration set.

`fit --reference` uses the explicitly non-production Python reference objective.
`fit --backend rust` requires the installed `fast_mlsirm._core` extension and
fails clearly if it is unavailable. `fit --backend auto` resolves to Rust and
also fails closed when the extension is unavailable.
`fit --backend numpy` uses the Python reference objective. `fit --backend rust`
requires the installed `fast_mlsirm._core` extension and fails clearly if it is
unavailable. `fit --backend auto` uses the Rust objective when the compiled
core is available and fails closed otherwise. Automatic resolution never
silently selects NumPy.

The production backend axis is `{rust, auto}`. GPU acceleration is a *device*
The backend axis stays `{numpy, rust, auto}`. GPU acceleration is a *device*
sub-option of the Rust backend rather than a separate backend, selected with
`fit --backend rust --rust-device {auto,cpu,gpu}` (or `FitConfig(backend="rust",
rust_device=...)`). The Rust core carries a [wgpu](https://github.com/gfx-rs/wgpu)
Expand Down Expand Up @@ -462,9 +464,9 @@ repeated blank-looking report sections or placeholder-only columns.
## Repository Layout

```text
python/fast_mlsirm/ Python public API, orchestration, and reference API
python/fast_mlsirm/ Python public API and reference backend
crates/mlsirm-core/ Rust likelihood and gradient core
crates/fast-mlsirm-py/ PyO3 binding for the Rust production backend
crates/fast-mlsirm-py/ PyO3 binding for the compiled Rust backend
tests/ Python smoke and numerical tests
docs/ PRD/TRD summary and roadmap
examples/enterprise_demo/ Synthetic procurement evidence manifests
Expand Down
7 changes: 3 additions & 4 deletions docs/PRD.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Raw scores, RAGAS values, LLM judgments, or human ratings are observations, not

### PRD-PRN-002 Rust owns production psychometric arithmetic

Likelihoods, gradients, Hessians, optimization, information, psychometric scoring/ranking, and other production mathematical kernels are Rust-owned. Python may orchestrate, validate, marshal, report, and retain transparent reference implementations for parity/fallback where explicitly governed.
Likelihoods, gradients, Hessians, optimization, information, psychometric scoring/ranking, and other production mathematical kernels are Rust-owned. Python may orchestrate, validate, marshal, report, and retain explicit reference implementations for parity testing where governed.

### PRD-PRN-003 Correlation is not accuracy

Expand All @@ -80,9 +80,8 @@ The package shall expose stable versioned interfaces usable independently and by
The following are implemented on protected `main` as of this baseline unless explicitly marked otherwise:

- MLS2PLM-family binary simulation and point estimation, including `MIRT`, `MLSRM`, `MLS2PLM`, `ULSRM`, and `ULS2PLM` constraints.
- Rust-backed likelihood/gradient/distance kernels through PyO3/maturin. The
production backend is Rust-only; NumPy is available through the explicit
`fit_reference` parity API and its CLI reference mode.
- Rust-backed likelihood/gradient/distance kernels through PyO3/maturin, with
an explicit NumPy reference/parity path and parity tests; `auto` fails closed without the compiled Rust core.
- Missing-response handling, optimization, recovery, fit and dimensionality diagnostics.
- Fixed-item calibration/linking, CAT item-information selection, ATA form assembly.
- Response-process diagnostics, model-fit summaries, multigroup/multilevel-context summaries exposed by current APIs.
Expand Down
2 changes: 1 addition & 1 deletion docs/TRD.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ These belong to downstream bounded contexts such as `ContextualWisdomLab/psychom

```text
python/fast_mlsirm/ Public Python API, validation/orchestration, reports,
transparent reference/fallback paths
governed reference/parity paths
crates/mlsirm-core/ Rust psychometric/numerical source of truth
crates/fast-mlsirm-py/ PyO3 bindings and Python transport
scripts/ Release, evidence, governance, study runners
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ A conversation, issue, PR body, design note, or paper summary is not an Accepted
| ADR | Status | Decision |
|---|---|---|
| [0001](0001-domain-neutral-measurement-boundary.md) | Accepted | `fast-mlsirm` owns reusable measurement/psychometric contracts and kernels; hosted runtime belongs downstream. |
| [0002](0002-rust-first-numerical-ownership.md) | Accepted | Rust owns production psychometric arithmetic; Python validates/orchestrates/reports and retains governed reference/fallback paths. |
| [0002](0002-rust-first-numerical-ownership.md) | Accepted | Rust owns production psychometric arithmetic; Python validates/orchestrates/reports and retains governed reference/parity paths. `auto` fails closed without the compiled Rust core. |
| [0003](0003-content-addressed-measurement-contracts.md) | Accepted | Assessment/rubric/scoring artifacts use canonical versioned, content-addressed provenance and replay verification. |
| [0004](0004-governed-rubric-item-bank-lifecycle.md) | Proposed | Build candidate-blind evidence-grounded rubric/item generation into a governed psychometric item-bank lifecycle. |
| [0005](0005-automated-scoring-raters.md) | Accepted | Human and automated scorers are fallible raters; calibration/validation must model rater effects and preserve terminal states. |
Expand Down
9 changes: 4 additions & 5 deletions docs/buyer_demo_storyboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,16 @@ The demo should show a complete buyer review without customer data:
and commit SHA.
- Show `commercial_release_manifest.json` as the top-level evidence index
for the buyer run.
- Explain that Rust/PyO3 is the required production numerical backend.
- Show `fast_mlsirm.fit_reference` or `fit --reference` only as the named
NumPy parity path; production configuration never falls back silently.
- Explain that Rust/PyO3 is the production numerical owner. `backend=auto`
fails closed without `fast_mlsirm._core`. Pass `backend=numpy` only for
the explicit reference/parity path.
2. **Synthetic Data**
- Generate MLS2PLM synthetic response data.
- Show that no buyer data is required for acceptance reproduction.
- Open `benchmark_report.html` to review runtime budget, command duration,
backend coverage, and required artifact coverage.
3. **Fit Workflow**
- Run production fitting with `backend=auto` or `backend=rust`; use the
explicit `--reference` mode only for parity inspection.
- Run fitting with `backend=auto` and explicit Rust evidence when required.
- Show `fit_summary.json`, resolved backend, objective, log-likelihood, and
generated parameter artifact.
4. **Diagnostics Workflow**
Expand Down
6 changes: 6 additions & 0 deletions docs/changelog.d/831-rust-core-loader-fail-closed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Fail-closed compiled Rust loader handling

## Fixed

- Normalize a discoverable but unloadable compiled Rust core to a package-owned runtime error while preserving the original loader exception as its cause.
- Reject non-string and `str`-subclass backend/device control values before caller-defined conversion or normalization callbacks can execute, while preserving case/whitespace normalization for exact built-in strings.
15 changes: 15 additions & 0 deletions docs/changelog.d/833-runtime-contract-buyer-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Runtime contract buyer-facing ownership

## Fixed

- Locked the Claude runtime-contract TOML block to package metadata and
Rust-required `auto` ownership, and removed the stale buyer-facing claim that
`auto` selected NumPy when the compiled core is missing. README, `FitConfig`
comments, commercial Operational Notes, the buyer demo storyboard, sales
`--check-import` help, PRD, TRD, and ADR-0002 now tell purchasers to install
the Rust extension for production fitting. Explicit parity/reference work uses
`fast-mlsirm fit --reference` at the CLI and the `fast_mlsirm.fit_reference`
API in Python; direct production `fast_mlsirm.fit(...)` does not accept NumPy
as a production backend. Release acceptance now rejects a NumPy outcome on
`fit --backend auto`. The auto fail-closed error names the Python reference
API without reflecting local paths or ABI details.
10 changes: 10 additions & 0 deletions docs/changelog.d/872-config-integer-callback-safety.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Harden configuration integer trust boundaries

## Fixed

- Reject caller-defined integer subclasses and arbitrary `__index__` providers before public simulation and fit configuration validation can dispatch caller-controlled coercion.
- Preserve exact built-in integers and genuine NumPy integer scalars while validating simulation size, optimizer-work, quadrature, latent-integration, seed, and verbosity controls through built-in integer values.
- Store those trusted integers back on the frozen configs so later size products and `seed + restart` cannot wrap narrow NumPy scalars.
- Normalize `dimensionality_diagnostics` `k_folds`, `seed`, and `latent_dims` to built-in integers before the candidate-by-fold budget product or `seed + fold_idx` can wrap a narrow NumPy scalar.
- Normalize `fit_diagnostics` `parameter_count` and `m2_q_*` to built-in integers before AIC/BIC arithmetic or `int(q_*)` can dispatch caller `__index__` hooks.
- Run the same simulation and fit validators at construction so memory-safety bounds cannot be bypassed by skipping an explicit `validate()` call.
15 changes: 9 additions & 6 deletions docs/commercial_readiness.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ evidence index over the exact artifacts being offered.
rendering.
- CLI workflows for simulation, fitting, fit diagnostics, dimensionality
diagnostics, response-process diagnostics, and report rendering.
- Rust/PyO3 production backend for fitting and psychometric numerical kernels.
- Explicit NumPy parity through `fast_mlsirm.fit_reference` and
`fast-mlsirm fit --reference`; it is not a production `FitConfig` choice.
- Production backend selection through `FitConfig(backend="rust"|"auto")`
and `fast-mlsirm fit --backend`; missing Rust fails closed.
- Rust/PyO3 backend as the default `auto` runtime path through
`fast_mlsirm._core`. `auto` fails closed when the compiled core is
unavailable.
- Explicit NumPy reference backend for parity testing only.
- Backend selection through `FitConfig(backend=...)` and
`fast-mlsirm fit --backend`.
- Dense response matrices with missing values represented by `NaN`, `-1`, or an
explicit mask.
- Automated benchmark evidence reporting from release-acceptance timing.
Expand Down Expand Up @@ -159,7 +160,9 @@ r_pi = sqrt(sum_k (xi_pk - zeta_ik)^2 + eps)

- Source and editable installs require a Rust toolchain because maturin builds
`fast_mlsirm._core`.
- Installed wheels can use the NumPy backend by default.
- Installed wheels ship the compiled Rust core. `auto` uses that core and
fails closed if it is missing. Pass `backend="numpy"` only for the explicit
reference/parity path.
- The Rust backend is a dense-matrix backend. It is not a sparse storage layer.
- Real assessment data should be handled under the buyer's own privacy,
governance, retention, and audit policies.
41 changes: 20 additions & 21 deletions docs/doctoring/automatic_backend_rust_authority.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,35 @@

`backend="auto"` is a convenience selector for the production Rust/PyO3 numerical implementation. It is not permission to switch to an independent NumPy implementation when the compiled extension is unavailable. A missing compiled core therefore fails closed before psychometric numerical work begins.

The production `FitConfig` and CLI backend vocabularies are `{auto, rust}`.
NumPy is retained only behind the explicitly named `fit_reference` API and
`fit --reference` mode for parity and research inspection. It is never selected
implicitly by automatic production resolution. Rust device selection remains a
different axis: a Rust GPU request may fall back to the parity-verified Rust
CPU implementation because the numerical owner and formula contract remain
Rust-owned.

The public `fit` signature has no reference-enabling keyword, and
`FitConfig.validate()` has no public NumPy opt-in flag. The named reference API
enters a scoped authority context and calls the same fit implementation with
an explicit NumPy configuration. Low-level objective calls may still request
`backend="numpy"` explicitly for Rust/NumPy parity; that kernel-level escape
does not make NumPy a production fit backend.
The explicit `backend="numpy"` surface is retained in this bounded migration as
a reference/parity choice. It is never selected implicitly by automatic
production resolution. Rust device selection remains a different axis: a Rust
GPU request may fall back to the parity-verified Rust CPU implementation because
the numerical owner and formula contract remain Rust-owned.

## Failure boundary

Python's import machinery provides a direct capability probe: `importlib.util.find_spec()` returns `None` when no module specification is found and importing the module is a separate operation. The package uses that boundary to distinguish an unavailable extension from an available compiled module. The PyO3 and maturin primary documentation describe the native extension as the Python-importable compiled module produced and distributed with the package. Those mechanics support a fail-closed contract when the package's required production extension is missing; they do not justify substituting a different numerical implementation.

The package exposes a stable, non-reflective error for an unavailable automatic production backend rather than reflecting local paths, ABI details, environment data, or import exception text into the fallback decision. Actual import/ABI failures remain diagnostic failures rather than being reclassified as permission to run NumPy arithmetic.
The package exposes a stable, non-reflective error for an unavailable automatic production backend rather than reflecting local paths, ABI details, environment data, or import exception text into the resolution decision. The message tells the purchaser the next action: install a wheel or editable build that provides `fast_mlsirm._core`, or pass `backend="numpy"` only for the explicit reference/parity path. If discovery succeeds but the native module raises `ImportError` or `OSError` while loading, the package normalizes that loader failure to a stable package-owned `RuntimeError` and retains the original exception as `__cause__` for operator diagnostics; it never interprets an unloadable compiled core as permission to run NumPy arithmetic.

Backend and Rust-device selector strings are also trust-boundary controls. Only exact built-in strings are normalized; arbitrary objects and `str` subclasses are rejected before caller-defined `__str__`, `strip`, or related callbacks and before native-core discovery. Exact built-in values retain the established whitespace/case normalization and allowlist behavior.

## Falsification and acceptance

This decision is falsified if any ordinary `FitConfig` or `backend="auto"` call
can select NumPy because the Rust extension is missing or incompatible.
Acceptance requires tests proving that automatic resolution selects Rust when
available, fails closed when absent, production configuration rejects the
NumPy name, and `fit_reference` is the only public fitting escape hatch.
This decision is falsified if any ordinary `backend="auto"` call can select
NumPy because the Rust extension is missing or incompatible. It is also
falsified if a purchaser-facing demo, layout, sales-import help, or
release-acceptance gate still treats Rust as optional acceleration or treats
NumPy as a valid automatic outcome. Acceptance requires tests proving that
automatic resolution selects Rust when available, fails closed when absent or
unloadable, rejects untrusted selector objects before callbacks/native
discovery, and explicit NumPy resolution remains an explicit caller decision.
Installed-wheel/package evidence must continue to prove the Rust extension is
present in supported production artifacts.
present in supported production artifacts. Buyer demo copy, repository-layout
copy, sales `--check-import` help, and the `fit --backend auto` acceptance
check must name the same next action: install `fast_mlsirm._core`, or pass
`backend="numpy"` only for the explicit reference/parity path.

## References

Expand Down
Loading
Loading