2026-08-18 00:44:41 +02:00
param (
2026-08-18 02:25:22 +02:00
[ string ] $PythonPath = " " ,
[ string ] $NodePath = " "
2026-08-18 00:44:41 +02:00
)
$ErrorActionPreference = " Stop "
$root = Split-Path -Parent $PSScriptRoot
$dotnet = Get-Command dotnet -ErrorAction Stop
2026-08-18 02:25:22 +02:00
$nodeExecutable = if ( [ string ] :: IsNullOrWhiteSpace ( $NodePath ) ) {
$node = Get-Command node -ErrorAction SilentlyContinue
if ( $node ) { $node . Source } else { Join-Path $env:ProgramFiles " nodejs\node.exe " }
}
else {
[ IO.Path ] :: GetFullPath ( $NodePath )
}
if ( -not ( Test-Path -LiteralPath $nodeExecutable -PathType Leaf ) ) {
throw " Node.js executable not found: $nodeExecutable "
}
2026-08-18 00:44:41 +02:00
$pythonExecutable = if ( [ string ] :: IsNullOrWhiteSpace ( $PythonPath ) ) {
( Get-Command python -ErrorAction Stop ) . Source
}
else {
[ IO.Path ] :: GetFullPath ( $PythonPath )
}
if ( -not ( Test-Path -LiteralPath $pythonExecutable ) ) {
throw " Python executable not found: $pythonExecutable "
}
$typescript = Join-Path $root " node_modules\typescript\bin\tsc "
if ( -not ( Test-Path -LiteralPath $typescript ) ) {
throw " TypeScript compiler not found: $typescript "
}
2026-08-18 02:54:10 +02:00
foreach ( $dependency in @ ( " cultcache-ts " , " cultnet-ts " ) ) {
$dependencyRoot = Join-Path $root " packages\ $dependency "
Push-Location $dependencyRoot
try {
if ( $dependency -eq " cultnet-ts " ) {
& $nodeExecutable ( Join-Path $dependencyRoot " tools\generate-swarm-contracts.mjs " )
if ( $LASTEXITCODE -ne 0 ) {
throw " CultNet TypeScript contract generation failed. "
}
}
& $nodeExecutable $typescript -p tsconfig . json - -pretty false
if ( $LASTEXITCODE -ne 0 ) {
throw " TypeScript dependency build failed: $dependency "
}
}
finally {
Pop-Location
}
}
2026-08-18 00:44:41 +02:00
& $dotnet . Source test ( Join-Path $root " tests\GameCult.Mesh.Tests\GameCult.Mesh.Tests.csproj " ) `
- -filter " FullyQualifiedName~ReactiveDocument_SchedulingScalesWithChangedDocumentsOnly " `
- -nologo `
- -verbosity minimal
if ( $LASTEXITCODE -ne 0 ) {
throw " CultMesh C# reactive scaling verification failed. "
}
Push-Location ( Join-Path $root " packages\cultmesh-ts " )
try {
2026-08-18 02:25:22 +02:00
& $nodeExecutable $typescript -p tsconfig . json - -pretty false
2026-08-18 00:44:41 +02:00
if ( $LASTEXITCODE -ne 0 ) {
throw " CultMesh TypeScript source build failed. "
}
2026-08-18 02:25:22 +02:00
& $nodeExecutable $typescript -p tsconfig . test . json - -pretty false
2026-08-18 00:44:41 +02:00
if ( $LASTEXITCODE -ne 0 ) {
throw " CultMesh TypeScript test build failed. "
}
$testFiles = Get-ChildItem -LiteralPath . \ dist-test \ test -Filter " *.test.js " |
Sort-Object Name |
ForEach-Object FullName
2026-08-18 02:25:22 +02:00
& $nodeExecutable - -test @testFiles
2026-08-18 00:44:41 +02:00
if ( $LASTEXITCODE -ne 0 ) {
throw " CultMesh TypeScript reactive scaling verification failed. "
}
}
finally {
Pop-Location
}
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
$pythonPath = ( @ ( " cultcache-py " , " cultnet-py " , " cultmesh-py " ) | ForEach-Object { Join-Path $root " packages\ $_ \src " } ) -join [ IO.Path ] :: PathSeparator
2026-08-18 00:44:41 +02:00
$previousPythonPath = $env:PYTHONPATH
$env:PYTHONPATH = if ( [ string ] :: IsNullOrWhiteSpace ( $previousPythonPath ) ) {
$pythonPath
}
else {
$pythonPath + [ IO.Path ] :: PathSeparator + $previousPythonPath
}
try {
& $pythonExecutable -m unittest discover `
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
-s ( Join-Path $root " packages\cultmesh-py\tests " ) `
-p " test_cultmesh.py " `
2026-08-18 00:44:41 +02:00
-k " reactive_scheduling_scales_with_changed_documents_only "
if ( $LASTEXITCODE -ne 0 ) {
throw " CultMesh Python reactive scaling verification failed. "
}
}
finally {
$env:PYTHONPATH = $previousPythonPath
}
Write-Output '{"runtimes":["csharp","typescript","python"],"documentCounts":[1,100,1000],"changedFraction":0.01,"idleSchedules":0,"invariant":"scheduling is proportional to explicitly changed documents"}'