Cut 5 (Python). stores_by_type maps each type to one store and generic_stores
becomes a single generic_store. add_backing_store raises on a type already
claimed and add_generic_store on a second generic store; a refused
registration claims nothing.
_stores_for_type and _all_specific_stores are deleted. _store_for_type returns
the home store and raises when stores are attached but none is home to the
type. It returns None only for a cache with no stores, which the contract
keeps as an in-memory cache. put, put_envelope, put_envelopes and delete each
write one store; pull_all_backing_stores pulls each distinct store once.
Tests: test_second_generic_store_rejected, test_type_claimed_twice_rejected,
test_types_route_to_home_store. No bytes change.
Version 0.2.0 -> 0.3.0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
setuptools >= 77 rejects "License :: OSI Approved :: MIT License" once a
PEP 639 license expression is set, and `pip install -e` builds with the
newest setuptools. That has failed both CI workflows at the install step
since 2026-09-04. `license = "MIT"` is the expression; the classifier
said the same thing and is gone from all three Python manifests.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
One distribution named cultcache-py carried three libraries. Rust and
TypeScript layer them as cultcache < cultnet < cultmesh; Python now does
the same, one package per layer, module names unchanged so every
`from cultmesh_py import ...` keeps working.
Two imports ran against that layering and are resolved by moving code to
where the dependency direction says it lives, not by adding a shim:
- cultnet_py.interop_peer imported the Verse/peer catalog, authority
lease, stream, and simulation-fact contracts from cultmesh_py. They are
wire contracts a bare peer has to speak without owning a node, which is
why cultnet-rs and cultnet-ts keep them in the net layer. They now live
in cultnet_py.cultmesh_contracts; cultmesh_py re-exports them.
- cultcache_py.benchmark imported cultnet_py. It measures framed CultNet
parse beside cache paths, so it and compare_csharp move to cultnet_py.
verify checks all three packages and moves to cultmesh_py.
The single 6,693-line test module is split by what each test touches: a
test using any cultmesh_py name lands in cultmesh-py, else cultnet_py in
cultnet-py, else cultcache-py. Each suite runs with only its layer and the
layers below on the path, so a test that reaches up fails to import
rather than quietly binding the packages back together. 11 / 53 / 78 =
the same 142.
Workflows install all three; the publish job is one matrix over the
three packages keyed by tag prefix. The TS test harnesses put all three
src directories on PYTHONPATH. cultcache-py bumps to 0.2.0 because it no
longer contains cultnet_py or cultmesh_py; the new packages start at
0.1.0. Nothing was ever published, so no PyPI consumer moves.
cultcache-py 11 tests, path: cultcache-py/src
cultnet-py 53 tests, path: + cultnet-py/src
cultmesh-py 78 tests, path: + cultmesh-py/src
python -m cultmesh_py.verify -> ok, all five checks
cultnet_py.interop_peer imports with cultmesh_py absent
Not run here: the cultnet-ts interop ring and cultcache-ts store matrix
need dotnet/kotlin peers; their PYTHONPATH change is four lines.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Rust consumers pin a CultLib revision through cargo git dependencies, which is
how Ghostlight, Epiphany, CodexConnector and Odin already consume the core
stack. npm and pip cannot take a package from a subdirectory of a git repo at
all, which is why the TypeScript and Python consumers vendored forks instead:
there was nothing else to depend on. This gives them a versioned artifact.
Tagging cultcache-ts-v* or cultcache-py-v* builds, tests and publishes. The tag
version must match the manifest version or the workflow refuses rather than
shipping a mislabelled artifact. PyPI uses trusted publishing, so no long-lived
token is stored; npm needs an NPM_TOKEN secret.
cultcache-ts is now @gamecult/cultcache-ts. The scope is not cosmetic: it makes
the package unambiguously org-owned on a public registry, and it is what lets
CultLib hold the name rather than racing for an unscoped one. CultLib's own
cultmesh-ts and cultnet-ts and their imports move with it; all three packages
typecheck and cultcache-ts passes 12 of 13 tests, the exception being the
cross-runtime store test that shells out to Python, which cannot run on this
machine because its Python has no msgpack.
Licensing was inconsistent with the repo. cultcache-py declared itself "Private
GameCult infrastructure" and cultcache-rs and cultmesh-rs declared nothing,
inside a repo whose LICENSE is MIT and whose every other package says MIT. All
three now say MIT. That was load-bearing here: a package that calls itself
private is not one to push to a public registry.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>