Commit graph CultLib/packages/cultcache-py/tests
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
b6b1d9c660 Bound the Python fragment reassembly map
cultcache-py removed a fragment buffer only on successful reassembly, the
same shape cultnet-ts had: a set that loses one fragment is kept for the
life of the session and the map grows under any loss on a fragmenting
channel. Cap at 64 to match the other runtimes, evict the oldest stranded
set (dict insertion order) rather than refuse the payload, and expose
fragment_sets_evicted so the drop is observable.

The fragment id already wrapped correctly here; only the Rust allocator
saturated.

  PYTHONPATH=src python -m unittest tests.test_cultcache -> 142 run, 1
  pre-existing error (interop fixture path is cwd-relative, same on main)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 11:42:17 +02:00
Meta Vens
fd676ce74d Finish the sequence-domain split in Python and Kotlin
Separating the reliable and lossy sequence domains landed in Rust, TypeScript
and C# on a branch. The Python and Kotlin halves were written at the same time
and left uncommitted in the worktree, so three runtimes spoke the new contract
and two spoke the old one -- the exact cross-runtime disagreement this
repository exists to prevent.

Both were already complete and implement the same three rules as the others:
ordered delivery requires reliability, sequenced channels carry their own
per-channel counters with latest-wins delivery, and duplicate suppression is
gated on packet.reliable so lossy datagrams are remembered nowhere.

Python also gains the three tests Rust, TypeScript and C# each received and it
did not: lossy packets cannot create reliable ordered gaps, unreliable
sequenced delivery is scoped to its channel, and unreliable ordered delivery is
rejected. Python passes 141.

Kotlin is committed unverified. This machine has no JVM or Gradle, and the
package carries no runtime-local test file, so its only gate is the interop
lane in CI. The change mirrors the four runtimes proven here line for line, but
mirroring is not evidence and CI is the witness.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 00:40:05 +02:00
Meta Vens
75c180782a Acknowledge retransmits across RUDP horizons 2026-08-23 19:15:22 +02:00
Meta Vens
fda51fad50 Acknowledge reliable RUDP windows cumulatively 2026-08-23 19:00:33 +02:00
Meta Vens
f4dabfefb0 Bound RUDP reliable transmission windows 2026-08-23 18:35:49 +02:00
Meta Vens
669f638111 Share one scheduler across Python reactive documents 2026-08-18 02:25:22 +02:00
Meta Vens
ba680020b9 Prove reactive scheduling scales with edits 2026-08-18 00:44:41 +02:00
Meta Vens
1e1cfd840c Remove CultMesh Python mutation polling 2026-08-17 20:03:42 +02:00
Meta Vens
9266bbec22 Add Python reactive document sync 2026-06-28 00:24:46 +01:00
Meta Vens
6627eb4746 Add Python publication catalog sync 2026-06-28 00:00:15 +01:00
Meta Vens
420e91f4f9 Add Python publication document sync 2026-06-27 23:44:06 +01:00
Meta Vens
abdfff6f4e Add publication sync and reconciliation coverage 2026-06-27 23:37:15 +01:00
Meta Vens
719ea7950a Add Python typed document subscriptions 2026-06-27 23:23:50 +01:00
Meta Vens
c1ceb860a0 Add typed Python snapshot fanout coverage 2026-06-27 23:09:16 +01:00
Meta Vens
bd98bfe979 Add Python fanout document sync helper 2026-06-27 23:03:30 +01:00
Meta Vens
1885e2244a Expose Python facade document sync 2026-06-27 22:58:50 +01:00
Meta Vens
854165e943 Add Python one-call document snapshot sync 2026-06-27 22:56:17 +01:00
Meta Vens
31af506691 Emit Python alias-shaped mesh watch changes 2026-06-27 22:51:18 +01:00
Meta Vens
4f4a4699e5 Resolve Python collection reads by schema alias 2026-06-27 22:40:42 +01:00
Meta Vens
a825974e9a Resolve Python shard snapshots by schema alias 2026-06-27 22:29:56 +01:00
Meta Vens
d9ffdd77a4 Resolve Python raw deletes by schema alias 2026-06-27 22:13:28 +01:00
Meta Vens
54543b5db0 Resolve Python shard discovery filters by schema alias 2026-06-27 22:00:13 +01:00
Meta Vens
c0eacb885b Resolve Python subscription filters by schema alias 2026-06-27 21:50:00 +01:00
Meta Vens
bff2e552c8 Resolve Python snapshot discovery by schema alias 2026-06-27 21:42:32 +01:00
Meta Vens
dcf5f73003 Canonicalize Python payload-resolved schema sync 2026-06-27 21:27:25 +01:00
Meta Vens
defc416f9f Convert Python node aliases through payload schema 2026-06-27 20:55:17 +01:00
Meta Vens
a2ec06f90e Resolve Python node document aliases transparently 2026-06-27 20:52:26 +01:00
Meta Vens
a0b31b8f2a Recover schema-stamped cache records across runtimes 2026-06-27 14:34:53 +01:00
Meta Vens
8da99394cc Add Python CultMesh RUDP server listener 2026-06-17 06:34:30 +01:00
Meta Vens
ce9d709dae Add Python RUDP schema client transport 2026-06-16 23:37:59 +01:00
Meta Vens
0680855925 Mirror authorized RUDP peer sugar 2026-06-16 16:52:20 +01:00
Meta Vens
44c010a867 Add Python CultMesh RUDP factories 2026-06-16 16:42:03 +01:00
Meta Vens
9120d0a372 Add Python CultMesh catalog factories 2026-06-16 16:31:13 +01:00
Meta Vens
e65c0d0fc6 Add Python schema transport client port 2026-06-16 16:13:56 +01:00
Meta Vens
4335ab10bd Fix RUDP ordered control sequence parity 2026-06-16 13:18:39 +01:00
Meta Vens
259ede745f Add Python RUDP reconnect loop 2026-06-16 06:28:22 +01:00
Meta Vens
4a54966b10 Add portable reconnect controllers 2026-06-16 05:29:45 +01:00
Meta Vens
9e97d94796 Advertise RUDP reconnect policy in profiles 2026-06-16 04:01:41 +01:00
Meta Vens
b3be836115 Add portable reconnect policy helpers 2026-06-16 03:07:31 +01:00
Meta Vens
c85b5fb561 Add RUDP reliable queue bounds across runtimes 2026-06-15 15:24:11 +01:00
Meta Vens
8287926921 Add RUDP ping timeout parity 2026-06-15 03:26:40 +01:00
Meta Vens
743f580f9b Add RUDP fragmentation in TypeScript and Python 2026-06-15 01:54:53 +01:00