Skip to content

Production review and ownership handoff

This is the 8 September 2026 review record for bearshape 0.1.0rc0. The candidate is independently versioned from beartype and requires beartype>=0.23.0rc0,<0.24. Exact 0.23.0rc0 remains the current required test target until a supported replacement is implemented and validated.

Release readiness is withheld. Native composite-union rollback remains incorrect in one important composition case. The owner selected full composition through supported upstream integration and permits a later beartype candidate; explicitly limited native CuPy static support is accepted. The owner approved merging the reviewed production work on 8 September 2026. PR #31 integrated all reviewed heads into main at df81f00e2f62bda956244e680c980f87db1d4671; its post-merge CI passed all 36 jobs. Repository/publication protections still require configuration, and publication requires separate authorization.

Ownership update, 10 September 2026: the GitHub repository is now beartype/bearshape, and documentation is hosted at https://beartype.github.io/bearshape/. The existing Pages workflow was successfully redeployed after transfer. Repository links in this report now use the new owner; dated validation results and artifact identities remain unchanged. PyPI ownership and trusted-publisher configuration remain unverified.

Purpose and intended users

bearshape checks array shape, dtype and related tree constraints at Python API boundaries using beartype. Its main users are scientific Python developers, model and numerical-library authors, and application teams that want useful runtime failures alongside editor/type-checker support. It complements native NumPy, JAX, Torch and CuPy types while retaining backend operations.

Use it around meaningful function and module boundaries. Strict checks inspect metadata; tree checks visit leaves; Like checks may convert data and allocate or transfer memory. It is not a static shape prover, a replacement array backend, or an untrusted-expression sandbox. Value expressions are developer-authored contracts; permitted attribute access can invoke ordinary Python behavior.

The next evolution should close the integration boundary with beartype and maintain the tested annotation contract. New syntax, additional checker engines and speculative caching are not needed for this release. Zuban is outside the selected support contract. A future addition needs an explicit plan and real positive, negative and inference fixtures.

Observed support

Surface Evidence Boundary
CPython Linux 3.10–3.14; macOS/Windows 3.10 and 3.14 CPU jobs Other interpreter implementations are unverified
beartype Exact 0.23.0rc0 in required lanes and normal installs Native composite-union rollback blocks full compatibility; the metadata range is not proof for future releases
NumPy Locked current and 2.2 floor; strict/Like/scalar/structured/endian/temporal tests Extended precision depends on platform
JAX Locked current and 0.5 floor; jit/vmap/grad numerical and violation cases Checks inside JIT run while tracing; see frameworks
Torch Locked current and 2.6 floor; autograd and compile cases Compile evidence uses backend="eager" with outer Python validation
optree Locked current and 0.14 floor; containers and structure binding Default registry only; no custom namespace API
CuPy 95 tests on H200, CUDA 12.9, CuPy 14.2.0, Python 3.10.20/3.14.3 Other GPU/driver combinations and multiple-device behavior are unverified; native static typing is limited
Static checkers pyright, mypy, ty and pyrefly positive/negative/inference batches on Python 3.10–3.14 and configured floors Dimensions are runtime constraints; advanced syntax and native CuPy limitations are explicit
Minimal install Normally installed wheel with only declared dependencies at Python endpoints Root import loads none of NumPy/JAX/Torch/CuPy/optree; custom strict arrays work without NumPy

The locked checker versions are pyright 1.1.411, mypy 2.3.1, ty 0.0.79 and pyrefly 1.2.0. Separate floors exercise pyright 1.1.408 and mypy 1.19, plus the selected ty/pyrefly versions. The harness checks exact diagnostic locations and categories, including deliberate invalid calls; a checker crash or missing tool fails the run. See static typing for accepted annotation forms.

Implementation map

