libdualnative is a bounded, deterministic Rust and WebAssembly library for two related JSON operations:
- three-way merge of scalar/object state from a common base; and
- RFC 8785 canonical JSON with a structured SHA-256 content identity.
It is experimental pre-1.0 software. Merge input is recursively array-free and arrays fail closed with a typed result. The separate CID domain accepts arrays. Conflict results can reproduce raw application values and must be handled as sensitive data.
From a source checkout:
cargo +1.82.0 test --all-targets
cargo +1.82.0 run --example basic_merge
cargo +1.82.0 run --example array_rejectionThe basic example merges independent changes to name and owner:
use libdualnative::v1::{merge_json_v1, MergeOutcomeV1};
let response = merge_json_v1(
r#"{"name":"draft","owner":"A"}"#,
r#"{"name":"ready","owner":"A"}"#,
r#"{"name":"draft","owner":"B"}"#,
);
match response.outcome {
MergeOutcomeV1::Merged { merged } => {
println!("{}", serde_json::to_string(merged.value())?);
}
other => println!("merge did not succeed: {other:?}"),
}
# Ok::<(), Box<dyn std::error::Error>>(())The normalized merged value is:
{"name":"ready","owner":"B"}The public v0.1.0-alpha.1 prerelease can be consumed from its immutable Git tag without relying on a registry:
[dependencies]
libdualnative = { git = "https://github.com/DualNative/libdualnative-v1", tag = "v0.1.0-alpha.1" }
serde_json = "1"The package is intentionally absent from crates.io. Do not replace the exact tag with a branch dependency when reproducibility matters.
The caller supplies:
- base: the last state shared by both editors;
- source: one independently edited state; and
- target: the other independently edited state.
Independent object-member changes merge. Identical changes agree. Divergent changes to the same value, incompatible type changes, and delete-versus-edit produce complete typed conflicts. Missing object members and explicit JSON null are different states.
Merge accepts bounded JSON null, booleans, finite binary64 numbers, strings, and objects at any supported depth. An array at any depth returns typed arrays_not_supported with its input side and RFC 6901 path. It is never delegated to legacy array behavior.
See API_REFERENCE.md for the seven native operations and DTOs.
Use compute_cid_json_v1 when the requirement is canonical identity rather than merge:
use libdualnative::v1::compute_cid_json_v1;
let cid = compute_cid_json_v1(r#"{"b":2,"a":1.0}"#)?;
assert_eq!(cid.algorithm, "libdualnative-rfc8785-sha256-v1");
# Ok::<(), Box<dyn std::error::Error>>(())The full identity includes schema, domain, algorithm, and digest. The digest alone is not the complete identity. CID accepts ordered arrays even though merge does not.
Negative zero is intentionally admitted and normalized to positive zero. Callers requiring RFC 8785 Erratum 7920 parser behavior must reject -0 before calling the library.
The WASM package has no root export and exposes only:
- libdualnative-wasm-v1/node
- libdualnative-wasm-v1/web
Each entry exposes init_v1, merge_json_v1, and compute_cid_json_v1. Operations accept primitive JSON strings and return complete compact JSON strings.
Download these two assets from the v0.1.0-alpha.1 prerelease, then verify the sidecar before installing locally:
libdualnative-wasm-v1-0.1.0-alpha.1.tgz
libdualnative-wasm-v1-0.1.0-alpha.1.tgz.sha256
Get-FileHash -Algorithm SHA256 .\libdualnative-wasm-v1-0.1.0-alpha.1.tgz
npm install --ignore-scripts .\libdualnative-wasm-v1-0.1.0-alpha.1.tgzMinimal Node use:
import {
init_v1,
merge_json_v1,
} from "libdualnative-wasm-v1/node";
await init_v1();
const response = JSON.parse(merge_json_v1(
'{"name":"draft","owner":"A"}',
'{"name":"ready","owner":"A"}',
'{"name":"draft","owner":"B"}',
));
console.log(response);Browser consumers import libdualnative-wasm-v1/web and await init_v1 before computation. Default Web initialization fetches the package-relative WASM module; callers may instead supply one accepted Web initialization input. Generated bindings are internal and are not supported import paths.
The package remains marked private=true to block npm registry publication. That flag does not prevent installation from the verified local archive.
Generated output remains ignored under target/wasm-v1:
$env:PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = '1'
npm --prefix wasm ci --ignore-scripts
$env:PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = $null
npm --prefix wasm exec playwright -- install chromium firefox
npm --prefix wasm run build
npm --prefix wasm run test:node
npm --prefix wasm run test:node-failure
npm --prefix wasm run test:adversarial
npm --prefix wasm run test:types
npm --prefix wasm run test:inspect
npm --prefix wasm run test:memory
npm --prefix wasm run test:browserThe normative facade boundary is LIBDUALNATIVE_V1_WASM_FACADE_CONTRACT.md.
Every merge, CID, and JSON Pointer operation uses the non-configurable libdualnative-core-resource-budget-v1. Its ceilings include:
- 1 MiB per input;
- depth 64;
- 65,536 value nodes;
- 32,768 object members;
- 1 MiB canonical output;
- 2 MiB merge response; and
- 4,096 conflicts.
A limit failure returns no partial merge, CID, or diagnostic result. There is no option, feature, constructor, environment variable, or alternate public API that raises these limits.
The complete rules are in CORE_V1_RESOURCE_BUDGET_AND_RECURSION_CONTRACT.md. Normalized semantics are in CORE_V1_CONTRACT.md, and semantic identities are in V1_IDENTITY_REGISTRY.md.
libdualnative does not provide:
- array merge;
- profiles or receipts;
- structural diff;
- persistence or synchronization;
- migration between engine generations;
- authorization, HTTP, WordPress, or agent runtime behavior;
- configurable limits; or
- a redacted diagnostic surface.
Conflict DTOs contain normalized values from all three inputs. Do not log, transmit, or render them into HTML without application-level redaction and safe rendering.
The API is explicitly versioned under libdualnative::v1. Package SemVer does not override its semantic engine, domain, schema, algorithm, or resource-budget identities.
The latest release is the experimental GitHub-only
v0.1.0-alpha.1 prerelease,
whose source commit is
d986f1ab9b851dea8dd8d4a99dac6ef92acd8f25. The Rust crate and WASM archive
are not published to crates.io or npm. Release assets include SHA-256
sidecars; verify them before use.
Current main may contain documentation, tests, and package-shell maintenance
that is not part of that tagged release. Semantic changes require a new
reviewed identity and a separately versioned release.
Core local checks:
cargo +1.82.0 test --workspace --locked
cargo +1.82.0 test --release --locked --test v1_resource_subprocess
cargo +1.82.0 fmt --all -- --check
cargo +1.92.0 clippy --workspace --all-targets --all-features --locked -- -D warningsThe public CI workflow runs the native suite on Linux and Windows with Rust 1.82 and 1.92, plus Node, Chromium, and Firefox checks for the WASM package.
scripts/release-v1.sh assembles the native crate, WASM archive, and checksum
sidecars. The reproducibility entry point creates two clean source directories,
builds in the pinned container with networking disabled, and compares every
deterministic output byte:
bash ./scripts/prove-reproducible-builds.sh <exact-commit>Build output is ignored under target/release-v1. These commands only assemble local files; they do not create a tag or release, upload assets, deploy, or publish a registry package.
The source-package adaptation that keeps the complete 84-process small-stack proof reproducible is documented in PROVENANCE.md.
Source is available under the MIT License. Contributions use the same license without a separate CLA or DCO; see CONTRIBUTING.md. Direct dependency licensing is summarized in THIRD_PARTY_NOTICES.md.
Do not report suspected vulnerabilities in a public issue or include live secrets in a report. Follow SECURITY.md.