Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The Defingerprinter Handbook

Defingerprinter is an offline diagnostic-redaction library. Use it to redact a short, deliberately constructed diagnostic summary before export. The Rust implementation combines deterministic scrubbing with the pinned Rampart q4 named-entity model, running on a reduced CPU-only ONNX Runtime.

Start with the quickstart, then choose your interface:

Your applicationIntegrationDependencies at runtime
Rustengine::RedactorModel files and ONNX Runtime
C, C++Version-2 C ABIShared library, model, runtime
PythonStandard-library ctypes examplePython plus native artifacts
Common LispIncluded ASDF / CFFI bindingsCFFI, Babel, Bordeaux-Threads plus native artifacts
Node.js, GoSupervised JSONL subprocess examplesYour language runtime plus the executable closure
Something elseUTF-8 JSONL over stdin/stdoutThe executable closure

The Python, C++, Node.js, and Go examples are integration recipes, not separately published SDKs. Rust, the C ABI, the JSONL executable, and Common Lisp bindings are included in this repository. Examples throughout this book use synthetic diagnostics.

The native examples close their sessions before printing successful output. The subprocess examples validate responses and wait for clean process exit before printing.

The lifecycle, in one line

Create → redact → close → export only success.

On a redaction failure, omit the diagnostic summary. Redaction is probabilistic and does not guarantee anonymity. See the integration contract and privacy and integration responsibilities.

What is pinned?

The model artifacts and runtime sources are pinned by the Nix build. The shared-library ABI is independently versioned. ABI compatibility does not promise identical redaction output across model or policy revisions. Preserve the complete Nix closure when deploying.

The handbook is built with mdBook. Browse the Rust API reference for generated rustdoc.

Quickstart

From the repository root:

nix build .#default
PREFIX=$(cd result && pwd -P)
printf '%s\n' '{"op":"redact","language":"en","text":"Contact alice@example.com"}' \
  | "$PREFIX/bin/defingerprinter"

Response:

{"ok":true,"text":"Contact [EMAIL_1]"}

Use the API

Local documentation

nix develop .#docs -c mdbook serve --hostname 127.0.0.1 --port 3000
cargo doc --locked --no-deps

Open http://127.0.0.1:3000 for the handbook or target/doc/defingerprinter/index.html for the Rust API.

The integration contract

Input and output

PropertyContract
EncodingUTF-8; ABI lengths count bytes, not characters
Language declarationExactly en, es, fr, de, it, pt, or nl
Input byte limit16,384
Input Unicode scalar limit4,096
Script policyLatin, Common, and Inherited scripts
Numeric charactersASCII digits only
ControlsOnly newline, carriage return, and tab are permitted
OutputAt most 1,024 Unicode scalars
C output storageAllocate at least 4,096 bytes plus separate size_t written storage
JSONL record limit32,768 bytes, including the line terminator

A language declaration is the caller’s claim, not automatic language detection. Validation does not establish the model’s accuracy for a particular language. Test representative synthetic diagnostics from your application.

The output limit truncates the redacted result; design summaries short enough to preserve useful context. Unsupported input and artifact/runtime/inference failures return no diagnostic text. Placeholder identity is request-local, and no rehydration map is exported.

Lifetime

Creation does not load model files. The first valid redaction call loads and verifies artifacts lazily. Close every context deterministically to drop its model session.

The C ABI serializes redaction and destruction per handle. Destruction waits for in-flight work. Each concurrent call needs its own output buffer and length storage. Use separate handles for concurrent inference. Keep the shared library loaded until every live handle and call finishes.

Rust callers own a mutable Redactor and arrange their own synchronization. Common Lisp bindings hold their context lock through decoding, so closure waits for that work too.

Artifact authority

For Nix-built C contexts, pass NULL,0 for both model and runtime paths to use compiled immutable defaults. For a manual build, pass both trusted absolute UTF-8 paths. Paths are setup authority, never fields in an untrusted diagnostic request.

The ONNX Runtime selection is process-global. Load one runtime path per process; restart the process to change it. Keep model files and runtime binaries immutable during execution.

Export rule

Consume output only after a successful status. On every failure, omit the summary rather than exporting the original. A previously returned string belongs to the caller; apply your application’s authorization, export, and retention policy.

