ADR-007: Native Boundary and Distribution
If Palamedes is Rust-first, it still needs a practical delivery and integration model for Node-based tooling.
Status: Accepted Date: 2026-03-17 Revised: 2026-08-25
Context
That boundary has to satisfy a few constraints:
- usable in normal Node.js environments today
- compatible with CLI and bundler plugin use cases
- coarse-grained enough that the boundary does not become the new source of complexity
- realistic to package and distribute across supported platforms
At the same time, Palamedes should avoid turning the binding layer into a second semantic API surface.
Decision
Palamedes uses native Node bindings via napi-rs and distributes them through platform-specific packages behind @palamedes/core-node.
The boundary rules are:
- prefer coarse native operations over fine-grained helper exports
- keep the TypeScript wrapper thin and ergonomic
- allow simple serialized payloads where they keep the boundary straightforward
- do not treat the binding layer as the primary place to model i18n semantics
The package model is:
- four Rust crates: the semantic core, Node binding, native CLI, and plugin SDK
- two platform-aware TypeScript wrapper families:
@palamedes/core-nodeand@palamedes/cli - six platform-specific packages for each family, carrying either the compiled Node addon or CLI binary artifacts
Native Target Policy
A native target is a Node operating-system, architecture, and (on Linux) C library combination. It produces one package in each native family and requires a matching release build, smoke test, optional dependency, resolver branch, and support documentation.
Palamedes supports exactly these six targets:
- macOS arm64
- Linux x64 with glibc
- Linux x64 with musl
- Linux arm64 with glibc
- Linux arm64 with musl
- Windows x64 with MSVC
This keeps the native surface aligned with current developer machines and server/container deployments. The two Linux C libraries are separate targets: an addon compiled against glibc cannot be treated as an Alpine-compatible musl addon.
Intel macOS (darwin/x64) and Windows on ARM (win32/arm64) are deliberately
unsupported. A build runner being available, or an architecture being able to
emulate another one, is not sufficient reason to multiply every release's
artifacts and maintenance surface. On Apple Silicon, users should install an
arm64 Node runtime rather than run an x64 Node runtime under Rosetta.
A new target needs evidence of recurring user demand or a deployment requirement that the six targets cannot reasonably cover. Before it is accepted, it must have a reproducible native build and load smoke test on a matching runner, and its ongoing cost must be justified across both the CLI and Node binding package families. Adding a target is therefore an explicit update to this ADR and the platform-support contract, not an incidental packaging change.
Alternatives Considered
1. WASM-first delivery
Rejected because the immediate use cases are Node-centric and the packaging/runtime trade-offs were not worth taking as the initial core path.
2. Fine-grained native APIs
Rejected because they encourage semantic drift at the boundary and increase cross-language chatter.
3. Keep native code as internal implementation detail only
Rejected because the native core is not just an optimization layer; it is the main semantic engine.
Consequences
@palamedes/core-nodeshould expose a compact set of meaningful operations.- Boundary design should optimize for stable workflow calls, not for perfect one-to-one exposure of internal Rust modules.
- Native artifact distribution is part of the product architecture, not an afterthought.
- Every release publishes and verifies twelve native artifacts: one CLI binary and one Node addon for each of the six supported targets.
- Unsupported Node processes fail explicitly and point users to a supported runtime, rather than silently selecting an emulated or incompatible binary.
- Future refinements may improve marshalling details, but the coarse-grained boundary principle remains the stable rule.