Module Responsibility and maintenance boundary
__init__.py, _imports.py Lightweight public identity, factories/symbols and explicit optional-dependency loading
_dimensions.py, _shape.py Shape tokens, arithmetic and bounded parsing of permitted expressions
_dtypes.py Dtype families, structured equality, byte order and normalization
_runtime_hints.py Runtime hint classes and useful instance-check failure reporting
_array_types.py Strict and Like validation, selected converters and failed-leaf rollback
_memo.py Invocation-owned automatic state, explicit ContextVars and scope lookup
_decorator.py Memo-only/combined decorator modes, metadata, async cleanup and explicit contexts
_tree.py, optree.py Tree leaves and structures with explicit traversal backend
numpy.py, jax.py, torch.py, cupy.py Backend aliases, conversion boundaries and static declarations
_typing.py Shared static input families; no runtime backend imports
claw.py Direct re-export of the caller-sensitive upstream import hook

Automatic memo discovery recognizes generated beartype frames and stores state in the live frame's locals. This is an integration assumption on upstream code generation, covered at exact rc0 across the declared CPython matrix. Explicit contexts use ContextVars; child tasks inherit live memo references unless they create their own context. Failed leaf checks restore their own mutations, and diagnostic formatting restores state without retaining failed user objects.

A leaf callback cannot observe rejection of an entire surrounding native union alternative after a later ordinary type check fails. Neither global replay caches nor bytecode guesses provide that missing boundary. No upstream monkeypatch or replacement type-checker implementation was introduced.

Feature-to-test map

Paths below are relative to the repository root. Each row identifies existing positive and negative evidence, not merely annotation declarations.

Contract Representative tests
Named/fixed/anonymous dimensions and rank tests/test_shape.py: TestNamedDim, TestFixedDim, TestAnonymous; tests/test_numpy.py: TestCrossArgConsistency
Arithmetic, variadics and broadcasting tests/test_shape.py: TestSymbolicDim, TestVariadicDim, TestNamedDimEdgeCases; tests/test_numpy.py: TestBroadcastableDims, TestMultipleVariadicRejected
Scalar and invalid token combinations tests/test_dimensions.py: TestMixedScalarRejection, TestScalarArithmeticRejection, TestBooleanDimRejection
Value lookup, arithmetic and rejected expressions tests/test_shape.py: TestValueDim, TestSymbolicEdgeCases; tests/test_numpy.py: TestValueExpressions; tests/test_decorator.py: TestValueWithCheckDecorator
Dtype families, byte order, structured fields and temporal units tests/test_dtypes.py: TestDtypeSpecMatches, TestByteorderMatching, TestShapedVoidAndStructured, TestDatetimeTimedelta; tests/test_numpy.py: TestStructuredDtypeIntegration, TestStructuredLikeDtypeEnforcement
Scalar-like range/casting and boolean rejection tests/test_numpy.py: TestScalarLikeBoundariesExtended, TestScalarLikeCastingVariants, TestNumericScalarBooleanRejection
Strict versus Like backend conversion Backend Test*ConversionContract classes; NumPy TestCustomConversionContract; CuPy TestCuPyConverterContract
Return checks, nested calls and readable diagnostics tests/test_numpy.py: TestReturnViolations, TestNestedCalls, TestDiagnosticMessages; corresponding backend tests
Invocation lifetime, independent checks, memory release and threads tests/test_memo.py: TestIndependentCheckLifetime, TestThreadSafety, TestFrameBasedMemo
Decorator metadata, modes, async cancellation and task contexts tests/test_decorator.py: TestDecoratorEdgeCases, TestAsyncCheckDecorator, TestMemoIsolation, TestCheckRejectsGenerators, TestCallBoundaryContracts
Tree leaf dtype/shape, structures and return constraints tests/test_tree.py: TestBasicTree, TestCrossLeafConsistency, TestStructureBindingFailures, TestMultiLevelFailures, TestReturnTypeFailures
Real import-hook caller discovery tests/test_claw.py: test_instruments_caller_package imports actual temporary packages/submodules with default/custom configuration
Framework transformations and device behavior tests/test_jax.py: TestJaxTransformations; tests/test_torch.py: TestTorchTransformations; tests/test_cupy.py: TestCuPyDeviceBehavior, TestCuPyTrees
Optional imports and custom backend operation tests/test_coverage_edges.py: TestOptionalBackendImports, TestVersionExport; tools/smoke_minimal.py from a minimal installed wheel
Static consumers and expected failures tests/test_typecheck.py, tests/typing/check_*consumers.py, tests/typing_negative/invalid_*calls.py
Documentation and distributions tests/test_examples.py, tools/check_docs.py, tools/check_notebook.py, tools/check_distribution.py, tools/check_installed.py
Publication identity and bypass rejection tests/test_release.py: 23 cases using disposable Git histories, events and foreign hook environments