See statuses and recovery for the complete version-2 status table and deployment for artifact layout.

Privacy and integration responsibilities

Application policy

Callers choose the authorization, export, and retention policy appropriate to their application. Create a redactor, redact the diagnostic, and consume only successful output.

Construct a small summary

Prefer a short description of the failing operation and its coarse outcome. Evaluate redaction coverage with representative synthetic fixtures.

The pipeline scrubs credentials, paths, hosts, and opaque identifiers; applies structured recognizers and sentinel pre-masking; runs WordPiece NER in overlapping word-aligned windows; repairs spans; and emits default-deny placeholders for all detected labels. Detection is probabilistic and does not guarantee anonymity. Evaluate omissions and false positives before export.

Lifetime and memory

Destroying a C handle waits for in-flight model work and drops the session. Common Lisp closure also waits for Lisp decoding. Successful strings belong to the caller; manage queued and retained output according to your application policy.

The implementation does not write input text to disk. It uses one CPU thread, with spinning, graph optimization, and runtime telemetry disabled. Review application logging, crash reports, and tracing separately.

Allocator caches and the process-global runtime may retain memory after session closure. Use a supervised subprocess when you require a hard deadline or process-isolated reclamation. Termination does not guarantee secure memory wiping.

Failure reporting

Report static status codes and coarse counters. Omit the summary on failure. The examples print static failures and export only validated successful output.

Rust

use defingerprinter::engine::Redactor;
use std::env;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let model = env::var("DEFINGERPRINTER_MODEL_DIR")?;
    let runtime = env::var("DEFINGERPRINTER_RUNTIME")?;
    let mut redactor = Redactor::new(model.into(), runtime.into());

    let text = redactor.redact("en", "Contact alice@example.com")?;
    println!("{text}");
    Ok(())
}

Output: Contact [EMAIL_1]. Reuse the redactor for further calls; its session is released when dropped.

See the Rust API reference and Redactor methods.

Setup

Add the checkout to your application’s Cargo.toml:

[dependencies]
defingerprinter = { path = "/path/to/defingerprinter" }

The example reads the model directory and ONNX Runtime library path from the environment. From this checkout, Nix supplies both:

nix develop -c cargo run --locked --example rust

Rust API reference

Open the Rust API reference.

C

#include "defingerprinter.h"
#include <stdio.h>

int main(void) {
    if (defingerprinter_abi_version() != DEFINGERPRINTER_ABI_VERSION)
        return 1;

    defingerprinter_handle handle;
    int32_t status = defingerprinter_create(NULL, 0, NULL, 0, &handle);
    if (status != DEFINGERPRINTER_OK) {
        fprintf(stderr, "create failed: %d\n", (int)status);
        return 1;
    }

    const uint8_t language[] = "en";
    const uint8_t input[] = "Contact alice@example.com";
    uint8_t output[DEFINGERPRINTER_OUTPUT_CAPACITY];
    size_t written;
    status = defingerprinter_redact(handle,
        language, sizeof language - 1, input, sizeof input - 1,
        output, sizeof output, &written);
    defingerprinter_destroy(handle);

    if (status != DEFINGERPRINTER_OK) {
        fprintf(stderr, "redact failed: %d\n", (int)status);
        return 1;
    }
    fwrite(output, 1, written, stdout);
    putchar('\n');
    return 0;
}

Output: Contact [EMAIL_1]. written is the output byte count; the output has no NUL terminator.

Build and run

Use the Nix package and its model/runtime defaults:

PREFIX=$(cd result && pwd -P)
mkdir -p target
cc examples/c.c -I"$PREFIX/include" -L"$PREFIX/lib" \
  -Wl,-rpath,"$PREFIX/lib" -ldefingerprinter -o target/example-c
./target/example-c

C++

Call the C ABI directly:

#include "defingerprinter.h"
#include <array>
#include <iostream>
#include <string>

