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

Native library reference. Extended CLI examples use jacs-compat. This source reference can include unreleased changes; see the package guide for installation and MCP profiles for available tools.

Migration Guide

Source and published packages: Version 0.16.1 has a recorded release inventory. Version 0.16.0 is the first release whose source includes the native PQ DID/JWK and Agent Card behavior. See the identity integration guide.

Version-numbered sections below are historical migration notes, not the current install or API reference. Use the package guide and current language chapters for supported calls, defaults and profiles. The algorithm migration section describes current explicit upgrades.

Version Compatibility

JACS maintains backward compatibility for document verification:

  • Documents signed with older versions can be verified with newer versions
  • Older JACS versions cannot verify documents using newer cryptographic algorithms

0.9 → 0.10

v0.10.0 adds inline text signatures, image / media embedding, and the new MissingSignature error variant. No existing API changes behaviour.

Additive surface — no opt-in required

Five new methods per binding, five new CLI verbs, five new MCP tools. Pre-existing call sites are unaffected.

DomainNew surface
CLIjacs-compat sign-text, jacs-compat verify-text, jacs-compat sign-image, jacs-compat verify-image, jacs-compat extract-media-signature
Python (jacs.simple / JacsClient)sign_text, verify_text, sign_image, verify_image, extract_media_signature
Node (@hai.ai/jacs/simple / JacsClient)signText, verifyText, signImage, verifyImage, extractMediaSignature
Go (jacsgo)SignText, VerifyText, SignImage, VerifyImage, ExtractMediaSignature
MCPjacs_sign_text, jacs_verify_text, jacs_sign_image, jacs_verify_image, jacs_extract_media_signature

Breaking consideration: ErrorKind exhaustive matches

The new MissingSignature variant on jacs::error::ErrorKind will trigger an unreachable-pattern warning (or a compile error in -D warnings) on Rust call sites that exhaustively match without a catch-all.

#![allow(unused)]
fn main() {
// before — does not compile under -D warnings in v0.10.0
match err.kind() {
    ErrorKind::InvalidSignature => ...,
    ErrorKind::DocumentNotFound => ...,
}

// after — option 1: explicit arm
match err.kind() {
    ErrorKind::InvalidSignature => ...,
    ErrorKind::DocumentNotFound => ...,
    ErrorKind::MissingSignature => ...,
}

// after — option 2: catch-all
match err.kind() {
    ErrorKind::InvalidSignature => ...,
    ErrorKind::DocumentNotFound => ...,
    _ => ...,
}
}

Python / Node / Go bindings are unaffected — their error types are not exhaustive enums.

Verify semantics change is opt-in via strict

verify-text and verify-image are new commands in v0.10.0. There is no existing caller to break. The default mode is permissive (missing-signature is a typed status, not an error); --strict opts in to error-on-missing.

No existing command (jacs-compat verify, jacs-compat document verify, etc.) changes its exit codes or its return shape.

Migrating Node.js from 0.6.x to 0.7.0

Breaking Change: Async-First API

In v0.7.0, all NAPI operations return Promises by default. Sync variants are available with a Sync suffix, following the Node.js convention (like fs.readFile vs fs.readFileSync).

Before (v0.6.x):

const agent = new JacsAgent();
agent.load('./jacs.config.json');
const doc = agent.createDocument(JSON.stringify(content));
const isValid = agent.verifyDocument(doc);

After (v0.7.0, async -- recommended):

const agent = new JacsAgent();
await agent.load('./jacs.config.json');
const doc = await agent.createDocument(JSON.stringify(content));
const isValid = await agent.verifyDocument(doc);

After (v0.7.0, sync -- for scripts/CLI):

const agent = new JacsAgent();
agent.loadSync('./jacs.config.json');
const doc = agent.createDocumentSync(JSON.stringify(content));
const isValid = agent.verifyDocumentSync(doc);

Method Renaming Summary