The endpoint and platform jobs exercise these files with the actual interpreter and dependencies. Expected missing-CuPy and platform-precision skips are visible; preflight checks fail if an expected CPU backend is absent. The native-union case below remains outside the passing contract and blocks release rather than being hidden behind an expected-failure marker.

Audit findings and independent review units

Finding Change and closing evidence Status
A01: exact rc0 installation #13, #25: normal resolver installs and endpoint artifact consumers Implemented and validated
A02: caller-package import hook #14: direct alias and real package import violations Implemented and validated
A03: stale state and retained failures #15: live-frame ownership, independent failures, mutation, weak references and threads Implemented and validated
A04: composite alternative rollback Exact hosted wheel still rejects the valid example below Release blocker; upstream path selected
A05: converter fallback and trust #16, #26: actual backend converter oracles and GPU cases Implemented and validated
A06: consumer typing #17, #19, #20: four engines, Like inputs and structural Tree callers CPU surfaces validated; limited CuPy policy accepted
A07: incomplete distributions #18, #25: license, typing marker, source inputs and installed consumers Implemented and validated
A08: publication without validation #28: immutable workflow/package identity, full gate and same artifacts Code validated; administrative controls pending
A09: documentation drift #24: formatter round trip, rendered structures, executed snippets/notebook Implemented and validated
A10: hooks/CI/dependency drift #23: locked reusable matrix, CPU resolution, four-checker pre-push and strict aggregate gate Implemented and validated

#21 supplies framework/minimal proofs. #22 and #27 are validation aggregates that preserve focused PR branches. Merge prerequisites and living decisions are recorded in each PR's ExecPlan and the roadmap in #12.

The approved history is integrated through #31, preserving every reviewed head without squashing. GitHub marked main-based PRs merged automatically; dependent PRs were closed with an integration record after ancestor verification. Twenty clean worktrees and their local branches were removed after preserving coverage/build evidence; the corresponding remote branches were deleted with expected-head checks. The original checkout remains on main.

Actual hook installation then exposed two defects addressed in #33: file checks ran at the commit-message stage and rejected a worktree Git path, and inherited Git repository variables redirected disposable release-test commands. File hooks now default to pre-commit, the unused commit-message shim is removed, and Git commands explicitly targeting another repository clear Git's local environment variables. CI checks installed stage routing; four new cases cover foreign Git environments and ensure fixture construction preserves the hook owner's config and HEAD. Real commits and pre-push runtime/checker checks pass. Hosted run 34231187384 passed the complete matrix at 0717df2f2194cdcb10bcf37ba291c09117c42238.

The main Pages run 34229683120 built successfully and skipped deployment. Contributor instructions now require an explicit docs dispatch and explain removal of an old commit-message shim.

Accepted native-union integration path

This valid call is still rejected by the tested wheel with exact rc0:

import numpy as np
from beartype import beartype
from bearshape import C, N
from bearshape.numpy import F32


@beartype
def choose(pair: tuple[F32[N], str] | tuple[F32[C], int], y: F32[N]) -> None:
    pass


choose((np.ones(2, dtype=np.float32), 1), np.ones(3, dtype=np.float32))

The first alternative binds N=2, then fails its str check. The second alternative succeeds and should bind only C=2. y should then establish N=3, but the failed alternative's binding survives and rejects it.

The owner selected an upstream-supported whole-alternative integration boundary and permits a later beartype candidate. Preserve full composition. A warning, bytecode heuristic, monkeypatch or restricted annotation contract does not close this defect. On 8 September 2026, PyPI offered only 0.23.0rc0 in the 0.23 series; the approval is a direction, not evidence of an available fix. An isolated normal install of upstream development commit a2729e0 identifies itself as 0.23.0rc1 and still rejects the same valid call. That unpublished revision is evidence of the remaining defect, not a supported new compatibility floor.