int main() {
    if (defingerprinter_abi_version() != DEFINGERPRINTER_ABI_VERSION)
        return 1;

    defingerprinter_handle handle;
    auto status = defingerprinter_create(nullptr, 0, nullptr, 0, &handle);
    if (status != DEFINGERPRINTER_OK) {
        std::cerr << "create failed: " << status << '\n';
        return 1;
    }

    const std::string language = "en";
    const std::string input = "Contact alice@example.com";
    std::array<uint8_t, DEFINGERPRINTER_OUTPUT_CAPACITY> output;
    size_t written;
    status = defingerprinter_redact(handle,
        reinterpret_cast<const uint8_t*>(language.data()), language.size(),
        reinterpret_cast<const uint8_t*>(input.data()), input.size(),
        output.data(), output.size(), &written);
    defingerprinter_destroy(handle);

    if (status != DEFINGERPRINTER_OK) {
        std::cerr << "redact failed: " << status << '\n';
        return 1;
    }
    std::cout << std::string(reinterpret_cast<const char*>(output.data()), written) << '\n';
}

Output: Contact [EMAIL_1].

Build and run

Use the Nix package and its model/runtime defaults:

PREFIX=$(cd result && pwd -P)
mkdir -p target
c++ -std=c++17 examples/cpp.cpp -I"$PREFIX/include" -L"$PREFIX/lib" \
  -Wl,-rpath,"$PREFIX/lib" -ldefingerprinter -o target/example-cpp
./target/example-cpp

Python

With the ctypes declarations below:

if lib.defingerprinter_abi_version() != 2:
    raise RuntimeError("Expected Defingerprinter ABI 2")
handle = Handle()
check(lib.defingerprinter_create(None, 0, None, 0, c.byref(handle)))
try:
    language = b"en"
    text = "Contact alice@example.com".encode("utf-8")
    output = c.create_string_buffer(4096)
    written = c.c_size_t()
    check(lib.defingerprinter_redact(
        handle, language, len(language), text, len(text),
        output, len(output), c.byref(written)
    ))
    print(output.raw[:written.value].decode("utf-8"))  # Contact [EMAIL_1]
finally:
    check(lib.defingerprinter_destroy(handle))

Binding setup

import ctypes as c
import sys

# C ABI declarations.
lib = c.CDLL(sys.argv[1])
Handle = c.c_uint64
lib.defingerprinter_abi_version.argtypes = []
lib.defingerprinter_abi_version.restype = c.c_uint32
lib.defingerprinter_create.argtypes = [
    c.c_char_p, c.c_size_t, c.c_char_p, c.c_size_t, c.POINTER(Handle)
]
lib.defingerprinter_create.restype = c.c_int32
lib.defingerprinter_redact.argtypes = [
    Handle, c.c_char_p, c.c_size_t, c.c_char_p, c.c_size_t,
    c.c_void_p, c.c_size_t, c.POINTER(c.c_size_t)
]
lib.defingerprinter_redact.restype = c.c_int32
lib.defingerprinter_destroy.argtypes = [Handle]
lib.defingerprinter_destroy.restype = c.c_int32


def check(status):
    if status != 0:
        raise RuntimeError(f"Defingerprinter status {status}")

Run

With the Nix package:

PREFIX=$(cd result && pwd -P)
python3 examples/python.py "$PREFIX/lib/libdefingerprinter.dylib"

Use .so instead of .dylib on Linux.

JavaScript / Node.js

import { execFileSync } from 'node:child_process';

const request = {
  op: 'redact', language: 'en', text: 'Contact alice@example.com',
};
const output = execFileSync(process.argv[2], [], {
  input: JSON.stringify(request) + '\n',
  encoding: 'utf8',
});
const response = JSON.parse(output);
if (!response.ok) throw new Error(response.reason);
console.log(response.text); // Contact [EMAIL_1]

Call the JSONL subprocess interface with Node’s standard library; the Nix-built executable uses compiled model and runtime defaults.

nix build
PREFIX=$(cd result && pwd -P)
node examples/node.mjs "$PREFIX/bin/defingerprinter"

Go

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"os/exec"
)

