Commit graph CultLib/packages/cultcache-py/src/cultcache_py
Author SHA1 Message Date
Meta Vens
5be87c7696 Refuse re-entrant Python cache mutations and adopt legacy-key globals on load
Attach, pull, registration, writes and deletes raise when started on a
thread already inside a mutation of the same cache (a store call,
decoder, encoder, extractor or updater), instead of running inside it
and corrupting the state being built. Reads stay reentrant. Internal
delegation (put_global, update, delete_global, register_registry,
add_backing_store) goes through private helpers so it is not re-entry.

A pull adopts a global persisted under a key other than __global__ as
that type's global without writing. The first write or delete of it
removes the legacy record in the same call; a failed removal takes the
new record back out. Two globals of one type on disk are still refused.

The lock and unlocked-read comments now state the per-call guarantee.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 12:27:55 +02:00
Meta Vens
15c6a5d513 Serialize the Python cache behind one reentrant lock per instance
The Python cache took no lock, so "refused write changes neither store nor
cache" and the one-home rule held only for one caller at a time. A put paused
between its store push and its apply let a concurrent attach route the type to
a new home (the record stayed in the old store), and let a second global put
reach disk first so cache and disk disagreed.

CultCache now holds a threading.RLock around attach, pull, registration, every
write and delete (globals and update included), name/index resolution and
snapshots, matching the C# per-cache gate and the TS serial queue. Single-dict
reads stay unlocked. No public API change.

Tests: test_concurrent_put_and_attach_cannot_land_record_in_non_home_store and
test_concurrent_global_puts_cannot_both_reach_disk force the interleaving with
a store that pauses inside push; both fail on 4dc7f3a and pass here. The
contract's Python cells drop the one-caller caveat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 12:03:45 +02:00
Meta Vens
9288f93c08 Validate a Python CultCache write whole before touching a store
put, put_envelope and put_envelopes now compute the new records' name and
index keys and resolve the home store before any push; applying to the cache
afterwards runs no extractor. Each held record keeps the lookup keys it
produced at admission, so overwrite and delete remove lookups without calling
user extractors. put_envelope and put_envelopes refuse an empty key and a
global under any key but __global__. Per-type dicts are created only after the
store accepts. register_document_type, register_name_lookup and register_index
derive every lookup under the candidate extractors first and install
extractors and lookups together, so a raising extractor installs nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 11:48:59 +02:00
Meta Vens
446122daef Build the whole next state before a pull swaps it in
A pull is all-or-nothing, but TS cleared the cache and then ran user name and
index accessors while re-applying entries, and Python swapped values in and
then rebuilt lookups. An accessor that threw left the cache partly loaded.

TS: the hydrated maps (entries, type entry ids, name and index lookups, global
keys) are one HydratedState. The pull builds a fresh one, running every check
and accessor, and replaces #hydrated in one assignment at the end. The
reset-then-apply sequence, #resetHydratedState, the staged global-key map and
the three per-map getOrCreate helpers are deleted; apply/remove take the
target state. The duplicate-global check now lives only in apply. Name and
index lookup rebuilds build a local map before assigning.

Python: the pull derives names and indexes into local dicts and assigns
values, envelopes, names and indexes together; _rebuild_indexes delegates to
the same pure _derive_lookups.

Rust already stages into a local map and assigns self.entries last, with no
derived lookups; unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 01:03:30 +02:00
Meta Vens
1f442f3345 Refuse duplicate global records while staging a pull
A load is all-or-nothing. TypeScript checked for a second record of a
global type inside #applyHydratedEntry, which runs after
#resetHydratedState, so a refused pull left the cache cleared and partly
reloaded. The check now runs in the staging pass beside the home check,
before anything is cleared; the apply step's pull-only branch is gone.

Python already validated before swapping, but its check only fired when
the __global__ record arrived second, so a store holding __global__ then
another key of a global type loaded both. Any second record of a global
type is now refused regardless of order.

Rust has no global document types and replaces its entry map only after
staging; it had neither flaw.

pullWithDuplicateGlobalLeavesCacheUnchanged (TS) and
test_pull_with_duplicate_global_leaves_cache_unchanged (Python) fail on
9ab1706 and pass here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 00:57:09 +02:00
Meta Vens
a23a33e184 Keep cultcache-py record homes stable and refuse strays at load
Conform cultcache-py to the store-composition contract, following C#:

- add_backing_store and add_generic_store raise, attaching nothing, when
  the new store would move the home of any document type the cache
  already holds.
