From 2b4467d3151cdfc3d023766e8a117009898bf826 Mon Sep 17 00:00:00 2001 From: Alex Maldonado Date: Thu, 15 Oct 2026 07:53:27 -0400 Subject: [PATCH] feat: install tools/oracle as the paperspec package Other repositories (paperstack, then paperpack) import the reference implementation instead of reimplementing the specification; scienting-core will take the role later behind the same vectors. - pyproject.toml: distribution and import name `paperspec`, version 0.1.0, Python >= 3.12, no base dependencies; `tools/oracle` is the package, and `schemas/`, `sql/` and `vectors/` ship as the data subpackages `paperspec.schemas`, `paperspec.sql` and `paperspec.vectors`. Extras `schema`, `crypto` and `tools`. - tools/oracle/__init__.py: the Python 3.14 and cryptography/jsonschema gate runs only when imported as `tools.oracle` by the repository's own tools. - crypto.py imports `cryptography` on first use of HKDF and Ed25519, with an ImportError naming `paperspec[crypto]`; schema.py imports `jsonschema` and `referencing` in `Store.validator` and `Store.registry`. package.py, db.py and receipts.py then import on a bare interpreter. - pixi.toml installs the package editable (pixi.lock re-solved; no conda pin changed), so the tools and the package resolve to the same code. - tools/README.md names the library surface and the extras; CHANGELOG.md records the packaging change under 0.1.0, since check.py requires the changelog's first heading to equal the format version. No computed value, vector, fixture, schema or SQL file changed. Co-Authored-By: Claude Fable 5.1 --- CHANGELOG.md | 9 +++ pixi.lock | 167 +++++++++++++++++++++++++++++++++++++++ pixi.toml | 5 ++ pyproject.toml | 49 ++++++++++++ tools/README.md | 7 +- tools/oracle/__init__.py | 24 ++++-- tools/oracle/crypto.py | 34 +++++--- tools/oracle/schema.py | 8 +- 8 files changed, 279 insertions(+), 24 deletions(-) create mode 100644 pyproject.toml diff --git a/CHANGELOG.md b/CHANGELOG.md index d872cd0..949ced4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,3 +19,12 @@ The first release. - Guides for newcomers: an overview, the concepts, a walkthrough of the example paper, runnable examples, the rationale, a FAQ and a glossary. - Tools that build and check everything, validate packages and zipped packages and run proposed corrections through the gate, load packages into SQLite or a bundle, and stress the reference implementation with random inputs. - A pixi workspace pinned to Python 3.14, with tasks for checking, building, linting, validating and loading. + +### 0.1.1 + +Packaging and lazy imports only; the formats stay at 0.1.0, and no computed value, vector, fixture, schema or SQL file changed. + +- `tools/oracle` installs as the Python package `paperspec` (`pyproject.toml`, setuptools), with `schemas/`, `sql/` and `vectors/` as the data subpackages `paperspec.schemas`, `paperspec.sql` and `paperspec.vectors`, so other repositories import the reference implementation instead of reimplementing it. +- The base package has no dependencies and runs on Python 3.12; the extras `paperspec[schema]` (`jsonschema`, `referencing`), `paperspec[crypto]` (`cryptography`) and `paperspec[tools]` (both) cover `schema` and the HKDF and Ed25519 functions of `crypto`, which now import their packages on first use. +- The Python 3.14 and package gate in `tools/oracle/__init__.py` runs only when the repository's own tools import it as `tools.oracle`. +- The pixi workspace installs the package editable, so the tools and the package resolve to the same code. diff --git a/pixi.lock b/pixi.lock index d062685..821e626 100644 --- a/pixi.lock +++ b/pixi.lock @@ -26,6 +26,8 @@ environments: default: channels: - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda @@ -74,6 +76,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - pypi: ./ linux-aarch64: - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-20_gnu.conda - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/bzip2-1.0.8-h4777abc_10.conda @@ -121,6 +124,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - pypi: ./ osx-64: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda @@ -163,6 +167,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-2026.9.1-py314h3cfb53e_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-ha6c374e_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-hbc1a06c_7.conda + - pypi: ./ osx-arm64: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda @@ -205,6 +210,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-2026.9.1-py314hc05cd11_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hf451053_7.conda + - pypi: ./ packages: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda build_number: 20 @@ -217,6 +223,7 @@ packages: - openmp_impl <0.0a0 license: BSD-3-Clause license_family: BSD + purls: [] run_exports: strong: - _openmp_mutex >=4.5 @@ -230,6 +237,7 @@ packages: - libgcc >=14 license: bzip2-1.0.6 license_family: BSD + purls: [] run_exports: weak: - bzip2 >=1.0.8,<2.0a0 @@ -243,6 +251,7 @@ packages: - __glibc >=2.17,<3.0.a0 license: MIT license_family: MIT + purls: [] run_exports: weak: - c-ares >=1.34.8,<2.0a0 @@ -260,6 +269,8 @@ packages: - python_abi 3.14.* *_cp314 license: MIT license_family: MIT + purls: + - pkg:pypi/cffi?source=compressed-mapping run_exports: {} size: 307262 timestamp: 1788485305536 @@ -278,6 +289,8 @@ packages: constrains: - __glibc >=2.17 license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT + purls: + - pkg:pypi/cryptography?source=compressed-mapping run_exports: {} size: 1830947 timestamp: 1790790516832 @@ -290,6 +303,7 @@ packages: - libgcc >=14 license: MIT license_family: MIT + purls: [] run_exports: weak: - icu >=78.3,<79.0a0 @@ -305,6 +319,7 @@ packages: - binutils_impl_linux-64 2.46.1 license: GPL-3.0-only license_family: GPL + purls: [] run_exports: {} size: 742109 timestamp: 1791565468810 @@ -320,6 +335,7 @@ packages: - libabseil-static =20260526.0=cxx17* license: Apache-2.0 license_family: Apache + purls: [] run_exports: weak: - libabseil >=20260526.0,<20260527.0a0 @@ -334,6 +350,7 @@ packages: - libgcc >=15 license: MIT license_family: MIT + purls: [] run_exports: weak: - libbrotlicommon >=1.2.0,<1.3.0a0 @@ -348,6 +365,7 @@ packages: - libgcc >=15 license: MIT license_family: MIT + purls: [] run_exports: weak: - libbrotlidec >=1.2.0,<1.3.0a0 @@ -362,6 +380,7 @@ packages: - libgcc >=15 license: MIT license_family: MIT + purls: [] run_exports: weak: - libbrotlienc >=1.2.0,<1.3.0a0 @@ -375,6 +394,7 @@ packages: - __glibc >=2.17,<3.0.a0 license: BSD-2-Clause OR GPL-2.0-or-later license_family: GPL + purls: [] run_exports: weak: - libev >=4.33,<4.34.0a0 @@ -390,6 +410,7 @@ packages: - expat 2.8.5.* license: MIT license_family: MIT + purls: [] run_exports: {} size: 77893 timestamp: 1790346247149 @@ -401,6 +422,7 @@ packages: - libgcc >=15 license: MIT license_family: MIT + purls: [] run_exports: weak: - libffi >=3.7.0,<3.8.0a0 @@ -417,6 +439,7 @@ packages: - libgomp 16.2.0 he0feb66_7 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL + purls: [] run_exports: {} size: 1058444 timestamp: 1790303109576 @@ -427,6 +450,7 @@ packages: - __glibc >=2.17,<3.0.a0 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL + purls: [] run_exports: strong: - _openmp_mutex >=4.5 @@ -441,6 +465,7 @@ packages: constrains: - xz 5.8.3.* license: 0BSD + purls: [] run_exports: weak: - liblzma >=5.8.3,<6.0a0 @@ -454,6 +479,7 @@ packages: - libgcc >=14 license: BSD-2-Clause license_family: BSD + purls: [] run_exports: {} size: 92759 timestamp: 1786650399772 @@ -471,6 +497,7 @@ packages: - openssl >=3.5.7,<4.0a0 license: MIT license_family: MIT + purls: [] run_exports: weak: - libnghttp2 >=1.68.1,<2.0a0 @@ -485,6 +512,7 @@ packages: - libgcc >=15 - libstdcxx >=15 license: Python-2.0 + purls: [] run_exports: weak: - libpython >=3.14,<4.0a0 @@ -499,6 +527,7 @@ packages: - libgcc >=15 - libzlib >=1.3.2,<2.0a0 license: blessing + purls: [] run_exports: weak: - libsqlite >=3.54.0,<4.0a0 @@ -514,6 +543,7 @@ packages: - libstdcxx-ng ==16.2.0=*_7 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL + purls: [] run_exports: {} size: 6615436 timestamp: 1790303132935 @@ -525,6 +555,7 @@ packages: - __glibc >=2.17,<3.0.a0 license: BSD-3-Clause license_family: BSD + purls: [] run_exports: weak: - libuuid >=2.42.4,<3.0a0 @@ -538,6 +569,7 @@ packages: - __glibc >=2.17,<3.0.a0 license: MIT license_family: MIT + purls: [] run_exports: weak: - libuv >=1.52.1,<2.0a0 @@ -552,6 +584,7 @@ packages: - zlib 1.3.2 *_3 license: Zlib license_family: Other + purls: [] run_exports: weak: - libzlib >=1.3.2,<2.0a0 @@ -564,6 +597,7 @@ packages: - __glibc >=2.17,<3.0.a0 - libgcc >=14 license: X11 AND BSD-3-Clause + purls: [] run_exports: weak: - ncurses >=6.6,<7.0a0 @@ -591,6 +625,7 @@ packages: - libbrotlidec >=1.2.0,<1.3.0a0 license: MIT license_family: MIT + purls: [] run_exports: weak: - nodejs >=26.10.0,<27.0a0 @@ -605,6 +640,7 @@ packages: - libgcc >=15 license: Apache-2.0 license_family: Apache + purls: [] run_exports: weak: - openssl >=3.6.5,<4.0a0 @@ -635,6 +671,7 @@ packages: - tzdata - zstd >=1.5.7,<1.6.0a0 license: Python-2.0 + purls: [] run_exports: weak: - python_abi 3.14.* *_cp314 @@ -652,6 +689,7 @@ packages: - ncurses >=6.6,<7.0a0 license: GPL-3.0-only license_family: GPL + purls: [] run_exports: weak: - readline >=8.3,<9.0a0 @@ -669,6 +707,8 @@ packages: - __glibc >=2.17 license: MIT license_family: MIT + purls: + - pkg:pypi/rpds-py?source=compressed-mapping run_exports: {} size: 302823 timestamp: 1791202484532 @@ -683,6 +723,7 @@ packages: constrains: - xorg-libx11 >=1.8.13,<2.0a0 license: TCL + purls: [] run_exports: weak: - tk >=8.6.13,<8.7.0a0 @@ -696,6 +737,7 @@ packages: - libzlib >=1.3.2,<2.0a0 license: BSD-3-Clause license_family: BSD + purls: [] run_exports: weak: - zstd >=1.5.7,<1.6.0a0 @@ -711,6 +753,7 @@ packages: - openmp_impl <0.0a0 license: BSD-3-Clause license_family: BSD + purls: [] run_exports: strong: - _openmp_mutex >=4.5 @@ -723,6 +766,7 @@ packages: - libgcc >=14 license: bzip2-1.0.6 license_family: BSD + purls: [] run_exports: weak: - bzip2 >=1.0.8,<2.0a0 @@ -735,6 +779,7 @@ packages: - libgcc >=15 license: MIT license_family: MIT + purls: [] run_exports: weak: - c-ares >=1.34.8,<2.0a0 @@ -751,6 +796,8 @@ packages: - python_abi 3.14.* *_cp314 license: MIT license_family: MIT + purls: + - pkg:pypi/cffi?source=compressed-mapping run_exports: {} size: 329121 timestamp: 1788485263944 @@ -768,6 +815,8 @@ packages: constrains: - __glibc >=2.17 license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT + purls: + - pkg:pypi/cryptography?source=compressed-mapping run_exports: {} size: 1795185 timestamp: 1790790512228 @@ -779,6 +828,7 @@ packages: - libstdcxx >=14 license: MIT license_family: MIT + purls: [] run_exports: weak: - icu >=78.3,<79.0a0 @@ -793,6 +843,7 @@ packages: - binutils_impl_linux-aarch64 2.46.1 license: GPL-3.0-only license_family: GPL + purls: [] run_exports: {} size: 905349 timestamp: 1791565559147 @@ -807,6 +858,7 @@ packages: - libabseil-static =20260526.0=cxx17* license: Apache-2.0 license_family: Apache + purls: [] run_exports: weak: - libabseil >=20260526.0,<20260527.0a0 @@ -820,6 +872,7 @@ packages: - libgcc >=15 license: MIT license_family: MIT + purls: [] run_exports: weak: - libbrotlicommon >=1.2.0,<1.3.0a0 @@ -833,6 +886,7 @@ packages: - libgcc >=15 license: MIT license_family: MIT + purls: [] run_exports: weak: - libbrotlidec >=1.2.0,<1.3.0a0 @@ -846,6 +900,7 @@ packages: - libgcc >=15 license: MIT license_family: MIT + purls: [] run_exports: weak: - libbrotlienc >=1.2.0,<1.3.0a0 @@ -858,6 +913,7 @@ packages: - libgcc >=14 license: BSD-2-Clause OR GPL-2.0-or-later license_family: GPL + purls: [] run_exports: weak: - libev >=4.33,<4.34.0a0 @@ -872,6 +928,7 @@ packages: - expat 2.8.5.* license: MIT license_family: MIT + purls: [] run_exports: {} size: 79463 timestamp: 1790346233159 @@ -882,6 +939,7 @@ packages: - libgcc >=15 license: MIT license_family: MIT + purls: [] run_exports: weak: - libffi >=3.7.0,<3.8.0a0 @@ -897,6 +955,7 @@ packages: - libgomp 16.2.0 h8acb6b2_7 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL + purls: [] run_exports: {} size: 627793 timestamp: 1790302345214 @@ -905,6 +964,7 @@ packages: md5: 259ded79ac497cfbcf5434540e8b81b6 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL + purls: [] run_exports: strong: - _openmp_mutex >=4.5 @@ -918,6 +978,7 @@ packages: constrains: - xz 5.8.3.* license: 0BSD + purls: [] run_exports: weak: - liblzma >=5.8.3,<6.0a0 @@ -930,6 +991,7 @@ packages: - libgcc >=14 license: BSD-2-Clause license_family: BSD + purls: [] run_exports: {} size: 113885 timestamp: 1786650485380 @@ -946,6 +1008,7 @@ packages: - openssl >=3.5.7,<4.0a0 license: MIT license_family: MIT + purls: [] run_exports: weak: - libnghttp2 >=1.68.1,<2.0a0 @@ -959,6 +1022,7 @@ packages: - libgcc >=15 - libstdcxx >=15 license: Python-2.0 + purls: [] run_exports: weak: - libpython >=3.14,<4.0a0 @@ -972,6 +1036,7 @@ packages: - libgcc >=15 - libzlib >=1.3.2,<2.0a0 license: blessing + purls: [] run_exports: weak: - libsqlite >=3.54.0,<4.0a0 @@ -986,6 +1051,7 @@ packages: - libstdcxx-ng ==16.2.0=*_7 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL + purls: [] run_exports: {} size: 6236630 timestamp: 1790302364054 @@ -996,6 +1062,7 @@ packages: - libgcc >=15 license: BSD-3-Clause license_family: BSD + purls: [] run_exports: weak: - libuuid >=2.42.4,<3.0a0 @@ -1008,6 +1075,7 @@ packages: - libgcc >=14 license: MIT license_family: MIT + purls: [] run_exports: weak: - libuv >=1.52.1,<2.0a0 @@ -1020,6 +1088,7 @@ packages: - zlib 1.3.2 *_3 license: Zlib license_family: Other + purls: [] run_exports: weak: - libzlib >=1.3.2,<2.0a0 @@ -1031,6 +1100,7 @@ packages: depends: - libgcc >=14 license: X11 AND BSD-3-Clause + purls: [] run_exports: weak: - ncurses >=6.6,<7.0a0 @@ -1058,6 +1128,7 @@ packages: - libzlib >=1.3.2,<2.0a0 license: MIT license_family: MIT + purls: [] run_exports: weak: - nodejs >=26.10.0,<27.0a0 @@ -1071,6 +1142,7 @@ packages: - libgcc >=15 license: Apache-2.0 license_family: Apache + purls: [] run_exports: weak: - openssl >=3.6.5,<4.0a0 @@ -1100,6 +1172,7 @@ packages: - tzdata - zstd >=1.5.7,<1.6.0a0 license: Python-2.0 + purls: [] run_exports: weak: - python_abi 3.14.* *_cp314 @@ -1116,6 +1189,7 @@ packages: - ncurses >=6.6,<7.0a0 license: GPL-3.0-only license_family: GPL + purls: [] run_exports: weak: - readline >=8.3,<9.0a0 @@ -1132,6 +1206,8 @@ packages: - __glibc >=2.17 license: MIT license_family: MIT + purls: + - pkg:pypi/rpds-py?source=hash-mapping run_exports: {} size: 297482 timestamp: 1791202301155 @@ -1145,6 +1221,7 @@ packages: constrains: - xorg-libx11 >=1.8.13,<2.0a0 license: TCL + purls: [] run_exports: weak: - tk >=8.6.13,<8.7.0a0 @@ -1157,6 +1234,7 @@ packages: - libzlib >=1.3.2,<2.0a0 license: BSD-3-Clause license_family: BSD + purls: [] run_exports: weak: - zstd >=1.5.7,<1.6.0a0 @@ -1170,6 +1248,7 @@ packages: - python-gil license: MIT license_family: MIT + purls: [] run_exports: {} size: 8144 timestamp: 1784221492234 @@ -1181,6 +1260,8 @@ packages: - python license: MIT license_family: MIT + purls: + - pkg:pypi/attrs?source=hash-mapping run_exports: {} size: 64927 timestamp: 1773935801332 @@ -1190,6 +1271,7 @@ packages: depends: - __unix license: ISC + purls: [] run_exports: {} size: 131780 timestamp: 1784754889428 @@ -1201,6 +1283,7 @@ packages: - python >=3.14,<3.15.0a0 - python_abi * *_cp314 license: Python-2.0 + purls: [] run_exports: {} size: 51745 timestamp: 1791826571351 @@ -1216,6 +1299,8 @@ packages: - python license: MIT license_family: MIT + purls: + - pkg:pypi/jsonschema?source=compressed-mapping run_exports: {} size: 82084 timestamp: 1787579299493 @@ -1228,6 +1313,8 @@ packages: - python license: MIT license_family: MIT + purls: + - pkg:pypi/jsonschema-specifications?source=hash-mapping run_exports: {} size: 19236 timestamp: 1757335715225 @@ -1238,6 +1325,7 @@ packages: - nodejs license: MIT license_family: MIT + purls: [] run_exports: {} size: 2246247 timestamp: 1789995027396 @@ -1249,6 +1337,8 @@ packages: - python license: BSD-3-Clause license_family: BSD + purls: + - pkg:pypi/pycparser?source=compressed-mapping run_exports: {} size: 58624 timestamp: 1791592610066 @@ -1259,6 +1349,7 @@ packages: - cpython 3.14.8.* - python_abi * *_cp314 license: Python-2.0 + purls: [] run_exports: {} size: 51714 timestamp: 1791826718684 @@ -1270,6 +1361,7 @@ packages: - python 3.14.* *_cp314 license: BSD-3-Clause license_family: BSD + purls: [] run_exports: {} size: 6791 timestamp: 1788302824440 @@ -1284,6 +1376,8 @@ packages: - python license: MIT license_family: MIT + purls: + - pkg:pypi/referencing?source=hash-mapping run_exports: {} size: 51788 timestamp: 1760379115194 @@ -1295,6 +1389,8 @@ packages: - python license: PSF-2.0 license_family: PSF + purls: + - pkg:pypi/typing-extensions?source=hash-mapping run_exports: {} size: 52631 timestamp: 1783002732887 @@ -1302,6 +1398,7 @@ packages: sha256: b928c30ddcb0e3f544c6eade8352737e6e610e263276b90232db6a578ef899d8 md5: fcb489df604d100968b737f2cb6076c6 license: LicenseRef-Public-Domain + purls: [] run_exports: {} size: 118849 timestamp: 1784250406640 @@ -1312,6 +1409,7 @@ packages: - __osx >=11.0 license: bzip2-1.0.6 license_family: BSD + purls: [] run_exports: weak: - bzip2 >=1.0.8,<2.0a0 @@ -1324,6 +1422,7 @@ packages: - __osx >=11.0 license: MIT license_family: MIT + purls: [] run_exports: weak: - c-ares >=1.34.8,<2.0a0 @@ -1340,6 +1439,8 @@ packages: - python_abi 3.14.* *_cp314 license: MIT license_family: MIT + purls: + - pkg:pypi/cffi?source=compressed-mapping run_exports: {} size: 292318 timestamp: 1788485600231 @@ -1357,6 +1458,8 @@ packages: constrains: - __osx >=11.0 license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT + purls: + - pkg:pypi/cryptography?source=compressed-mapping run_exports: {} size: 1819278 timestamp: 1790790581781 @@ -1367,6 +1470,7 @@ packages: - __osx >=11.0 license: MIT license_family: MIT + purls: [] run_exports: weak: - icu >=78.3,<79.0a0 @@ -1383,6 +1487,7 @@ packages: - libabseil-static =20260526.0=cxx17* license: Apache-2.0 license_family: Apache + purls: [] run_exports: weak: - libabseil >=20260526.0,<20260527.0a0 @@ -1396,6 +1501,7 @@ packages: - __osx >=11.0 license: MIT license_family: MIT + purls: [] run_exports: weak: - libbrotlicommon >=1.2.0,<1.3.0a0 @@ -1409,6 +1515,7 @@ packages: - libbrotlicommon 1.2.0 he0616a4_4 license: MIT license_family: MIT + purls: [] run_exports: weak: - libbrotlidec >=1.2.0,<1.3.0a0 @@ -1422,6 +1529,7 @@ packages: - libbrotlicommon 1.2.0 he0616a4_4 license: MIT license_family: MIT + purls: [] run_exports: weak: - libbrotlienc >=1.2.0,<1.3.0a0 @@ -1434,6 +1542,7 @@ packages: - __osx >=11.0 license: Apache-2.0 WITH LLVM-exception license_family: Apache + purls: [] run_exports: {} size: 573303 timestamp: 1791337833929 @@ -1444,6 +1553,7 @@ packages: - __osx >=11.0 license: BSD-2-Clause OR GPL-2.0-or-later license_family: GPL + purls: [] run_exports: weak: - libev >=4.33,<4.34.0a0 @@ -1458,6 +1568,7 @@ packages: - expat 2.8.5.* license: MIT license_family: MIT + purls: [] run_exports: {} size: 75347 timestamp: 1790370393156 @@ -1468,6 +1579,7 @@ packages: - __osx >=11.0 license: MIT license_family: MIT + purls: [] run_exports: weak: - libffi >=3.7.0,<3.8.0a0 @@ -1481,6 +1593,7 @@ packages: constrains: - xz 5.8.3.* license: 0BSD + purls: [] run_exports: weak: - liblzma >=5.8.3,<6.0a0 @@ -1493,6 +1606,7 @@ packages: - __osx >=11.0 license: BSD-2-Clause license_family: BSD + purls: [] run_exports: {} size: 79639 timestamp: 1786651070313 @@ -1509,6 +1623,7 @@ packages: - openssl >=3.5.7,<4.0a0 license: MIT license_family: MIT + purls: [] run_exports: weak: - libnghttp2 >=1.68.1,<2.0a0 @@ -1522,6 +1637,7 @@ packages: - __osx >=11.0 - libcxx >=20 license: Python-2.0 + purls: [] run_exports: weak: - libpython >=3.14,<4.0a0 @@ -1535,6 +1651,7 @@ packages: - icu >=78.3,<79.0a0 - libzlib >=1.3.2,<2.0a0 license: blessing + purls: [] run_exports: weak: - libsqlite >=3.54.0,<4.0a0 @@ -1547,6 +1664,7 @@ packages: - __osx >=11.0 license: MIT license_family: MIT + purls: [] run_exports: weak: - libuv >=1.52.1,<2.0a0 @@ -1561,6 +1679,7 @@ packages: - zlib 1.3.2 *_3 license: Zlib license_family: Other + purls: [] run_exports: weak: - libzlib >=1.3.2,<2.0a0 @@ -1572,6 +1691,7 @@ packages: depends: - __osx >=11.0 license: X11 AND BSD-3-Clause + purls: [] run_exports: weak: - ncurses >=6.6,<7.0a0 @@ -1598,6 +1718,7 @@ packages: - icu >=78.3,<79.0a0 license: MIT license_family: MIT + purls: [] run_exports: weak: - nodejs >=26.10.0,<27.0a0 @@ -1611,6 +1732,7 @@ packages: - ca-certificates license: Apache-2.0 license_family: Apache + purls: [] run_exports: weak: - openssl >=3.6.5,<4.0a0 @@ -1638,6 +1760,7 @@ packages: - tzdata - zstd >=1.5.7,<1.6.0a0 license: Python-2.0 + purls: [] run_exports: weak: - python_abi 3.14.* *_cp314 @@ -1654,6 +1777,7 @@ packages: - ncurses >=6.6,<7.0a0 license: GPL-3.0-only license_family: GPL + purls: [] run_exports: weak: - readline >=8.3,<9.0a0 @@ -1670,6 +1794,8 @@ packages: - __osx >=11.0 license: MIT license_family: MIT + purls: + - pkg:pypi/rpds-py?source=hash-mapping run_exports: {} size: 302047 timestamp: 1791202491493 @@ -1680,6 +1806,7 @@ packages: - __osx >=11.0 - libzlib >=1.3.2,<2.0a0 license: TCL + purls: [] run_exports: weak: - tk >=8.6.13,<8.7.0a0 @@ -1693,6 +1820,7 @@ packages: - libzlib >=1.3.2,<2.0a0 license: BSD-3-Clause license_family: BSD + purls: [] run_exports: weak: - zstd >=1.5.7,<1.6.0a0 @@ -1705,6 +1833,7 @@ packages: - __osx >=11.0 license: bzip2-1.0.6 license_family: BSD + purls: [] run_exports: weak: - bzip2 >=1.0.8,<2.0a0 @@ -1717,6 +1846,7 @@ packages: - __osx >=11.0 license: MIT license_family: MIT + purls: [] run_exports: weak: - c-ares >=1.34.8,<2.0a0 @@ -1733,6 +1863,8 @@ packages: - python_abi 3.14.* *_cp314 license: MIT license_family: MIT + purls: + - pkg:pypi/cffi?source=compressed-mapping run_exports: {} size: 292373 timestamp: 1788485305495 @@ -1750,6 +1882,8 @@ packages: constrains: - __osx >=11.0 license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT + purls: + - pkg:pypi/cryptography?source=compressed-mapping run_exports: {} size: 1770710 timestamp: 1790790523277 @@ -1760,6 +1894,7 @@ packages: - __osx >=11.0 license: MIT license_family: MIT + purls: [] run_exports: weak: - icu >=78.3,<79.0a0 @@ -1776,6 +1911,7 @@ packages: - libabseil-static =20260526.0=cxx17* license: Apache-2.0 license_family: Apache + purls: [] run_exports: weak: - libabseil >=20260526.0,<20260527.0a0 @@ -1789,6 +1925,7 @@ packages: - __osx >=11.0 license: MIT license_family: MIT + purls: [] run_exports: weak: - libbrotlicommon >=1.2.0,<1.3.0a0 @@ -1802,6 +1939,7 @@ packages: - libbrotlicommon 1.2.0 h1dcdb26_4 license: MIT license_family: MIT + purls: [] run_exports: weak: - libbrotlidec >=1.2.0,<1.3.0a0 @@ -1815,6 +1953,7 @@ packages: - libbrotlicommon 1.2.0 h1dcdb26_4 license: MIT license_family: MIT + purls: [] run_exports: weak: - libbrotlienc >=1.2.0,<1.3.0a0 @@ -1827,6 +1966,7 @@ packages: - __osx >=11.0 license: Apache-2.0 WITH LLVM-exception license_family: Apache + purls: [] run_exports: {} size: 577501 timestamp: 1791337239735 @@ -1837,6 +1977,7 @@ packages: - __osx >=11.0 license: BSD-2-Clause OR GPL-2.0-or-later license_family: GPL + purls: [] run_exports: weak: - libev >=4.33,<4.34.0a0 @@ -1851,6 +1992,7 @@ packages: - expat 2.8.5.* license: MIT license_family: MIT + purls: [] run_exports: {} size: 69565 timestamp: 1790370026495 @@ -1861,6 +2003,7 @@ packages: - __osx >=11.0 license: MIT license_family: MIT + purls: [] run_exports: weak: - libffi >=3.7.0,<3.8.0a0 @@ -1874,6 +2017,7 @@ packages: constrains: - xz 5.8.3.* license: 0BSD + purls: [] run_exports: weak: - liblzma >=5.8.3,<6.0a0 @@ -1886,6 +2030,7 @@ packages: - __osx >=11.0 license: BSD-2-Clause license_family: BSD + purls: [] run_exports: {} size: 73289 timestamp: 1786651074391 @@ -1902,6 +2047,7 @@ packages: - openssl >=3.5.7,<4.0a0 license: MIT license_family: MIT + purls: [] run_exports: weak: - libnghttp2 >=1.68.1,<2.0a0 @@ -1915,6 +2061,7 @@ packages: - __osx >=11.0 - libcxx >=20 license: Python-2.0 + purls: [] run_exports: weak: - libpython >=3.14,<4.0a0 @@ -1928,6 +2075,7 @@ packages: - icu >=78.3,<79.0a0 - libzlib >=1.3.2,<2.0a0 license: blessing + purls: [] run_exports: weak: - libsqlite >=3.54.0,<4.0a0 @@ -1940,6 +2088,7 @@ packages: - __osx >=11.0 license: MIT license_family: MIT + purls: [] run_exports: weak: - libuv >=1.52.1,<2.0a0 @@ -1954,6 +2103,7 @@ packages: - zlib 1.3.2 *_3 license: Zlib license_family: Other + purls: [] run_exports: weak: - libzlib >=1.3.2,<2.0a0 @@ -1965,6 +2115,7 @@ packages: depends: - __osx >=11.0 license: X11 AND BSD-3-Clause + purls: [] run_exports: weak: - ncurses >=6.6,<7.0a0 @@ -1991,6 +2142,7 @@ packages: - libabseil * cxx17* license: MIT license_family: MIT + purls: [] run_exports: weak: - nodejs >=26.10.0,<27.0a0 @@ -2004,6 +2156,7 @@ packages: - ca-certificates license: Apache-2.0 license_family: Apache + purls: [] run_exports: weak: - openssl >=3.6.5,<4.0a0 @@ -2031,6 +2184,7 @@ packages: - tzdata - zstd >=1.5.7,<1.6.0a0 license: Python-2.0 + purls: [] run_exports: weak: - python_abi 3.14.* *_cp314 @@ -2047,6 +2201,7 @@ packages: - ncurses >=6.6,<7.0a0 license: GPL-3.0-only license_family: GPL + purls: [] run_exports: weak: - readline >=8.3,<9.0a0 @@ -2063,6 +2218,8 @@ packages: - __osx >=11.0 license: MIT license_family: MIT + purls: + - pkg:pypi/rpds-py?source=compressed-mapping run_exports: {} size: 286096 timestamp: 1791202353806 @@ -2073,6 +2230,7 @@ packages: - __osx >=11.0 - libzlib >=1.3.2,<2.0a0 license: TCL + purls: [] run_exports: weak: - tk >=8.6.13,<8.7.0a0 @@ -2086,8 +2244,17 @@ packages: - libzlib >=1.3.2,<2.0a0 license: BSD-3-Clause license_family: BSD + purls: [] run_exports: weak: - zstd >=1.5.7,<1.6.0a0 size: 433687 timestamp: 1786599629846 +- pypi: ./ + name: paperspec + requires_dist: + - jsonschema>=4.26 ; extra == 'schema' + - referencing>=0.35 ; extra == 'schema' + - cryptography>=46 ; extra == 'crypto' + - paperspec[crypto,schema] ; extra == 'tools' + requires_python: '>=3.12' diff --git a/pixi.toml b/pixi.toml index b9f51ed..74f869b 100644 --- a/pixi.toml +++ b/pixi.toml @@ -20,6 +20,11 @@ markdownlint-cli2 = ">=0.23.3" nodejs = ">=22" cryptography = ">=46" +# The tools import the oracle as `tools.oracle`; other programs install it as `paperspec`. The editable +# self-install makes both resolve to the same code. +[pypi-dependencies] +paperspec = { path = ".", editable = true } + [tasks] check = { cmd = "python tools/check.py", depends-on = [ "lint", diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ac5818c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,49 @@ +[build-system] +build-backend = "setuptools.build_meta" + +requires = ["setuptools>=77"] + +[project] +name = "paperspec" +version = "0.1.0" +description = "The scienting formats: the reference implementation of every rule, with the JSON Schemas, SQL and test vectors as package data" +readme = "README.md" +license = "Apache-2.0" +license-files = ["LICENSE.md"] +authors = [{ name = "Scientific Computing Studio", email = "maintainer@scient.ing" }] +requires-python = ">=3.12" +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Science/Research", + "Programming Language :: Python :: 3 :: Only", + "Topic :: Scientific/Engineering", +] +# The base package is standard library only. `schema.py` needs jsonschema and +# `crypto.py` needs cryptography; the repository's own tools need both. +dependencies = [] + +[project.optional-dependencies] +schema = ["jsonschema>=4.26", "referencing>=0.35"] +crypto = ["cryptography>=46"] +tools = ["paperspec[crypto,schema]"] + +[project.urls] +Homepage = "https://paperspec.scient.ing" +Repository = "https://git.scient.ing/infra/paperspec" +Documentation = "https://paperspec.scient.ing/docs/" + +# `tools/oracle` is the package `paperspec`; the schemas, SQL and vectors travel +# with it as data subpackages, so a consumer reads them with importlib.resources. +[tool.setuptools] +packages = ["paperspec", "paperspec.schemas", "paperspec.sql", "paperspec.vectors"] + +[tool.setuptools.package-dir] +"paperspec" = "tools/oracle" +"paperspec.schemas" = "schemas" +"paperspec.sql" = "sql" +"paperspec.vectors" = "vectors" + +[tool.setuptools.package-data] +"paperspec.schemas" = ["*.json"] +"paperspec.sql" = ["*.sql"] +"paperspec.vectors" = ["*.jsonl"] diff --git a/tools/README.md b/tools/README.md index 7a3a7dc..9ad9832 100644 --- a/tools/README.md +++ b/tools/README.md @@ -67,5 +67,8 @@ Hashes are kept incrementally. A node's five values depend only on its own subtree, so after a correction the oracle rehashes only the nodes that changed and their ancestors, and a container's projections are its children's cached projections joined. `check.py` compares the result after every correction with a slow computation that projects each node from its whole subtree. -Do not build on it as a library. -Implementations follow the [specification](../spec/README.md) and test themselves against the [vectors](../vectors/README.md). +Other repositories of the project do not reimplement the specification: they install this repository as the Python package `paperspec` (`pip install .`, or a git pin) and import the oracle, so that every key, identifier, canonical JSON string, package check and database form comes from one piece of code. +The library surface they use is `keys`, `text`, `jcs`, `package`, `db`, `schema` and `rights`, with the schemas, SQL and vectors as package data (`importlib.resources.files("paperspec") / "sql" / "doc.sql"`). +Those modules run on Python 3.12 with the standard library alone, except that `schema` needs `paperspec[schema]` (`jsonschema` and `referencing`) and the HKDF and Ed25519 functions of `crypto` need `paperspec[crypto]`; `paperspec[tools]` installs both. +When `scienting-core` exists it takes this role behind the same vectors, and the import changes without any computed value changing. +Independent implementations follow the [specification](../spec/README.md) and test themselves against the [vectors](../vectors/README.md). diff --git a/tools/oracle/__init__.py b/tools/oracle/__init__.py index 9288a99..6865ba3 100644 --- a/tools/oracle/__init__.py +++ b/tools/oracle/__init__.py @@ -1,13 +1,21 @@ -"""The reference computations behind every expected value in this repository; see tools/README.md.""" +"""The reference computations behind every expected value in this repository; see tools/README.md. + +Imported as `tools.oracle` by the repository's own tools, which need Python 3.14, `cryptography` and +`jsonschema` for everything they check; the gate below exits with a message when one is missing. +Installed and imported as `paperspec` by other programs, the gate is skipped: the standard-library modules +(`keys`, `text`, `jcs`, `package`, `db`, `rights`, ...) run on Python 3.12, and `schema` and `crypto` import +their packages on first use, so `paperspec[schema]` and `paperspec[crypto]` are needed only for those two. +""" import importlib.util import sys -if sys.version_info < (3, 14): - sys.exit("The paperspec tools need Python 3.14 or later; this is Python %d.%d." % sys.version_info[:2]) +if __name__ == "tools.oracle": + if sys.version_info < (3, 14): + sys.exit("The paperspec tools need Python 3.14 or later; this is Python %d.%d." % sys.version_info[:2]) -_missing = [m for m in ("cryptography", "jsonschema") if importlib.util.find_spec(m) is None] -if _missing: - sys.exit("The paperspec tools need %s %s; run them through pixi, or install %s with pip." - % ("the package" if len(_missing) == 1 else "the packages", " and ".join(_missing), - "it" if len(_missing) == 1 else "them")) + _missing = [m for m in ("cryptography", "jsonschema") if importlib.util.find_spec(m) is None] + if _missing: + sys.exit("The paperspec tools need %s %s; run them through pixi, or install %s with pip." + % ("the package" if len(_missing) == 1 else "the packages", " and ".join(_missing), + "it" if len(_missing) == 1 else "them")) diff --git a/tools/oracle/crypto.py b/tools/oracle/crypto.py index aab1c3e..58070e2 100644 --- a/tools/oracle/crypto.py +++ b/tools/oracle/crypto.py @@ -1,22 +1,31 @@ """Keyed hashing, key derivation, Merkle roots and Ed25519. Non-normative; see spec/05-hashes.md and spec/08-fidelity.md. -HKDF and Ed25519 come from the `cryptography` package. +HKDF and Ed25519 come from the `cryptography` package, imported on first use so that the hashing, Merkle +and did:key functions work without it. """ import hashlib -from cryptography.exceptions import InvalidSignature -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey -from cryptography.hazmat.primitives.kdf.hkdf import HKDF - from . import jcs +def _cryptography(): + """The `cryptography` modules HKDF and Ed25519 use: exceptions, hashes, ed25519 and hkdf.""" + try: + from cryptography import exceptions + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.asymmetric import ed25519 + from cryptography.hazmat.primitives.kdf import hkdf + except ImportError: + raise ImportError("HKDF and Ed25519 need the cryptography package: install paperspec[crypto]") from None + return exceptions, hashes, ed25519, hkdf + + def hkdf(ikm, salt, info, length=32): """HKDF-SHA-256, RFC 5869.""" - return HKDF(algorithm=hashes.SHA256(), length=length, salt=salt, info=info).derive(ikm) + _, hashes, _, kdf = _cryptography() + return kdf.HKDF(algorithm=hashes.SHA256(), length=length, salt=salt, info=info).derive(ikm) def team_key(secret): @@ -199,20 +208,23 @@ def verify_consistency(root_m, root_n, m, n, proof): def ed25519_public(secret): """The 32-byte public key of a 32-byte Ed25519 secret.""" - return Ed25519PrivateKey.from_private_bytes(secret).public_key().public_bytes_raw() + _, _, ed25519, _ = _cryptography() + return ed25519.Ed25519PrivateKey.from_private_bytes(secret).public_key().public_bytes_raw() def ed25519_sign(secret, msg): - return Ed25519PrivateKey.from_private_bytes(secret).sign(msg) + _, _, ed25519, _ = _cryptography() + return ed25519.Ed25519PrivateKey.from_private_bytes(secret).sign(msg) def ed25519_verify(public, msg, signature): """True when `signature` is a valid Ed25519 signature of `msg` under `public`; never raises.""" if len(public) != 32 or len(signature) != 64: return False + exceptions, _, ed25519, _ = _cryptography() try: - Ed25519PublicKey.from_public_bytes(public).verify(signature, msg) - except (InvalidSignature, ValueError): + ed25519.Ed25519PublicKey.from_public_bytes(public).verify(signature, msg) + except (exceptions.InvalidSignature, ValueError): return False return True diff --git a/tools/oracle/schema.py b/tools/oracle/schema.py index 7bba4b8..9b00493 100644 --- a/tools/oracle/schema.py +++ b/tools/oracle/schema.py @@ -3,14 +3,12 @@ Non-normative. The spec's schemas are restricted to the keywords below (spec/22-conformance.md) so that every 2020-12 validator reads them alike; `lint` refuses any schema that leaves the subset. +`jsonschema` and `referencing` are imported on first use, so `Store.load`, `deref` and `lint` work without them. """ import json import os -from jsonschema import Draft202012Validator -from referencing import Registry, Resource - KEYWORDS = { "$schema", "$id", "$ref", "$defs", "$comment", "title", "description", "examples", "default", "deprecated", "type", "enum", "const", "properties", "required", "additionalProperties", "patternProperties", @@ -36,6 +34,8 @@ class Store: def registry(self): if self._registry is None: + from referencing import Registry, Resource + names = sorted(f for f in os.listdir(self.root) if f.endswith(".schema.json")) self._registry = Registry().with_resources( [(self.load(f)["$id"], Resource.from_contents(self.load(f))) for f in names]) @@ -43,6 +43,8 @@ class Store: def validator(self, name): if name not in self._validators: + from jsonschema import Draft202012Validator + self._validators[name] = Draft202012Validator(self.load(name), registry=self.registry()) return self._validators[name]