Skip to content

feat(record): QuantityRecord, the inert interchange form (#82) - #88

Merged
dprada merged 3 commits into
mainfrom
feat/quantity-record-82
Sep 24, 2026
Merged

dprada merged 3 commits into
mainfrom
feat/quantity-record-82

Conversation

@dprada

@dprada dprada commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator

Part of #82. Design, alternatives and measurements: #83.

What

QuantityRecord: a new form, "record". It is an inert interchange form: values plus a verified description of their unit, so that a number is never read in a unit it was not written in. QuantityRecordBundle seals the many scalars of one document together. The API is provisional until the 1.0 checklist promotes it.

  • Manifest (qrec/0.3):

    • the canonical unit name (pint's long form, independent of a registry's display format);
    • its SI description (factor, offset, exponents; QUDT semantics);
    • optional field and kind;
    • dtype, shape and blocks.
  • Seal. blake2b-128 over a language-independent canonical manifest and the values' little-endian bytes. Any change made outside the codec fails on read.

  • Reading. A record is refused when:

    • the manifest is missing (there is no default unit);
    • the seal is broken;
    • the unit name disagrees with its SI description beyond 1e-6 (this catches a unit renamed and then resealed).

    to_quantity() is the reader's handshake: field, unit, dimensionality and kind.

  • Form integration.

    • get_form detects records.
    • convert() delegates whenever the source or the target is "record".
    • The dispatch entries make get_value, get_unit, get_dimensionality, change_value and quantity(..., form="record") work.
    • No backend is imported at load time.
  • Diagnostics. RecordError (PUW-ERR-REC-001) is added to the SMonitor catalog and to the exception contract test.

  • Third parties.

    • _private/record_reference_reader.py verifies records using the standard library only.
    • tests/quantity_record_vectors/ holds frozen vectors, with SI values written by hand, regenerated by devtools/generate_quantity_record_vectors.py.
  • Strict JSON. The JSON encoding refuses NaN and infinity, which other languages cannot parse. base64 holds any value.

Why

A unit lost at a boundary gives a wrong number with no error. Cases:

PyUnitWizard owns this topic for MOLI and MolSysSuite (uibcdf/moli#13, uibcdf/molsyssuite#46). The change supersedes devguide/serialization_contract_draft.md: its safety rules are met, and its promotion gate of two real integrations is met by uibcdf/sabueso#32 and uibcdf/molsysmt#240.

Governance

  • Pre-1.0 API freeze. New public surface is admitted only with an explicit update to the RC checklist. devguide/release_1.0.0_checklist.md now lists QuantityRecord as a provisional surface to promote or keep provisional.
  • Canonical guide. standards/PYUNITWIZARD_GUIDE.md gains a short section on storing and exchanging quantities, a clarification that records are not "passports", and a new anti-pattern: handing a standardized value to another tool as a bare number. Component copies will be synchronized by the MolSysSuite guide process.
  • Report. devguide/pending_proposals/quantity_record.md (status active); the index is regenerated.

Deferred

Each item needs a consumer case first:

Checks

  • ruff format --check . and ruff check .
  • Full suite locally: 626 passed, 1 skipped (the baseline was 567). 58 new record tests plus one exception-contract case.
  • Docs built locally with Sphinx; no warnings on the new pages.
  • python devtools/devguide_index.py --check
  • Hosted CI.

🤖 Generated with Claude Code

dprada and others added 3 commits September 24, 2026 02:59
A form, "record", whose objects carry values with a verified description of
their unit: canonical name plus SI factor, offset and exponents, an optional
quantity kind, and a blake2b seal over a language-independent canonical
manifest and the values' little-endian bytes. Reading refuses a missing
manifest (no default unit), a broken seal, or a name that disagrees with its
SI description; to_quantity() is the reader's handshake (field, unit,
dimensionality, kind). QuantityRecordBundle seals many scalars of one document.

The form is registered like "string": get_form detects records, convert()
delegates when the source or target is "record", and the dispatch entries
make get_value, get_unit, get_dimensionality, change_value and
quantity(..., form="record") work. RecordError (PUW-ERR-REC-001) joins the
SMonitor catalog. A standard-library reference reader and frozen test vectors
let third parties verify records without PyUnitWizard.

Provisional API: design and alternatives in #83.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A user guide page (what a record guarantees, bundles, encodings, kinds, and why
it is not a passport), the API reference entries, and a section of the
canonical integration guide on storing and exchanging quantities. The guide's
passport anti-pattern now says why QuantityRecord is not one, and names a
second anti-pattern: handing a standardized value to another tool as a bare
number (uibcdf/molsysviewer#96).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The report for #82 (implemented MVP, deferred scope, measurements, refuted
alternatives, acceptance criteria). The serialization contract draft is marked
superseded: its safety rules are met and its promotion gate is met by
uibcdf/sabueso#32 and uibcdf/molsysmt#240. The 1.0 checklist admits the new
surface as provisional, as the pre-1.0 API freeze requires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-24T09:06:40.079141Z 1635203 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@codecov