func main() {
	request, err := json.Marshal(map[string]string{
		"op": "redact", "language": "en", "text": "Contact alice@example.com",
	})
	if err != nil {
		log.Fatal(err)
	}
	cmd := exec.Command(os.Args[1])
	cmd.Stdin = bytes.NewReader(append(request, '\n'))
	output, err := cmd.Output()
	if err != nil {
		log.Fatal(err)
	}
	var response struct {
		OK     bool   `json:"ok"`
		Text   string `json:"text"`
		Reason string `json:"reason"`
	}
	if err := json.Unmarshal(output, &response); err != nil {
		log.Fatal(err)
	}
	if !response.OK {
		log.Fatal(response.Reason)
	}
	fmt.Println(response.Text) // Contact [EMAIL_1]
}

Call the JSONL subprocess interface with Go’s standard library; the Nix-built executable uses compiled model and runtime defaults.

nix build
PREFIX=$(cd result && pwd -P)
go run examples/go/main.go "$PREFIX/bin/defingerprinter"

Common Lisp

(require :asdf)
(asdf:load-asd (uiop:getenv "DEFINGERPRINTER_ASD"))
(asdf:load-system "defingerprinter")
(defingerprinter:load-library (uiop:getenv "DEFINGERPRINTER_LIBRARY"))

(defingerprinter:with-redactor (redactor)
  (multiple-value-bind (text status)
      (defingerprinter:redact redactor "Contact alice@example.com" :language "en")
    (unless (eq status :ok)
      (error "redact failed: ~A" status))
    (write-line text)))

Output: Contact [EMAIL_1]. redact returns the text and a status; with-redactor closes the context on exit.

Run

Install CFFI, Babel, and Bordeaux-Threads. With the Nix package:

PREFIX=$(cd result && pwd -P)
DEFINGERPRINTER_ASD="$PREFIX/share/common-lisp/source/defingerprinter/defingerprinter.asd" \
DEFINGERPRINTER_LIBRARY="$PREFIX/lib/libdefingerprinter.dylib" \
  sbcl --script examples/lisp.lisp

Use .so instead of .dylib on Linux.

Any language: JSONL

Run the defingerprinter executable and exchange one UTF-8 JSON object per line. It reads sequentially from stdin and flushes one response per request to stdout. The model loads lazily on the first valid redaction call.

Requests

OperationRequestSuccessful response
Redact{"op":"redact","language":"en","text":"synthetic diagnostic"}{"ok":true,"text":"redacted diagnostic"}
Inspect{"op":"status"}{"ok":true,"ready":false} before loading; {"ok":true,"ready":true} after successful redaction
Benchmark{"op":"benchmark","iterations":100}Timing fields with ok:true

status reports whether a model session is loaded. It does not initialize the model. Benchmarking accepts 1 through 1,000 iterations and measures a fixed synthetic diagnostic. Its fields include iterations, input_chars, load_ms, median_ms, and p95_ms; it is a local development operation rather than ordinary diagnostic export.

Request objects reject unknown fields and invalid types. Use JSON serialization, not string interpolation. Embedded newlines in text must be JSON-escaped, so every record occupies one physical line.

Failures

{"ok":false,"dropped":true,"reason":"unsupported_input"}

Reasons include unsupported_input, redaction_failed, and invalid_request. They are static categories, not the original input. Accept diagnostic output only from a valid redaction response with ok:true and a string text.

Supervision checklist

  1. Start a trusted absolute executable path without a shell.
  2. Apply a finite deadline to the process and round trips.
  3. Serialize transactions; there are no request IDs.
  4. Bound UTF-8 request and response records to 32,768 bytes, including the newline.
  5. Validate the exact response keys and types expected for each operation.
  6. On failure, timeout, malformed JSON, oversized output, or EOF, omit the summary, terminate if needed, and reap the child.
  7. Close stdin after the final response and require clean EOF and successful process exit before publishing.

See Node.js and Go for complete standard-library examples.

Manual artifacts

The Nix executable has compiled defaults. A manual bundle resolves default artifacts relative to the executable, or accepts setup-time flags:

/absolute/path/to/defingerprinter \
  --model-dir /absolute/path/to/model \
  --runtime /absolute/path/to/libonnxruntime.dylib

Pass artifact paths at startup, never in per-diagnostic requests. Use .so on Linux.

Integration recipes

Export a diagnostic once

Keep collection, redaction, and publication as separate steps. Fail closed at each boundary:

context = create()
summary = construct_short_diagnostic()
result = redact(context, declared_language, summary)
close(context)
if result succeeded:
    export(result.text)
else:
    omit summary

Export static error counters separately from text.

Output queues

Successful output belongs to the caller. Bound queued text and apply your application’s export and retention policy at the publication boundary.

Multiple workers

Use distinct redactors or C handles for concurrent inference. Keep the selected runtime path consistent process-wide. Bound worker count and input queues; the library’s one-thread inference setting does not bound the number of sessions your application creates.

For one shared C handle, allocate distinct output and written storage for every call. The handle lock serializes native work, but it does not serialize your later reads from shared output memory.

An unresponsive native call

Use a subprocess when a hard deadline is required. On deadline expiry, omit the summary, terminate the process, and reap it. Start a fresh session for the next request. An in-process thread timeout alone does not stop native inference.

Repeated diagnostic fragments

Create one short summary per event. Placeholder identity is local to a request, so do not treat equal-looking placeholders across events as stable entity identifiers. Store only the successful redacted text if your retention policy permits it.

A manual bundle

Resolve model and runtime paths from a trusted installation root before creating contexts. Pass both absolute paths explicitly to C or Lisp. With Rust, construct Redactor with those paths. With the CLI, supply setup-time flags or use its bundle-relative layout. Preserve pinned artifacts and notices as a unit.

Statuses and recovery

Every nonzero C status means omit the diagnostic summary. The caller owns any output buffer, but must ignore its contents on failure. written is reset to zero when valid writable length storage is supplied.

Version-2 C ABI statuses

ValueSymbolMeaning / next action
0OKConsume exactly written UTF-8 bytes
1INVALID_ARGUMENTCorrect pointer/length or path setup
2INVALID_HANDLEUse a current live handle; fix ownership
3ReservedNot returned by ABI 2
4UNSUPPORTED_INPUTReview declared language, encoding, bounds, and script policy
5ARTIFACT_ERRORVerify the pinned model files and setup paths
6RUNTIME_ERRORVerify native runtime availability and process-wide path selection
7INFERENCE_ERROROmit text and inspect the deployment with synthetic input
8BUFFER_TOO_SMALLAllocate at least 4,096 bytes before the next call
9INTERNAL_ERROROmit text; inspect with static diagnostics and a fresh process

The full C names have the DEFINGERPRINTER_ prefix. Error categories are intentionally coarse; they are not guaranteed to identify every underlying cause.

The API retains no source text for retry. Allocate the full output capacity up front instead of using an output-sizing probe. Choose retries according to application policy, without logging the original request.

Rust

RedactionError has four variants:

  • Artifact: invalid pinned artifacts.
  • Runtime: native runtime unavailable.
  • Inference: redaction inference failed.
  • Input: unsupported diagnostic input.

The engine clears its model session after model failures. A later valid call can attempt lazy initialization again. Rust owns the returned String on success; on error there is no output string.

Common Lisp

redact returns nil and a static status on failure. Use its second value, not truthiness of a guessed result. Setup errors are conditions with a static status and a retry restart. Correct setup before invoking the restart; close contexts deterministically.

JSONL

The executable returns ok:false, dropped:true, and a static reason, not ABI numeric statuses. Native artifact/runtime/inference failures are grouped as redaction_failed. Treat transport failures, timeout, and malformed responses as failed redaction too.

Investigate without real diagnostics

Reproduce with a fixed synthetic summary, verify source/model/runtime revisions, then run the native package and its C ABI/CFFI checks. Report static codes and build provenance.

Deployment

Nix package layout

bin/defingerprinter
lib/libdefingerprinter.dylib           # .so on Linux
include/defingerprinter.h
share/common-lisp/source/defingerprinter/
share/licenses/defingerprinter/

Model and runtime are separate store outputs referenced by immutable compile-time paths. Copy or deploy the complete Nix closure, rather than moving just the executable or shared library. Obtain the closure’s references with your usual Nix deployment tooling.

nix build .#default
nix flake check

C ABI and CFFI flake checks perform real inference. Run representative synthetic integration checks in the final deployment environment too. Build-time sandbox isolation and runtime network-denial testing are separate concerns.

