SENTO-ZMQ-RESEARCH-001 PZMQ Binding Audit
| Version | Date | Description of change | Did nsaspy approve it |
|---|---|---|---|
| 0.1.0 | 2026-07-19 | Start audit with explicit adopt, fork, or replace decision | Yes |
Related Nodes
Research Question
Initial Source Read
The PZMQ repository describes itself as a ZeroMQ 4.0 and later Common Lisp binding. Its own README lists important deficiencies: surfaces outside its examples and tests have not been tested, conveniences are limited to those examples and tests, performance is not fully optimized, and the automatic tests are described as barebone.3
That is a real warning. It does not prove the binding is unusable. It proves direct adoption without a source and runtime audit would be reckless.
PyZMQ4 is maintained by the ZeroMQ organization and states that it fully supports the stable libzmq 3.x and 4.x APIs. Its repository includes extensive tests, performance tooling, packaging support, and documentation. It will be used as a reference oracle5 and cross-language peer for expected stable behavior, not as a substitute for the Common Lisp binding.6
Required Baseline
Record before testing:
- PZMQ repository commit
- PZMQ ASDF system version when present
- libzmq release and build configuration
- SBCL version
- CFFI version
- operating system and architecture
- Nix package revisions
- whether draft libzmq APIs are disabled
- compiler and linker versions
No final recommendation is valid without this version matrix.
Source Audit Matrix
| Area | Required evidence | Failure consequence |
|---|---|---|
| FFI declarations | Signatures match current stable C headers | Replace declaration or binding |
| Foreign memory | Message and buffer ownership is explicit and safe | Critical; fork or replace |
| Conditions | Native errors map to structured Lisp conditions | Extend or wrap |
| Interrupts | Blocking calls handle SBCL interrupts correctly | Extend before adoption |
| Context | Context creation, shutdown, and termination are complete | Critical; fork or replace |
| Sockets | Create, configure, bind, connect, disconnect, close | Critical |
| Multipart | Frame boundaries and more-parts state remain correct | Critical |
| Polling | Poller behavior is bounded and interruptible | Critical |
| Monitoring | Stable socket-monitor events are exposed | Extend or replace |
| Security | CURVE and ZAP-related stable options are exposed | Extend or replace |
| Options | All required stable production options are covered | Extend or replace |
| Non-blocking I/O | EAGAIN and timeout behavior is correct | Critical |
| Thread ownership | API does not encourage cross-thread socket sharing | Wrap or redesign |
| Tests | Regressions are detectable across supported systems | Fork or replace |
| Packaging | Reproducible Nix build and pinned source | Required before adoption |
Runtime Test Matrix
Context and shutdown
- create and terminate an empty context
- close all sockets before termination
- force a blocked receive to stop by declared shutdown path
- detect leaked sockets
- repeat context startup and shutdown one thousand times
- verify no lingering native or Lisp threads
Socket patterns
Test over inproc://, ipc:// where supported, and loopback tcp://:
- PAIR
- PUB and SUB
- PUSH and PULL
- ROUTER and DEALER
REQ and REP are tested only for compatibility and are not the primary StarIntel runtime protocol.
Messaging
- zero-length frame
- one frame
- multipart frame sequence
- large bounded payload
- non-blocking send at high-water mark
- non-blocking receive without data
- send and receive timeout
- disconnect and reconnect
- ROUTER identity preservation
- concurrent outstanding DEALER requests
Polling and monitoring
- poll multiple sockets
- wake and stop a poller
- connect and disconnect monitor events
- handshake success and failure
- retry events
- monitor shutdown
Security
- CURVE client and server handshake
- approved key succeeds
- unknown key fails
- malformed key fails
- message is not delivered before authentication and authorization
Differential tests with PyZMQ
Use the same message envelope and fixtures across Common Lisp and Python:
- Common Lisp ROUTER with Python DEALER
- Python ROUTER with Common Lisp DEALER
- Common Lisp PUB with Python SUB
- Python PUB with Common Lisp SUB
- Common Lisp PUSH with Python PULL
- Python PUSH with Common Lisp PULL
- multipart and binary payload round trips
- high-water-mark and timeout behavior
- clean shutdown from either side
Differences are classified as:
- libzmq behavior
- PZMQ binding defect
- PyZMQ binding behavior
- test assumption defect
- platform difference
Decision Gate
Adopt upstream PZMQ
Choose adoption only when:
- no critical memory or lifecycle defect is found
- required stable APIs and options are present
- missing conveniences can be hidden by the StarIntel adapter
- tests can be added without restructuring the binding
- exact source and libzmq versions can be pinned reproducibly
Fork and extend PZMQ
Choose a fork when:
- its CFFI7 foundation and memory ownership are sound
- most required declarations are correct
- the missing work is bounded to modern options, monitoring, tests, conditions, and packaging
- preserving its existing API costs less than a new binding
The fork must be narrow, versioned, and suitable for eventual upstream contribution where practical.
Create a new narrow binding
Choose replacement when:
- memory ownership is unsafe or unclear
- the generated or handwritten declarations substantially diverge from current stable libzmq
- context and socket lifecycle cannot be repaired locally
- the public API prevents safe ownership and structured conditions
- testability is poor enough that repair cannot be trusted
- the amount of dead or obsolete code exceeds the stable API surface StarIntel actually needs
A replacement binds only the approved stable operations. It must not attempt to expose every draft or experimental libzmq feature.
Deliverables
- pinned source and toolchain manifest
- source-level audit report with file and line references
- stable API feature matrix
- failing and passing runtime tests
- PyZMQ interoperability fixtures
- memory and shutdown findings
- adopt, fork, or replace recommendation
- estimated implementation and maintenance cost
- affected design updates
Current Preliminary Conclusion
Do not adopt PZMQ blindly. Its own documented test limitations are enough to require the full audit. The likely outcomes are either a maintained fork with a serious test suite or a smaller replacement binding. The audit must decide based on memory safety, lifecycle correctness, stable API coverage, and repair cost rather than repository age or aesthetics.
Footnotes and Glossary
Citations
Footnotes:
PZMQ: The orivej/pzmq Common Lisp foreign-function binding for ZeroMQ 4.0 and later.
libzmq: The native ZeroMQ core library that implements message sockets, transports, queues, polling, monitoring, and security.
orivej/pzmq official repository and README, retrieved 2026-07-19: https://github.com/orivej/pzmq
PyZMQ: The maintained Python binding for ZeroMQ.
Reference oracle: A trusted comparison implementation used to determine expected behavior when testing another implementation.
zeromq/pyzmq official repository and README, retrieved 2026-07-19: https://github.com/zeromq/pyzmq
CFFI: The Common Foreign Function Interface, a Common Lisp library used to declare and call functions from native C libraries.