codecov Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.33945% with 77 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.18%. Comparing base (a7ad8c5) to head (1635203).
⚠️ Report is 4 commits behind head on main.
✅ All tests successful. No failed tests found.

@dprada

dprada commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator Author

Hosted evidence for head 1635203:

  • CI (run 35978583678): success, 1/1.
  • MolSysSuite policy (run 35978584617): success.
  • CI Full Matrix, dispatched on this branch (run 35978768864): success, 8/8 test jobs on Linux and macOS × Python 3.11, 3.12, 3.13 and 3.14. The 'Report scheduled matrix result' job only runs on schedules and was skipped.

Inspected with gh-run-receptor and confirmed with gh run view.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1635203306

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pyunitwizard/record.py
Comment on lines +159 to +160
"factor": float(base_one.magnitude) - offset,
"offset": offset,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reject nonlinear units before emitting an affine descriptor

For logarithmic Pint units such as dB or dBm, conversion to SI is nonlinear, but this records an affine transform inferred only from magnitudes 0 and 1. The reference reader subsequently applies value * factor + offset, so values other than those sample points produce incorrect SI results; _cross_check repeats the same approximation and therefore still accepts the record. Detect and reject non-affine units, or encode their actual transformation.

Useful? React with 👍 / 👎.

Comment thread pyunitwizard/record.py
Comment on lines +437 to +439
def values(self) -> np.ndarray:
"""The recorded values, read-only: they cannot change behind the seal."""
return self._values

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Return a copy instead of exposing the sealed array

Because _values is an owning NumPy array, callers can run record.values.setflags(write=True) and then modify it despite the initial read-only flag. to_quantity() will return the modified values while digest remains the seal for the original data, silently breaking the record's central integrity guarantee; return a defensive copy or expose storage whose writeability cannot be re-enabled.

Useful? React with 👍 / 👎.

Comment thread pyunitwizard/record.py
Comment on lines +339 to +340
if sum(manifest["blocks"]) != values.size or len(data.get("block_digests", [])) != len(manifest["blocks"]):
raise RecordError(reason="the blocks do not cover the values", field=field)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Normalize malformed block metadata to RecordError

When persisted input has structurally malformed block metadata—for example, "blocks": null or "block_digests": null—the unguarded sum() or len() raises a raw TypeError instead of the documented cataloged RecordError. Validate these fields or translate such failures so callers can reliably handle rejected records through the domain exception contract.

AGENTS.md reference: AGENTS.md:L115-L119

Useful? React with 👍 / 👎.

@dprada
dprada merged commit 1635203 into main Sep 24, 2026
12 checks passed
@dprada
dprada deleted the feat/quantity-record-82 branch September 24, 2026 09:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant