Welcome.
JACS is primarily a portable cryptographic signing and verification library. Its core job is to let agents, services, and tools sign data in one language or integration surface and verify it in another without a central server.
The main primitive is a verifiable JSON document: canonical JSON bytes, schema-declared structure, content hash, signer identity, signing algorithm, and cryptographic signature. Rust is the core implementation; Python, Node.js, Go, CLI, and MCP bindings must preserve the same signing, verification, error, and schema contracts.
Use JACS at trust boundaries: documents, tool outputs, files, text, images, email payloads, A2A artifacts, and agreements. Storage backends, MCP tools, adapters, and generated schema docs exist to make portable signatures usable across libraries, languages, and deployment contexts; they are not the core product by themselves.
You may use the top level Cargo.toml to understand the repo. jacs/ is the directory with the core library. ./jacspy is the python wrapper with functionality for integrations ./jacsnpm is the npm/node wrapper with functionality for integrations
Look for examples in tests for how to use the library. README.md and CHANGELOG.md may be useful to understand some future goals and what has been done.
-
Observability. Good logging and how a system admin monitors the system. More 12-factor. Structured JSON logs to stdout, env-driven config (
RUST_LOG,LOG_FORMAT,LOG_LEVEL),/metricson Prometheus,/healthand/health/ready, request IDs propagated. Auth and verification failures log at WARN, not DEBUG. Every PRD says what the sysadmin sees when this fails: which log line, which metric, which alert.jacs mcpand the JACS CLI must initialize a tracing subscriber before serving — silent stdio is not acceptable. -
Vertical integration. In a buy-or-build decision, prefer a well-integrated monolith over a bloated open-source dependency we use 10% of, when the feature is simple, sure, and well known. Every PRD that introduces or depends on an external service includes a buy/build assessment: what surface we use, what ships unused, what the smallest owned alternative would cost.
-
Simplicity. We don't want a cap on tasks. We want small reversible changes. 100 tasks is fine if each is clear, well-defined, simple, and atomic. The bar is per-task: each task is reversible — its diff can be reverted in one commit without dependent fallout. When stuck, cut scope before adding layers.
binding-core/src/simple_wrapper.rs::SimpleAgentWrapper is the public binding API. All language bindings (PyO3, napi-rs, CGo) call into it. New methods that need to be exposed to Python/Node/Go go here, and you must update binding-core/tests/fixtures/method_parity.json (see Feature Parity Enforcement below) or four snapshot tests fail.
binding-core/src/lib.rs::AgentWrapper is an internal Arc<Mutex<Agent>> wrapper used by SimpleAgentWrapper and the optional a2a / attestation feature impls. Do not add new public binding methods directly to AgentWrapper.
- haisdk at
~/personal/haisdk— wraps JACS, pins exact JACS versions inrust/Cargo.toml,python/pyproject.toml, andnode/package.json. Local dev: haisdk'srust/Cargo.tomlpatches to../../JACS/jacs,../../JACS/binding-core,../../JACS/jacs-mcp. After a JACS bump, runmake check-versionsin haisdk before publishing JACS. - hai (API) at
~/personal/hai/api— verifies JACS signatures via middleware. Will fail at startup if the JACS auth contract changes.
cargo test -p <pkg> --test <file> -- --nocapture <pattern> 2>&1 | tail -80The -- separator is required; without it cargo reports unexpected argument. Don't reformat the recipe on each run.
Cross-language feature parity is enforced through canonical JSON fixtures that serve as the single source of truth. When you add or remove a method, error kind, CLI command, MCP tool, or adapter, you must update the relevant fixture — snapshot tests in all languages will fail otherwise.
| Fixture | What it tracks | Consumed by |
|---|---|---|
binding-core/tests/fixtures/method_parity.json |
32 SimpleAgentWrapper public methods |
Rust, Python, Node, Go |
binding-core/tests/fixtures/parity_inputs.json |
14 ErrorKind variants + behavioral notes |
Rust, Python, Node, Go |
binding-core/tests/fixtures/adapter_inventory.json |
Framework adapter modules and public functions | Rust, Python, Node |
binding-core/tests/fixtures/cli_mcp_alignment.json |
CLI-to-MCP tool mapping (aligned, CLI-only, MCP-only) | Rust |
jacs-cli/contract/cli_commands.json |
38 CLI commands + 4 feature-gated | Rust (extracted from Clap tree) |
jacs-mcp/contract/jacs-mcp-contract.json |
48 MCP tools with parameter schemas | Python, Node, Go |
| Change | Update these fixtures | Tests that will catch you |
|---|---|---|
Add/remove a SimpleAgentWrapper method |
method_parity.json |
method_parity.rs, test_method_parity.py, method-parity.test.js, method_parity_test.go |
Add/remove an ErrorKind variant |
parity_inputs.json (error_kinds array) |
parity.rs, test_error_parity.py, error-parity.test.js, error_parity_test.go |
| Add/remove a CLI command | cli_commands.json + cli_mcp_alignment.json |
cli_command_snapshot.rs, cli_mcp_alignment.rs |
| Add/remove an MCP tool | jacs-mcp-contract.json + cli_mcp_alignment.json |
mcp_contract.test.js, test_mcp_contract.py, mcp_contract_drift_test.go, cli_mcp_alignment.rs |
| Add/remove a framework adapter | adapter_inventory.json |
adapter_inventory.rs, test_adapter_inventory.py, adapter-inventory.test.js |
Each language defines its own exclusions and name mappings (e.g., to_yaml is excluded from Python/Go because those bindings don't expose it). The fixture is always the source of truth.
See RELEASING.md for the complete release process, including the full file checklist, tag-based CI workflow, and troubleshooting failed releases.
When bumping the JACS version, all of the following locations must be updated to the same version. The publish order matters — crates.io requires dependencies to be published before dependents.
# Version bump (updates ALL files automatically, including storage crates)
make bump-patch # 0.9.6 -> 0.9.7
make bump-minor # 0.9.6 -> 0.10.0
make bump-major # 0.9.6 -> 1.0.0
make versions # show all detected versions from source files
make check-versions # fail if any main-track versions don't match
# Pre-publish compile check (catches -D warnings failures before publish)
RUSTFLAGS="-D warnings" cargo check -p jacs -p jacs-core -p jacs-binding-core -p jacs-mcp -p jacs-cli -p jacs-wasm
# CI-triggered releases (via git tags)
make release-jacs # crates.io (jacs-core, jacs-media, jacs, binding-core, jacs-mcp, jacs-cli)
make release-jacspy # PyPI
make release-jacsnpm # npm (native bindings)
make release-jacs-wasm # npm @jacs/wasm (browser bindings; tag wasm-vX.Y.Z)
make release-cli # GitHub Release binaries
make release-jacs-storage # storage backend crates
make release-everything # all of the above
# Retry failed releases (deletes tag, retags, pushes)
make retry-jacspy
make retry-jacsnpm
make retry-jacs-wasm
make retry-cli
# Local publish (requires credentials)
make publish-jacs # all Rust crates in dependency order
make publish-jacspy # PyPI
make publish-jacsnpm # npm (native bindings)
make publish-jacs-wasm # npm @jacs/wasm (browser bindings)
# Browser bindings build + test (PRD §3.2 / §4.8)
make build-wasm # wasm-pack build + finalize pkg metadata
make test-wasm # wasm-pack test --headless --chromeCrates must be published in this order with ~30s delays between each for crates.io indexing:
jacs-core(portable protocol layer — no JACS dependencies; consumed byjacsandjacs-wasm)jacs-media(signing helpers for media; depends on nothing in this list)jacs(depends onjacs-coreandjacs-media)jacs-binding-core(depends onjacs)jacs-mcp(depends onjacs+jacs-binding-core)jacs-cli(depends onjacs+jacs-mcp)
The CI workflow (release-crate.yml) and make publish-jacs handle this order automatically. The browser bindings crate (jacs-wasm) is published separately to npm only via release-wasm.yml; it is not pushed to crates.io.
| File | Field |
|---|---|
jacs/Cargo.toml |
version |
jacs-core/Cargo.toml |
version |
jacs-wasm/Cargo.toml |
version |
binding-core/Cargo.toml |
version |
jacs-media/Cargo.toml |
version |
jacs-mcp/Cargo.toml |
version |
jacs-cli/Cargo.toml |
version |
jacspy/Cargo.toml |
version |
jacsnpm/Cargo.toml |
version |
jacsgo/lib/Cargo.toml |
version |
| File | Dependency |
|---|---|
jacs/Cargo.toml |
jacs-core = { version = "X.Y.Z", path = ... } |
jacs/Cargo.toml |
jacs-media = { version = "X.Y.Z", path = ... } |
jacs-wasm/Cargo.toml |
jacs-core = { version = "X.Y.Z", path = ... } |
binding-core/Cargo.toml |
jacs = { version = "X.Y.Z", path = ... } |
jacs-mcp/Cargo.toml |
jacs = { version = "X.Y.Z", path = ... } |
jacs-mcp/Cargo.toml |
jacs-binding-core = { version = "X.Y.Z", path = ... } |
jacs-cli/Cargo.toml |
jacs = { version = "X.Y.Z", path = ... } |
jacs-cli/Cargo.toml |
jacs-mcp = { version = "X.Y.Z", path = ... } |
IMPORTANT: These depend on the jacs core crate. When you bump the main
JACS version, you must also bump the storage crate versions (at least a
patch bump) because their jacs dep version changes and crates.io won't let
you re-publish the same version. make release-jacs-storage will skip them
if the tag already exists.
| File | Fields |
|---|---|
jacs-duckdb/Cargo.toml |
version (bump!), jacs = { version = "X.Y.Z" } |
jacs-redb/Cargo.toml |
version (bump!), jacs = { version = "X.Y.Z" } |
jacs-surrealdb/Cargo.toml |
version (bump!), jacs = { version = "X.Y.Z" } |
jacs-postgresql/Cargo.toml |
version (bump!), jacs = { version = "X.Y.Z" } |
| File | Field |
|---|---|
jacspy/pyproject.toml |
version |
jacsnpm/package.json |
version |
jacs-wasm/package.template.json |
version (rewritten into pkg/package.json by bash jacs-wasm/scripts/finalize-pkg.sh — see Task 020) |
| File | Field |
|---|---|
jacs-mcp/contract/jacs-mcp-contract.json |
server.version |
| File | What to update |
|---|---|
README.md |
Footer version line |
jacs/README.md |
Footer version line |
jacs-cli/README.md |
Footer version line |
CHANGELOG.md |
Add new ## X.Y.Z section at top |
These pin the exact JACS version used by haisdk. Update after publishing JACS to crates.io.
| File | Dependencies |
|---|---|
rust/haisdk/Cargo.toml |
jacs = { version = "=X.Y.Z" } and jacs_local_path |
rust/hai-mcp/Cargo.toml |
jacs, jacs-binding-core, jacs-mcp version pins |
rust/haisdk-cli/Cargo.toml |
jacs-mcp version pin |
python/pyproject.toml |
jacs==X.Y.Z |
After bumping, verify with:
# Check all JACS versions match
make check-versions
# Verify workspace compiles cleanly (same flags as CI)
RUSTFLAGS="-D warnings" cargo check -p jacs -p jacs-binding-core -p jacs-mcp -p jacs-cli
# Regenerate lockfile
cargo generate-lockfile
# Run tests
make test-rust-prThe canonical install command for users is:
cargo install jacs-cliThis installs a single jacs binary with CLI + MCP server built in. The MCP server is started with jacs mcp (stdio transport only, no HTTP).
Do NOT reference any of these deprecated patterns in docs:
cargo install jacs --features clijacs mcp installjacs mcp run- A separate
jacs-mcpbinary for end users