Import from another flake

{
  inputs.defingerprinter.url = "git+file:///absolute/path/to/defingerprinter?ref=trunk";
  outputs = { self, defingerprinter }: {
    packages.aarch64-darwin.default =
      defingerprinter.packages.aarch64-darwin.default;
  };
}

Pin the actual revision in your consumer lockfile. trunk is this repository’s main bookmark. No published remote is assumed.

Compatibility

Library release 0.1.0 uses ABI 2. Version 2 permits compatible shared-library replacements on the same platform and architecture. Check defingerprinter_abi_version() before creating contexts. Incompatible future ABIs require a new version. Model and redaction-policy behavior are separately pinned.

The flake exposes default, defingerprinter, model, runtime, and book packages for aarch64-darwin, aarch64-linux, and x86_64-linux. Darwin arm64 is locally tested; validate Linux targets natively. The pinned Nix Darwin runtime requires macOS 14 according to the selected SDK/toolchain.

Manual bundles

The optional packaging scripts prepare a macOS arm64 archive with native artifacts and notices. The CLI can resolve portable defaults relative to its executable. C and CFFI callers must pass absolute artifact paths for this bundle. See the reduced runtime build for the exact build procedure and provenance.

Apply distribution signing and notarization as appropriate. Validate the final signed package, matching architecture, loader paths, actual inference, and process cleanup.

Licenses and notices

The original library code is MIT-licensed. Incorporated upstream code and model artifacts have their own licenses and attribution requirements. Review LICENSE, ATTRIBUTION.md, and licenses/ in the source checkout, and preserve the installed notices when distributing a package or closure.

Development and documentation

Nix builds

The flake uses flake-parts for per-system outputs and Crane for Rust builds. nix/default.nix defines packages, shared Cargo artifacts, documentation, checks, and development shells. nix/runtime.nix builds the reduced ONNX Runtime; nix/dependencies.nix pins its sources.

CI builds natively on macOS ARM64, Linux ARM64, and Linux x86-64. It discovers targets from the flake outputs, including Rust formatting, Clippy, C ABI, and Common Lisp checks.

Combined site and GitHub Pages

nix build .#site --out-link result-site

Open result-site/index.html for the handbook and the Rust API reference for Rust documentation. The flake builds both in the sandbox with pinned tools and vendored Cargo dependencies, treats rustdoc warnings as errors, and checks doctests. Documentation builds need no model or ONNX Runtime artifacts.

The site is configured for https://chakanysystems.github.io/defingerprinter/, including 404 assets under the project subdirectory. The flake CI workflow discovers packages, checks, and development shells from the flake outputs and builds them on native runners. After all build jobs pass, it publishes the built site output through GitHub Pages on pushes to trunk or a manual run.

In the repository’s Settings → Pages, select GitHub Actions as the publishing source. The github-pages environment rules must permit trunk.

Separate documentation outputs

nix build .#book --out-link result-book
nix build .#rustdoc --out-link result-rustdoc

Open result-book/index.html or result-rustdoc/defingerprinter/index.html. The standalone handbook uses relative asset paths.

Source lives in docs/book/ and book.toml. Language chapters include checked-in files from examples/; edit the runnable source rather than duplicating it in Markdown. Missing chapters fail the build.

For a local editing server:

nix develop .#docs -c mdbook serve --hostname 127.0.0.1 --port 3000

The Rust API reference covers public APIs and compiles its examples. Inference examples use no_run because execution requires model artifacts and runtime paths.

Documentation checks

nix develop -c sh scripts/check-docs.sh

The script builds mdBook and strict rustdoc, compiles rustdoc examples, checks Cargo examples, and syntax-checks C/C++ sources. It checks Node.js, Python, and Go examples when those toolchains are installed. These are build/syntax checks, not a substitute for actual-model integration runs.

Library checks

cargo test --locked
cargo clippy --locked --all-targets -- -D warnings
nix flake check

Actual-model Cargo tests are explicitly ignored in ordinary runs and require RAMPART_MODEL_DIR and RAMPART_RUNTIME. C ABI and Common Lisp Nix checks use the compiled artifacts for real inference. See the repository README for reference-parity testing.