Run uv run --locked python tools/probe_union.py from the repository root to reproduce the current failure with version information. Its nonzero exit is explicit blocker evidence. A later candidate must preserve bindings from earlier successful parameters, discard a whole failed alternative's shape/tree state, commit successful alternatives, isolate diagnostic checks and restore state on exceptions. Cover nested unions, return checks, explicit/automatic scopes, async paths and object lifetime before changing the compatibility floor and rerunning the complete normal-install matrix. No upstream message or patch has been submitted on the owner's behalf.

CuPy static boundary

CuPy 14.2.0 supplies neither py.typed nor usable native ndarray stubs. A real installed-CuPy consumer reveals Unknown/Any for ordinary native operations: strict pyright/mypy report missing typing information, while ty/pyrefly can accept the code with Unknown. The protocol fallback proves only its small structural surface; it does not establish native method inference.

The owner accepted explicitly limited static CuPy support alongside the verified GPU runtime behavior. This is no longer an open release-policy decision. Keep the limitation visible in typing docs and release notes; do not claim native method inference or parity with NumPy/JAX/Torch. Any future stronger typing claim needs actual installed-CuPy positive, negative and inference consumers.

Validated artifact identity

The hook follow-up run 34231187384 validated source 0717df2f2194cdcb10bcf37ba291c09117c42238. Both Python endpoint consumers passed 1,105 tests, with one explicit absent-CuPy skip each. Its wheel SHA256 remains c1806203da013c9eaf2482a309c686a984c0a7031a495efbd100a824536b57d2, identical to the GPU-tested artifact. Its updated sdist SHA256 is 19b7e7252f2c64e4ea014e92a14b7a25a8409db20612205da257c1d04df9e85b. The earlier release-specific runs below remain evidence of publication gating; no publication was triggered by these merges.

The actual validation-only release run completed with 38 successful jobs and its sole publishing job intentionally skipped. It ran all runtime/checker/floor/docs/notebook/archive/minimal/installed checks against source commit 3ee4c1d8a0c3d22f537a24b447a9d75e025a5b2d. The release-evidence artifact records:

version: 0.1.0rc0
wheel SHA256:
c1806203da013c9eaf2482a309c686a984c0a7031a495efbd100a824536b57d2
sdist SHA256:
e2454095144fb4bb5a08d3bd998d0f6a8bee4bc5ad81dfab763c339ca91666df

The wheel was built from the source archive and installed normally outside the checkout at both Python endpoints. The exact same wheel also passed all 95 CuPy GPU cases on both endpoints without skips. The consumer driver copies the archive's own lockfile, tests and tooling, leaves package source absent, and verifies site-packages origins and exact beartype rc0. The same artifact pair is what the publisher would download; it does not rebuild distributions. Later source changes produce their own source-archive hashes and require their applicable validation before publication.

The subsequent Node 24 validation run also completed 38 successful jobs and skipped publication. It includes the four additional call-boundary cases: 1,101 installed-consumer tests pass at each endpoint with one absent-CuPy skip. Its source is 6f40e4a484b70535c58e2c86a645a64ab1079143; its wheel is byte-identical to the GPU-validated wheel above. Its updated source archive has SHA256:

38d89d4ce1b351601ff8602a9a27b4e9af46db056be3ac0eaf41a68ac56a3706

#30 updates artifact/Pages actions to reviewed Node 24 releases. Required docs validation successfully packaged the 46-file Pages archive, including this report and the logo. The inspected action logs no longer contain the Node 20 deprecation warning. Actual Pages deployment remains unperformed during this review. The current upstream download action still emits a Buffer() deprecation notice; no suppression was added. Artifact digest verification and all consumers passed.

Performance observations

A matched comparison uses normally installed baseline/candidate wheels on the same macOS arm64 host, CPython 3.10.20, beartype 0.23.0rc0, NumPy 2.2.6, optree 0.19.1 and typing_extensions 4.16.0. The baseline is the rc0-floor artifact before runtime corrections; the candidate is the hosted artifact identified above. Five repetitions report medians and min/max, without a CI timing gate.

