@axtary/ledger
v0.6.1
Published
Append-only JSONL action ledger with hash-chain verification for Axtary.
Maintainers
Readme
@axtary/ledger
Append-only JSONL action ledger with hash-chain verification for Axtary.
Early 0.x release: the runtime path is real and tested, but the API is not stable yet and may change between minor versions.
The source repository is currently private. Public product documentation and runnable guides are at axtary.com/docs.
npm install @axtary/ledgerWhat It Does
- Appends Axtary ledger records to local JSONL.
- Serializes concurrent processes with an inter-process lease lock.
- Appends in place and synchronizes the file plus directory metadata before returning success; prior ledger bytes are never rewritten.
- Maintains a private O(1) head checkpoint and reconstructs it from the verified JSONL chain after a crash or legacy-file migration.
- Maintains a
previousLedgerHash -> ledgerHashchain. - Reads ledger records back in order.
- Verifies hash-chain continuity and record integrity.
- Exports verified records by inclusive date range and decision type.
- Emits a detached signed attestation over an export, and an RFC 6962
transparency-log signed tree head (
merkleRoot/treeSize) with inclusion and consistency proofs (proveLedgerInclusion/proveLedgerConsistency+verifyLedgerInclusionProof/verifyLedgerConsistencyProof). Inclusion proves one record is committed by a signed head; consistency proves a later head append-only-extends an earlier one. Both fail closed unless bound to a signed head. - Verifies cross-issuer evidence with
verifyCrossIssuerActionPass: a pinned public trust root, ActionPass token, ledger attestation bundle, inclusion proof, and fresh status-list response must all agree before the report is valid. This is verify-only and does not authorize execution. - Reconstructs a delegation+execution incident offline from ledger records alone
(
analyzeForensics/reconstructIncident) and asserts attenuation structure, exact authorization→execution correlation, and cascade containment — a read-only auditor primitive, not anomaly detection. - Formats verified exports as JSON, raw ledger JSONL, or SIEM-friendly JSONL events.
- Syncs a verified export plus an optional detached attestation bundle to an optional hosted endpoint.
- Exposes
verifyLedgerExportIntegrityso receivers can re-check record hashes, chain/head/count/filter consistency, and approval↔execution equivalence before trusting an export. - Exports opt-in OTLP/HTTP GenAI
execute_toolspans from ledger records, carrying decisions, reasons, hashes, and audit dimensions without payload bodies or secrets. - Keeps OTLP export and hosted sync network access explicit: both require the caller to inject a transport and fail closed without one, so the package does not use ambient global network access on its own.
- Fails closed on malformed JSONL records.
- Records parent-to-child ActionPass delegation edges inside the hash chain.
- Records denied, pending, committed, and rolled-back budget events with configured cost/limit and usage before/after.
- Carries a payload-free, hash-bound audit context on new records (tenant, agent, human owner, task, tool, and resource) so operator search does not infer identity from hashes or provider summaries.
- Carries trace IDs and sanitized provider evidence for GitHub, Slack, and Linear records when that evidence was present at decision time.
Quickstart
This example runs as-is with Node 20+:
import { evaluatePolicy } from "@axtary/policy";
import { parseNormalizedAction, demoAction } from "@axtary/actionpass";
import { LocalJsonlLedger, verifyLedgerFile } from "@axtary/ledger";
const ledger = new LocalJsonlLedger(".axtary/ledger.jsonl");
// Every decision appends a record chained to the previous record's hash.
const action = parseNormalizedAction(demoAction);
await ledger.appendDecision({ action, decision: evaluatePolicy(action) });
// Verification fails closed on any edited, reordered, or deleted line.
const verification = await verifyLedgerFile(".axtary/ledger.jsonl");
console.log(verification.valid, verification.records.length);Design Notes
The ledger is local-first. It is not a database, SIEM, or remote audit service. It gives the proxy and adapters a durable local trail that can later be uploaded, exported, or re-verified. Export and sync both verify the full hash chain before returning or sending filtered records. SIEM JSONL events include timestamps, trace IDs, outcomes, provider/resource summaries, policy metadata, reasons, and hashes without expanding protected action payloads or credential-bearing provider responses.
OpenTelemetry export is an operational projection, not the audit proof.
exportLedgerRecordsToOtlp posts verified records to an OTLP/HTTP traces
endpoint, and LocalJsonlLedger can be constructed with OtlpHttpTraceExporter
to stream spans after durable append. The span shape uses standard GenAI tool
attributes (gen_ai.operation.name, gen_ai.tool.name, gen_ai.tool.type)
plus Axtary namespaced attributes for decision, reason, payload hash, ledger
hash, policy, pass id, trace/correlation id, and audit context. It never emits
normalized payload bodies, provider tokens, auth headers, result rows, file
contents, or raw shell commands.
LocalJsonlLedger uses a filesystem lease lock (<ledger>.lock) so separate
processes resolve and append against one chain head. The JSONL file is opened
in append mode, one line is written, and the file is synchronized. A private
<ledger>.head.json checkpoint (mode 0600) stores byte length, line number,
and the last hash for steady-state O(1) appends; it is never treated as audit
evidence. The checkpoint is atomically replaced and the containing directory
is synchronized. If a process dies after the durable JSONL append but before
checkpoint replacement or lock release, the lease becomes stale and the next
writer verifies the JSONL chain, reconstructs the head, and continues.
Hosted sync should use signed ledger sync tokens rather than dashboard user sessions. Sync tokens carry a signed kid so hosted verification can rotate keys with AXTARY_LEDGER_SYNC_TOKEN_KID and a JSON AXTARY_LEDGER_SYNC_TOKEN_SECRETS keyring. The hosted Axtary profile also caps token lifetime at one hour and pins the runtime's P-256 attestation-key thumbprint and issuer. A sync request therefore needs both the bearer and a detached signature over the exact export from the pinned private key; possession of the bearer alone is insufficient. Hosted retention can be bounded with AXTARY_LEDGER_SYNC_MAX_BATCHES, AXTARY_LEDGER_SYNC_MAX_AGE_DAYS, or stricter limits embedded in the signed sync token.
The hosted sync store now sits behind a persistence adapter. The default adapter is owner-only local JSON at .axtary/hosted-ledger-sync.json; set AXTARY_LEDGER_SYNC_STORE=neon plus AXTARY_LEDGER_SYNC_DATABASE_URL to use the Neon/Postgres adapter. Apply migrations/neon/001_hosted_ledger_sync_batches.sql for deployed environments; docs/neon-hosted-sync-runbook.md covers the hosted setup and verification flow. Source file paths default to basename reduction and can be fully redacted. The hosted receiver verifies the runtime attestation before redaction, then HMAC-receipts the stored projection so a modified receipt-bearing row cannot be shown as trusted evidence. Receiptless pre-hardening rows are preserved but excluded from trusted reads until a fresh attested sync. A database-side deletion can still hide evidence; the runtime's portable attestation remains the independent proof.
Unified audit search is available through /api/ledger/search with optional
q, decision, from, to, and limit query parameters. In local dashboard
mode it searches the verified local JSONL chain plus tenant-scoped synced
batches; hosted mode searches only tenant-scoped synced evidence. The broad
query covers agent, human owner, task, tool, resource, hashes, policy, reasons,
traces, and provider evidence. Historical records without auditContext
remain valid but those identity dimensions are labeled unavailable. The older
/api/ledger/sync?view=records endpoint remains a synced-only view, and SIEM
exports remain available through /api/ledger/sync?format=siem-jsonl.
Fail-closed and secret boundaries:
- Ledger export fails if any source record breaks JSON parsing, previous-hash continuity, or record-hash integrity.
- Hosted sync accepts only structurally verified, token-key-bound attested exports through the sync route and keeps provider credentials, sync tokens, dashboard sessions, cookies, and auth headers out of stored batches and browser payloads.
- Provider evidence is extracted from normalized actions, not raw provider HTTP responses. GitHub content payloads record paths and lengths rather than file bodies unless an explicit sanitized diff is supplied.
Concurrent cooperating writers are serialized by the package lock. Direct
writes that bypass LocalJsonlLedger are unsupported; size drift is detected
before and after append and fails closed as ledger_concurrent_write_detected.
