Treeship
About

Changelog

Recent Treeship releases, newest first. The complete history remains in the repository changelog.

The latest Treeship releases are documented here.

The format follows Keep a Changelog; versioning follows Semantic Versioning. The canonical source and complete release history live in CHANGELOG.md at the repo root. Edit that file, not this page.

Unreleased

0.21.0 (2026-07-21)

The accountability release. Treeship starts answering not just "was this signed?" but "what was it authorized to do, what did it actually touch, and how confident are we the effect really happened?", with the verdict separating operational confidence from cryptographic validity.

Added

  • action/v2 receipt format (verification path). An additive superset of action.v1: the signed payload now carries a per-hop mandate (the authority an action ran under, grantor, scope, audience, expiry, delegation depth, revocation) and an effect block (what the action touched, input/output/ readback hashes, bytes moved, cost, side effects, context snapshot). Fully optional and backward-compatible; existing v1 artifact IDs are unaffected. This release ships the format, the core types, and verification; CLI emission of v2 receipts is a follow-up.
  • effect_confidence, the honest effect verdict ("the ack is not the act"). An actor declares how confident it is that an action's real-world effect happened (verified / partial / ambiguous / unknown / not_verified). verify_effect reconciles that claim against independent, actor-unmintable evidence (an external readback, or a witness a trusted authority vouches for): a Verified claim with no such evidence is downgraded, never taken on faith, and the verdict separates operational confidence from cryptographic validity. Honest lesser claims pass through unchanged, the verifier blocks inflation, it does not erase an actor's own hedging.
  • Runtime identity on action/v2. Optional provider / model / tool_schema_hash / system_prompt_hash, bound into the signed statement, so a verifier holding a pinned expectation can detect a swapped model, an altered tool set, or a changed system prompt after the fact.
  • Witness map on effects. Independent corroborating observers per effect. A witness lifts the confidence ceiling only once a WitnessAuthority confirms its signature against a trusted, non-actor key observing the same post-state; the default authority trusts nothing, so a bundled witness never inflates a verdict on its own.
  • Unified verifier in the browser and SDKs. Resolution and presentation verification moved into treeship-core and exposed via WASM + the TS/Python SDKs, so browser and edge verification run the same logic as the CLI.
  • Memory-provenance binding spine. Quarantine-gated approvals (a high-privilege action's grant is withheld if a memory-provenance check comes back dirty, and the denial is itself signed), and signed blocked.v1 refusal artifacts that make negative space, what an agent was stopped from doing, and why, first-class evidence.

Changed

  • treeship trust add --kind accepts the v0.19 split kinds cert_issuer, hub_org, and revoker alongside the existing ones, closing the pin-exchange gap where a counterparty following the documented flow hit a CLI error.
  • treeship verify surfaces the action/v2 effect verdict, a runtime: line and an effect: line on the timeline, and the same verdict in --json, kept clearly distinct from the signature check.

Docs

  • Full docs truth-sync. Every documented CLI command, Hub API route, and SDK contract regenerated from code and rewritten to match what v0.20+ actually does, plus CI drift gates (generated command matrix, feature inventory, docs routes) that fail on any code-vs-docs divergence, so a docs lie now requires deliberately overriding a failing check rather than simply forgetting.

0.20.0 (2026-07-12)

The private-verification release. Treeship gets a way for an agent to prove a subset of what it is authorized to do without revealing the rest, and the experimental zero-knowledge path gets rebuilt on an honest foundation.

Added

  • Selective capability disclosure — treeship present --disclose <caps>. An agent card commits to its full capability set, but a holder can present only the capabilities a given verifier needs. Built as an SD-JWT-style disclosure layered over the existing DSSE signature (core: disclose_capabilities / reconstruct_capabilities): the verifier reconstructs the disclosed subset, checks it against the signed commitment, and learns nothing about the withheld entries — while the card's signature still verifies. The withheld set cannot be forged or silently extended, and an omitted capability cannot be presented as disclosed. Round-trip tested end to end.

Changed

  • Honest zero-knowledge rebuild (statement-first). The experimental Groth16 path was not sound to ship — it had no trusted-setup ceremony and its proofs were not bound to the statement being proven, so a proof could be replayed or forged (identified in the ZK audit). That path is now quarantined, the dead ZK code deleted, and the docs corrected to stop implying it was authoritative. A statement-first private-verification design (renamed from zk-verification) supersedes it and names the two tiers concretely; treeship zk-setup and zk-tls-setup scaffold the SRI-identified building blocks. The zk feature remains pre-release and non-authoritative — nothing in the default trust path depends on it.

Docs

  • Private-verification spec (statement-first) with the first-principles argument and citations, and a first-draft "cyberlogic" theory of Treeship's trust model.

0.19.1 (2026-07-11)

The portable-verification release. The headline is a small command that closes a real gap: a Treeship signature is over the DSSE PAE (DSSEv1 &lt;len> &lt;payloadType> &lt;len> &lt;payload>), not the payload JSON, and nothing exported those exact bytes — so a counterparty verifying a receipt with a third-party Ed25519 library had to guess the PAE construction and failed. The "portable, offline, don't-trust-us" promise was true in the bytes but unusable without reading the source.

Added

  • treeship receipt export <id> — the offline-verifiable triple. Emits the exact {message (the PAE), signature, public_key, algorithm} in copy-safe base64, so a counterparty confirms a receipt with any Ed25519 library and zero Treeship code. --format json pipes the triple straight to a partner. The message is reconstructed from the same payload_type+payload the verifier sees, so what is exported is provably what the signature covers; the public key comes from the local keystore (export is for a receipt you produced — a received receipt is checked with treeship verify).
  • scripts/verify-receipt.py — the reference verifier. Dependency-light, independent Ed25519, offline: treeship receipt export &lt;id> --format json | python3 scripts/verify-receipt.pyVALID. The canonical thing a partner copies.
  • Receipt format reference. The stable, partner-facing description of the byte format — envelope, PAE, id derivation, a language-agnostic verification recipe — so anyone can implement a verifier without reading the Rust. The format is frozen; this documents it.

Fixed

  • A clean session reports verification_status: "pass", not "warn" (#231). The always-on receipt_body_binding scope caveat added in 0.19.0 is informational (every package carries it), but it was flipping every cryptographically-clean session's top-line verdict to warn, making pass unreachable and breaking any caller that gates on status == "pass". It is now surfaced in warnings for transparency but no longer downgrades the verdict; real warnings (git-degraded, event-log skips, determinism drift) still do. Caught by the post-publish smoke test; regression-tested.
  • Release tooling: npm pinned to 11.5.1 (#230). The 0.19.0 publish failed on npm because npm@latest on the runner's Node 24 shipped a build whose bundled sigstore module was unresolvable, crashing trusted-publishing provenance (crates.io and PyPI published fine). npm is now pinned to the trusted-publishing floor instead of tracking @latest.
  • Dead verify URL in the lobster-cash demo. It printed the non-resolving hub.treeship.dev/verify/; the public verify URL is https://treeship.dev/verify/<id> (the hub's own hub_url).

0.19.0 (2026-07-09)

The security-hardening release. Two independent AI-assisted adversarial audits were run against the codebase, and every confirmed finding that ships in the default binary is fixed, each with a regression test that fails before the fix. Highlights below; the recurring theme both audits named was "a higher-level surface reporting verified / authentic / pass from attacker-controlled input without anchoring to a verified signature," and that class is now closed across the CLI, the WASM browser verifier, the hub, the SDKs, and the bridges.

Changed (breaking)

  • The ship trust-root kind is split into hub_org / cert_issuer / revoker. A single --kind ship pin used to authorize three unrelated powers at once — promoting a local journal claim to a global single-use claim (hub-org checkpoints), issuing agent certificates, and revoking capabilities — so pinning a hub for dedup silently also let it mint certs and kill your capabilities. Each verifier is now scoped to the one power it needs. Hard cutover: no verifier honors ship any more, and treeship trust add --kind ship is rejected with guidance to the three new kinds. Existing trust.json files still load (the deprecated ship kind stays parseable but inert); a legacy ship pin is inert until re-pinned. Nothing published breaks — the trust kind is a local, verifier-side setting, not stored on the hub or embedded in any artifact. keys export now emits the new kinds. Migration: treeship trust add <key_id> <pubkey> --kind cert_issuer (or hub_org / revoker).

Security

  • treeship verify on a fetched receipt/URL/package no longer reports "authentic" without checking a signature. These paths ran only keyless, self-referential checks (recomputed Merkle root vs the receipt's own root, inclusion proofs, leaf-count, timeline) and then printed "Verified. This receipt is authentic." (JSON outcome: "pass"). Any internally-consistent forgery, or an empty receipt, passed. Verification now says "Structurally consistent" with an explicit warning that signatures and issuer were NOT verified, the JSON outcome is structural-pass (never pass) with signatures_verified: false, and an empty receipt is a fail. The same fix lands on the WASM verify_receipt surface.
  • The hub DPoP signing key is encrypted at rest. It was written to config.json as plaintext hex, so a passive read (backup, dotfile sync, container layer, a committed .treeship, a co-tenant) handed an attacker full impersonation of your ship to the hub. It is now sealed under the machine key with the same AES-256-GCM path the ship key already uses — a stolen config.json is useless on another machine.
  • The browser/WASM verify_capability now actually verifies signatures. It reported a card as key-bound / "verified" from the unverified signatures[0].keyid string (a public label, not a secret), so a forged card naming your pinned key could show as cryptographically bound to you in a receipt viewer. It now builds a real verifier from the pinned trust roots and requires the card's key to have produced a valid signature — matching the native CLI by construction.
  • The hub verifies checkpoint signatures and binds each signer to its first public key. PublishCheckpoint stored signer/signature/public key with no Ed25519 verification, which let an attacker mint the "ownership" signal used to shadow another tenant's consistency chain. The CLI now sends the exact signed canonical bytes; the hub verifies them and cross-checks the stored fields, plus a trust-on-first-use signer → public_key binding.
  • At-rest keystore keys derive from a secret, not from guessable machine identifiers. The AES key wrapping every signing key was SHA256(machine-id/serial/hostname ‖ path) — all guessable — so an exfiltrated keys/*.json was forgeable by anyone who knew your hostname. It now derives from a 32-byte OS-CSPRNG machine_seed (mode 0600, the primary entropy on every platform), with the machine id mixed in only as a binding salt; the old derivations remain as decrypt-only migration fallbacks that re-wrap on first open. Two hosts with identical machine-id/hostname but different seeds can no longer decrypt each other's keystore. The seed file is also hardened against planted symlinks / loose permissions.
  • Ed25519 verification is strict across the whole core, and treeship verify cryptographically checks chain linkage. Every core verifier now uses verify_strict (rejecting signature malleability, small-order keys, and cross-SDK split-view; previously only present.rs did). And the chain walk now cross-checks each child's signed parentId against the walked parent instead of trusting the unsigned parent_id storage metadata, exiting non-zero with SIGNED LINKAGE BROKEN on a mismatch rather than printing "no tampering detected" for a check it never ran.
  • verify-capability: three ways it could be fooled, all closed. meta.tool can no longer rescue a concrete out-of-scope action into scope; key-boundness / the action cross-check / revocation authorization now use the re-verified signer rather than the unverified signatures[0].keyid; and provenance grades read off a card that is not key-bound are marked SELF-ASSERTED (not machine-verified). The Layer-4 authorization pass additionally hardened invitation countersigning (re-validates the joiner's statement before the host signs), capability revoke (refuses to print success for a revocation no verifier will honor), and scope checking (fails closed on extra constraints it cannot evaluate).
  • The trust-ladder can no longer be laundered. A self-declared attestation_class on a work-history record was tallied verbatim, so a hand-signed session.v1 with attestation_class: "countersigned" laundered into the highest trust bucket. session.v1 can no longer be minted through the generic attest path, the predicate validator now enforces enum/const, and profile / history cap the declared class to what the record's own evidence supports.
  • Hub object-level authorization and availability. Publishing a Merkle proof or consistency row now requires the caller to own the artifact / checkpoint / signer (was any authenticated dock, enabling cross-tenant shadowing). The hub gained request timeouts (Slowloris protection), a timeout + response cap on its Rekor call (no full-hub freeze during a Rekor outage), and a body cap on the unauthenticated /dock/authorize. session close now stamps a reconcile_degraded marker when the git backstop was disabled mid-session, and package verify warns on it, instead of sealing an incomplete file ledger as clean.
  • CLI security nonces use the OS CSPRNG, and the A2A bridge stops trusting unverified JSON. Every key/nonce/token in the CLI (hub keypair, DPoP jti, approval and invitation nonces) moved from thread_rng to OsRng, with sub-128-bit nonces widened to 128. The A2A verifyReceipt no longer returns shipId / withinDeclaredBounds computed from the raw fetched JSON when the receipt did not verify (and no longer defaults withinDeclaredBounds to true when there is no declaration).
  • The Claude Code / Kimi plugins redact secrets from captured commands. Bash commands were recorded verbatim into the session timeline, which is publishable to a no-auth URL, so AWS_SECRET_ACCESS_KEY=… cmd / Bearer … / --token=… leaked into shareable receipts. Env-assignment secrets, secret CLI flags, and bearer tokens are now redacted before capture.
  • Lower-severity hardening: stored artifact key_id is taken from the signature that actually verified (not signatures.first()); format_device_code no longer panics on a multibyte device code; an unreadable file's digest is recorded as null rather than an empty string; the SDK↔WASM trust-root ABI is fixed; and the event-log read no longer aborts the whole session on one bad byte, nor does package verify imply it authenticated the unsigned session narrative.
  • zk-feature (pre-release) soundness: the RISC0 chain guest now fails closed on empty content/signatures (was scoring an empty chain as validly signed), the hand-rolled base64 in the signature path is replaced with a real decoder, and the Groth16 verifier output no longer echoes an unverified artifact_id as bound. The zk feature remains pre-release and non-authoritative.

Added

  • treeship match --exercised <glob> + GET /v1/agents/match — find agents by exercised evidence (work-history slice 4). Declared capability gets an agent found; exercised history gets it chosen. The Hub indexes tools_exercised from the signed session.v1 records it already holds and proposes candidates (filterable by --class / --min-sessions); the client re-verifies every candidate on this machine and re-ranks by verified sessions, showing records that do not verify as unverified rather than trusting the Hub's own match. See the work-history spec.
  • GET /v1/stats — public adoption metrics. Counts only, never identifiers: artifacts pushed (total / last 7d), docks attached vs active (active = pushed in the window), session receipts uploaded, distinct agents seen. Any query failure returns 500, never a partial zeros document — zeros on error would read as "no adoption", a lie in the dangerous direction. Verify-page traffic and package downloads deliberately stay in external systems; the hub reports only what its own tables can answer, so every number is checkable against the same database the transparency endpoints serve.

Changed

  • treeship --help now reads like a protocol, not a platform. The core loop (quickstart → init → add → wrap → attest → session → verify → hub → publish) surfaces first; 21 extension and experimental commands (ui, dashboard, templates, otel, daemon, merkle, bundle, the zk family, …) are hidden from default help but unchanged in behavior. treeship help --all lists everything.
  • One site list now stamps AND checks every release-version site. check-release-versions.py --write <version> inverts every parser into a writer; release.sh's parallel sed/npm/node bump list is deleted. Forgetting one side of the dual list is the drift class behind the 0.9.6, 0.10.2, and 0.17.0 misses. The four npm package-lock.json files become first-class checked sites (26 → 30).
  • CI now runs the full shipped-binary test surface (treeship-cli, treeship-core-wasm, the hub's Go tests, and the plugin hook parity suite), not just treeship-core — so a fix's regression test actually gates merges on push.

0.18.0 (2026-07-07)

Added

  • treeship profile / verify-profile — the checkpoint-pinned track record (work-history slice 3). A profile ("N sessions, N actions, these tools, this span") is a claim, and claims are asserted until checked. Every number is a deterministic aggregation over the log's first tree_size leaves at a pinned checkpoint whose root travels in the payload; verify-profile rebuilds exactly that prefix, cross-checks its root against the pin, recomputes every field through the same shared aggregation path as compute (the two cannot drift), and compares — match grades the profile checked; mismatch is a provable lie, named field by field (nonzero exit, structured JSON). --attest signs the profile as a ship claim (profile.v1, new registered predicate — the operator's claim about the agent's record, never the agent grading itself). Demonstrated live: an attested profile re-verifies checked even after the log grows, because the pin freezes the prefix. Reputation pinned to a root is falsifiable; reputation that floats is marketing. See the work-history spec.
  • treeship history <agent> + GET /v1/agents/history — the work-history projection (work-history slice 2). An agent's work history is its transparency log filtered to signed session.v1 records — no new store, no new trust surface. The Hub serves raw signed envelopes plus Merkle anchors (it filters and serves, never interprets); the CLI re-verifies every envelope on your machine against your own trust roots, re-proves each anchored entry's Merkle inclusion offline, and reads the typed fields from the verified envelope — never from transport metadata. Sortable and filterable on schema fields (--class self|runtime|countersigned, --since <rfc3339>, --limit), newest first; --format json emits one structured object, and a served entry whose inclusion proof fails flips the exit code nonzero, per the honest-verdicts rule. The output states the honest bound on every run: history proves what was recorded, never everything that happened. See the work-history spec.

Fixed

  • status now shows WHICH store it resolved. Store discovery walks up from the working directory and silently switches between project-local and global .treeship config — previously visible only in doctor, and the root cause of a whole class of wrong-keystore confusion (it struck three separate times in one working day, including twice against the maintainers). status prints store: project-local -- <path> in text mode and a store object in JSON.
  • A failed append-only proof no longer advances the audit witness. The witness is the monitor's memory of the last state the hub proved; previously it advanced on any monotonic growth, so a hub that could not prove its log only appended (possible rewrite) triggered the alarm exactly once and every subsequent audit compared against the new, unproven baseline and reported clean. The witness now persists only when neither equivocation nor an append-only failure contradicted it — the alarm keeps firing until the hub actually proves the extension, and the output says witness NOT advanced (extension unproven).

Fixed

  • Hub hardening (audit Batch G). Four fixes and a test wall for the network layer: (1) request bodies on POST /v1/artifacts and all POST /v1/merkle/* are now capped at 10 MB (the receipts handler always was; the other four buffered unbounded uploads into memory). (2) New indexes artifacts(payload_type, signed_at) and artifacts(dock_id) — the public, unauthenticated GET /v1/agents and /v1/agents/log endpoints were full-table scans that decode every receipt row per request and only get slower as the log grows. (3) PRAGMA foreign_keys=ON: every REFERENCES clause in the schema was decorative (SQLite ignores them unless enabled); a write claiming a dock the hub never registered is now refused by the database itself. (4) Re-POSTing a checkpoint no longer inserts a duplicate row forever — InsertCheckpoint is idempotent on its natural key (signer, root, tree_size) and returns the original row's id, matching the artifact/consistency insert discipline. (5) The public GET /v1/verify/{id} no longer echoes the verifier's stderr and internal error strings to anonymous callers (logged server-side; the response is a generic verdict — anyone wanting detail runs treeship verify themselves). And the DPoP authentication boundary — through which every authenticated write funnels — now has a test suite (it had zero): the accept path plus jti replay, foreign-key signature, unknown dock, htm/htu binding mismatches, clock skew, alg confusion, malformed JWTs, and payload tampering under a stale signature are all pinned.

0.17.1 (2026-07-06)

Fixed

  • The version train now covers the plugin manifests (the 0.9.5-plugin bug). The Claude Code plugin's own plugin.json advertised 0.9.5 — eight releases stale — because it was covered by neither release.sh nor the version guard (the sibling marketplace manifest was; the plugin's own manifest was not). Same class: the OpenClaw plugin manifests sat at 0.10.3. All three are bumped, release.sh prepare now bumps them, and the preflight guard walks them (23 → 26 sites), so this class of drift fails the release instead of shipping. Also swept in the same pass: PLATFORM.md/TREESHIP.md no longer hardcode a "current version" in prose (one claimed "all packages are at v0.9.5"); the README's roadmap block ("v0.10.1 in flight", fully shipped long ago) is replaced with a pointer to the living vision.md; the report skill's treeship package export instruction (a command that does not exist) now describes the real portable-package flow; the Kimi skill's cargo install treeship-cli (an orphaned 0.4.0 stub the rest of the docs explicitly warn against) now says npm install -g treeship; and stale @v0.10.0 skill-pin examples are current. Found by a user with the repo checked out; generalized by the audit.
  • Merkle inclusion proofs are now generated from the checkpoint's tree, not the current one. merkle publish and merkle proof computed authentication paths over the FULL current tree while pairing them with the latest checkpoint — but a path is a function of the total leaf count, so whenever any artifact was appended after checkpointing (a completely normal workflow), every emitted proof reconstructed the wrong root and the hub served proofs that verifiably fail, making legitimate, in-log artifacts read inclusion INVALID to every auditor. Both commands now rebuild the tree truncated to exactly the checkpoint's tree_size and cross-check the rebuilt root against the checkpoint's before emitting anything (the same rule merkle publish's consistency proofs and present already applied), and merkle proof gains the missing membership guard: an artifact newer than the checkpoint is refused with the fix (treeship checkpoint) instead of handed a proof that cannot verify. Verified live both ways: with the store grown past the checkpoint, an old artifact's proof verifies offline against the checkpoint root, and a too-new artifact is refused. Also fixed in the same class: the receipt verifier reported 0/0 inclusion proofs passed as a green check — a check that ran nothing now reports a warning, not a pass.
  • hub attach probes the hub before claiming "reconnected". With stored keys, attach previously set the active connection and reported success without ever contacting the server — so keys that outlived the hub's dock registration (e.g. after a hub database reset) produced a confident "reconnected" followed by 401s on every push. Reconnect now makes one authenticated, read-only DPoP request first; if the stored keys no longer authenticate, it says so plainly and falls through to a fresh device flow instead of lying. Found live during the hub-recovery incident, confirmed by the audit.
  • A2A handoff attestation actually records handoffs now. The @treeship/a2a middleware invoked attest handoff with flags the CLI does not have (--task-id, --context, --a2a-message-id) and omitted the required --artifacts, so every handoff attestation failed — silently, because bridge errors are deliberately swallowed. A2A delegation boundaries were never recorded. The bridge now does the honest two-step: it first attests the delegation itself as an action signed by the sender (a2a.delegate, with the task/context/message ids in meta), then records the handoff transferring that attested artifact — both real CLI primitives, no phantom flags. Found by the full-codebase audit.
  • The MCP bridge no longer introduces itself as 0.10.0. The handshake version clients see was hardcoded seven releases stale; it now reads from the package manifest and rides the release train.
  • Verification commands no longer exit 0 on hostile verdicts, and emit real JSON. resolve, audit, and verify-capability computed hostile verdicts — a REVOKED card, out-of-scope violations, an OMISSION against the committed anchor, an invalid inclusion proof, an equivocating checkpoint, a failed append-only proof — printed a warning, and returned success, so any script, CI gate, or monitor keying off the exit code treated a detected history rewrite as green. All four verification surfaces (including verify-presentation) now exit nonzero on a hostile verdict. audit --watch deliberately keeps looping and alerting instead of exiting — a monitor's job is to keep watching. In JSON mode (--format json) each of the four now emits one structured verdict object carrying every field the text output shows; previously resolve/verify-capability returned a bare success envelope, verify-presentation returned essentially nothing (its verdict lines went through info, which JSON mode suppresses), and audit streamed multiple concatenated JSON objects no parser could consume. Programmatic callers — the bridges, the gateway, CI — can finally gate on these commands.

0.17.0 (2026-07-06)

Added

  • Challenge mode — the handshake (registry-topology slice 3). A static presentation proves the record; challenge mode proves the bearer. The verifier mints a nonce; the agent answers with present --challenge <nonce>, signing a domain-separated canonical that binds the agent URI, the exact card, the nonce, and a bearer-signed timestamp (every variable field digest-folded, so no field can inject separators and shift the others); the verifier runs verify-presentation --challenge <the same nonce> and the response is checked only against the subject key the card verification itself established (chain verdict or the verifier's own AgentCert pin — never a key the wire supplied). Verdict: challenge: verified — bearer controls <key> (response 2s old), status verified (key-bound, anchored, live). Fail-closed everywhere: signing requires the agent's own key and refuses the ship-key fallback (a ship signature would prove the operator, not the agent); a card/key drift refuses to answer; an unverified card means there is no key to check against and the challenge fails rather than "verifying" against nothing. Adversarially exercised live and in tests: a captured response replayed against a new nonce, a response signed by a non-card key, a response replayed for a different card or agent, and a tampered timestamp all reject with specific reasons.
  • treeship present / verify-presentation — agent verification with no registry in the loop (registry-topology slice 2). In real TLS the server hands you its certificate chain; present gives agents the same move: one file carrying the current capability card, the certificate chain to its ship, any known revocations referencing the card (included, never filtered — the verifier judges their authority), and a Merkle staple — the latest checkpoint plus the card's inclusion proof, computed over the tree truncated to exactly the checkpoint's size with the root cross-checked before anything is written. verify-presentation re-verifies everything fully offline against the verifier's own trust roots: direct leaf pin or certificate-chain walk to a pinned ship root (the same code path as resolve --hub), authorized revocations honored, staple checkpoint signature + inclusion re-checked, and freshness reported as an explicit boundrevocation: none included — current as of the staple (9m old) — with --max-staple-age 30s|15m|2h|1d turning the bound into an enforced verdict (STALE). Honest constraint printed in both commands: a static presentation is replayable — it proves the record, not the bearer; challenge mode (nonce signing, slice 3) is what proves live key control. Adversarially exercised: a card forged to grant itself an extra capability reports UNVERIFIED (stale signature caught by the chain's subject-key check), an unparseable envelope fails hard, and a verifier with no pins gets actionable pin commands, not false verdicts.

Added

  • treeship onboard — an agent goes from nothing to verifiable in one command. Composes the existing lifecycle — agent register --own-key, attest card (--from-harness / --tools-json / --from-a2a / --tools), and with --publish the full publish + merkle checkpoint + merkle publish anchor — into one idempotent pass, and ends by printing the trust bundle: the exact trust add command(s) a counterparty runs (agent_cert for the agent key, hub_checkpoint for the ship key) plus the resolve/audit commands that verify the agent. One name in, one URI out: onboard deployer and onboard agent://deployer are the same call, closing the register-takes-a-name / everything-else-takes-a-URI trap. Adds no new attestation surface — every artifact is minted by the same code paths as the individual commands. Born directly from the 13-item friction ledger of a real end-to-end dogfood run.

  • Certificate-chain delivery: pin the ship, verify its agents (registry-topology slice 1). agent register --own-key now also mints the protocol-native certificate — a typed agent_cert.v1 receipt whose envelope signature by the ship key binds agent://<name> to its per-agent public key (idempotent per agent+key; the .agent package cert remains the human-portable rendering). treeship publish pushes the cert chain with the resolvable set, the Hub serves it verbatim in a new certs field, and resolve --hub walks the chain when the leaf key is not directly pinned: cert envelope verified against a pinned Ship root (the pubkey always comes from your trust store, never the wire), agent URI + subject key matched, validity window enforced fail-closed, and the certified subject key must itself verify the card. Verdicts read verified (chain to pinned ship root) / yes (AgentCert via ship chain). This is the TLS chain shape: a counterparty pins one ship key and verifies every agent under it, instead of pinning each leaf — the gap found in the end-to-end dogfood. Chain-certified keys also count for self-revocations served in the bundle. Adversarially tested: unpinned ship, wrong trust-root kind, expired / not-yet-valid / missing-window certs, subject and agent-URI mismatches, stolen-cert-wrong-signer, and keyid/signer mismatch all reject.

Fixed

  • Two agents in one workspace no longer collapse into one card (actor→key mapping destroyed). derive_agent_id hashed (surface, host, workspace) — the agent's name was not part of its own identity — so registering a second agent from the same workspace reused the first one's card id and the upsert silently overwrote it: the clobbered agent's actor→key mapping (registered_key_for_actor, the lookup per-actor signing resolves through) vanished, downgrading its future receipts to self-asserted and orphaning its certificate. Found live when fable-scout's registration destroyed fable-code's. The name is now part of the id hash, and registered_key_for_actor picks the newest matching card deterministically (filesystem iteration order must never decide which key an actor signs with). Existing cards keep their on-disk ids; a re-register after this fix creates a correctly-keyed card alongside any stale one.
  • Hub: re-pushing an already-published artifact no longer 500s. InsertArtifact was a bare INSERT, so re-publishing an agent's resolvable set (exactly what an idempotent onboard --publish on every agent boot does) hit the artifact_id primary key and surfaced as a 500 to the pushing client, aborting the publish. Artifacts are content-addressed, signed envelopes — a re-push of the same id is the same bytes — so the insert is now ON CONFLICT(artifact_id) DO NOTHING: idempotent, and deliberately never an update, so a colliding id can never overwrite bytes the hub already serves. Regression-tested both ways (duplicate no-op + overwrite rejection).

0.16.0 (2026-07-06)

Added

  • treeship keys export — hand a counterparty your public key. Prints a key's PUBLIC half in the pinnable ed25519:<base64url> form together with the exact treeship trust add command(s) the counterparty runs: --kind agent_cert for a per-agent key (--agent agent://deployer), --kind ship + --kind hub_checkpoint for the ship key — a remote verifier needs both to get verified on resolve and anchored & verified on the transparency check. This is the out-of-band half of the trust model: every remote verification is checked against the verifier's own trust roots, and until now there was no sanctioned command that produced the key material those roots need. The private key never leaves the store. --format json for scripts.
  • session.v1 — every closed session becomes a typed, signed work-history record (work-history slice 1). treeship session close now mints a session.v1 receipt into the agent's chain alongside the sealed package: headline, outcome, duration, tools_exercised (computed from captured tool usage, never hand-written), counts, and a receipt_digest binding the record to the exact sealed evidence it summarizes. Each record carries an attestation class from the work-history ladder — self (the agent's own receipts), runtime (a harness hook or bridge the agent cannot forge emitted the log), countersigned (a second party signed evidence embedded in the package, e.g. consumed human approvals) — computed from capture-path evidence actually present, labeled, never laundered (anchored is a later, derivable state). The record is validated against the registered session.v1 predicate schema before signing (fail-closed), signed with the actor's own key when it has one (key-bound history for registered agents), and chained to the session-close artifact. Best-effort: a record failure warns and never wedges the close — the sealed package remains the source of truth. This is the atom of the work-history spec: identity says who the agent is, capability says what it claims, work history says what it has demonstrably done.

Fixed

  • resolve no longer labels an unpinned checkpoint signer "signature INVALID". Checkpoint verification fails closed both when the signature is genuinely bad and when the signer simply is not pinned under hub_checkpoint in your trust roots — but those demand opposite reactions (distrust the hub vs pin a root), and reporting the second as "INVALID" is a mislabel. resolve now distinguishes them: an unpinned signer reports checkpoint signer not in your trust roots with the ready-to-run trust add … --kind hub_checkpoint command (the pubkey comes from the served checkpoint; pinning remains your explicit decision, and verification still happens on your machine), while a pinned signer whose signature fails still reports INVALID.
  • Keystore survives macOS hostname renames (self-healing machine key). The machine key wrapping on-disk private keys was derived from hostname + username, and macOS silently renames kern.hostname (network name collisions, DHCP), after which the keystore failed with a misleading MAC verification failed — wrong machine and the documented recovery was to abandon it. The keystore's own TODO predicted exactly this; it is now implemented. The primary machine key is derived from a hardware identifier (/etc/machine-id on Linux, IOPlatformSerialNumber on macOS), which survives hostname and network changes; machines with neither identifier keep the v1 derivation with its co-located seed file, preserving project-local keystore isolation. Decryption tries an ordered fallback chain covering every wrapping an existing keystore may carry — the v1 key under the current hostname, the raw-path key (pre-canonicalization symlinks), and on macOS the v1 key under scutil LocalHostName variants, which typically retain the name the store was written under after kern.hostname drifts away. Any entry that decrypts via a fallback is transparently rewrapped under the primary (same locked, idempotent migration path as the v1-format upgrade), so the fix both recovers already-drifted keystores with no user action and immunizes them against the next rename. Verified against a real keystore bricked by three successive hostname renames: it self-healed on first use. Note: entries rewrapped under the hardware key are not decryptable by older Treeship binaries; keep one CLI version per machine.
  • Capability matching now understands harness-captured glob patterns (Bash(git:*)). tool_matches only expanded a trailing *, but the patterns attest card --from-harness captures from a Claude Code settings.json carry the wildcard inside the permission scope, Bash(git:*), Read(*), so every such pattern silently degraded to an exact string match that could never fire, and a card captured from a real config reported every real action out-of-scope. The wildcard may now sit anywhere in the pattern (one *, prefix and suffix must both hold), so Bash(git:*) matches Bash(git:status) and still rejects Bash(gh:pr); the falsifiable cross-check keeps flagging genuinely off-card actions (payments.charge). Trailing-glob (family.*), bare-*, and exact matching are unchanged. Shared treeship_core::capability fix, so the CLI and the WASM browser verifier keep returning the same verdict. Found by dogfooding the full TLS-for-agents flow end to end.

0.15.0 (2026-06-26)

Added

  • MCP bridge emits provable receipts by default (protocol-integration slice 1). On startup the @treeship/mcp bridge provisions a per-agent signing key for its actor (agent register --own-key --quiet), so the attest action --actor <agent> calls it already makes sign with that agent's own key. Receipts now verify as actor proof: proven (key-bound) instead of asserted, with no change to the attestation path, per-actor signing already did the work once the key exists. Best-effort and idempotent: if treeship is missing or uninitialized the bridge still runs (receipts fall back to the shared key, with a one-line note), and restarts reuse the same key rather than minting new ones. The first step of wiring the shipped identity stack into the protocols real agents already speak. See protocol-integration spec.
  • treeship agent register --own-key is now idempotent, and a new --quiet flag skips the on-disk .agent package. Re-registering an agent that already has a per-agent key reuses that key (no key pile-up, no duplicate AgentCert pins) instead of minting a second one, and --quiet produces the card + key + pin without dropping a .agent directory into the working directory. Both exist so programmatic callers (the MCP/A2A bridges, which register on every startup) can provision identity safely and quietly; interactive register is unchanged.
  • treeship attest card --tools-json <path> — operator-declared capability sets (protocol-integration slice 2). The runtime companion to --from-harness: where --from-harness captures tools observed in a config (graded captured), --tools-json records an operator's explicit declaration from a JSON file (a ["tool", ...] array, or { "tools": [...] }). Each entry is stamped declared in capability_provenance with the file as its source, an operator's claim, honestly labeled and traceable to where it came from, never presented as captured or discovered. A capability already captured from a harness keeps the stronger captured grade. This is the deliberate slice-2 choice: the @treeship/mcp bridge exposes Treeship's own meta-tools (treeship_verify, ...), not the agent's domain tools, so auto-capturing from the bridge would be the attestation layer attesting to its own presence. A card's value is the signature binding a claim to an identity and key; an operator declaration is honest precisely when it is labeled as one. See protocol-integration spec.
  • treeship attest card --from-a2a <AgentCard.json> + A2A bridge provable receipts (protocol-integration slice 3). Maps an A2A agent's own published AgentCard.skills to agent_card.v1 capabilities, stamped with a new discovered provenance grade (read from the agent's own descriptor) and the AgentCard's url as source. discovered is a real grade, distinct from captured (harness) and declared (operator), weaker than receipt-exercised, and verify-capability / resolve count it in its own bucket so A2A skills are never mislabeled as operator-declared. Protocol-level capabilities (streaming, push) are excluded, transport, not domain capabilities. Unlike the MCP meta-tools dead end, an A2A AgentCard carries the agent's own skills, so it is a legitimate source. Separately, the @treeship/a2a middleware now provisions its actor's per-agent key on construction (provisionAgentKey, idempotent + best-effort), the slice-1 treatment for A2A, so its task and handoff receipts verify as proven (key-bound). Net: an A2A agent's self-description becomes a key-bound, verifiable Treeship card, and its receipts are provably the agent's.
  • treeship audit now witnesses checkpoints across runs (transparency-log slice 3a). Each audit persists the highest verified checkpoint it sees per (hub, signer) under ~/.treeship/merkle/witnessed/, and compares the current one against it: a consistency: line reports monotonic growth, and audit warns on a forking or regressing hub, two different roots at the same tree_size (equivocation), or a tree_size that went backwards. This is gossip/witnessing: it catches a log that contradicts itself, the "monitors catch anomalies" property. Honest framing throughout: witnessing does not cryptographically prove append-only (a later tree extends an earlier one), that is the consistency proof, slice 3b, whose design (publish-side generation via the shipped Merkle consistency primitive, since the Hub holds no tree of its own) is now captured in the transparency-log spec.
  • Cryptographic append-only proof for the transparency log (slice 3b). treeship audit now proves a hub's log was only appended to (no silent rewrite) since you last witnessed it, the true Certificate-Transparency property. Because the Hub holds no Merkle tree ("the Hub creates nothing"), the proof is generated publisher-side: treeship merkle publish computes a Merkle consistency proof from the previous checkpoint to the current one (over the tree truncated to exactly the checkpoint size, with the truncated root cross-checked against the checkpoint's own root before anything is pushed) and POSTs it; the Hub stores and serves it verbatim via GET /v1/merkle/consistency. audit fetches the chain and re-verifies every link offline against the shipped verify_consistency primitive, enforcing four properties so a malicious hub cannot pass a valid-but-irrelevant chain: it must start at the witnessed checkpoint, every link's proof must verify, links must be contiguous, and it must end at the current checkpoint. The audit reports append-only VERIFIED — current tree cryptographically extends tree_size N, or warns of a possible history rewrite if a served chain fails. Generation↔verification agreement and tamper rejection (wrong start, gap, short endpoint, wrong head, mutated proof) are unit-tested against real trees. See the transparency-log spec.

Fixed

  • Keystore: canonicalize the store path before deriving the machine key. The machine key that wraps on-disk private keys hashes the keystore directory path, so the same directory referenced by two different path strings (a symlink in the path: macOS /var/private/var, or a symlinked $HOME) derived two different keys and failed to decrypt with a misleading MAC verification failed — wrong machine error on a perfectly valid keystore. Store::open now canonicalizes the path first, so one directory always yields one key. The raw-path key is retained as a decrypt-only fallback, so any keystore written before this change still opens, this hardening never locks an existing user out. Regression-tested both ways (symlinked-path consistency + legacy raw-path fallback).

0.14.0 (2026-06-25)

Added

  • Merkle consistency proofs (core, transparency-log slice 3 foundation). MerkleTree::consistency_proof(old_size) and verify_consistency(version, old_size, old_root, new_size, new_root, proof): prove (and verify offline) that a later tree extends an earlier one, the first m leaves unchanged, only appends. This is the append-only / no-rewrite guarantee for the transparency log. Derived and validated for this tree's exact level-by-level-promotion shape: round-trip tested against compute_root (ground truth) for every (m, n) up to 20 across both v1 and v2, plus tamper rejection (wrong roots, mutated proof) and edge cases. See merkle-consistency spec.

  • treeship audit --watch — transparency-log monitor mode. Re-audits an agent's history on an interval (--interval, default 30s) and keeps alerting on omission, the "monitors catch anomalies" piece of the transparency log. Transparency-log slice 4.

  • treeship audit --hub <url> <agent> — transparency-log audit. Pulls an agent's history from a Hub, re-verifies each anchored entry's Merkle inclusion offline against your trust roots, and checks completeness against the agent's committed evidence_anchor, so omission is detectable (OMISSION — committed 5, observed 3). The client half of Certificate Transparency for agents: the Hub serves metadata + anchors, your machine decides. Transparency-log slices 1b + 2.

  • Hub agent transparency log (GET /v1/agents/log?agent=<uri>). An agent's append-only receipt history, its actions and cards as metadata + Merkle anchors only, never payloads, newest-first, plus the latest committed_anchor (the card's evidence_anchor). The server half of Certificate Transparency for agents; a client (treeship audit, next) re-verifies each anchored entry's inclusion and checks completeness against the committed anchor. Slice 1 of the transparency-log spec.

  • treeship publish <agent>. Pushes an agent's resolvable set, its current capability card and any revocations, to the configured Hub through the authenticated hub push path, so anyone can resolve --hub it over the network. Completes the agent resolver (DNS + OCSP + Certificate Transparency for agents): local + remote resolution, revocation, and a Merkle transparency anchor, all re-verified client-side. New Agent Resolution and capability provenance docs.

  • Transparency anchor in agent resolution. GET /v1/agents now includes the current card's Merkle inclusion proof when it has been anchored, and treeship resolve --hub re-verifies it offline (the checkpoint signature against your trust roots + inclusion in the signed root), reporting transparency: anchored & verified (checkpoint #N) or not anchored. Proves a resolved card is in the log, not just signed, the Certificate-Transparency property for agents. Slice 2 of the agent resolver.

  • Hub agent resolver endpoint (GET /v1/agents?agent=<uri>). Resolves an agent URI to its verifiable bundle, the current capability card and any revocations, returned as raw signed envelopes. The Hub performs no verification or authorization here; it serves bytes, and the client (treeship resolve) re-verifies and grades them offline, so the Hub and the CLI / WASM verifier cannot drift. Slice 1b of the agent resolver (the network half). See agent-resolver spec.

  • treeship resolve --hub <url> — network resolution end to end. Resolves an agent from a Hub over the network and re-verifies the returned card and revocations client-side against your own trust roots (an Ed25519 check; the Hub's word is never trusted). Reports signature: verified | UNVERIFIED, key-bound, revocation status, and the card's captured-capability mix. The exercised grade is local-only (the bundle carries no action receipts). This connects the resolver's two halves: an agent URI now resolves to a re-verifiable bundle from anywhere, the "DNS for agents" lookup. Slice 1c of the agent resolver.

  • Capability provenance: captured vs exercised vs declared. verify-capability now grades each declared capability by its provenance — captured (read off the card's optional capability_provenance block, set from harness config at mint time), exercised (backed by captured action receipts, computed from the cross-check), or declared-only (asserted, never wired or used) — and reports the breakdown. treeship resolve surfaces the same breakdown, so a resolved agent's capability mix travels with it. A new optional capability_provenance field on agent_card.v1 (additive; cards without it are treated as fully declared). The first step from a registry of claims to a registry of captured truth. See capability-provenance spec.

  • treeship attest card --from-harness <path>. Builds a card's capability set from the agent's real harness config (a Claude Code settings.json's permissions.allow list) instead of a hand-typed --tools string, and stamps each entry captured with its source. Deterministic capture at the source: the capability set comes from observed config, not the operator or the model. --tools entries (if also given) are merged as declared.

Older releases

Read the complete release history in the repository.