Workload Baseline median (µs) Candidate median (µs) Candidate min–max (µs)
native/3 0.120 0.120 0.119–0.124
strict/3 21.827 19.818 19.702–20.084
strict/1000000 21.732 19.730 19.668–19.761
explicit/3 14.554 14.042 13.979–14.076
like/3 11.356 10.208 10.177–10.223
value/3 16.295 15.770 15.730–15.795
nested/3 44.192 40.943 40.873–41.015
like-sequence/1000000 13950.325 13596.900 13385.142–13735.867
tree/1 34.232 32.245 32.095–34.930
tree/100 517.754 487.143 485.315–488.897
diagnostic/3 124.950 120.643 119.869–122.627

Run uv run --locked python tools/benchmark_runtime.py to reproduce the workloads. The script records versions, origins and per-case call counts. Ordinary cases use 10,000 calls per repetition; expensive sequence conversions, 100-leaf trees and diagnostics use explicit smaller counts recorded in JSON. The results are local observations, not cross-platform guarantees or a comparison with another shape library.

Strict native checks show little dependence on element count. Sequence conversion scales with data size; tree validation scales with leaf count. Profiling the strict path identifies scope/frame discovery and repeated signature inspection as remaining costs. No new cache or optimization was introduced while the composition boundary is unresolved. Prefer checks at meaningful API boundaries and revisit scope work after the integration contract is settled.

Migration and administrative handoff

Users move from the prior beartype generation to the rc0 floor. Backend Like validation now obeys the selected converter and retains the original argument; values accepted only through a NumPy fallback may be rejected. Like/Tree static annotations accept the tested ordinary callers while preserving useful native types. Bare @check remains memo-only; combine it with @beartype or use @check(conf=...) for checking. Public identity remains lowercase bearshape.

Read-only GitHub inspection on 2026-09-08 found no protection on main, no repository rulesets, and no required approval reviewers on pypi. That environment currently permits main branches and v* tags, with administrator bypass enabled. github-pages permits docs/main branches. These controls were not changed. PyPI publisher configuration and package ownership have not been verified through an authenticated package-owner interface.

The 2026-09-10 transfer check confirmed Actions and all six workflows are active, Pages uses GitHub Actions, and github-pages still permits main/docs. The repository has no Actions secrets, Actions variables or webhooks to migrate. main remains unprotected with no repository rulesets; the pypi environment still has no required reviewers and permits main and v* tags. These are existing release prerequisites, not new transfer repairs.

Before publication, the owner and organization should approve and apply the following concrete settings:

  1. Protect main with reviewed pull requests, stale-approval dismissal, blocked force-push/deletion, and the observed validate / Required validation check from the final CI workflow. Verify the exact check identity after the PR set is merged; do not require a stale job name.
  2. Protect release tags against unreviewed creation, replacement and deletion. Add named receiving maintainers or a designated release team as the authorized release actors.
  3. Require release-review approval on pypi, prevent self-review, disallow administrator bypass, and restrict deployments to approved version tags. The publish workflow must itself run from the matching tag and commit.
  4. In PyPI publishing settings, verify the trusted publisher uses owner beartype, repository bearshape, workflow filename pypi.yml and environment pypi. A publisher bound to the former owner does not match the new GitHub identity. Retain OIDC rather than adding an upload token; see PyPI's setup instructions.
  5. GitHub ownership and Pages restoration are confirmed. Separately verify PyPI ownership and recovery access. Published 0.0.1 retains the old URL metadata; updated source metadata applies to future distributions. The old Pages address has no GitHub redirect; readers must use the new address.
  6. Complete the selected upstream union integration. Run the final immutable candidate validation, inspect hashes and GPU evidence, then separately authorize prerelease publication and any remaining PyPI ownership operations.

The repository changes make these steps reviewable; they do not perform them. See the repository's CONTRIBUTING.md for validation-only, tag-based release and explicitly dispatched Pages deployment procedures. AGENTS.md and the CLAUDE.md symlink share current agent guidance; tools/README.md documents the existing validators. PR #31 aligns those files with the accepted decisions.