Metadata-Version: 2.4
Name: py-jam-erasure-bindings
Version: 0.1.0
Summary: Native Python bindings for JAM Appendix H erasure coding
Author-email: JAMdot Technologies <devops@jamdot.tech>
License-Expression: GPL-3.0-only
Project-URL: Homepage, https://github.com/JAMdotTech/py-jam-erasure-bindings
Project-URL: Repository, https://github.com/JAMdotTech/py-jam-erasure-bindings
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Rust
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: POSIX :: Linux
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: COPYING
License-File: LICENSES/PyJAMaz-Apache-2.0.txt
Provides-Extra: dev
Requires-Dist: build==1.3.0; extra == "dev"
Requires-Dist: pytest==8.4.2; extra == "dev"
Requires-Dist: twine==6.1.0; extra == "dev"
Dynamic: license-file

# py-jam-erasure-bindings

Native Python bindings for the Graypaper 0.7.2 Appendix H erasure transform.
The package has no Python runtime dependencies and no dependency on PyJAMaz.
It exposes Python functions; no public Rust crate API is supported.

## Install and use

```sh
python -m pip install py-jam-erasure-bindings==0.1.0
```

CPython 3.12, 3.13, and 3.14 use the same `cp312-abi3` binary on each supported
platform: Linux x86_64/ARM64 (manylinux2014) and macOS x86_64/ARM64 (11.0+).
Published wheels need no Rust compiler. Building the source distribution needs
Rust 1.82 or newer and a C toolchain; CI uses Rust 1.85.0.

```python
from jam_erasure import encode_shards, recover_shards

data = b"Appendix H"
shards = encode_shards(data, 2, 4)  # Tiny: 2 originals, 4 recovery shards
padded = recover_shards(2, 4, [(2, shards[2]), (5, shards[5])])
assert padded[:len(data)] == data
assert padded[len(data):] == bytes(len(padded) - len(data))
```

## Binding contract

- `encode_shards(data, original_count, recovery_count) -> list[bytes]` accepts
  the existing PyO3 byte-vector inputs. `data` must be non-empty. Both shard
  counts must be positive and supported by `reed-solomon-simd=3.1.0`.
- Input is zero-padded to a multiple of `2 * original_count`. Output is
  systematic-first: `original_count` contiguous data shards followed by
  `recovery_count` parity shards. Shard size is
  `2 * ceil(len(data) / (2 * original_count))`.
- `recover_shards(original_count, recovery_count, shards) -> bytes` requires
  exactly `original_count` `(global_index, shard_bytes)` pairs. Indexes are
  unique and lie in `0 .. original_count + recovery_count - 1`; order is
  immaterial. Shards must share a non-zero even size. The result includes
  zero padding and does not take an original-length argument.
- Tiny uses `(2, 4)` and Full uses `(342, 681)`. Both may be used in one process;
  there is no ambient profile setting. This low-level API retains its original
  behavior, including accepting other count combinations supported by the backend.
- Native validation/backend failures and caught unwinding panics become
  `ValueError`; PyO3 argument conversion retains `TypeError`/`OverflowError`.
  Computation releases the Python GIL.

Callers own protocol length limits before allocation, empty-blob conventions,
selection of a threshold set from extra shards, original-length/padding checks,
and commitment authentication. In PyJAMaz these remain in `pyjamaz.erasure`
and its availability layer. This binding recovers bytes; recovery alone does
not authenticate them. No shard data is read from disk or the network.

## Specification and evidence

Normative source: [Graypaper 0.7.2](https://github.com/gavofyork/graypaper/tree/v0.7.2),
Appendix H, including H.1–H.3 prose and the referenced little-endian `E2` encoding.
The implementation preserves the existing per-two-byte-column transform;
applying the backend once to multi-word shards changes protocol bytes.

| Rules | Implementation | Evidence |
| --- | --- | --- |
| H.1–H.2; zero padding prose | `encode_impl` splits padded input; `recover_impl` joins systematic data | `test_boundaries_and_padding`, all W3F cases |
| H.3 transposition | Per-word encode/decode loops; Tiny tables derive the identical linear map from the pinned backend | Rust `batched_lanes_match_appendix_h_column_reference`, W3F encoding |
| H.4 systematic-first chunks | `encode_impl` returns original shards then recovery shards | `test_w3f_encoding_vectors` for six Tiny and six Full fixtures |
| H.5 indexed recovery | `recover_impl` validates threshold cardinality/indexes and reconstructs each column | `test_w3f_recovery_vectors` with systematic, recovery-only and mixed selections; malformed-input tests |
| H.6–H.7 field polynomial | Pinned `reed-solomon-simd=3.1.0`, polynomial `0x1002D` | Every W3F encoded byte and recovered input |
| H.8–H.9 Cantor basis and LE words/indexes | Pinned backend plus two-byte slicing and little-endian Tiny tables | W3F vectors and Rust independent per-column comparison |
| H.10–H.11 interpolation and evaluation | Pinned backend encode/decode calls | W3F parity/recovery cases and Rust recovery-only roundtrip |

The complete-systematic fast path, deterministic selection from surplus shards,
and rejection of nonzero reconstructed padding remain caller responsibilities.
All twelve fixtures retain [their source commit and license](tests/fixtures/erasure/SOURCE.md).
No protocol interpretation or compatibility setting is introduced by extraction.

## Build and validate

```sh
python -m venv .venv
.venv/bin/python -m pip install '.[dev]'
cargo test --locked
.venv/bin/python -m pytest -q
.venv/bin/python -m build
.venv/bin/python scripts/check_artifacts.py dist
```

PEP-517 builds use release optimization, thin LTO, one codegen unit, a committed
Cargo lock, and `abi3-py312`; CI sets a portable CPU target. The source
distribution includes the Rust source, Cargo lock, tests, fixture provenance,
and license texts. Fixtures and tests are excluded from the binary wheel.

## Publishing

The release workflow builds all four platform wheels from an sdist and tests
installed binaries outside the checkout on CPython 3.12, 3.13, and 3.14.
A matching `vVERSION` or `pypi/vVERSION` tag publishes only after all seventeen
validation jobs pass. The `pypi` GitHub environment uses the organization secret
`PYPI_API_TOKEN`; it must have permission to publish this PyPI project. Token
values are never committed or printed. Package author/contact metadata is
JAMdot Technologies <devops@jamdot.tech>.

The original GitHub-only `v0.1.0` tag is retained as extraction evidence. The
first PyPI release uses `pypi/v0.1.0`, which includes the corrected author
contact and its newly built artifacts. Do not use the earlier GitHub assets as
PyPI release artifacts. The artifact checker enforces the approved author email.

If publishing fails after successful validation, run `release.yml` manually
with the original `release_run_id` and `release_tag`. Recovery verifies the
repository, workflow, tag commit, all required jobs, and immutable artifact IDs
before downloading and checking the exact artifacts. It does not rebuild or
move tags, and does not silently skip previously uploaded files.

Native upgrades require a new package release, lockfile review, regression
evidence, and an explicit consumer dependency change. Builds use `--locked`
and portable CPU targets. See [PROVENANCE.md](PROVENANCE.md) for source history
and licensing.