- add_backing_store(store, []) attaches the generic store (same as
  TypeScript, Rust and C#'s untyped attach) instead of silently doing
  nothing.
- pull_all_backing_stores stages every store's records and replaces the
  cache's view only when all loaded. A record delivered by a store that is
  not its home raises naming the key and both stores; nothing is admitted
  and no store is written.

The zero-store in-memory test passes on 6bb41f3 as well (Python already
conformed); the other new tests fail there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 00:50:48 +02:00
Meta Vens
5967a1f25d Give every cultcache-py document type one home store
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>
2026-09-14 00:37:31 +02:00
Meta Vens
95493862bd Split the Python runtime into cultcache-py, cultnet-py, cultmesh-py
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>
2026-09-10 13:10:47 +02:00
Meta Vens
a0b31b8f2a Recover schema-stamped cache records across runtimes 2026-06-27 14:34:53 +01:00
Meta Vens
3fa04cc014 Add Python CultNet transport port 2026-06-14 21:38:59 +01:00
Meta Vens
94e9b4b2b4 Add CultNet transport profile contract 2026-06-14 20:32:25 +01:00
Meta Vens
b57d3c7441 Align Python cache benchmark with C# shape 2026-06-14 20:08:25 +01:00
Meta Vens
1e37841726 Add Python CultNet security primitives 2026-06-14 17:39:07 +01:00
Meta Vens
9b9c4e1310 Verify Python capability truth 2026-06-14 17:05:19 +01:00
Meta Vens
cf151fa67a Add Python CultMesh daemon entrypoint 2026-06-14 15:28:25 +01:00
Meta Vens
31cf0377bc Partition Python cache hot storage 2026-06-14 15:12:40 +01:00
Meta Vens
432e5443e4 Verify Python exports and health probes 2026-06-14 14:56:11 +01:00
Meta Vens
f2a8f05193 Verify Python peer error export 2026-06-14 14:44:49 +01:00
Meta Vens
392a54bc80 Advertise Python CultNet error contract 2026-06-14 14:16:41 +01:00
Meta Vens
a3ccaa0290 Gate Python C# parity claims 2026-06-14 13:56:56 +01:00
Meta Vens
5255a46ddd Add Python durable shard log node option 2026-06-14 11:54:44 +01:00
Meta Vens
fae72cc951 Persist Python authoritative shard logs 2026-06-14 11:28:30 +01:00
Meta Vens
9f8c156f1d Forward Python shard writes over CultNet 2026-06-14 06:39:31 +01:00
Meta Vens
4d3ae1af87 Persist Python shard replica cursors 2026-06-14 06:33:22 +01:00
Meta Vens
cf0849c73b Add Python shard replication pull helper 2026-06-14 06:27:50 +01:00
Meta Vens
078786e81a Verify Python CultMesh mutation discovery 2026-06-14 05:59:57 +01:00
Meta Vens
0827713076 Use median samples for Python C# comparison 2026-06-14 05:53:46 +01:00
Meta Vens
f0914b5172 Verify Python local CultMesh wire smoke 2026-06-14 05:43:19 +01:00
Meta Vens
ef2501f767 Add typed Python witness artifact bundles 2026-06-14 05:04:06 +01:00
Meta Vens
fd7389b299 Add typed Python simulation consensus API 2026-06-14 04:57:41 +01:00
Meta Vens
5886bde804 Add Python CultNet raw snapshot facade 2026-06-14 04:21:05 +01:00
Meta Vens
91f7ac32af Add Python CultNet subscription change facade 2026-06-14 04:13:44 +01:00
Meta Vens
699df36cce Add Python CultNet shard log facade 2026-06-14 04:06:38 +01:00
Meta Vens
c1afc6b77f Add Python CultNet shard catalog facade 2026-06-14 03:59:20 +01:00
Meta Vens
c47b84560e Add Python CultNet schema catalog facade 2026-06-14 03:51:50 +01:00
Meta Vens
828947a188 Share Python CultNet wire schema catalog 2026-06-14 03:45:19 +01:00
Meta Vens
27db7894b5 Add Python CultMesh local server 2026-06-14 03:18:38 +01:00
Meta Vens
44870695b7 Expand Python runtime verifier exports 2026-06-14 03:09:35 +01:00
Meta Vens
96fd34231a Add sampled Python C# benchmark comparison 2026-06-14 02:54:06 +01:00
Meta Vens
473bece217 Add Python CultMesh database watchers 2026-06-14 02:03:44 +01:00
Meta Vens
c125b0fb20 Add Python CultMesh database facade 2026-06-14 01:56:35 +01:00
Meta Vens
bd50ecd15b Add Python CultMesh client facade parity 2026-06-14 01:50:15 +01:00
Meta Vens
87f7868af1 Cache Python document schema catalogs 2026-06-14 01:43:14 +01:00
Meta Vens
93c51478f4 Optimize Python CultCache incremental lookups 2026-06-14 01:37:35 +01:00
Meta Vens
e41fc8f019 Add Python C# cache benchmark parity receipt 2026-06-14 01:30:50 +01:00
Meta Vens
41c43bb5e2 Add Python runtime parity verifier 2026-06-14 01:03:39 +01:00
Meta Vens
5ac5f2fb81 Benchmark Python runtime hot paths 2026-06-13 23:40:53 +01:00
Meta Vens
9202b33fd2 Mark Python runtime package typed 2026-06-13 22:36:12 +01:00
Meta Vens
26de320f7f Add Python CultNet interop peer 2026-06-13 21:21:51 +01:00
Meta Vens
a1ef58cb2d Add Python CultCache wire parity package 2026-06-13 20:35:58 +01:00