Repository navigation
feat(record): QuantityRecord, the inert interchange form (#82) - #88
Conversation
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>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Hosted evidence for head
Inspected with gh-run-receptor and confirmed with |
There was a problem hiding this comment.
💡 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".
| "factor": float(base_one.magnitude) - offset, | ||
| "offset": offset, |
There was a problem hiding this comment.
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 👍 / 👎.
| def values(self) -> np.ndarray: | ||
| """The recorded values, read-only: they cannot change behind the seal.""" | ||
| return self._values |
There was a problem hiding this comment.
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 👍 / 👎.
| 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) |
There was a problem hiding this comment.
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 👍 / 👎.
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.QuantityRecordBundleseals the many scalars of one document together. The API is provisional until the 1.0 checklist promotes it.Manifest (
qrec/0.3):fieldandkind;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:
to_quantity()is the reader's handshake: field, unit, dimensionality and kind.Form integration.
get_formdetects records.convert()delegates whenever the source or the target is"record".get_value,get_unit,get_dimensionality,change_valueandquantity(..., form="record")work.Diagnostics.
RecordError(PUW-ERR-REC-001) is added to the SMonitor catalog and to the exception contract test.Third parties.
_private/record_reference_reader.pyverifies records using the standard library only.tests/quantity_record_vectors/holds frozen vectors, with SI values written by hand, regenerated bydevtools/generate_quantity_record_vectors.py.Strict JSON. The JSON encoding refuses NaN and infinity, which other languages cannot parse.
base64holds 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
devguide/release_1.0.0_checklist.mdnow lists QuantityRecord as a provisional surface to promote or keep provisional.standards/PYUNITWIZARD_GUIDE.mdgains 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.devguide/pending_proposals/quantity_record.md(statusactive); the index is regenerated.Deferred
Each item needs a consumer case first:
unitsattribute;Checks
ruff format --check .andruff check .python devtools/devguide_index.py --check🤖 Generated with Claude Code