neomundi 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- neomundi-2.0.0/KNOWN_LIMITATIONS.md +857 -0
- neomundi-2.0.0/LICENSE +201 -0
- neomundi-2.0.0/LICENSE_NOTICE.md +17 -0
- neomundi-2.0.0/MANIFEST.in +12 -0
- neomundi-2.0.0/PILOT_INTEGRATION_GUIDE.md +721 -0
- neomundi-2.0.0/PKG-INFO +188 -0
- neomundi-2.0.0/README.md +165 -0
- neomundi-2.0.0/docs/AUDIT_SDK_V2.md +62 -0
- neomundi-2.0.0/docs/COMPATIBILITY_350.md +130 -0
- neomundi-2.0.0/docs/CONTRAT_API.md +347 -0
- neomundi-2.0.0/docs/LIMITES.md +524 -0
- neomundi-2.0.0/docs/MIGRATION_V2.md +30 -0
- neomundi-2.0.0/docs/PYPI_RELEASE_READINESS.md +48 -0
- neomundi-2.0.0/docs/QUALIFICATION_LIVE_SDK_V2.md +41 -0
- neomundi-2.0.0/docs/STREAMING.md +124 -0
- neomundi-2.0.0/docs/STREAMING_FR.md +135 -0
- neomundi-2.0.0/docs/VERIFICATION_V2.md +61 -0
- neomundi-2.0.0/examples/01_premiere_mesure.py +125 -0
- neomundi-2.0.0/examples/02_mesure_partielle.py +148 -0
- neomundi-2.0.0/examples/03_gestion_erreurs.py +248 -0
- neomundi-2.0.0/examples/04_streaming_sse.py +56 -0
- neomundi-2.0.0/examples/90_cle_reelle.py.disabled +119 -0
- neomundi-2.0.0/pyproject.toml +66 -0
- neomundi-2.0.0/setup.cfg +4 -0
- neomundi-2.0.0/src/neomundi/__init__.py +165 -0
- neomundi-2.0.0/src/neomundi/_retry.py +114 -0
- neomundi-2.0.0/src/neomundi/client.py +637 -0
- neomundi-2.0.0/src/neomundi/errors.py +357 -0
- neomundi-2.0.0/src/neomundi/models.py +1275 -0
- neomundi-2.0.0/src/neomundi/py.typed +0 -0
- neomundi-2.0.0/src/neomundi/streaming.py +501 -0
- neomundi-2.0.0/src/neomundi.egg-info/PKG-INFO +188 -0
- neomundi-2.0.0/src/neomundi.egg-info/SOURCES.txt +62 -0
- neomundi-2.0.0/src/neomundi.egg-info/dependency_links.txt +1 -0
- neomundi-2.0.0/src/neomundi.egg-info/requires.txt +4 -0
- neomundi-2.0.0/src/neomundi.egg-info/top_level.txt +1 -0
- neomundi-2.0.0/tests/conftest.py +161 -0
- neomundi-2.0.0/tests/fixtures/errors/e401.json +1 -0
- neomundi-2.0.0/tests/fixtures/errors/e402.json +1 -0
- neomundi-2.0.0/tests/fixtures/errors/e413.json +1 -0
- neomundi-2.0.0/tests/fixtures/errors/e422.json +1 -0
- neomundi-2.0.0/tests/fixtures/errors/e429.json +1 -0
- neomundi-2.0.0/tests/fixtures/errors/e500.json +1 -0
- neomundi-2.0.0/tests/fixtures/errors/e503.json +1 -0
- neomundi-2.0.0/tests/fixtures/full_measurement.json +178 -0
- neomundi-2.0.0/tests/fixtures/gated_tier01.json +104 -0
- neomundi-2.0.0/tests/fixtures/no_check_attempted.json +110 -0
- neomundi-2.0.0/tests/fixtures/partial_measurement.json +175 -0
- neomundi-2.0.0/tests/fixtures/production_331/rest_complete_allow.json +202 -0
- neomundi-2.0.0/tests/fixtures/synthetic_drift.json +66 -0
- neomundi-2.0.0/tests/test_350_degraded_errors_synthetic.py +144 -0
- neomundi-2.0.0/tests/test_350_wire_extension_synthetic.py +87 -0
- neomundi-2.0.0/tests/test_audit_distribution.py +89 -0
- neomundi-2.0.0/tests/test_captured_fixtures.py +206 -0
- neomundi-2.0.0/tests/test_client.py +278 -0
- neomundi-2.0.0/tests/test_contract_drift.py +285 -0
- neomundi-2.0.0/tests/test_errors.py +305 -0
- neomundi-2.0.0/tests/test_examples.py +85 -0
- neomundi-2.0.0/tests/test_measurement_version_331.py +272 -0
- neomundi-2.0.0/tests/test_models.py +521 -0
- neomundi-2.0.0/tests/test_no_secret_leak.py +154 -0
- neomundi-2.0.0/tests/test_pypi_release_guard.py +42 -0
- neomundi-2.0.0/tests/test_retry.py +190 -0
- neomundi-2.0.0/tests/test_streaming.py +375 -0
|
@@ -0,0 +1,857 @@
|
|
|
1
|
+
# Known Limitations — NeoMundi Python SDK v2.0.0
|
|
2
|
+
|
|
3
|
+
[English](KNOWN_LIMITATIONS.md) · [Français](KNOWN_LIMITATIONS_FR.md)
|
|
4
|
+
|
|
5
|
+
**Status: v2.0.0 — Experimental / Pilot Evaluation**
|
|
6
|
+
|
|
7
|
+
Last updated: 2026-10-26; older backend observations remain dated below
|
|
8
|
+
Audited backend reference: commit `68d6057cf620e430e6488da3cfcec9c471c75ed6`, measurement version `3.3.0`
|
|
9
|
+
REST surface additionally verified against measurement version **`3.3.1`** (deployed commit `aeff13c`) — see §6
|
|
10
|
+
|
|
11
|
+
This document is for developers evaluating the SDK in a pilot context. It
|
|
12
|
+
states what works, what was verified and how, and which backend defects
|
|
13
|
+
were observed. Nothing here is softened: a measurement instrument that
|
|
14
|
+
hides its limits cannot be trusted with the measurements it reports.
|
|
15
|
+
|
|
16
|
+
> **Reading rule.** Where this document says *observed*, a value was
|
|
17
|
+
> recorded from a running instance and the conditions are stated. Where it
|
|
18
|
+
> says *derived from code*, the statement comes from reading the backend
|
|
19
|
+
> source at the commit above. The two are never mixed.
|
|
20
|
+
|
|
21
|
+
> **V2 update:** `stream()` / `govern_stream()` now support SSE with a
|
|
22
|
+
> separate terminal result and typed interruption errors. Historical
|
|
23
|
+
> backend findings below are not claims of a fresh production audit.
|
|
24
|
+
> See [the V2 audit](docs/AUDIT_SDK_V2.md).
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 1. SDK status
|
|
29
|
+
|
|
30
|
+
| | |
|
|
31
|
+
|---|---|
|
|
32
|
+
Version | `2.0.0` |
|
|
33
|
+
Maturity | **Experimental.** Suitable for pilot evaluation, not for production reliance. |
|
|
34
|
+
Scope | REST `POST /v1/govern` and synchronous SSE `POST /v1/govern/stream` |
|
|
35
|
+
Runtime dependency | `httpx` only |
|
|
36
|
+
Python | ≥ 3.9; V2 validated locally on 3.12; CI passed on 3.9, 3.11, 3.12 and 3.13 |
|
|
37
|
+
Distribution | **Not on PyPI.** Install from this public repository or an identified wheel. |
|
|
38
|
+
Licence | **Not settled** — see `LICENSE_NOTICE.md` |
|
|
39
|
+
|
|
40
|
+
### What "locally validated" means, precisely
|
|
41
|
+
|
|
42
|
+
**Current V2 suite:** 282 passed, 12 skipped; CI passed on Python 3.9,
|
|
43
|
+
3.11, 3.12 and 3.13. Live REST/SSE qualification remains pending; see
|
|
44
|
+
[VERIFICATION_V2.md](docs/VERIFICATION_V2.md).
|
|
45
|
+
|
|
46
|
+
The two scopes below are **historical, before the V2 additions**, not current
|
|
47
|
+
V2 test totals. Conflating these scopes would overstate what was verified.
|
|
48
|
+
|
|
49
|
+
| Scope | Count | What it establishes |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
**Reference suite** (what CI runs) | **225 passed, 12 skipped** | the SDK behaves correctly against payloads **derived from reading** the backend contract, and against the sanitised `3.3.1` production payload shipped in `tests/fixtures/production_331/` |
|
|
52
|
+
**Local suite** (reference + captured payloads) | **347 passed** | the same, **plus** that the modelled contract matches what a running instance actually emitted |
|
|
53
|
+
|
|
54
|
+
The 12 skips are the `3.3.0` captured-payload tests. Those captures are
|
|
55
|
+
execution data and are **excluded from this repository**
|
|
56
|
+
(`tests/fixtures/captured/` is in `.gitignore`), so those tests skip
|
|
57
|
+
wherever the directory is absent — including CI. They are not optional
|
|
58
|
+
checks that were turned off; they are checks that require data this
|
|
59
|
+
repository deliberately does not carry.
|
|
60
|
+
|
|
61
|
+
The `3.3.1` payload is the exception, and deliberately so: it carries no
|
|
62
|
+
prompt, no response text and no evaluator identity, so it could be
|
|
63
|
+
sanitised and shipped. What was replaced is listed field by field in its
|
|
64
|
+
`_fixture_redacted` key.
|
|
65
|
+
|
|
66
|
+
### Deployment compatibility — the central caveat
|
|
67
|
+
|
|
68
|
+
The SDK's models were derived from the backend's request and response
|
|
69
|
+
models **at one named commit**. No frozen OpenAPI specification exists
|
|
70
|
+
server-side, and `/openapi.json` is disabled in production builds.
|
|
71
|
+
|
|
72
|
+
Compatibility was verified against **a local instance built from that
|
|
73
|
+
commit**, and the REST contract was additionally verified against
|
|
74
|
+
measurement version **`3.3.1`** by source comparison and by replaying a
|
|
75
|
+
real production response (§6). It has **not** been verified by this SDK
|
|
76
|
+
**calling** any hosted deployment. A deployment serving a
|
|
77
|
+
`measurement_version` outside the verified list, or carrying fields added
|
|
78
|
+
since, may behave differently.
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
import neomundi
|
|
82
|
+
result = client.govern(...)
|
|
83
|
+
|
|
84
|
+
served = result.audit.measurement_version
|
|
85
|
+
|
|
86
|
+
if served not in neomundi.API_CONTRACT["verified_measurement_versions"]:
|
|
87
|
+
# Unverified contract: fields may have changed meaning. Stop here.
|
|
88
|
+
...
|
|
89
|
+
elif served != neomundi.API_CONTRACT["measurement_version"]:
|
|
90
|
+
# Parsing is safe, but the series is not comparable with one produced
|
|
91
|
+
# under a different measurement version. Record `served` alongside
|
|
92
|
+
# every observation.
|
|
93
|
+
...
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**The two checks answer different questions, and collapsing them is the
|
|
97
|
+
mistake this split exists to prevent.** The first asks *can this client
|
|
98
|
+
read the response correctly* — a transport question. The second asks *may
|
|
99
|
+
these two numbers sit in the same series* — a scientific one. `3.3.1`
|
|
100
|
+
passes the first and fails the second against a `3.3.0` series.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 2. Supported functionality
|
|
105
|
+
|
|
106
|
+
### Verified by execution against a running instance
|
|
107
|
+
|
|
108
|
+
A campaign of ten REST calls was run against a local instance built from
|
|
109
|
+
the reference commit, at tier `TIER_02`, with all three evaluators live
|
|
110
|
+
(Llama Guard 3 1B, nomic-embed-text, Llama 3.2 3B on a local Ollama).
|
|
111
|
+
|
|
112
|
+
| Capability | Result |
|
|
113
|
+
|---|---|
|
|
114
|
+
`POST /v1/govern`, request construction and response parsing | 10/10 calls parsed |
|
|
115
|
+
**Zero type discrepancies** (`parse_warnings`) | 0 across all captured payloads |
|
|
116
|
+
**Zero unmodelled fields** (`unknown_fields`) | 0 across all captured payloads |
|
|
117
|
+
`measurement_version` served | `3.3.0`, matching the SDK's declared contract |
|
|
118
|
+
Round-trip fidelity (`to_dict()` returns the payload unchanged) | verified on every captured payload |
|
|
119
|
+
Root aliases agree with their blocks (`g_score`/`g_final`) | verified on every captured payload |
|
|
120
|
+
Coverage is re-derivable from the published counters | verified on every captured payload |
|
|
121
|
+
Three decision bases exercised | `measured_thresholds`, `incomplete_measurement`, `no_check_attempted` |
|
|
122
|
+
**Degraded path**: all three evaluators unreachable | aggregates `null`, decision `FLAG`, basis `incomplete_measurement`, coverage `0/3`, each cause named (`moderation_service_unavailable`, `coherence_service_unavailable`, `hallucination_judge_unavailable`) |
|
|
123
|
+
Partial coverage | observed at `2/3` (missing coherence) and `0/3` |
|
|
124
|
+
No secrets in responses | the redaction pass found **nothing to redact** in any captured payload |
|
|
125
|
+
|
|
126
|
+
### Verified by replaying a production response served under `3.3.1`
|
|
127
|
+
|
|
128
|
+
One response really emitted by the production deployment on **2026-10-21**
|
|
129
|
+
under `measurement_version 3.3.1` was replayed through the client over a
|
|
130
|
+
simulated transport on **2026-10-22**. A sanitised copy ships as
|
|
131
|
+
`tests/fixtures/production_331/rest_complete_allow.json` and is exercised
|
|
132
|
+
by `tests/test_measurement_version_331.py`.
|
|
133
|
+
|
|
134
|
+
| Checked on the `3.3.1` payload | Result |
|
|
135
|
+
|---|---|
|
|
136
|
+
`parse_warnings` | **0** |
|
|
137
|
+
`unknown_fields` — a root field `3.3.1` would have added | **0** |
|
|
138
|
+
Round-trip (`to_dict()` returns the payload unchanged) | exact |
|
|
139
|
+
Root aliases agree with their blocks (`g_score`, `g_final`) | yes |
|
|
140
|
+
Coverage re-derivable from the published counters (`3/3 → 1.0`) | yes |
|
|
141
|
+
Four required blocks present | yes |
|
|
142
|
+
Twelve per-signal provenance fields exposed, `null` included | yes |
|
|
143
|
+
`trajectory_points` and `provider` still `None` on REST | yes |
|
|
144
|
+
`quality.stability_score` + `metrics.stability_score` = 1.0 — the inverted homonyms still coexist | yes |
|
|
145
|
+
`baseline.delta_E` still equals `reference − observed` (the implementation, not the description) | yes |
|
|
146
|
+
`decision` / `decision_basis` | `ALLOW` / `measured_thresholds` |
|
|
147
|
+
|
|
148
|
+
**This is a replay, not a live call.** The SDK did not reach the hosted
|
|
149
|
+
service: the payload was served to it by `httpx.MockTransport`. What it
|
|
150
|
+
establishes is that the contract this client models is the contract the
|
|
151
|
+
production deployment emitted — not that an end-to-end call succeeds.
|
|
152
|
+
See §6 for what remains missing and why.
|
|
153
|
+
|
|
154
|
+
**One finding worth carrying into any comparison.** On that response, the
|
|
155
|
+
three configuration fingerprints (`prompt_template_hash`,
|
|
156
|
+
`inference_config_hash`, `config_fingerprint`) were populated **only for
|
|
157
|
+
the two signals produced by the factuality judge**. `stability_score`,
|
|
158
|
+
`coherence_score` and `semantic_risk` carried `status: "measured"` with
|
|
159
|
+
all three fingerprints `null`. Two observations therefore cannot be shown
|
|
160
|
+
to share an evaluator configuration on those three signals — the data to
|
|
161
|
+
check it is not published. The SDK exposes the `null`s; it invents
|
|
162
|
+
nothing. A test holds this observation so that a change to it is noticed.
|
|
163
|
+
|
|
164
|
+
### Verified by the test suite, without a running instance
|
|
165
|
+
|
|
166
|
+
| Capability | Where |
|
|
167
|
+
|---|---|
|
|
168
|
+
`X-API-Key` authentication; key **format** validated before any network call | `test_client.py` |
|
|
169
|
+
Nine error families: 401, 402, 413, 422, 429, 5xx, timeout, network, invalid response | `test_errors.py` (39 tests) |
|
|
170
|
+
**No automatic retry**, on any status; a timeout is never retried even with an explicit policy | `test_retry.py` (26 tests) |
|
|
171
|
+
`null` preserved; `None` never substituted by `0.0`; `None` distinct from `[]` | `test_models.py` |
|
|
172
|
+
Unknown fields preserved in `raw` and listed, never fatal | `test_models.py` |
|
|
173
|
+
No numeric default anywhere in the sources (AST-checked, and the guard is itself tested) | `test_contract_drift.py` |
|
|
174
|
+
No backend import, no undeclared dependency, no embedded threshold | `test_contract_drift.py` |
|
|
175
|
+
No key in REST `repr`, exception messages, logs, request body or URL; repository-wide key sweep | `test_no_secret_leak.py` (11 tests) |
|
|
176
|
+
The three historical REST examples run and exit 0; V2 adds an SSE example | `test_examples.py` |
|
|
177
|
+
|
|
178
|
+
### Not verified
|
|
179
|
+
|
|
180
|
+
- **any call made by this SDK to a hosted deployment** — no end-to-end
|
|
181
|
+
call has ever been executed, under `3.3.0` or `3.3.1`. The credential
|
|
182
|
+
needed for one was not available to the verification session;
|
|
183
|
+
- consequently: authentication, error statuses, timeouts, rate limiting
|
|
184
|
+
and quota behaviour **against production** — all nine error families
|
|
185
|
+
are exercised against a simulated transport only;
|
|
186
|
+
- behaviour under concurrency or sustained load;
|
|
187
|
+
- behaviour against a measurement version outside
|
|
188
|
+
`API_CONTRACT["verified_measurement_versions"]`;
|
|
189
|
+
- the `3.3.1` **incomplete-measurement** path: the single `3.3.1`
|
|
190
|
+
response available is a complete measurement, so the withheld-aggregate
|
|
191
|
+
behaviour has been re-verified at `3.3.1` **by source identity only**,
|
|
192
|
+
not by observation;
|
|
193
|
+
- repeat-call variance of the factuality judge (see SL-005).
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## 3. Known backend limitations
|
|
198
|
+
|
|
199
|
+
These are **backend** defects observed or derived at the reference commit.
|
|
200
|
+
The SDK does not work around them — it surfaces them. Working around a
|
|
201
|
+
measurement defect in a client would make the defect invisible without
|
|
202
|
+
making it go away.
|
|
203
|
+
|
|
204
|
+
> **All three were re-read against the deployed commit `aeff13c` /
|
|
205
|
+
> `3.3.1` on 2026-10-22.** The method was source comparison between
|
|
206
|
+
> `68d6057` and `aeff13c`, plus the replayed production response. **A
|
|
207
|
+
> version number changing is not evidence that a defect closed**, and no
|
|
208
|
+
> entry below was weakened on that basis: BL-001 and BL-003 live in files
|
|
209
|
+
> that are byte-identical between the two commits, so they stand
|
|
210
|
+
> unchanged. BL-002 is the one entry that moves, because an actual
|
|
211
|
+
> correction was shipped — and it closes one named class, not the entry.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
### BL-001 — TRIAL account and key status inconsistency
|
|
216
|
+
|
|
217
|
+
| | |
|
|
218
|
+
|---|---|
|
|
219
|
+
**Status** | Open — observed |
|
|
220
|
+
**Affected version** | backend commit `68d6057`; measurement version `3.3.0`. **Re-read at `aeff13c` / `3.3.1` on 2026-10-22: unchanged, still open** |
|
|
221
|
+
**Scope** | accounts whose `tier` is `TRIAL` **and** whose `status` is `ACTIVE` |
|
|
222
|
+
**Severity for a pilot** | **High** — blocks use of a trial key |
|
|
223
|
+
**SDK impact** | none; the SDK reports the server's responses faithfully |
|
|
224
|
+
|
|
225
|
+
> **Re-read at `3.3.1`.** `app/auth/api_key.py` is **byte-identical**
|
|
226
|
+
> between `68d6057` and the deployed `aeff13c`. The quota debit still
|
|
227
|
+
> carries `AND status = 'TRIAL'`, and the statement that suspends the
|
|
228
|
+
> account's keys still carries no equivalent guard. The defect therefore
|
|
229
|
+
> stands at `3.3.1`, established by **source comparison** — the account
|
|
230
|
+
> journey was not re-run, and no trial key was available to re-observe it.
|
|
231
|
+
|
|
232
|
+
**What was observed.** On a local instance built from the reference
|
|
233
|
+
commit, a freshly provisioned trial account was taken through the
|
|
234
|
+
documented journey (registration, e-mail verification, administrator
|
|
235
|
+
approval). The resulting key was accepted by an authenticated non-metered
|
|
236
|
+
endpoint. The **first** `POST /v1/govern` call then returned
|
|
237
|
+
**HTTP 402** with a payload stating the trial quota was exhausted, and
|
|
238
|
+
**every subsequent call returned HTTP 401**.
|
|
239
|
+
|
|
240
|
+
Database state immediately afterwards:
|
|
241
|
+
|
|
242
|
+
| Field | Value |
|
|
243
|
+
|---|---|
|
|
244
|
+
`accounts.status` | `ACTIVE` |
|
|
245
|
+
`accounts.tier` | `TRIAL` |
|
|
246
|
+
`accounts.trial_requests_remaining` | `500` |
|
|
247
|
+
`accounts.trial_requests_total` | `500` |
|
|
248
|
+
`api_keys.status` | **`SUSPENDED`** |
|
|
249
|
+
|
|
250
|
+
The quota was **not** exhausted — none of it had been consumed — yet the
|
|
251
|
+
key was permanently suspended.
|
|
252
|
+
|
|
253
|
+
**Conditions reproduced.** One account, one local instance, the documented
|
|
254
|
+
provisioning journey. **Not generalised**: this was not tested against any
|
|
255
|
+
hosted deployment, nor against accounts provisioned by other means, nor
|
|
256
|
+
against accounts whose `status` is something other than `ACTIVE`. Whether
|
|
257
|
+
hosted deployments carry the same account state is unverified.
|
|
258
|
+
|
|
259
|
+
**Technical cause (confirmed by reading the source).** The quota debit is
|
|
260
|
+
guarded by a condition on `accounts.status`:
|
|
261
|
+
|
|
262
|
+
```sql
|
|
263
|
+
UPDATE accounts
|
|
264
|
+
SET trial_requests_remaining = trial_requests_remaining - 1
|
|
265
|
+
WHERE id = :account_id
|
|
266
|
+
AND status = 'TRIAL' -- the condition in question
|
|
267
|
+
AND trial_requests_remaining > 0
|
|
268
|
+
RETURNING trial_requests_remaining
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
A database migration included at the reference commit records that trial
|
|
272
|
+
detection was moved from `status` to `tier`, stating that `status`
|
|
273
|
+
describes whether an account is running and that *"trial detection
|
|
274
|
+
everywhere now keys off `tier=='TRIAL'`"*. The portal consequently writes
|
|
275
|
+
`status='ACTIVE'`, `tier='TRIAL'`. The `UPDATE` above therefore matches no
|
|
276
|
+
row; an empty result is interpreted as an exhausted quota; the suspension
|
|
277
|
+
branch then runs, and the statement that suspends the keys carries no
|
|
278
|
+
equivalent guard, so the keys are suspended while the account is not.
|
|
279
|
+
|
|
280
|
+
**Workaround for a pilot.** None available from the client side. A pilot
|
|
281
|
+
needs a key on an account the server's quota path accepts — in practice a
|
|
282
|
+
paid tier with a funded wallet, or an account whose state the operator has
|
|
283
|
+
adjusted. **Request this from your NeoMundi contact rather than working
|
|
284
|
+
around it.**
|
|
285
|
+
|
|
286
|
+
**Internal tracking.** Backend correction plan, item BL-001.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
### BL-002 — SSE chunking-dependent governance decision
|
|
291
|
+
|
|
292
|
+
| | |
|
|
293
|
+
|---|---|
|
|
294
|
+
**Status** | **Partly corrected in `3.3.1` — `Fixed — pending verification` for the named class; the entry stays open for the residue** |
|
|
295
|
+
**Affected version** | observed at backend commit `68d6057` / `3.3.0`. **Corrected at `aeff13c` / `3.3.1`, for one class only** |
|
|
296
|
+
**Scope** | `POST /v1/govern/stream` only. **REST is unaffected.** |
|
|
297
|
+
**Severity for a pilot** | **High for streaming**; applies when using the V2 SSE client |
|
|
298
|
+
**SDK impact** | **applies to the SSE path; V2 transports the reported trajectory without repairing it** |
|
|
299
|
+
|
|
300
|
+
> **SDK 2 transports SSE but does not correct this backend defect.**
|
|
301
|
+
> Read [STREAMING.md](docs/STREAMING.md) for integrity and termination
|
|
302
|
+
> checks. These checks do not validate the trajectory or the metrics.
|
|
303
|
+
|
|
304
|
+
#### What `3.3.1` changed, and what it did not
|
|
305
|
+
|
|
306
|
+
**`3.3.1` exists for this defect, and for nothing else.** Between
|
|
307
|
+
`68d6057` and the deployed `aeff13c`, one application file changed —
|
|
308
|
+
`app/api/v1/streaming.py` — plus the version string in `app/config.py`.
|
|
309
|
+
The trajectory rules R1/R2/R3 now read **only** the trajectory points
|
|
310
|
+
whose coherence *and* risk were really measured, plus the final point.
|
|
311
|
+
The published trajectory itself (`series`, `profile`, `total_variation`,
|
|
312
|
+
`min_stability`, `max_stability`) is unchanged, so no stored series is
|
|
313
|
+
reinterpreted.
|
|
314
|
+
|
|
315
|
+
Measured in the backend's own regression suite, on one text with one set
|
|
316
|
+
of judge verdicts and only the slicing varying:
|
|
317
|
+
|
|
318
|
+
| Fragments | Decision before | Decision after |
|
|
319
|
+
|---:|---|---|
|
|
320
|
+
| 1 | `ALLOW` | `ALLOW` |
|
|
321
|
+
| 10 | `ALLOW` | `ALLOW` |
|
|
322
|
+
| 20 | **`FLAG`** | `ALLOW` |
|
|
323
|
+
| 50 | **`FLAG`** | `ALLOW` |
|
|
324
|
+
| 300 | **`FLAG`** | `ALLOW` |
|
|
325
|
+
|
|
326
|
+
**What is still true at `3.3.1`, and the backend locks it with tests so
|
|
327
|
+
that it stays visible:**
|
|
328
|
+
|
|
329
|
+
- **ΔG is still not invariant to the slicing.** The published `series`,
|
|
330
|
+
`profile` and `total_variation` still vary with fragmentation; a
|
|
331
|
+
backend test *requires* that they do, so the residue stays measured
|
|
332
|
+
rather than presumed gone;
|
|
333
|
+
- **a stream sliced too coarsely has no measured trajectory at all** —
|
|
334
|
+
with default intervals the first window carrying both signals arrives
|
|
335
|
+
at fragment 120. Below that, no rule can fire, so whether a drift is
|
|
336
|
+
*observable* still depends on the framing;
|
|
337
|
+
- the asynchronous race between evaluators subsists; the correction
|
|
338
|
+
discards windows whose signals had not landed, it does not make their
|
|
339
|
+
arrival deterministic;
|
|
340
|
+
- four decision thresholds remain hard-coded, and two of them appear in
|
|
341
|
+
the production environment template **without being read by any line of
|
|
342
|
+
code**.
|
|
343
|
+
|
|
344
|
+
**Consequences for a pilot using SSE directly — unchanged.** Do not
|
|
345
|
+
compare `delta_g.profile` or `delta_g.total_variation` across providers,
|
|
346
|
+
clients or versions. Prefer REST for anything that must be reproducible.
|
|
347
|
+
|
|
348
|
+
**Evidence, and its limits.** The above is **derived from the backend
|
|
349
|
+
source and its regression suite at `aeff13c`** — read on 2026-10-22. No
|
|
350
|
+
production SSE stream under `3.3.1` was examined in that 22 October
|
|
351
|
+
verification campaign. A later transport success alone would not establish
|
|
352
|
+
invariance to fragmentation. The correction is therefore
|
|
353
|
+
`Fixed — pending verification`, and the entry as a whole stays open.
|
|
354
|
+
|
|
355
|
+
**What was observed.** Four streams were run against a local instance,
|
|
356
|
+
each with a correct answer, each with the factuality judge reporting
|
|
357
|
+
`factual_hallucination_score = 0.0` — that is, no hallucination measured —
|
|
358
|
+
and final stability between 0.86 and 0.94.
|
|
359
|
+
|
|
360
|
+
| Content fragments | ΔG profile | Total variation | **Decision** |
|
|
361
|
+
|---:|---|---:|---|
|
|
362
|
+
1 | `FLAT` | 0.0 | **ALLOW** |
|
|
363
|
+
1 | `FLAT` | 0.0 | **ALLOW** |
|
|
364
|
+
10 | `FLAT` | 0.0 | **ALLOW** |
|
|
365
|
+
300 | `DROP` | 0.158 | **FLAG** |
|
|
366
|
+
|
|
367
|
+
The measured quality was equivalent across all four. The decision was
|
|
368
|
+
not.
|
|
369
|
+
|
|
370
|
+
**Mechanism (observed).** Intermediate governance windows fire every
|
|
371
|
+
*N* **network fragments**, not every *N* tokens. Each window computes a
|
|
372
|
+
provisional stability from whichever rolling signals have reported by that
|
|
373
|
+
point; the factuality judge runs only at end of stream, so intermediate
|
|
374
|
+
windows use a neutral default for it. The published trajectory of one
|
|
375
|
+
300-fragment stream:
|
|
376
|
+
|
|
377
|
+
```
|
|
378
|
+
fragment 20 → stability 1.0000 delta 0.0000
|
|
379
|
+
fragment 40 → stability 1.0000 delta 0.0000
|
|
380
|
+
fragment 60 → stability 0.8896 delta −0.1104 ← coherence reports here
|
|
381
|
+
fragment 80 → stability 0.8896 delta 0.0000
|
|
382
|
+
fragment 100 → stability 0.8867 delta −0.0029
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
The "drop" is the coherence measurement **arriving**, not a degradation
|
|
386
|
+
during generation. A profile rule then reads that drop as instability and
|
|
387
|
+
escalates the decision.
|
|
388
|
+
|
|
389
|
+
A stream short enough that no intermediate window fires produces a
|
|
390
|
+
single-point trajectory, classified `FLAT`, and no escalation occurs.
|
|
391
|
+
|
|
392
|
+
**Two further observations on the same responses.** On a flagged stream,
|
|
393
|
+
the published `reasons` array contained both
|
|
394
|
+
*"all metrics within bounds"* and *"possible hallucination or drift in
|
|
395
|
+
progress"*, while the judge had measured no hallucination at all; and
|
|
396
|
+
`decision_basis` was `measured_thresholds` — the field that exists to
|
|
397
|
+
explain why a decision was taken classified this escalation as resting on
|
|
398
|
+
measured thresholds.
|
|
399
|
+
|
|
400
|
+
**Guidance for a pilot using SSE directly.**
|
|
401
|
+
|
|
402
|
+
- Do not compare `delta_g.profile`, `delta_g.total_variation` or the
|
|
403
|
+
streaming `decision` across providers, SDK versions or client libraries:
|
|
404
|
+
fragmentation differs between them.
|
|
405
|
+
- Treat intermediate `governance_check` and `stability_check` events as
|
|
406
|
+
**provisional** — the backend marks them `provisional: true` and that
|
|
407
|
+
flag is meaningful.
|
|
408
|
+
- Prefer the REST route for anything that must be reproducible.
|
|
409
|
+
|
|
410
|
+
**Internal tracking.** Backend correction plan, item BL-002.
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
### BL-003 — `delta_g` published while the aggregate is withheld
|
|
415
|
+
|
|
416
|
+
| | |
|
|
417
|
+
|---|---|
|
|
418
|
+
**Status** | Open — observed. **Contract interpretation under review.** |
|
|
419
|
+
**Affected version** | backend commit `68d6057`; measurement version `3.3.0`. **Re-read at `aeff13c` / `3.3.1` on 2026-10-22: unchanged, still open** |
|
|
420
|
+
**Scope** | `POST /v1/govern`, when a required signal was not measured |
|
|
421
|
+
**Severity for a pilot** | **Medium** — a misread field, not a corrupted measurement |
|
|
422
|
+
**SDK impact** | none; the SDK exposes the raw value and documents the caveat |
|
|
423
|
+
|
|
424
|
+
> **Re-read at `3.3.1`.** `app/api/v1/governance.py` and everything under
|
|
425
|
+
> `app/models/` are **byte-identical** between `68d6057` and `aeff13c`:
|
|
426
|
+
> the code that publishes `delta_g` and the code that withholds the
|
|
427
|
+
> aggregates are the same code. The limitation therefore stands,
|
|
428
|
+
> established by **source comparison**. It was **not** re-observed: the
|
|
429
|
+
> only `3.3.1` response available is a complete measurement, so it
|
|
430
|
+
> exhibits neither the withheld aggregate nor the hazard. A `3.3.1`
|
|
431
|
+
> response with an unmeasured required signal would be needed to
|
|
432
|
+
> re-observe it, and none exists.
|
|
433
|
+
|
|
434
|
+
**What was observed.** On responses where a required signal could not be
|
|
435
|
+
measured, the server correctly withheld the aggregates — `g_score`,
|
|
436
|
+
`quality.stability_score` and `g_final` were all `null`, the decision was
|
|
437
|
+
`FLAG`, and `decision_basis` was `incomplete_measurement` — while
|
|
438
|
+
`delta_g` carried a number. Two of ten REST captures showed this, as did
|
|
439
|
+
the fully degraded capture (`g_score: null`, `delta_g: −0.56199`).
|
|
440
|
+
|
|
441
|
+
**What the contract says, and why this is stated carefully.** `delta_g` is
|
|
442
|
+
documented as the variation in `G` since the account's previous request.
|
|
443
|
+
It is a **separate field** from the aggregates, and the withholding rule
|
|
444
|
+
that the backend documents applies explicitly to `g_score`,
|
|
445
|
+
`quality.stability_score` and `g_final` — `delta_g` is not named in it.
|
|
446
|
+
On that reading, a populated `delta_g` beside a null `g_score` is the
|
|
447
|
+
contract behaving as written rather than a violation of it.
|
|
448
|
+
|
|
449
|
+
**What is nonetheless a real hazard, independent of that reading.** The
|
|
450
|
+
value is computed from the current request's internal `G`, which in this
|
|
451
|
+
case is precisely the figure the withholding rule exists to suppress —
|
|
452
|
+
and the reference stored for the *next* request is that same unpublished
|
|
453
|
+
figure. A series of `delta_g` values spanning a degraded request is
|
|
454
|
+
therefore not interpretable, whatever the contract intends. That is why
|
|
455
|
+
this is listed rather than dismissed.
|
|
456
|
+
|
|
457
|
+
**Guidance — this is the part that matters for your integration.**
|
|
458
|
+
|
|
459
|
+
Respect statuses, coverage and the aggregate publication rule rather than
|
|
460
|
+
reading numbers directly:
|
|
461
|
+
|
|
462
|
+
```python
|
|
463
|
+
if result.measurement_incomplete:
|
|
464
|
+
# Aggregates are withheld. Do not read delta_g as a quality signal
|
|
465
|
+
# for this observation, and do not chain it with neighbouring
|
|
466
|
+
# observations.
|
|
467
|
+
result.missing_required_signals # which signals were not measured
|
|
468
|
+
result.measurement.coverage # how much of the measurement happened
|
|
469
|
+
result.governance.decision_basis # 'incomplete_measurement'
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
And in general: **do not read `delta_g` as a drift measurement.** It
|
|
473
|
+
compares one request to whatever the account's previous request happened
|
|
474
|
+
to be, whatever question that request asked, within a one-hour window.
|
|
475
|
+
Two unrelated tasks from the same account produce a `delta_g` that
|
|
476
|
+
describes the change of task, not a change in the system.
|
|
477
|
+
|
|
478
|
+
**Internal tracking.** Backend correction plan, item BL-003.
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## 4. SDK-specific limitations
|
|
483
|
+
|
|
484
|
+
These are scope decisions. V2 adds SSE transport; the scientific limits below remain.
|
|
485
|
+
|
|
486
|
+
| ID | Not included | Why |
|
|
487
|
+
|---|---|---|
|
|
488
|
+
**SL-001** | **Live deployment/provider validation of V2 SSE** | Transport is implemented and tested offline. Scientific/backend limitations in BL-002 remain; no new hosted SDK SSE call was made. |
|
|
489
|
+
**SL-002** | **TypeScript / JavaScript SDK** | requires a frozen OpenAPI specification, which does not exist server-side |
|
|
490
|
+
**SL-003** | **Longitudinal processing** — series, trends, drift | comparing two observations requires conditions the SDK cannot verify for you (same measurement version, same coverage, same evaluator models, same β weights, same route). Deciding that a series is valid is a methodological judgement, not a transport concern. |
|
|
491
|
+
**SL-004** | **Automatic score interpretation** — no `is_safe()`, no quality level, no embedded threshold | thresholds are server-side configuration and are not publicly calibrated. A client-side threshold would be an invention. Verified absent by AST analysis of the sources. |
|
|
492
|
+
**SL-005** | **Correction of backend defects** | the SDK surfaces BL-001 to BL-003; it does not compensate for them. Related: the factuality judge runs at a non-zero sampling temperature, so two identical calls can return different scores. **That spread has not been measured.** Until it is, no difference between two observations is interpretable. |
|
|
493
|
+
| | Also absent | asynchronous client, other REST routes (replay, exports, RGC), caching, pagination, PDF certificate verification |
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
497
|
+
## 5. Developer guidance
|
|
498
|
+
|
|
499
|
+
### 5.1 Check measurement coverage before reading any score
|
|
500
|
+
|
|
501
|
+
This is the first thing to read in a response, not the last.
|
|
502
|
+
|
|
503
|
+
```python
|
|
504
|
+
result = client.govern(..., llm_response=text, llm_prompt=prompt)
|
|
505
|
+
|
|
506
|
+
if result.measurement_incomplete:
|
|
507
|
+
print("incomplete:", result.missing_required_signals)
|
|
508
|
+
# The FLAG you are about to see is NOT a statement about the content.
|
|
509
|
+
|
|
510
|
+
m = result.measurement
|
|
511
|
+
m.status # 'complete' | 'partial' | 'not_attempted'
|
|
512
|
+
m.checks_attempted # checks actually launched
|
|
513
|
+
m.checks_succeeded # of those, how many produced a usable result
|
|
514
|
+
m.coverage # the ratio — and the value of `confidence`
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
**`coverage == 1.0` does not mean "fully measured".** It is `1.0` when
|
|
518
|
+
nothing was attempted. Always read it together with `status`:
|
|
519
|
+
|
|
520
|
+
```python
|
|
521
|
+
if m.status == "not_attempted":
|
|
522
|
+
# No server-side check ran. coverage 1.0 means "nothing was expected",
|
|
523
|
+
# not "everything was measured".
|
|
524
|
+
...
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
`coverage` is re-derivable, so you can verify it rather than trust it:
|
|
528
|
+
|
|
529
|
+
```python
|
|
530
|
+
if m.checks_attempted:
|
|
531
|
+
assert round(m.checks_succeeded / m.checks_attempted, 6) == m.coverage
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### 5.2 Handling `null`
|
|
535
|
+
|
|
536
|
+
**Never substitute a number for a `null`.** The SDK contains no numeric
|
|
537
|
+
default anywhere in its sources, and a test enforces that. Do not
|
|
538
|
+
reintroduce one in your integration.
|
|
539
|
+
|
|
540
|
+
```python
|
|
541
|
+
# For display — never raises.
|
|
542
|
+
result.format_score("g_score", nd=4) # '0.4920' | 'non mesuré'
|
|
543
|
+
result.format_score("g_score", when_none="n/a")
|
|
544
|
+
|
|
545
|
+
# For a computation — raises an exception that names the cause.
|
|
546
|
+
from neomundi import AggregateWithheldError
|
|
547
|
+
try:
|
|
548
|
+
value = result.require("g_score")
|
|
549
|
+
except AggregateWithheldError as exc:
|
|
550
|
+
exc.missing_required_signals
|
|
551
|
+
exc.decision_basis
|
|
552
|
+
|
|
553
|
+
# What not to do:
|
|
554
|
+
f"{result.g_score:.2f}" # TypeError as soon as the aggregate is withheld
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
A `null` has **three** possible meanings. Distinguish them:
|
|
558
|
+
|
|
559
|
+
| Meaning | How to recognise it |
|
|
560
|
+
|---|---|
|
|
561
|
+
**not measured** — the check ran, produced nothing | the signal is in `unmeasured_signals`, with its cause |
|
|
562
|
+
**not sold at your tier** | the signal is in neither signal list, and `measurement.status` is `complete` |
|
|
563
|
+
**not applicable on this route** | `trajectory_points` and `provider` are always `None` on REST — "not applicable here", not "zero" |
|
|
564
|
+
|
|
565
|
+
### 5.3 "Measured as zero" is not "not measured"
|
|
566
|
+
|
|
567
|
+
This distinction is the product. In every failure mode, the backend's
|
|
568
|
+
instruments return the **most reassuring value they can** — risk `0.0`,
|
|
569
|
+
coherence `1.0`, factuality `0.0`. A signal's validity is therefore
|
|
570
|
+
**never** readable from its numeric value.
|
|
571
|
+
|
|
572
|
+
```python
|
|
573
|
+
# A measured zero:
|
|
574
|
+
result.quality.semantic_risk # 0.0 -> measured, nothing detected
|
|
575
|
+
result.is_measured("semantic_risk") # True
|
|
576
|
+
|
|
577
|
+
# An absence:
|
|
578
|
+
result.quality.factual_hallucination_score # None -> not measured
|
|
579
|
+
result.unmeasured_reason("factual_hallucination_score")
|
|
580
|
+
# 'hallucination_judge_unavailable'
|
|
581
|
+
|
|
582
|
+
# Four states, and the SDK does not collapse them:
|
|
583
|
+
result.measurement_state("coherence_score")
|
|
584
|
+
# MeasurementState.MEASURED
|
|
585
|
+
# MeasurementState.NOT_MEASURED -> a cause is available
|
|
586
|
+
# MeasurementState.NOT_ATTEMPTED_OR_GATED -> not launched OR gated by tier
|
|
587
|
+
# MeasurementState.UNKNOWN -> the response does not say
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
`NOT_ATTEMPTED_OR_GATED` is **deliberately ambiguous**: the server does not
|
|
591
|
+
distinguish "never launched" from "withheld by commercial tier" on the
|
|
592
|
+
wire. The SDK does not invent that distinction. Read
|
|
593
|
+
`measurement.status` and `measurement.checks_attempted` to narrow it.
|
|
594
|
+
|
|
595
|
+
One observed consequence worth knowing: when no check is launched at all,
|
|
596
|
+
the response carried `quality.coherence_score = 1.0` as a value while the
|
|
597
|
+
factuality fields were `null`, and the aggregates were published at their
|
|
598
|
+
maximum. A `1.0` coherence is therefore not always an observation.
|
|
599
|
+
|
|
600
|
+
### 5.4 Keep the audit and version metadata
|
|
601
|
+
|
|
602
|
+
Keep these with every observation you intend to use as evidence or compare
|
|
603
|
+
later. Without them an observation is a number without provenance.
|
|
604
|
+
|
|
605
|
+
```python
|
|
606
|
+
result.request_id # the observation's identity
|
|
607
|
+
result.timestamp_raw # the server's instant, as sent
|
|
608
|
+
result.audit.measurement_version # the only version with scientific meaning
|
|
609
|
+
result.audit.normalizer_version
|
|
610
|
+
result.measurement.coverage # how much of the measurement happened
|
|
611
|
+
result.evaluators # per-signal instrument, model, cause
|
|
612
|
+
|
|
613
|
+
# The three fingerprints are the only way to verify that two observations
|
|
614
|
+
# were produced under the same evaluator configuration — `measurement_version`
|
|
615
|
+
# does not cover evaluator identity.
|
|
616
|
+
ev = result.evaluator_of("factual_hallucination_score")
|
|
617
|
+
ev["model_used"], ev["config_fingerprint"], ev["prompt_template_hash"]
|
|
618
|
+
|
|
619
|
+
result.esi.betas if result.esi else None # the weights actually applied
|
|
620
|
+
result.to_dict() # the full raw payload
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
Two results are comparable only under **all** of: same
|
|
624
|
+
`measurement_version`, same coverage, same evaluator models, same β
|
|
625
|
+
weights, same route. The SDK gives you what you need to check that; it
|
|
626
|
+
does not check it for you (SL-003).
|
|
627
|
+
|
|
628
|
+
### 5.5 A `FLAG` is not evidence of hallucination
|
|
629
|
+
|
|
630
|
+
A `FLAG` is **advisory**. NeoMundi grants, refuses, suspends and modifies
|
|
631
|
+
no execution, and the signed contract format states this as a structural
|
|
632
|
+
invariant.
|
|
633
|
+
|
|
634
|
+
Read `decision_basis` before acting on a decision:
|
|
635
|
+
|
|
636
|
+
| Value | What the FLAG means |
|
|
637
|
+
|---|---|
|
|
638
|
+
`measured_thresholds` | a measured value crossed a configured threshold. The FLAG is about the content. |
|
|
639
|
+
`incomplete_measurement` | a required signal could not be measured. **The FLAG is not about the content** — it says the measurement did not complete. |
|
|
640
|
+
`no_check_attempted` | no server-side check ran. Nothing here was measured by the service. |
|
|
641
|
+
`None` | the observation predates this field. **Do not assume `measured_thresholds`.** |
|
|
642
|
+
|
|
643
|
+
Two further cautions, both grounded in observations above:
|
|
644
|
+
|
|
645
|
+
- the factuality judge is a language model scoring a text with no external
|
|
646
|
+
source, at a non-zero temperature. A high score is its opinion, traceable
|
|
647
|
+
and reproducible to the configuration — not a verified fact;
|
|
648
|
+
- `decision_basis` was observed reporting `measured_thresholds` on a
|
|
649
|
+
streaming escalation that came from the ΔG profile rule (BL-002). On the
|
|
650
|
+
streaming route, read `reasons` as well as `decision_basis`.
|
|
651
|
+
|
|
652
|
+
### 5.6 Reporting a discrepancy
|
|
653
|
+
|
|
654
|
+
The SDK is built so that a contract discrepancy is **visible rather than
|
|
655
|
+
absorbed**. Two fields carry it:
|
|
656
|
+
|
|
657
|
+
```python
|
|
658
|
+
result.parse_warnings # a field could not be read at the expected type
|
|
659
|
+
result.unknown_fields # a root field this SDK version does not model
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
| What you see | What it means | What to do |
|
|
663
|
+
|---|---|---|
|
|
664
|
+
`parse_warnings` non-empty | the instance emits something other than what the modelled contract declares | **report it.** This is an audit finding, not a client bug. Do not widen the parsing to make it go away. |
|
|
665
|
+
`unknown_fields` non-empty | the backend has gained a field since the reference commit | report it; the SDK needs extending and its version incrementing |
|
|
666
|
+
`InvalidResponseError` on a 2xx | a required block is missing, or the body is not JSON | report it with the exception's `body` |
|
|
667
|
+
`measurement_version` **outside** `verified_measurement_versions` | you are talking to a contract this SDK has not been checked against | **report it before relying on anything**, parsing included |
|
|
668
|
+
`measurement_version` in the verified list but ≠ `measurement_version` | parsing is safe; the **series** is not comparable with one produced under the declared version | record it with every observation; no report needed |
|
|
669
|
+
|
|
670
|
+
When reporting, include: the `request_id`, `result.audit.measurement_version`,
|
|
671
|
+
the `parse_warnings` entries verbatim, the base URL's **environment**
|
|
672
|
+
(not its credentials), and the SDK version. **Never include your API key**,
|
|
673
|
+
and redact any response content you are not permitted to share.
|
|
674
|
+
|
|
675
|
+
Use your NeoMundi technical contact for confidential material. Issues on
|
|
676
|
+
this public repository may contain only sanitised, non-sensitive reproductions.
|
|
677
|
+
|
|
678
|
+
---
|
|
679
|
+
|
|
680
|
+
## 6. Version compatibility
|
|
681
|
+
|
|
682
|
+
> **REST contract compatibility of `POST /v1/govern` with measurement
|
|
683
|
+
> version `3.3.1` is verified by local tests and by replaying a
|
|
684
|
+
> production capture. A direct call of the SDK against production has not
|
|
685
|
+
> been performed.**
|
|
686
|
+
|
|
687
|
+
| | |
|
|
688
|
+
|---|---|
|
|
689
|
+
**Audited reference** | backend commit `68d6057cf620e430e6488da3cfcec9c471c75ed6` |
|
|
690
|
+
**Measurement version of that reference** | `3.3.0` |
|
|
691
|
+
**Measurement versions whose REST surface is verified** | **`3.3.0` and `3.3.1`** |
|
|
692
|
+
**Normalizer version** | `1.0.0` — unchanged across both |
|
|
693
|
+
**Contract source** | the backend's request and response models at that commit |
|
|
694
|
+
**Frozen OpenAPI specification** | **none exists** — this is why a commit is named |
|
|
695
|
+
|
|
696
|
+
```python
|
|
697
|
+
import neomundi
|
|
698
|
+
neomundi.API_CONTRACT
|
|
699
|
+
# {'backend_commit': '68d6057cf620e430e6488da3cfcec9c471c75ed6',
|
|
700
|
+
# 'measurement_version': '3.3.0',
|
|
701
|
+
# 'verified_measurement_versions': ('3.3.0', '3.3.1'),
|
|
702
|
+
# 'normalizer_version': '1.0.0',
|
|
703
|
+
# 'route': 'POST /v1/govern',
|
|
704
|
+
# 'contract_source': 'govern-v3/app/models/responses.py, requests.py',
|
|
705
|
+
# 'openapi_frozen': False}
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
### Four version numbers, and they are not interchangeable
|
|
709
|
+
|
|
710
|
+
Confusing them is the most common way to reach a wrong conclusion about
|
|
711
|
+
this SDK, so they are named apart:
|
|
712
|
+
|
|
713
|
+
| | Value observed | Where it is read |
|
|
714
|
+
|---|---|---|
|
|
715
|
+
**Application build** | `3.0.0` | `GET /v1/health`, field `version` |
|
|
716
|
+
**Measurement version** | `3.3.1` | `result.audit.measurement_version` — **a response, and nowhere else** |
|
|
717
|
+
**Normalizer version** | `1.0.0` | `result.audit.normalizer_version` |
|
|
718
|
+
**RGC contract schema** | `0.1.0` | the signed contract, a route this SDK does not cover |
|
|
719
|
+
|
|
720
|
+
**`GET /v1/health` does not publish the measurement version.** Verified
|
|
721
|
+
again on 2026-10-22 against the production deployment: `HTTP 200`,
|
|
722
|
+
`version: 3.0.0`, `environment: production`, and no
|
|
723
|
+
`measurement_version` anywhere in the body. `/openapi.json`, `/docs`,
|
|
724
|
+
`/v1/version` and `/version` were all recorded as `404` on 2026-10-21.
|
|
725
|
+
**Establishing the measurement version therefore costs one metered
|
|
726
|
+
observation. There is no free way to read it.**
|
|
727
|
+
|
|
728
|
+
### Why `3.3.1` is in the verified list — the evidence, in full
|
|
729
|
+
|
|
730
|
+
Two pieces, both dated 2026-10-22, and **neither is a call made by this
|
|
731
|
+
SDK**.
|
|
732
|
+
|
|
733
|
+
**1. Source comparison, `68d6057` → `aeff13c` (the deployed commit).**
|
|
734
|
+
Seven files differ in total. Of the application code, exactly **two**:
|
|
735
|
+
`app/api/v1/streaming.py` and the version string in `app/config.py`. The
|
|
736
|
+
remaining five are documentation and tests. Specifically:
|
|
737
|
+
|
|
738
|
+
- **nothing under `app/models/`** — the declared source of this SDK's
|
|
739
|
+
contract — changed;
|
|
740
|
+
- `app/api/v1/governance.py`, the REST route itself, did not change;
|
|
741
|
+
- `app/auth/`, `app/core/`, `app/rgc/` and `app/main.py` did not change;
|
|
742
|
+
- the backend's own 1 266-line golden file locking its measurement
|
|
743
|
+
surface differs by **one line**: the version string. Every signal,
|
|
744
|
+
coverage state, gated field and provenance entry in it is identical.
|
|
745
|
+
|
|
746
|
+
The backend states the consequence itself, and it is worth having
|
|
747
|
+
verbatim: under `3.3.0` and `3.3.1` results differ for the same input
|
|
748
|
+
**only** on a streaming observation left at `ALLOW` by the measured
|
|
749
|
+
signals that produced a `DROP` or `OSCILLATING` profile from unmeasured
|
|
750
|
+
points. *"Everywhere else — the whole REST path, every SSE observation
|
|
751
|
+
whose decision came from the measured signals, every aggregate, every
|
|
752
|
+
coverage status, every gated field — the two are identical."*
|
|
753
|
+
|
|
754
|
+
**2. Replay of a real `3.3.1` production response.** One response emitted
|
|
755
|
+
by the production deployment on 2026-10-21 was replayed through the
|
|
756
|
+
client: **0 `parse_warnings`, 0 unknown root fields**, exact round-trip,
|
|
757
|
+
aliases agreeing, coverage re-derivable. Details and the full check list
|
|
758
|
+
are in §2.
|
|
759
|
+
|
|
760
|
+
**A note on which process produced it.** The deployment's `uptime` read
|
|
761
|
+
on 2026-10-22 places the start of the running process at
|
|
762
|
+
`2026-10-20T21:16:45`, the same instant computed from an independent
|
|
763
|
+
reading on 2026-10-21. The process that produced the replayed response is
|
|
764
|
+
therefore **the process still serving today** — it has not been restarted
|
|
765
|
+
since. That removes one doubt. It does not create an end-to-end call.
|
|
766
|
+
|
|
767
|
+
### What the verified list does *not* mean
|
|
768
|
+
|
|
769
|
+
- it does **not** mean an end-to-end call has succeeded. **No call by
|
|
770
|
+
this SDK to any hosted deployment has ever been made**, under either
|
|
771
|
+
version. Authentication, error statuses, timeouts and quota behaviour
|
|
772
|
+
against production remain unverified;
|
|
773
|
+
- it does **not** make two observations comparable across the two
|
|
774
|
+
versions. Comparability is a separate question, answered below;
|
|
775
|
+
- these REST compatibility proofs do **not** cover the streaming route.
|
|
776
|
+
V2 implements SSE; its source-derived contract and offline tests are
|
|
777
|
+
documented separately in [STREAMING.md](docs/STREAMING.md).
|
|
778
|
+
|
|
779
|
+
### Minimum check before a pilot campaign
|
|
780
|
+
|
|
781
|
+
1. call `GET /v1/health` — unauthenticated, consumes no quota — and
|
|
782
|
+
record the build version and the state of the three evaluators.
|
|
783
|
+
**Do not read a measurement version from it; it does not carry one.**
|
|
784
|
+
A `degraded` on `judge_factuality` can coexist with successful measurements.
|
|
785
|
+
A configured-name/catalogue mismatch was documented historically; do not
|
|
786
|
+
assume that cause for every new degraded probe. The replayed
|
|
787
|
+
`3.3.1` response was produced by a deployment reporting exactly that,
|
|
788
|
+
with the judge's own `status` reading `measured`;
|
|
789
|
+
2. make one `POST /v1/govern` call and record
|
|
790
|
+
`result.audit.measurement_version`;
|
|
791
|
+
3. confirm it is in `API_CONTRACT["verified_measurement_versions"]`;
|
|
792
|
+
4. confirm `result.parse_warnings` and `result.unknown_fields` are both
|
|
793
|
+
empty;
|
|
794
|
+
5. record `result.evaluator_of(...)["config_fingerprint"]` and confirm it
|
|
795
|
+
does not change during the campaign — noting that on `3.3.1` only the
|
|
796
|
+
factuality judge's two signals publish one at all (§2).
|
|
797
|
+
|
|
798
|
+
### Comparability — the rule, and where `3.3.1` sits in it
|
|
799
|
+
|
|
800
|
+
The backend documents its own rule, and it is worth repeating verbatim:
|
|
801
|
+
results under measurement versions `3.2.0` and `3.3.0` are **not**
|
|
802
|
+
comparable for the same input wherever the observation came from the
|
|
803
|
+
streaming path, a required signal went unmeasured, or the client is on an
|
|
804
|
+
entry tier. Any longitudinal study must sit entirely on one side of that
|
|
805
|
+
boundary.
|
|
806
|
+
|
|
807
|
+
For `3.3.0` against `3.3.1` the backend's statement is narrower, and
|
|
808
|
+
quoted above: on the REST path the two are identical. **Treat that as the
|
|
809
|
+
backend's claim about its own formula, not as a licence to mix series.**
|
|
810
|
+
The conservative practice holds and costs nothing: record
|
|
811
|
+
`result.audit.measurement_version` with every observation, and keep a
|
|
812
|
+
series on one side of the boundary. A measurement whose version is not
|
|
813
|
+
recorded cannot be rescued later.
|
|
814
|
+
|
|
815
|
+
---
|
|
816
|
+
|
|
817
|
+
## 7. Known limitations lifecycle
|
|
818
|
+
|
|
819
|
+
Each entry carries a stable identifier. Identifiers are never reused or
|
|
820
|
+
renumbered; a resolved entry keeps its identifier and changes status.
|
|
821
|
+
|
|
822
|
+
**Status vocabulary**
|
|
823
|
+
|
|
824
|
+
| Status | Meaning |
|
|
825
|
+
|---|---|
|
|
826
|
+
`Open — observed` | reproduced on a running instance; conditions stated |
|
|
827
|
+
`Open — derived from code` | established by reading the source; not yet reproduced |
|
|
828
|
+
`Under review` | the intended contract is being clarified before any fix |
|
|
829
|
+
`Fixed — pending verification` | corrected, not yet re-verified against an instance |
|
|
830
|
+
`Fixed — verified` | corrected **and** re-verified, with evidence referenced |
|
|
831
|
+
`By design` | not a defect; a documented scope decision |
|
|
832
|
+
|
|
833
|
+
### Register
|
|
834
|
+
|
|
835
|
+
| ID | Title | Status | Affected version | Scope | Internal plan |
|
|
836
|
+
|---|---|---|---|---|---|
|
|
837
|
+
**BL-001** | TRIAL account and key status inconsistency | `Open — observed` · **re-read at `3.3.1`, unchanged** | backend `68d6057` / `3.3.0`, **and `aeff13c` / `3.3.1`** | accounts with `tier=TRIAL` and `status=ACTIVE`, on a local instance | plan item BL-001 |
|
|
838
|
+
**BL-002** | SSE chunking-dependent governance decision | **one class `Fixed — pending verification` at `3.3.1`; entry stays `Open` for the residue** | observed at `68d6057` / `3.3.0`; corrected at `aeff13c` / `3.3.1` | `POST /v1/govern/stream` only; REST unaffected | plan item BL-002 |
|
|
839
|
+
**BL-003** | `delta_g` published while the aggregate is withheld | `Open — observed`, contract `Under review` · **re-read at `3.3.1`, unchanged** | backend `68d6057` / `3.3.0`, **and `aeff13c` / `3.3.1`** | `POST /v1/govern` when a required signal is unmeasured | plan item BL-003 |
|
|
840
|
+
**SL-001** | **Live deployment/provider validation of V2 SSE** | Pending; transport implemented and tested offline | SDK `2.0.0` | V2 SSE qualification; BL-002 remains separate | — |
|
|
841
|
+
**SL-002** | No TypeScript SDK | `By design` | SDK `0.1.0` | SDK scope | — |
|
|
842
|
+
**SL-003** | No longitudinal processing | `By design` | SDK `0.1.0` | SDK scope | — |
|
|
843
|
+
**SL-004** | No automatic score interpretation | `By design` | SDK `0.1.0` | SDK scope | — |
|
|
844
|
+
**SL-005** | Backend defects not worked around; judge variance unmeasured | `By design` / `Open` | SDK `0.1.0`; backend `3.3.0` | SDK scope; measurement methodology | plan, additional risks |
|
|
845
|
+
|
|
846
|
+
**Internal plan.** Each `BL-*` entry maps to an item in an internal
|
|
847
|
+
backend correction plan holding reproduction steps, source locations,
|
|
848
|
+
candidate corrections, regression tests and acceptance criteria. That plan
|
|
849
|
+
is **not published in this repository** and is not distributed to pilots.
|
|
850
|
+
Its existence is recorded here so that a reader knows these items are
|
|
851
|
+
tracked rather than merely noted.
|
|
852
|
+
|
|
853
|
+
**Keeping this document honest.** An entry moves to `Fixed — verified`
|
|
854
|
+
only when a re-run against an instance demonstrates it, and the evidence
|
|
855
|
+
is referenced. No entry is removed, softened or silently reworded. If you
|
|
856
|
+
find a limitation that is not listed, that omission is itself worth
|
|
857
|
+
reporting (§5.6).
|