Documentation configuration follows the official mdBook configuration reference.

Reduced CPU runtime

Defingerprinter uses ONNX Runtime 1.24.2, reduced to the original pinned Rampart q4 graph. Keep graph optimization disabled: graph rewrites can introduce kernels outside the operator list.

Nix build

nix build .#runtime
nix build .#default
nix flake check

nix/runtime.nix verifies the official source archive and immutable dependency archives. Nix fetches dependencies separately; CMake dependency downloads are disabled with FETCHCONTENT_FULLY_DISCONNECTED. The original ONNX loader, exceptions and contrib com.microsoft::MatMulNBits are retained. The configuration uses MinSizeRel, LTO, reduced operator registration, disabled ML/training/RTTI, and CPU execution only. It supports ONNX Runtime C API 22, as requested by the Rust binding.

C++ module dependency scanning is disabled because no modules are used and clang-scan-deps bypasses Nix compiler-wrapper include flags. Darwin minimum-version hook is 13.3, but the pinned SDK/toolchain selects macOS 14.0 during configuration. Treat the Nix output as requiring macOS 14; the older manual artifact below used a separate macOS 13 target.

Nix model fetches are fixed-output downloads pinned to revision b1993e4e68b082835b80ffc65acc03325ea2e501. All four files also have compile-time SHA256 checks in Rust. Runtime initialization is lazy, with one CPU thread, disabled spinning, graph optimization and telemetry. Model and runtime paths are compiled into the Nix library.

C ABI and CFFI flake checks load the actual model. Network isolation during Nix builds depends on the host’s sandbox configuration; validate runtime inference separately under a network-denying sandbox when certifying deployment.

On 2026-10-10, the verified Darwin arm64 Nix runtime occupied 9,811,888 bytes (9.36 MiB). The complete Defingerprinter runtime closure, including model and libiconv, was 73.6 MiB. Compiler source-path remapping and removal of an unused protobuf CMake export reduced the closure from 150.5 MiB; dependency source archives are explicitly forbidden as runtime references. C ABI and CFFI inference passed with networking denied.

Optional manual macOS arm64 bundle

Install Cargo, Python 3, CMake, Xcode command-line tools, and hf on the build machine. Obtain the four pinned model files:

hf download nationaldesignstudio/rampart \
  --revision b1993e4e68b082835b80ffc65acc03325ea2e501 \
  --include config.json tokenizer.json tokenizer_config.json onnx/model_q4.onnx \
  --local-dir models/rampart
JOBS=12 sh scripts/build-runtime.sh
sh scripts/package.sh

To reuse another local model directory, set DEFINGERPRINTER_MODEL_DIR to its absolute path. Downloads and compilation logs go under ignored target/native-runtime/. The build script creates a Python virtual environment, checks source/model hashes, regenerates the operator list using upstream tooling, and compares it with nix/rampart-operators.config. Dependency archive hashes and Python package versions are pinned by the script.

The package script builds the executable and C shared library, copies headers, CFFI sources, model assets and dependency notices, verifies actual inference, and emits target/defingerprinter-darwin-arm64.tar.gz with SHA256SUMS. The CLI resolves portable assets relative to its executable; C and CFFI callers must supply absolute artifact paths for this manual bundle. End users need no compiler, Node or Python. Ad-hoc signatures are development signatures; apply distribution signing and notarization before shipping.

Historical manual artifact

The pre-extraction macOS arm64 runtime built on 2026-10-10 occupied 6,360,128 bytes (6.07 MiB), versus 44,589,928 bytes (42.52 MiB) for the comparison Node runtime. The original q4 model is 14,693,379 bytes (14.01 MiB). These are manual-build measurements, not Nix closure sizes.

Manual runtime SHA256:

6723be987f6ef389f18841da98212ebf7f1faafab016f438662e0410ec8ec03a

C API 22 inference passed at sequence lengths 4, 16, 128 and 512 with finite logits and shape [1, sequence_length, 35]. Full tokenizer, NER-window and structured-redaction parity were checked against the pinned JavaScript reference during extraction. Compiler/SDK changes affect binary fingerprints; retain each build’s provenance and test the final artifact.

References