Skip to content

Commit 2f7ee4c

Browse files
SDK v0.4.0 + kmod audit log + Helm RBAC + async transport + persistence
Python SDK v0.4.0 (218 tests, up from 193): - PersistentSyncTable: crash-safe WAL with unordered delta replay and compaction. State survives restarts — the killer v0.4.0 feature. - AsyncSyncTable + AsyncCallbackTransport for asyncio integration - Benchmark --share flag for copy-paste shareable result summaries - Version bump to 0.4.0 Kernel module — audit log: - atomik_audit.c/h: ring buffer of last 1024 operations (timestamp_ns, PID, table_id, addr, op_type, value) - /proc/atomik/audit exposes audit trail as text - atomik_audit_record() called from all 4 ioctl handlers - Answers SOC2 question: "who did what, when?" Prometheus exporter upgrades: - /healthz and /ready endpoints for Kubernetes probes - SIGTERM handler for graceful shutdown - --log-level flag Helm chart — enterprise ready: - ServiceAccount, ClusterRole, ClusterRoleBinding (RBAC) - NetworkPolicy (Prometheus scrape only) - livenessProbe + readinessProbe on DaemonSet - NOTES.txt post-install instructions Changelog updated with v0.4.0 SDK + v0.5.0 kmod entries. Landing page badge updated to v0.4.0. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8d5cb16 commit 2f7ee4c

22 files changed

Lines changed: 1436 additions & 21 deletions

docs/landing/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -702,7 +702,7 @@ <h1>Stop moving data.<br><span class="highlight">Start evolving it.</span></h1>
702702
<div class="badges">
703703
<a href="https://pypi.org/project/atomik-core/" class="badge">
704704
<span class="badge-dot" style="background: var(--accent);"></span>
705-
PyPI <span class="badge-val">v0.3.0</span>
705+
PyPI <span class="badge-val">v0.4.0</span>
706706
</a>
707707
<a href="https://pypi.org/project/atomik-core/" class="badge">
708708
<span class="badge-dot" style="background: var(--accent2);"></span>