v0.6.xv0.7.0 Async (default)v0.7.0 Sync
agent.load(path)await agent.load(path)agent.loadSync(path)
agent.createDocument(...)await agent.createDocument(...)agent.createDocumentSync(...)
agent.verifyDocument(doc)await agent.verifyDocument(doc)agent.verifyDocumentSync(doc)
agent.verifyAgent()await agent.verifyAgent()agent.verifyAgentSync()
agent.updateAgent(json)await agent.updateAgent(json)agent.updateAgentSync(json)
agent.updateDocument(...)await agent.updateDocument(...)agent.updateDocumentSync(...)
agent.signString(data)await agent.signString(data)agent.signStringSync(data)
agent.createAgreement(...)await agent.createAgreement(...)agent.createAgreementSync(...)
agent.signAgreement(...)await agent.signAgreement(...)agent.signAgreementSync(...)
agent.checkAgreement(...)await agent.checkAgreement(...)agent.checkAgreementSync(...)

V8-Thread-Only Methods (No Change)

These methods remain synchronous without a Sync suffix because they use V8-thread-only APIs (Env, JsObject):

  • agent.signRequest(params) -- unchanged
  • agent.verifyResponse(doc) -- unchanged
  • agent.verifyResponseWithAgentId(doc) -- unchanged

Simplified API (Module-Level)

Historical module-level comparison; each block is a separate version-specific example.

Before (v0.6.x):

await jacs.quickstart({ name: 'my-agent', domain: 'agent.example.com' });
const signed = jacs.signMessage({ action: 'approve' });

After (v0.7.0 asynchronous API):

await jacs.quickstart({ name: 'my-agent', domain: 'agent.example.com' });
const signed = await jacs.signMessage({ action: 'approve' });

Alternative (v0.7.0 synchronous API):

jacs.quickstartSync({ name: 'my-agent', domain: 'agent.example.com' });
const signed = jacs.signMessageSync({ action: 'approve' });

Pure Sync Functions (No Change)

Historical synchronous API list (current methods may call native code; consult the current API reference):

  • hashString(), createConfig(), getPublicKey(), isLoaded(), exportAgent(), getAgentInfo()
  • getDnsRecord(), getWellKnownJson(), verifyStandalone()
  • Trust store: trustAgent(), listTrustedAgents(), untrustAgent(), isTrusted(), getTrustedAgent()

Migration Steps

  1. Update dependency: npm install @hai.ai/jacs@0.7.0
  2. Add await to all NAPI method calls, or append Sync to method names
  3. Update test assertions to handle Promises (use async/await in test functions)
  4. V8-thread-only methods (signRequest, verifyResponse, verifyResponseWithAgentId) need no changes

Migrating from 0.5.1 to 0.5.2

Migration Notes

Historical encryption change: 0.5.2 increased PBKDF2 to 600,000 iterations. Current new writes use the V2 Argon2id/AES-GCM envelope and retain legacy reads. Use the current jacs-compat key reencrypt workflow to change at-rest encryption; generating a new signing key is a separate identity rotation.

Deprecated Environment Variables

  • JACS_USE_SECURITY is now JACS_ENABLE_FILESYSTEM_QUARANTINE. The old name still works with a deprecation warning.

New Environment Variables

VariableDefaultDescription
JACS_MAX_SIGNATURE_AGE_SECONDS0 (no expiration)Maximum age of valid signatures. Set to a positive value to enable (e.g., 7776000 for 90 days).
JACS_REQUIRE_EXPLICIT_ALGORITHMfalseWhen true, reject verification if signingAlgorithm is missing.
JACS_ENABLE_FILESYSTEM_QUARANTINEfalseEnable filesystem quarantine (replaces JACS_USE_SECURITY).

Deprecated Method Aliases (0.9.0)

In v0.9.0, several method names were standardized. The old names remain as aliases for backward compatibility but are deprecated and will be removed in 1.0.0 (minimum 2 minor releases after deprecation).

Runtime Deprecation Warnings

Set the JACS_SHOW_DEPRECATIONS=1 environment variable to emit runtime warnings when deprecated methods are called:

export JACS_SHOW_DEPRECATIONS=1

This is recommended during development and CI to identify code that needs updating.

Deprecated Alias Table

SDKDeprecated MethodCanonical ReplacementSinceRemoval
Python (binding)agent.wrap_a2a_artifact()agent.sign_artifact()0.9.01.0.0
Python (A2A)a2a.wrap_artifact_with_provenance()a2a.sign_artifact()0.9.01.0.0
Node.js (binding)agent.wrapA2aArtifact()agent.signArtifact()0.9.01.0.0
Node.js (binding)agent.wrapA2aArtifactSync()agent.signArtifactSync()0.9.01.0.0
Node.js (A2A)a2a.wrapArtifactWithProvenance()a2a.signArtifact()0.9.01.0.0
Rust (core)agent.wrap_a2a_artifact()agent.sign_artifact()0.9.01.0.0
GoSignA2AArtifact() (simple API)Uses sign_artifact internally----

All aliases behave identically to their canonical replacements. No behavioral changes are needed when migrating -- only rename the method call.

Migration Examples

Python:

# Before (deprecated)
wrapped = agent.wrap_a2a_artifact(artifact_json, "artifact")

# After (canonical)
signed = agent.sign_artifact(artifact_json, "artifact")

Node.js:

// Before (deprecated)
const wrapped = await agent.wrapA2aArtifact(artifactJson, 'artifact');

// After (canonical)
const signed = await agent.signArtifact(artifactJson, 'artifact');

Python A2A integration:

# Before (deprecated)
wrapped = a2a.wrap_artifact_with_provenance(artifact, "artifact")

# After (canonical)
signed = a2a.sign_artifact(artifact, "artifact")

Node.js A2A integration:

// Before (deprecated)
const wrapped = await a2a.wrapArtifactWithProvenance(artifact, 'artifact');

// After (canonical)
const signed = await a2a.signArtifact(artifact, 'artifact');

Module-Level Function Deprecation (Reminder)

Module-level functions (e.g., jacs.load(), jacs.sign_request() in Python; load(), signRequest() in Node.js) were deprecated in earlier releases. Use JacsAgent instance methods instead. See the Python and Node.js API references for the full list.


Migrating from 0.2.x to 0.3.x

Configuration Changes

New Configuration Fields:

{
  "observability": {
    "logs": { "enabled": true, "level": "info" },
    "metrics": { "enabled": false },
    "tracing": { "enabled": false }
  }
}

Deprecated Fields:

  • jacs_log_level → Use observability.logs.level
  • jacs_log_file → Use observability.logs.destination

Migration Steps

  1. Update Configuration:

    # Backup current config
    cp jacs.config.json jacs.config.json.backup
    
    # Update to new format
    # Add observability section if needed
    
  2. Update Dependencies:

    # Node.js
    npm install @hai.ai/jacs@latest
    
    # Python
    pip install --upgrade jacs
    
  3. Verify Existing Documents:

    jacs-compat document verify -d ./jacs_data/documents/
    

Migrating Storage Backends

Filesystem to AWS S3

  1. Create S3 Bucket:

    aws s3 mb s3://my-jacs-bucket
    
  2. Update Configuration:

    {
      "jacs_default_storage": "aws",
      "jacs_data_directory": "s3://my-jacs-bucket/data"
    }
    
  3. Set Environment Variables:

    export AWS_ACCESS_KEY_ID="your-key"
    export AWS_SECRET_ACCESS_KEY="your-secret"
    export AWS_REGION="us-east-1"
    
  4. Migrate Documents:

    # Upload existing documents
    aws s3 sync ./jacs_data/ s3://my-jacs-bucket/data/
    
  5. Verify Migration:

    jacs-compat document verify -d s3://my-jacs-bucket/data/documents/
    

AWS S3 to Filesystem

  1. Download Documents:

    aws s3 sync s3://my-jacs-bucket/data/ ./jacs_data/
    
  2. Update Configuration:

    {
      "jacs_default_storage": "fs",
      "jacs_data_directory": "./jacs_data"
    }
    
  3. Verify Documents:

    jacs-compat document verify -d ./jacs_data/documents/
    

Migrating Cryptographic Algorithms

Ed25519 to Post-Quantum

Native JACS signing defaults to pq2025 (ML-DSA-87 / FIPS-204). Explicit ed25519 creation remains supported; agents using it continue to load and sign with the canonical ring-Ed25519 label until they rotate.

Key rotation is the designated migration path. Rotation without an override preserves the current algorithm. Explicit Ed25519-to-pq2025 upgrades are supported; PQ-to-Ed25519 downgrades are rejected.

  1. Rotate Keys:

    jacs-compat agent rotate-keys --algorithm pq2025
    
  2. Re-issue the Compatibility Key Binding (if present):

    The ES256 compatibility key binding is signed by the native root, so rotation supersedes it. Ecosystem exports fail with a "re-issue" error until a new binding is signed by the current native root:

    jacs-compat agent issue-compat-binding
    
  3. Re-sign Critical Documents (Optional):

    // Re-sign documents with new algorithm
    const oldDoc = JSON.parse(fs.readFileSync('./old-doc.json'));
    
    // Remove old signature fields
    delete oldDoc.jacsSignature;
    delete oldDoc.jacsSha256;
    
    // Create new signed version
    const newDoc = await agent.createDocument(JSON.stringify(oldDoc));
    

Note: Old documents remain valid with old signatures. Re-signing is only needed for documents that require the new algorithm.

Migrating Between Platforms

Node.js to Python

Both platforms use the same document format:

// Node.js - create document
const signedDoc = await agent.createDocument(JSON.stringify(content));
fs.writeFileSync('doc.json', signedDoc);
# Python - verify the same document
with open('doc.json', 'r') as f:
    doc_string = f.read()

is_valid = agent.verify_document(doc_string)

Sharing Agents Between Platforms

Agents can be used across platforms by sharing configuration:

  1. Export Agent Files:

    jacs_keys/
    ├── private.pem
    └── public.pem
    jacs.config.json
    
  2. Use Same Config in Both:

    // Node.js
    await agent.load('./jacs.config.json');
    
    # Python
    agent.load('./jacs.config.json')
    

Migrating Key Formats

Unencrypted to Encrypted Keys

  1. Encrypt Existing Key:

    # Backup original
    cp jacs_keys/private.pem jacs_keys/private.pem.backup
    
    # Encrypt with password
    openssl pkcs8 -topk8 -in jacs_keys/private.pem \
      -out jacs_keys/private.pem.enc -v2 aes-256-cbc
    
    # Remove unencrypted key
    rm jacs_keys/private.pem
    mv jacs_keys/private.pem.enc jacs_keys/private.pem
    
  2. Update Configuration:

    {
      "jacs_agent_private_key_filename": "private.pem"
    }
    
  3. Set Password:

    export JACS_PRIVATE_KEY_PASSWORD="your-secure-password"
    

Database Migration

Adding Database Storage

If migrating from filesystem to include database storage:

  1. Create Database Schema:

    CREATE TABLE jacs_documents (
      id UUID PRIMARY KEY,
      version_id UUID NOT NULL,
      document JSONB NOT NULL,
      created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
      UNIQUE(id, version_id)
    );
    
  2. Import Existing Documents:

    const fs = require('fs');
    const path = require('path');
    const { Pool } = require('pg');
    
    const pool = new Pool({ connectionString: process.env.DATABASE_URL });
    const docsDir = './jacs_data/documents';
    
    async function importDocuments() {
      const docDirs = fs.readdirSync(docsDir);
    
      for (const docId of docDirs) {
        const docPath = path.join(docsDir, docId);
        const versions = fs.readdirSync(docPath);
    
        for (const versionFile of versions) {
          const docString = fs.readFileSync(
            path.join(docPath, versionFile),
            'utf-8'
          );
          const doc = JSON.parse(docString);
    
          await pool.query(`
            INSERT INTO jacs_documents (id, version_id, document)
            VALUES ($1, $2, $3)
            ON CONFLICT (id, version_id) DO NOTHING
          `, [doc.jacsId, doc.jacsVersion, doc]);
        }
      }
    }
    
    importDocuments();
    

MCP Integration Migration

Adding JACS to Existing MCP Server

  1. Install JACS:

    npm install @hai.ai/jacs
    
  2. Wrap Existing Transport:

    // Before
    const transport = new StdioServerTransport();
    await server.connect(transport);
    
    // After
    import { createJACSTransportProxy } from '@hai.ai/jacs/mcp';
    
    const baseTransport = new StdioServerTransport();
    const secureTransport = createJACSTransportProxy(
      baseTransport,
      './jacs.config.json',
      'server'
    );
    await server.connect(secureTransport);
    
  3. Update Client:

    // Client also needs JACS
    const baseTransport = new StdioClientTransport({ command: 'node', args: ['server.js'] });
    const secureTransport = createJACSTransportProxy(
      baseTransport,
      './jacs.client.config.json',
      'client'
    );
    await client.connect(secureTransport);
    

HTTP API Migration

Migrating unbound request credentials to JACS v2