software/atomik_core/atomik_core/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,16 @@
3333
SPDX-License-Identifier: Apache-2.0
3434
"""
3535

36-
__version__ = "0.3.0"
36+
__version__ = "0.4.0"
3737

3838
from atomik_core.context import AtomikContext
3939
from atomik_core.table import AtomikTable
4040
from atomik_core.stream import DeltaStream, DeltaMessage
4141
from atomik_core.fingerprint import Fingerprint
4242
from atomik_core.sync import SyncTable
43+
from atomik_core.persistent import PersistentSyncTable
4344
from atomik_core.transport import MemoryTransport, CallbackTransport
45+
from atomik_core.async_transport import AsyncCallbackTransport, AsyncSyncTable
4446
from atomik_core.benchmark import (
4547
bench_rollback,
4648
bench_change_detection,
@@ -61,8 +63,11 @@
6163
"DeltaMessage",
6264
"Fingerprint",
6365
"SyncTable",
66+
"PersistentSyncTable",
6467
"MemoryTransport",
6568
"CallbackTransport",
69+
"AsyncCallbackTransport",
70+
"AsyncSyncTable",
6671
"bench_rollback",
6772
"bench_change_detection",
6873
"bench_convergence",
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
"""Async transport layer for SyncTable delta propagation.
2+
3+
Provides async-compatible transports for use with asyncio, aiohttp,
4+
FastAPI, WebSocket servers, and any other async framework.
5+
6+
Because XOR accumulation is instant (no I/O), only the network send
7+
needs to be async. Receives and reads remain synchronous.
8+
9+
Usage:
10+
from atomik_core.async_transport import AsyncCallbackTransport, AsyncSyncTable
11+
12+
# Low-level: async callback transport
13+
async def send_ws(data: bytes):
14+
await websocket.send(data)
15+
16+
transport = AsyncCallbackTransport(table, send_fn=send_ws)
17+
await transport.send_pending() # flush any queued deltas
18+
19+
# High-level: AsyncSyncTable wraps SyncTable with async put()
20+
async_table = AsyncSyncTable(256, send_fn=send_ws)
21+
await async_table.put(0, 0xDEADBEEF) # sends delta immediately
22+
23+
SPDX-License-Identifier: Apache-2.0
24+
"""
25+
26+
from __future__ import annotations
27+
28+
from atomik_core.stream import DeltaMessage
29+
from atomik_core.sync import SyncTable
30+
31+
32+
class AsyncCallbackTransport:
33+
"""Async-compatible transport for SyncTable.
34+
35+
Accepts an async send function for use with asyncio, aiohttp, FastAPI, etc.
36+
Since SyncTable's on_delta callback is synchronous, deltas are queued
37+
internally and flushed via the async send_pending() method.
38+
39+
Usage:
40+
async def send_ws(data: bytes):
41+
await websocket.send(data)
42+
43+
transport = AsyncCallbackTransport(table, send_fn=send_ws)
44+
await transport.send_pending() # flush any queued deltas
45+
"""
46+
47+
__slots__ = ("_table", "_send_fn", "_queue")
48+
49+
def __init__(self, table: SyncTable, send_fn=None) -> None:
50+
"""Create an async callback transport.
51+
52+
Args:
53+
table: The SyncTable to wrap.
54+
send_fn: An async callable that accepts bytes. Called with
55+
serialized DeltaMessages when send_pending() is awaited.
56+
"""
57+
self._table = table
58+
self._send_fn = send_fn
59+
self._queue: list[bytes] = []
60+
table.on_delta(self._on_local_delta)
61+
62+
def _on_local_delta(self, msg: DeltaMessage) -> None:
63+
"""Called synchronously when local table generates a delta.
64+
65+
Queues the serialized message for later async sending.
66+
"""
67+
self._queue.append(msg.to_bytes())
68+
69+
async def send_pending(self) -> int:
70+
"""Send all queued deltas via the async send function.
71+
72+
Returns:
73+
Number of deltas sent.
74+
"""
75+
if self._send_fn is None or not self._queue:
76+
count = len(self._queue)
77+
self._queue.clear()
78+
return count
79+
pending = self._queue
80+
self._queue = []
81+
for data in pending:
82+
await self._send_fn(data)
83+
return len(pending)
84+
85+
def receive_bytes(self, data: bytes) -> None:
86+
"""Process incoming delta bytes from the network.
87+
88+
Synchronous — XOR accumulation is instant, no I/O needed.
89+
90+
Args:
91+
data: Exactly 16 bytes in network byte order (DeltaMessage wire format).
92+
"""
93+
msg = DeltaMessage.from_bytes(data)
94+
self._table.receive(msg)
95+
96+
@property
97+
def pending_count(self) -> int:
98+
"""Number of deltas queued for sending."""
99+
return len(self._queue)
100+
101+
102+
class AsyncSyncTable:
103+
"""Async wrapper around SyncTable with automatic delta sending.
104+
105+
Provides an async put() that writes locally and immediately sends
106+
the delta via the configured async send function. Reads and receives
107+
remain synchronous since they involve no I/O.
108+
109+
Usage:
110+
async def send_ws(data: bytes):
111+
await websocket.send(data)
112+
113+
table = AsyncSyncTable(256, send_fn=send_ws)
114+
await table.put(0, 0xDEADBEEF) # write + send in one await
115+
value = table.get(0) # sync read (instant)
116+
"""
117+
118+
__slots__ = ("_table", "_transport")
119+
120+
def __init__(self, num_contexts: int = 256, width: int = 64, send_fn=None) -> None:
121+
"""Create an async-capable synchronized state table.
122+
123+
Args:
124+
num_contexts: Number of addressable context slots (default 256).
125+
width: Bit width per context (default 64).
126+
send_fn: An async callable that accepts bytes. Called with
127+
serialized DeltaMessages on each put().
128+
"""
129+
self._table = SyncTable(num_contexts=num_contexts, width=width)
130+
self._transport = AsyncCallbackTransport(self._table, send_fn=send_fn)
131+
132+
async def put(self, addr: int, value: int) -> DeltaMessage:
133+
"""Write a value and send the delta to peers.
134+
135+
Args:
136+
addr: Context address to write.
137+
value: Desired new state value.
138+
139+
Returns:
140+
The DeltaMessage that was sent (or would be sent if no send_fn).
141+
"""
142+
msg = self._table.put(addr, value)
143+
await self._transport.send_pending()
144+
return msg
145+
146+
def get(self, addr: int) -> int:
147+
"""Read the current state at an address.
148+
149+
Synchronous — reads are instant (reference XOR accumulator).
150+
151+
Args:
152+
addr: Context address to read.
153+
154+
Returns:
155+
Current state value.
156+
"""
157+
return self._table.get(addr)
158+
159+
def receive(self, msg: DeltaMessage) -> None:
160+
"""Apply a delta received from a remote peer.
161+
162+
Synchronous — XOR accumulation is instant. Does NOT trigger
163+
sending (prevents echo loops).
164+
165+
Args:
166+
msg: DeltaMessage from a remote peer.
167+
"""
168+
self._table.receive(msg)
169+
170+
def receive_bytes(self, data: bytes) -> None:
171+
"""Apply delta bytes received from the network.
172+
173+
Synchronous — XOR accumulation is instant.
174+
175+
Args:
176+
data: Exactly 16 bytes in network byte order.
177+
"""
178+
self._transport.receive_bytes(data)
179+
180+
@property
181+
def num_contexts(self) -> int:
182+
"""Number of context slots in this table."""
183+
return self._table.num_contexts
184+
185+
@property
186+
def width(self) -> int:
187+
"""Bit width per context."""
188+
return self._table.width
189+
190+
def snapshot(self) -> dict[int, int]:
191+
"""Serialize the full state of all contexts."""
192+
return self._table.snapshot()
193+
194+
def diff(self, other: AsyncSyncTable) -> list[int]:
195+
"""Return addresses where this table and another disagree."""
196+
return self._table.diff(other._table)
197+
198+
def __repr__(self) -> str:
199+
return f"AsyncSyncTable({self._table!r})"

software/atomik_core/atomik_core/benchmark.py

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"""
1515