Legacy request credentials signed only signer ID, timestamp, and nonce. They did not authenticate the HTTP method, target, query, body, or receiving service, so a fresh credential could be transplanted to another request. Legacy builders remain available for compatibility and emit a WARN; migrate callers to v2.

  1. Serialize the final request body once.
  2. Build a v2 header from the actual method, absolute URL (including query), those exact body bytes, and a non-empty service-specific audience.
  3. Send the body without reformatting it.
  4. On the server, resolve the expected signer to a trusted public key and verify against the actual request context.
  5. Atomically consume the nonce. Use a shared store for multiple replicas and retain it through issued_at + max_age; Rust callers should use request_auth_replay_ttl(&verified_claims, max_age).
  6. Reject legacy headers after every client is migrated.
APILegacyJACS v2
Rustprotocol::build_auth_header(&mut agent)SimpleAgent::build_request_auth_header(method, url, body, audience)
PythonSimpleAgent.build_auth_header()SimpleAgent.build_request_auth_header(method, url, body, audience)
Node.jsJacsSimpleAgent.buildAuthHeader()JacsSimpleAgent.buildRequestAuthHeader(method, url, body, audience)
Go JacsAgentBuildAuthHeader()BuildRequestAuthHeader(method, url, body, audience)
Go JacsSimpleAgentBuildAuthHeader()BuildRequestAuthHeader(method, url, body, audience)

The APIs are additive: the legacy name takes no request context, while the request-bound name requires the request values and emits JACS v2.<claims>.<signature>. A header without that prefix is not the new contract.

Legacy construction emits a structured WARN on every use. Strict deployments can set JACS_REJECT_UNBOUND_AUTH_HEADER=true after migrating callers. Verification policy should distinguish legacy and v2 explicitly, never silently fall back after a v2 verification failure.

For signed responses, migrate payload-only or generic document verification to sign_response / signResponse and strict event unwrapping. The v2 response signature covers payload and all metadata. Unknown signers, plain events, legacy envelopes, and mutations are errors; do not catch the error and parse the untrusted payload anyway.

Verification proves possession of the public key selected under your trust policy. It does not establish a real-world identity merely because the signed envelope contains a name, agent ID, domain, or timestamp.

Adding JACS to Existing Express API

  1. Install Middleware:

    npm install @hai.ai/jacs
    
  2. Add Middleware to Routes:

    import { JACSExpressMiddleware } from '@hai.ai/jacs/http';
    
    // Before
    app.use('/api', express.json());
    
    // After - for JACS-protected routes
    app.use('/api/secure', express.text({ type: '*/*' }));
    app.use('/api/secure', JACSExpressMiddleware({
      configPath: './jacs.config.json'
    }));
    
    // Keep non-JACS routes unchanged
    app.use('/api/public', express.json());
    
  3. Update Route Handlers:

    // Before
    app.post('/api/data', (req, res) => {
      const payload = req.body;
      // ...
    });
    
    // After
    app.post('/api/secure/data', (req, res) => {
      const payload = req.jacsPayload;  // Verified payload
      // ...
    });
    

Troubleshooting Migration

Common Issues

Documents Not Verifying After Migration:

  • Check algorithm compatibility
  • Verify keys were copied correctly
  • Ensure configuration paths are correct

Key File Errors:

  • Verify file permissions (600 for private key)
  • Check key format matches algorithm
  • Ensure password is set for encrypted keys

Storage Errors After Migration:

  • Verify storage backend is accessible
  • Check credentials/permissions
  • Ensure directory structure is correct

Verification Checklist

After any migration:

  1. Verify Configuration:

    jacs-compat config read
    
  2. Verify Agent:

    jacs-compat agent verify
    
  3. Verify Sample Document:

    jacs-compat document verify -f ./sample-doc.json
    
  4. Test Document Creation:

    echo '{"test": true}' > test.json
    jacs-compat document create -f test.json
    
  5. Verify Version:

    jacs-compat --version
    

Rollback Procedures

If migration fails:

  1. Restore Configuration:

    cp jacs.config.json.backup jacs.config.json
    
  2. Restore Keys:

    cp -r jacs_keys.backup/* jacs_keys/
    
  3. Restore Dependencies:

    # Node.js
    npm install @hai.ai/jacs@previous-version
    
    # Python
    pip install jacs==previous-version
    
  4. Verify Rollback:

    jacs-compat agent verify
    jacs-compat document verify -d ./jacs_data/documents/
    

See Also