1616
import json
17+
import platform
1718
import time
1819
import copy
1920
import sys
@@ -285,16 +286,77 @@ def bench_throughput() -> dict:
285286
}
286287

287288

289+
def format_share_text(results: list[dict]) -> str:
290+
"""Format benchmark results as a shareable text summary.
291+
292+
Args:
293+
results: List of benchmark result dicts from bench_* functions.
294+
295+
Returns:
296+
A formatted multi-line string suitable for copy-pasting.
297+
"""
298+
# Extract platform info
299+
uname = platform.uname()
300+
cpu = uname.processor or uname.machine
301+
py_version = platform.python_version()
302+
os_info = f"{uname.system} {uname.release} {uname.machine}"
303+
304+
# Extract key metrics from results
305+
rollback = next((r for r in results if r["test"] == "rollback"), {})
306+
detection = next((r for r in results if r["test"] == "change_detection"), {})
307+
convergence = next((r for r in results if r["test"] == "convergence"), {})
308+
bandwidth = next((r for r in results if r["test"] == "bandwidth"), {})
309+
throughput = next((r for r in results if r["test"] == "throughput"), {})
310+
311+
rollback_x = rollback.get("speedup", 0)
312+
detection_x = rollback.get("mem_reduction", 0)
313+
convergence_x = convergence.get("speedup", 0)
314+
315+
# Bandwidth: use the largest state size entry for max reduction
316+
bw_entries = bandwidth.get("entries", [])
317+
bw_reduction = max((e.get("reduction", 0) for e in bw_entries), default=0)
318+
319+
# Throughput: use accum as the headline ops/sec
320+
accum_ops = throughput.get("accum_ops_sec", 0)
321+
322+
lines = [
323+
"ATOMiK Benchmark Results",
324+
"========================",
325+
f"Platform: {os_info}",
326+
f"Python: {py_version}",
327+
"",
328+
f"Rollback: {rollback_x:,.1f}x faster than deepcopy",
329+
f"Detection: {detection_x:,.0f}x less memory",
330+
f"Convergence: {convergence_x:,.1f}x faster than replay",
331+
f"Bandwidth: {bw_reduction:,.0f}x reduction",
332+
f"Throughput: {accum_ops/1e6:.1f}M ops/sec",
333+
"",
334+
"-> pip install atomik-core",
335+
"-> python -m atomik_core benchmark",
336+
"-> https://atomik.tech/demo",
337+
]
338+
return "\n".join(lines)
339+
340+
288341
def main():
289342
json_mode = "--json" in sys.argv
343+
share_mode = "--share" in sys.argv
290344

291345
if json_mode:
292346
# Suppress printed output by redirecting stdout during benchmarks
293347
import io
294348
old_stdout = sys.stdout
295349
sys.stdout = io.StringIO()
296350

297-
if not json_mode:
351+
if not json_mode and not share_mode:
352+
print("╔══════════════════════════════════════════════════════════╗")
353+
print("║ ATOMiK Benchmark — Delta-State Algebra ║")
354+
print("║ ║")
355+
print("║ Comparing traditional approaches vs ATOMiK on YOUR ║")
356+
print("║ hardware. No configuration needed — just watch. ║")
357+
print("╚══════════════════════════════════════════════════════════╝")
358+
elif share_mode and not json_mode:
359+
# Still show the header in share mode (benchmarks print progress)
298360
print("╔══════════════════════════════════════════════════════════╗")
299361
print("║ ATOMiK Benchmark — Delta-State Algebra ║")
300362
print("║ ║")
@@ -311,7 +373,10 @@ def main():
311373

312374
if json_mode:
313375
sys.stdout = old_stdout
314-
json.dump({"benchmarks": results}, sys.stdout, indent=2)
376+
output = {"benchmarks": results}
377+
if share_mode:
378+
output["share_text"] = format_share_text(results)
379+
json.dump(output, sys.stdout, indent=2)
315380
print() # trailing newline
316381
else:
317382
print(f"\n{'='*60}")
@@ -322,6 +387,13 @@ def main():
322387
print(f"\n Learn more: https://atomik.tech")
323388
print(f" Source: https://github.com/MatthewHRockwell/ATOMiK")
324389
print(f" Install: pip install atomik-core")
390+
391+
if share_mode:
392+
print(f"\n{'='*60}")
393+
print(f" SHAREABLE SUMMARY (copy-paste this)")
394+
print(f"{'='*60}\n")
395+
print(format_share_text(results))
396+
325397
print()
326398

327399
return results

0 commit comments

Comments
 (0)