Treeship
About

Changelog

All notable changes to Treeship, newest first. The format follows Keep a Changelog and versioning follows Semantic Versioning.

All notable changes to Treeship are documented here.

The format follows Keep a Changelog; versioning follows Semantic Versioning. The canonical source is CHANGELOG.md at the repo root. Edit that file, not this page.

Unreleased

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.

0.13.0 (2026-06-24)

Added

  • Predicate registry: typed, schema-validated receipt payloads. Registered kind suffixes are bound to a JSON Schema and validated at attest time, before the receipt is signed; unregistered kinds attest sign-on-submit exactly as before (additive, backward compatible). Registered predicates: memory.write.v1, memory.read.v1, boundary.v1, agent_card.v1, agent_card_revocation.v1. The validator is dependency-free on purpose so the signing path and the WASM verifier stay lean. See reference/predicates.
  • Agent capability cards (agent_card.v1). treeship attest card mints a signed, typed card declaring an agent's identity and capability set; treeship verify-capability <card> checks it against the agent's captured action receipts. The card is key-bound only when its keyid is the envelope signer pinned under AgentCert; otherwise it is reported self-asserted. The cross-check reports in-scope / out-of-scope actions (exact or family.* glob) plus an optional evidence_anchor. Honest contract: consistency over captured evidence, never a completeness guarantee. (#127, #129, #130, #131, #132)
  • Per-actor signing: a provable actor. treeship agent register --own-key mints a dedicated per-agent key, certifies it (the ship signs as issuer), and pins it under AgentCert. treeship attest action / attest card / attest decision / attest handoff then sign with the agent's own key when the actor has one, so the actor is cryptographically provable rather than an asserted label. treeship verify reports actor proof: proven (key-bound) | asserted. Strictly additive: agents without a per-agent key sign with the ship key exactly as before. (#134, #135, #136)
  • Capability-card revocation (agent_card_revocation.v1). treeship revoke-capability <card> --reason … mints a signed revocation. verify-capability honors it only when its signer is authorized (the card's own key, or a Ship trust root) and then reports status: REVOKED; an unauthorized revocation is ignored (fail-closed). (#137)
  • Browser verification of capability cards. A new verify_capability(card, actions, trust_roots) WASM export and a typed verifyCapability() in @treeship/verify let a browser receipt viewer show key-bound status and the in/out-of-scope cross-check, returning the same verdict the CLI does. The matching logic lives in a shared treeship_core::capability module so the browser and CLI verifiers cannot drift. (#138)
  • treeship resolve <agent>. Resolves an agent URI to a verifiable bundle, re-derived from local artifacts: its per-agent key, current capability card, revocation status, and a provenance grade on every fact (captured = the machine observed it, checked = a claim cross-verified against captured evidence, asserted = a bare claim). The local, offline half of the agent resolver; the Hub serves the same bundle next. See agent-resolver spec. (#141)

0.12.0 (2026-06-06)

Added

  • treeship attest receipt --payload-file and --payload-digest for external proof receipts. Providers can now sign a receipt over a JSON payload read from a file (mutually exclusive with --payload) and record a payload_digest for large or out-of-band payloads. treeship attest action gains --output-digest, folded into the action's meta. The flags are provider-neutral: --system and --kind accept any value. New docs describe the provider contract at integrations/memory-proofs (usable by ZMem, Mem0, Zep, Letta, a SQLite store, or an in-house layer) and concepts/multi-agent-sessions. ZMem is one worked example, not built-in behavior.
  • Explicit Hub device-flow states. GET /v1/dock/authorized and POST /v1/dock/authorize now return a stable state field covering invalid, expired, pending, approved, attached, and already_attached, plus provider-neutral next_steps on attach. treeship hub attach reports distinct poll errors (404 / 410 / 409) and concrete next steps (status, attest receipt, hub push, verify). New packages/hub/internal/dock/dock_test.go covers every state and the regression below.

Fixed

  • Hub activation no longer flashes "device_code not found" before attaching. Device-flow finalization deleted the challenge row on consume, so a browser still polling GET /v1/dock/authorized after the CLI finalized received a 404 that rendered as "device_code not found" even though attach had succeeded. Finalization now atomically claims the challenge by dock_id (preserving the single-use guarantee) and retains the row behind a bounded grace window, so the activation page reads the terminal attached state. DPoP and Hub auth invariants are unchanged. An idempotent ALTER TABLE migration adds the dock_id column to existing Hub databases. (#119)

0.11.2 (2026-06-05)

Fixed

  • Receipt preview no longer hangs on "Verifying receipt...". render_preview_html filled the placeholder with a global string replace, but the template carried that token twice (the data slot and a JS placeholder check), so the receipt JSON was injected into a JS string literal and produced an uncaught SyntaxError that aborted the whole client-side verification script. The local treeship dashboard Session Receipt view, which re-renders from the current template, was affected; sealed previews generated before the JS check was added still worked. The template's check now uses a split sentinel that survives substitution, and render_preview_html fills only the first (data-slot) occurrence. Covered by a regression assertion in session::package tests. (#118)

0.11.1 (2026-05-29)

Added

  • treeship session abandon for wedged sessions. The command quarantines an active or closing session marker plus its event directory under .treeship/quarantine-* so a fresh session can start without deleting the evidence needed to debug the failure.
  • Dangerous-root override for intentional home-scoped sessions. treeship session start now refuses to start when the git toplevel is the user's home directory unless --allow-dangerous-root is passed.

Fixed

  • Bounded close-time git reconciliation. treeship session close no longer promotes an unbounded set of untracked files into per-file receipt events. If the untracked scan exceeds the cap, close records an in-band receipt warning and keeps tracked changes bounded.
  • Clearer report errors for incomplete packages. treeship session report now distinguishes an incomplete .treeship package missing receipt.json from "no closed sessions found."

0.11.0 (2026-05-28)

Added

  • Phase 1 of agent invitations: signed, single-use, expiring grants that let a second agent join an existing session. Adds two canonical statement types -- treeship/invitation/v1 and treeship/session-participant/v1 -- both under packages/core/src/statements/. Invitations carry session_ref, issuer (host pubkey), invitee_restriction (one of Pubkey { fingerprint }, Cert { issuer_pubkey, allowed_subjects }, or Open), granted_capabilities.action_types, expires_at, max_uses (always 1 in Phase 1), and a random nonce. The canonical signing string mirrors the v0.10.4 pipe-delimited shape ("v1|invitation|{session_ref}|{issuer}|{restriction_digest}|{capabilities_digest}|{expires_at}|{max_uses}|{nonce_digest}"); variable-length fields fold into sha256:<hex> digests so the canonical stays single-line and unambiguous. Participant envelopes require exactly two DSSE signatures: the joining agent first (assertion of "I'm joining"), then the host countersign over the same canonical bytes (assertion of "I observed this join"); either signature alone is invalid. verify_participant_envelope rejects MissingHostCountersign, TooManySignatures, any sig that doesn't verify, and any countersign whose key doesn't match the invitation's issuer pubkey. Mint-time validation enforces a 7-day protocol ceiling on lifetime (MAX_INVITATION_LIFETIME_SECS); the default is 1 hour (DEFAULT_INVITATION_LIFETIME_SECS). Spec: docs/specs/agent-invitations-rooms.md. 14 unit tests in invitation.rs + 6 in session_participant.rs pin the canonical-binding, signature, expiry, single-use, and immutability properties.
  • Three new CLI subcommands wire the lifecycle. treeship session invite <session_id> mints + signs an invitation against the keystore's default key (the session host), persists it under the artifact store, and emits an armored -----BEGIN TREESHIP INVITATION----- bootstrap blob on stdout (or raw JSON with --no-armor; --format json returns { invitation_id, bootstrap_blob, session_ref, expires_at }). Restriction selection is explicit: exactly one of --invitee-cert, --invitee-pubkey, or --open is required (Open is opt-in because it accepts any holder of the blob). treeship session join --invite <blob>|-|--invite-file <path> --actor agent://... decodes the blob, pins the issuer against SessionHost trust roots, verifies the envelope signature, checks expiry + restriction, writes the invitation's nonce into the Approval Use Journal with max_uses=1 (so a second join attempt fails with the journal's existing MaxUsesExceeded path), and emits a single-sig pending-countersign participant envelope. treeship session countersign <participant_artifact_id> reads the pending envelope, confirms the running default key matches the invitation's issuer, attaches the host countersign, self-verifies, and overwrites the storage record with the finalized two-sig envelope. Three CLI integration tests in packages/cli/tests/invitation_cli.rs exercise the mint -> join -> countersign roundtrip, the no-session-context error path, and the pending-countersign on-disk shape.
  • SessionHost trust root kind added to TrustRootKind in packages/core/src/trust/mod.rs. The invitation issuer pubkey embedded in every InvitationStatement must be pinned under this kind before treeship session join honors it -- without the pin a self-signed forgery would verify cryptographically but be quarantined by trust-root enforcement. treeship trust add <key_id> ed25519:<pubkey> --kind session_host --yes registers a new session host. Separate from Ship so a machine can trust hub-org checkpoints without implicitly trusting that hub to host multi-agent rooms.
  • Local Treeship dashboard. Added treeship dashboard, a localhost-only, read-only browser control plane over sealed .treeship session packages. The dashboard surfaces trust status, multi-Treeship instances, agent membership, receipt/report evidence, review items, setup capabilities, and local report previews without requiring Hub or hosted infrastructure. It also exposes documented JSON endpoints for status, treeships, sessions, agents, collaboration, and capabilities so the UI remains scriptable.
  • Robinhood Agentic Trading receipt template and docs. Added a Robinhood Agentic Trading template plus integration docs and a blog post framing Treeship as the proof/report layer for agentic trading workflows: capture strategy intent, approval evidence, broker/account scope, order outcomes, and risk acknowledgements as verifiable receipts. This is Treeship-side receipt support only; brokerage execution and policy-gateway enforcement stay outside this repo.

Changed (breaking)

  • TrustRootKind enum gained the SessionHost variant. Exhaustive match arms on TrustRootKind in third-party code MUST add the new arm. The on-disk JSON wire format gains "session_host" as a permitted kind value (treeship/trust_roots.json v1 schema). No existing roots are migrated; operators add session-host pins explicitly via treeship trust add. The treeship trust add --kind clap value parser was widened to include session_host; the trust CLI error/hint messages now list four kinds (hub_checkpoint | ship | agent_cert | session_host).

Documentation

  • AI-assisted development policy moved into repo governance. docs/quality/ai-assisted-development.md is the single source of truth for code-quality expectations across human and AI contributors; CONTRIBUTING.md, CLAUDE.md, AGENTS.md, and the treeship-dev contributor skill (both skills/treeship-dev and plugins/treeship-dev) now reference it instead of duplicating policy text. Targets the diligence failure mode of AI-generated code that passes review but fails in production: silent unwrap_or_default() in signed-bytes paths, verifier loops that pass vacuously on empty input, wire-controllable dispatch fields not bound into the canonical, TOCTOU between permission check and read, mixed RNG sources, fabricated test vectors, and scope drift in security-sensitive code. Concrete anti-patterns and examples are drawn from the v0.10.3 and v0.10.4 audit waves.
  • Feature inventory bootstrap. New docs/feature-inventory.yml is the hand-maintained source of truth for what Treeship actually ships -- 29 entries spanning core protocol, identity/approval/attribution, capture surfaces, supply chain, agent integrations, and zero-knowledge work. Each entry carries a status (stable / beta / experimental / roadmap / internal / deprecated), the CLI commands that exercise it, primary docs and test paths, and a one-line note. A permissive linter at scripts/check-feature-inventory.py walks every entry and warns when a listed CLI command isn't in packages/cli/src/main.rs, a docs/tests path doesn't exist, or a package name has no manifest. The linter also reverse-checks for top-level CLI commands not yet represented in the inventory. Warnings only today; build-failing enforcement is a deliberate later step. The new Feature matrix docs page renders the inventory in the docs nav so anyone looking for "what does Treeship actually do today" has a single, honest page to read. Closes the buried-features risk that the v0.10.x audit lane surfaced.
  • Per-agent skill library completed. Added skills/treeship-perplexity/SKILL.md (Perplexity Computer end-user skill, including the GitHub Access gh api recipes specific to Perplexity's tool environment) and the Kimi-format integrations/kimi-code-plugin/treeship-user/ skill bundle (SKILL.md + executable attest_action.py / attest_workflow.py / verify_artifact.py + references/{sdk_api,typescript_sdk,hub_api,mcp_bridge}.md). Brings supported per-agent skills to: end-user (skills/treeship), contributor (skills/treeship-dev), Perplexity (skills/treeship-perplexity), Kimi bundle (integrations/kimi-code-plugin/treeship-user), OpenClaw, Hermes, and the Claude Code plugin sub-skills.
  • New skills/README.md index. Single source of truth for which skill format each supported agent expects, where each skill lives in the repo, when to load which skill (end-user vs. contributor vs. platform-specific), and how to add a new agent skill. Cross-links to integrations/agent-skills/README.md (install commands) and integrations/agents.json (machine-readable inventory).
  • Cross-skill drift fixed. Both the new Perplexity and Kimi bundle skills had inherited stale facts from earlier drafts: the npm CLI package is treeship (not @treeship/cli), the model + provider signed-artifact path landed in v0.10.2 (#75) not v0.10.4, crates.io publishes treeship-core + treeship-cli + treeship-core-wasm (not just treeship-core), and pinned trust roots have been required for hub-checkpoint and agent-certificate verification since v0.10.3. skills/treeship/SKILL.md and integrations/hermes/README.md updated with the same trust-roots note so users hitting "untrusted issuer" on a fresh install don't read the failure as a bug.
  • Agent discovery system landed. integrations/agents.json (schema v1) is the single source of truth for every Treeship agent integration: vendor, actor URI, skill install path, MCP availability, marketplace URL, release status (shipped / candidate / unavailable). Mirrored to /.well-known/treeship-agents.json for public discovery. The docs/content/docs/integrations/install.mdx page is generated from this source via docs/scripts/sync-agents-matrix.mjs so the install matrix never drifts from the source of truth. Closes the broken integrations/agents.json cross-reference from the v0.10.4 skills README PR (#106). Pairs with the discovery direction recommended in docs/specs/workflow-declarations.md (separate PR).
  • Integration parity test harness. integrations/tests/parity.sh runs the claude-code-plugin and kimi-code-plugin hook scripts against shared fixtures and asserts both produce equivalent Treeship CLI invocations. Fixtures in integrations/tests/fixtures/ (hook input JSON) and expectations in integrations/tests/expectations/ (expected CLI args, one file per scenario). Covers Read / Write / Bash / WebFetch tool calls across both plugins. Catches the drift class that produced the OpenClaw event-type mismatch fixed in v0.10.4 (agent.requested_tool vs agent.called_tool).
  • Kimi plugin scripts and config template tracked. integrations/kimi-code-plugin/scripts/{pre,post}-tool-use.sh and session-{start,end}.sh, plus config.toml.template, were sitting untracked in the working tree. The Kimi plugin README referenced them as if they were tracked; this commit makes the README honest.

0.10.4 (2026-05-17)

The Audit Follow-ups (P1) release. v0.10.3 closed the P0 findings; v0.10.4 closes the P1 follow-ups that surfaced while landing them, plus an OpenClaw event-capture fix and the Codex contributor skill.

The connective theme: v0.10.3 fixed the headline downgrade (merkle_version), v0.10.4 fixes the long tail. Same bug class, same fix shape: bind every wire-controllable field that participates in dispatch or display.

Eight PRs from the v0.10.3 audit follow-up wave ship in this release: five P1s (#93, #94, #95, #96, #98) and three P2s (#92, #97, #99). Plus two unrelated improvements that landed alongside: an OpenClaw integration hook-capture fix (commit 41271cd) and the Codex contributor skill docs.

Security

  • Approval signing paths no longer fall back to empty bytes on serde encode failure (packages/core/src/statements/approval_use.rs:367, 466, 483, 500). Previously, serde_json::to_vec(...).unwrap_or_default() in canonical_hub_signing_bytes, approval_use_record_digest, approval_revocation_record_digest, and journal_checkpoint_record_digest would silently sign or verify against Vec<u8>::default() on an encode failure. The failure mode was catastrophic in shape (all empty-byte signatures verify against each other, so two different artifacts that both hit the encode failure would falsely cross-validate) even though unreachable in the current type system. Encoding failures now panic with a clear "report bug" message at the four sites rather than silently producing a forgery vector. A regression test (record_digests_never_match_empty_bytes_sha256) pins that no record digest ever matches the sha256 of empty bytes, so a future re-introduction of the silent fallback would fail the test. Signature changes were deliberately not propagated to the digest functions' callers because all four functions return non-Result types that thread through 50+ call sites; a panic at the sign site is strictly better than silent forgery, and the failure case is unreachable until a schema change introduces a fallible field (e.g. f32/f64).
  • Ed25519Signer now zeroizes its secret key on Drop. The signer wraps an ed25519-dalek SigningKey; on scope exit, the 32-byte secret scalar is wiped from memory. Pre-v0.10.4 signers left the scalar in heap memory until the allocator reused the slot. The wrapper implements Zeroize + Drop + ZeroizeOnDrop; the inner SigningKey also implements ZeroizeOnDrop via ed25519-dalek's zeroize feature (newly enabled), so secrets are wiped at both layers. Ed25519Signer::secret_bytes now returns Zeroizing<[u8; 32]> instead of a raw [u8; 32] so caller-side copies of the scalar are also wiped on scope exit -- the two in-tree callers (KeyStore::generate, KeyStore::rotate) inherit the new wrapper automatically.
  • Randomness sources audited and unified in keys/mod.rs (v0.10.4 P1). All security-sensitive randomness (key generation, nonces, salts) now uses OsRng directly. The few remaining thread_rng sites are test-only or non-security and are annotated inline with the rationale. Closes a v0.10.3 audit P1 flagging mixed RNG sources.
  • Closed TOCTOU window in keystore Store::signer() perm-check (keys/mod.rs:413-427). Previously the file was opened twice: once via check_key_file_perms(path) to verify mode bits, then a second time via load_entry(id) / fs::read(path) to pull the encrypted scalar. An attacker with write access to ~/.treeship/keys/ could swap <id>.json between the two opens by presenting an owner-only file to pass the gate, then racing in a loose-perm file containing an attacker-controlled scalar before the read. The new read_entry_with_perm_check helper opens the file exactly once, runs File::metadata (fstat) on the resulting descriptor, and reads the JSON body from the SAME descriptor. The path is never re-resolved after the open, so the inode is pinned. Matches the single-open plus fchmod-on-fd pattern added to session/event_log.rs:351-354 in v0.10.3 (audit lane F). The TREESHIP_ALLOW_INSECURE_KEY_PERMS=1 bypass continues to work for CI sandboxes.
  • Same fix applied to TrustRootStore::open() (trust/mod.rs:264-297). The trust root file at ~/.treeship/trust_roots.json is at least as security-sensitive as the keystore; a swap between perm-check and read could substitute attacker-controlled pinned issuer keys into the verifier. The loader now opens once, fstat-checks the fd via check_open_trust_file_perms, and reads the JSON from the same fd. Same bypass semantics as the keystore; the one-time WARN_INSECURE_PERMS_ONCE stderr warning still fires when the bypass is honored.

Changed (breaking)

  • Checkpoint canonical signing format bumped to v3. New checkpoints sign "v3|{canonical_version}|{merkle_version}|{algorithm_or_empty}|{zk_proof_digest_or_empty}|{index}|{root}|{tree_size}|{height}|{signer}|{signed_at}" where zk_proof_digest is the hex-encoded SHA-256 of the sorted-key canonical JSON of the ChainProofSummary (empty string for None). v3 closes a second-tier downgrade where an attacker could take a legitimately v2-signed checkpoint, mutate algorithm or attach/swap/strip zk_proof on the wire, and have verification still pass. The canonical_version byte is itself bound into v3 to prevent a downgrade-by-relabel attack: flipping canonical_version: 3 → 2 on the wire fails verification because the bytes re-canonicalize differently under v2 dispatch. v0.10.3-era v2 checkpoints continue to verify under v2 dispatch (legacy compat via #[serde(default)] = 2 on the new canonical_version field). Pre-v0.10.3 v1 checkpoints continue to verify byte-identically under the legacy form.
  • Third-party verifiers that reproduce the canonical string outside this Rust core (hand-rolled JS/Go/Python checkers, Hub server, in-browser receipt previews that don't go through WASM) MUST update. @treeship/verify is a thin WASM shim over core-wasm and inherits the change automatically; no consumer action needed there. Programs using treeship verify, treeship merkle verify, or the Python/TS SDKs also inherit automatically.
  • This is the second canonical break in two patch releases (v0.10.3 → v2, v0.10.4 → v3). They are landing close together intentionally so external verifiers update once for both rather than across the v0.10.3 + v0.10.4 window. After v0.10.4, the canonical format is expected to be stable through the v0.11.x line.
  • Checkpoint::canonical_for_signing signature changed to take canonical_version, algorithm: Option<&str>, and zk_proof: Option<&ChainProofSummary> arguments. The fn is pub(crate) so external callers should not be affected; the WASM shim and CLI verify paths go through Checkpoint::verify which handles dispatch internally.
  • New canonical_version: u8 field on Checkpoint with #[serde(default)] = 2 so v0.10.3-era checkpoints (which never carried this field) deserialize as v2 and continue to verify.
  • The algorithm field on Checkpoint is now informationally redundant with merkle_version but is bound into the v3 canonical anyway, so the on-wire value is locked. Slated for removal in a future canonical (v4) once a deprecation window has passed.

Fixed

  • CLI no longer panics on unexpected enum variants in treeship prove and treeship verify --external (prove.rs:127, verify_external.rs:224). The two unreachable!() calls were reachable via clap-parsed input; replaced with explicit Err returns with actionable messages.
  • OpenClaw plugin now captures tool calls under OpenClaw v0.10.3's hook API. Three issues fixed in one pass. (1) Hook registration: OpenClaw v0.10.3 switched to camelCase hook names (beforeToolCall/afterToolCall) with an api.on() registration method; the plugin now registers under BOTH the snake_case + camelCase names AND via both registerHook + api.on(), so events are captured regardless of which hook surface OpenClaw uses at runtime. (2) Event types: before_tool_call now emits agent.called_tool with phase=intent metadata (not the unsupported agent.requested_tool); the bash handler emits agent.called_tool with command metadata (not the unsupported agent.ran_shell); the network handler emits agent.connected_network with --destination. (3) Type safety: dynamic hook registration sites use as any casts for TypeScript without runtime impact. End-to-end verified on a real OpenClaw session: paired intent + result events captured correctly for read, exec, and network calls.

Internal

  • Seven new tests in merkle::checkpoint::canonical_v3_tests pin the canonical v3 fix: algorithm_tamper_detected, zk_proof_tamper_detected (covers attach/mutate/strip), v2_legacy_checkpoint_still_verifies, v1_legacy_checkpoint_still_verifies, cross_version_downgrade_v3_to_v2_rejected, unknown_canonical_version_rejected, and fresh_checkpoint_is_v3.
  • update_checkpoint_with_proof in the daemon is now broken-by-design under v3 because it mutates a signed checkpoint after the fact to attach a zk_proof summary. The v3 binding correctly fails verification on these post-hoc-mutated checkpoints. Marked with a TODO and tracked as a v0.10.5 follow-up: the proof either needs to be re-signed with the original hub signer (requires daemon to hold or proxy that key) or moved into a sibling envelope that's signed independently. The zk feature is opt-in (cfg gate) so this affects only operators who have explicitly enabled chain proofs.
  • Toolchain pinning at repo root (rust-toolchain.toml, .rustfmt.toml, clippy.toml). Local developers now use the same toolchain channel, rustfmt config, and clippy lint set as CI. rust-toolchain.toml pins channel 1.94.1 (current stable, matching the moving stable branch CI's dtolnay/rust-toolchain@29eef33 resolves to as of 2026-05-17); rustup will auto-install the channel plus clippy and rustfmt components on first cargo invocation. .rustfmt.toml pins edition = "2021" and max_width = 100 without forcing a workspace-wide reformat. clippy.toml pins msrv = "1.94" so MSRV-sensitive lints stay correct. CONTRIBUTING.md updated. Closes a v0.10.3 audit P2 flagging local-CI drift risk.

Documentation

  • Codex contributor skill added. skills/treeship-dev/SKILL.md plus plugins/treeship-dev/.codex-plugin/plugin.json give Codex (and any other agent that consumes the Codex plugin format) a single declarative skill describing how to work safely in the Treeship source repo: scope, required read order, crypto invariants, CLI UX rules, validation commands. Mirrors the structure of the skills/treeship agent skill from v0.10.2 (#67); same shape, different audience (contributor vs. end-user). docs/content/docs/integrations/codex.mdx is the docs page; integrations/codex/README.md covers local setup. Closes the v0.10.2 follow-on of "every supported agent gets a Treeship skill" by adding Codex to the list.
  • Documented subcrate version policy (docs/architecture/subcrate-versions.md). packages/vi, packages/zk-circom, and packages/zk-risc0 follow independent release cadences from the main Treeship monorepo (each is publish = false and not a hard dependency of the CLI release path). The version-consistency preflight (scripts/check-release-versions.py) intentionally does not cover them. CONTRIBUTING.md now points at the new doc and the project-structure table calls out the sibling-cadence crates. Closes a v0.10.3 audit P2 flagging version skew between packages/core (0.10.3) and packages/vi (0.6.0) / packages/zk-* (0.5.0). No genuine drift found; the stale packages/zk-circom/package.json at 1.0.0 (npm scaffolding default, never published, never consumed) is documented as cosmetic cleanup, not skew.

0.10.3 (2026-05-17)

The Audit Hardening release. A post-launch security audit of v0.10.2 surfaced eleven P0 findings across the crypto, trust, and supply-chain surfaces. v0.10.3 closes every one of them.

The connective theme: v0.10.0 made the receipt portable, v0.10.1 made the binary trustworthy, v0.10.2 made the receipt's authorship honest. v0.10.3 makes the underlying crypto and trust paths actually live up to what the protocol claimed. The keystore now uses real AEAD instead of a homemade construction. The Merkle tree has RFC 9162 domain separation, and the version selector is bound into the signed canonical so it cannot be downgraded. Checkpoint, hub-checkpoint, and agent-certificate verification require pinned trust roots, and untrusted issuers fail by default instead of warning. The verifier refuses zero-signature envelopes and unverified bundle imports. Supply chain: the Python SDK now verifies the CLI binary's SHA-256 before exec, GitHub Actions are pinned to commit SHAs, and the release workflow's write permissions are scoped to the release job only.

Ten landed PRs ship in this release. Five close P0 audit findings (#83, #86, #87, #88, #89); five close the supporting hardening and visibility lanes (#80, #81, #82, #84, #85). See TS-2026-001 (docs/security/TS-2026-001.md) for the keystore advisory and docs/security/threat-model.md for the in-repo threat model added in this release.

Security

  • Keystore at-rest encryption replaced with real AES-256-GCM AEAD (TS-2026-001). The pre-v0.10.3 keystore claimed AES-256-GCM in code and docs, but the implementation was a homemade SHA-256-CTR + HMAC-SHA-256 construction with a degenerate keystream (the same enc_key byte was reused once per plaintext byte modulo 32, so the construction was not a real stream cipher). Replaced with the RustCrypto aes-gcm 0.10 crate, which provides proper authenticated encryption with a 96-bit random nonce per write and a 128-bit GCM tag. New on-disk format: [magic=0x54, version=0x02, nonce(12), ciphertext || tag(16)].
  • Existing keystores are transparently migrated on first decrypt. Pre-v0.10.3 keystore entries continue to load via the legacy decrypt path (decrypt_legacy_v1) and are immediately re-encrypted in the new v2 format and rewritten on disk. No user action required. If migration write fails (e.g. read-only filesystem), signing still succeeds for the current call and migration is retried on the next load. The treeship-vi sibling keystore retains the legacy public symbols (aes_gcm_encrypt / aes_gcm_decrypt) so its on-disk format remains byte-stable until it migrates on its own cadence.
  • Local AEAD key buffer wrapped in zeroize::Zeroizing so the stack copy is wiped on drop. The aes-gcm cipher's internal expanded key schedule is outside our control, but the raw 32-byte machine-derived key copy at the encrypt/decrypt scope is cleared.
  • v2 AAD now binds entry id + public_key (closes intra-keystore swap). The original v2 AAD covered only the framing prefix; a local attacker with write access to ~/.treeship/keys/ could copy entry A's enc_priv_key ciphertext into entry B's JSON envelope and silently un-bind KeyInfo.public_key from the actual scalar in use. The v2 AAD now also length-prefixes and binds the entry id and public key, so any envelope-vs-ciphertext swap surfaces as a MAC failure. write_file_600 also now fsyncs the parent directory after rename so the H1 atomic-write doc-comment's durability promise matches reality on ext4/xfs.
  • Verifier refuses zero-signature envelopes and unverified bundle imports (audit lane H, #86). Verifier::verify previously returned Ok on envelopes whose signature list was empty because the verification loop simply did not run. bundle::import wrote every envelope to storage without verifying signatures. After this release, both paths reject before any state mutation. The fake chain_linkage = pass row that advertised a check the verifier never actually performed is gone, with a regression test pinning it down. bundle::import now uses a verifier built from the local keystore and filters by algorithm == "ed25519" to stay forward-compatible with non-Ed25519 keys.
  • Python SDK verifies the CLI binary's SHA-256 before exec (audit lane B, #82). treeship_sdk.bootstrap now ships three expected-checksum files (one per supported platform: linux-x86_64, darwin-aarch64, darwin-x86_64), embedded in the wheel from PyPI as one trust root, with the binary fetched from GitHub Releases as a second independent trust root. The bootstrap downloads to a unique temp .partial path, verifies SHA-256, fails closed on any mismatch (no chmod, no rename), and only then renames the verified bytes into place. Closes the asymmetric supply-chain gap the npm side closed in v0.10.1 (#72).
  • Event log file lock can never be skipped under contention (audit lane F, P0, #83). session/event_log.rs::append previously fell through a lock-acquisition timeout and wrote events without holding the lock, colliding sequence_no values under PostToolUse hook concurrency. The fix uses blocking lock_exclusive() (callers are fire-and-forget let _ = log.append(...), so erroring on contention would silently drop receipts; Claude Code's 60s hook timeout bounds wedge risk). Locked region tightened to a closure between lock_exclusive and unlock. 8-thread × 25-append concurrency test asserts sequence_no is exactly 0..200 with no duplicates or gaps.

Added

  • treeship trust subcommand for managing pinned trust roots (list, add, remove).
  • ~/.treeship/trust_roots.json config file (mode 0o600, JSON schema version 1) listing trusted issuers for checkpoint, ship (hub-org), and agent-certificate verification. Path override via TREESHIP_TRUST_ROOTS.
  • treeship_core::trust module with TrustRootStore, TrustRoot, TrustRootKind, TrustRootError.
  • treeship trust add and treeship trust remove now require interactive y/N confirmation or an explicit --yes flag. Both print the affected key's 16-hex-character SHA-256 fingerprint before the change. Non-interactive stdin without --yes refuses; JSON output mode requires --yes (a y/N prompt does not compose with JSON). Replacements of an existing (key_id, kind) entry show both the existing and incoming fingerprints so an attempted swap is visible.
  • One-time stderr warning when TREESHIP_TRUST_ROOTS is set (path override) or TREESHIP_ALLOW_INSECURE_KEY_PERMS=1 is honored (perms bypass). Deduplicated per-process via std::sync::Once. Both env vars are exactly the lever a supply-chain attacker would pull in a CI runner to redirect trust; the warning ensures the boundary move shows up in CI logs.

Changed

  • Merkle tree hashing now uses RFC 9162 domain separation (0x00 byte prefix on leaves, 0x01 byte prefix on internal nodes). Receipts in v0.10.3+ include a merkle_version: 2 field on the merkle section, on each inclusion proof, and on each checkpoint. v0.10.2 and earlier receipts continue to verify (dual-verify) because the field deserializes as 1 (legacy hashing) when absent. New receipts always emit 2. v1 verification removed in v0.13.0 (~3 minor releases out). Closes the second-preimage attack where a 64-byte artifact ID whose sha256 happened to equal a real internal-node hash could forge inclusion proofs, and the related single-leaf impersonation where a 32-byte internal hash plus an empty proof path appeared as a one-leaf tree.

Changed (breaking)

  • Checkpoint, hub-org checkpoint, and agent-certificate verification now require the embedded public key to match a configured trust root. Previously they trusted the embedded key (self-signed forgeries always passed). Run treeship trust add <key_id> <pubkey> --kind <hub_checkpoint|ship|agent_cert> for each known issuer before verifying.
  • Checkpoint::verify(&self)Checkpoint::verify(&self, trust: &TrustRootStore).
  • verify_hub_checkpoint_signature(&cp)verify_hub_checkpoint_signature(&cp, trust: &TrustRootStore). New HubCheckpointVerification::UntrustedIssuer variant covers the unpinned-issuer case.
  • verify_certificate(&cert)verify_certificate(&cert, trust: &TrustRootStore). New CertificateVerifyError::UntrustedIssuer { key_id } and CertificateVerifyError::NoTrustConfigured variants.
  • treeship_core::session::verify_package(path) now auto-loads ~/.treeship/trust_roots.json; tests that want an explicit store can call the new verify_package_with_trust(path, &trust). If the trust file is malformed or has overly-open permissions the function emits a trust-root Fail check row instead of silently falling back to an empty store.
  • The replay-hub-org check row now emits Fail by default (not Warn) when the hub signature is untrusted, tampered, or has the wrong kind. Previously these were Warn and --strict promoted them to Fail, which meant a self-signed forgery exited 0 in default mode with only a yellow warning. Non-security shortcomings (missing field, coverage gap) still warn by default and --strict still promotes them.
  • WASM exports verify_certificate, cross_verify, and verify_merkle_proof take an additional trust_roots_json: &str argument (empty string = fail closed). @treeship/verify exposes this via an optional trustRoots parameter on verifyCertificate and crossVerify.
  • Checkpoint canonical signing now includes merkle_version. v2 checkpoints sign "v2|{merkle_version}|{index}|{root}|{tree_size}|{height}|{signer}|{signed_at}" instead of the pre-fix "{index}|{root}|{tree_size}|{height}|{signer}|{signed_at}". v1 (legacy) checkpoints continue to sign and verify under the original byte-identical format, so pre-v0.10.3 receipts are unaffected. This closes a downgrade vector: without binding merkle_version into the signature, an attacker could take a legit v2-signed checkpoint, attach a v1-flavored proof (merkle_version: 1, empty path, leaf_hash = root), and have verify_proof dispatch through v1 hashing, accepting a forgery. Any third-party verifier that reproduces the canonical string outside the Rust core (hand-rolled JS/Go/Python checkers) MUST mirror the new dispatch. @treeship/verify is a thin WASM shim over core-wasm and inherits this change automatically; no consumer action needed there. Programs using treeship verify, treeship merkle verify, the Hub verifier, the Python/TS SDKs, or the in-browser receipt preview also inherit automatically.
  • MerkleTree::verify_proof signature changed. It now takes expected_version: u8 as its first argument (the trusted version, sourced from a signature-verified checkpoint or parent receipt section) and rejects if proof.merkle_version != expected_version. The old form that read the version straight off the proof blob is gone — proof.merkle_version is attacker-controllable, so dispatching on it was the downgrade primitive.
  • MerkleTree::with_version now returns Result<Self, MerkleError> and rejects anything outside {1, 2}. Receipts declaring an unknown merkle_version surface as a hard fail in both verify_receipt_json_checks and verify_package instead of silently being treated as v1.

Fixed

  • CLI JSON output for hub and session subcommands (audit lane G, #84). treeship hub ls, hub status, session, and session log previously emitted nothing on --format json because they routed through printer.info(...) which is a no-op in JSON mode. SDK consumers calling JSON.parse("") got swallowed errors and silent fallback values (e.g. hub.status() always returned {connected: false}). All four commands now emit structured JSON envelopes.

Internal

  • GitHub Actions pinned to commit SHAs (audit lane C, #80). All 28 uses: lines across ci.yml and release.yml now reference 40-char SHAs with the version tag in a comment. Workflow-level contents: write removed; scoped to the release job only. id-token: write scoped per-job. wasm-pack install switched from curl | sh to cargo install wasm-pack --version 0.14.0 --locked in both workflows.
  • TS SDK real CLI round-trip test (audit lane D, #85). packages/sdk-ts/test/roundtrip.test.ts exercises ship().attest() against a real built treeship binary, asserts the receipt verifies, then mutates a byte and asserts verification fails. Five new tests including a tamper assertion the author sanity-checked by inverting it before restoring.
  • Version-consistency preflight now covers marketplace.json (audit lane A, #81). scripts/check-release-versions.py walks both metadata.version and plugins[name=treeship].version in .claude-plugin/marketplace.json, and scripts/release.sh prepare bumps them. Closes the drift class that left v0.10.2 advertising 0.9.5 in the plugin marketplace. Stale "177 tests" mentions in CONTRIBUTING.md, AGENTS.md, and .github/PULL_REQUEST_TEMPLATE.md aligned to current count; SECURITY.md supported-versions table aligned to 0.10.x.
  • In-repo threat model (docs/security/threat-model.md) and TS-2026-001 advisory (docs/security/TS-2026-001.md) added under docs/security/. SECURITY.md updated to point at both.

Migration notes

  • v0.10.2 hub artifacts in the wild: any receipt or hub-signed checkpoint produced before this version embedded a hub public key that the verifier accepted on the basis of the embedded key alone. After this release the verifier requires that key to appear in ~/.treeship/trust_roots.json for the matching kind. Operators with existing hub artifacts must extract the embedded signer/hub_public_key value from one of their own trusted artifacts and add it via treeship trust add <key_id> <pubkey> --kind <hub_checkpoint|ship|agent_cert> before this version's verifier will accept their existing receipts. A treeship trust extract <artifact> convenience helper that pulls the embedded pubkey out of any signed artifact is tracked separately and not in this release; until then, extract by inspecting the hub_public_key (for hub-org checkpoints), public_key (for Merkle checkpoints), or signature.public_key (for agent certificates) field of the artifact JSON.
  • Keystore v1 → v2 migration is automatic and silent. Existing keystores load via the legacy decrypt path and are immediately re-encrypted in the v2 AEAD format on first decrypt. No operator action required. If migration write fails (e.g. read-only filesystem), signing still succeeds for the current call and migration is retried on the next load.

0.10.2 (2026-05-11)

The Multi-Agent Attribution and Universal Skills release. v0.10.0 made receipts agent-readable. v0.10.1 hardened the install path that delivers the binary which produces them. v0.10.2 closes two follow-on gaps the post-launch dogfooding surfaced: model/provider attribution silently disappearing on the signed-artifact path, and agents on every CLI re-learning how to use Treeship from scratch.

The connective theme: a receipt should say which model did the work, and any agent should know how to make one. v0.10.0 made the receipt portable. v0.10.1 made the binary trustworthy. v0.10.2 makes the receipt's authorship honest, and gives every supported agent the same playbook for producing one.

Two landed PRs ship in this release:

  • PR 0 (#75) — model+provider threaded through treeship attest decision into the session timeline
  • PR 1 (#67) — Treeship Agent Skills: one SKILL.md, every supported agent

Fixed (model+provider attribution on the signed-artifact path — #75)

  • DecisionStatement gains optional provider. Receipts produced via treeship attest decision now carry provider in the signed artifact. Pre-v0.10.2 artifacts without it still parse via #[serde(default)], so verification of older receipts is unchanged.
  • treeship attest decision --provider now accepted. Previously rejected by clap (--provider only existed on treeship session event). The documented Kimi-attribution invocation — --model kimi-k2 --provider moonshot — actually works as documented.
  • attest decision emits SessionEvent::AgentDecision into the active session timeline, populating model, provider, tokens_in, tokens_out, summary, confidence from the same CLI args. Without this, the receipt's agent_graph carried model: null, provider: null even when both were specified on the attest call. The published receipt page now shows the correct provider pills and token counts for these agents.
  • commands::session::append_active_session_event — internal helper that lets artifact-producing paths mirror themselves into the active session log, best-effort. No active session is not an error.
  • New unit tests in packages/core/src/statements/mod.rs: decision_statement_provider_roundtrips and decision_statement_legacy_payload_without_provider_decodes pin both the new field and backwards-compat for pre-v0.10.2 artifacts.

Added (Treeship Agent Skills — #67)

  • skills/treeship/SKILL.md — one declarative skill teaches Kimi Code CLI, Claude Code, Codex, Cursor, OpenClaw, and Hermes how to use Treeship: CLI commands, both SDKs, MCP bridge wiring, approval-gated workflows, multi-agent handoffs, and Hub API.
  • integrations/agent-skills/README.md — per-agent setup guide for the six supported CLIs.
  • docs/content/docs/integrations/agent-skills.mdx — docs page surfaced at treeship.dev/docs/integrations/agent-skills.
  • docs/content/blog/multi-agent-skills.mdx — launch post.

Pair the skill with the v0.10.0 receipt-sharing release: an agent on a fresh machine can install Treeship, run a session, share a receipt, and hand the user a verifiable URL — all from a single skill file, no re-explaining the API.

Compatibility

  • All v0.10.1 hardening (keystore perms, npm integrity, musl distro support, MCP stdio server, Python SDK correctness, CLI correctness) is unchanged. v0.10.2 is purely additive on top of v0.10.1.
  • Pre-v0.10.2 signed DecisionStatement artifacts continue to verify because the new provider field is #[serde(default)].
  • The receipt page's per-agent rendering (model pills, token counts, agent-card links) already shipped in #7's server-rendering rebuild on treeship.dev. #75 closes the data side that those UI elements were reading from.

0.10.1 (2026-05-01)

The Platform, SDK, Supply Chain, and MCP Hardening release. v0.10.0 closed the agent-native loop end-to-end (sandboxed bootstrap → setup → session → share → verifiable URLs). v0.10.1 hardens the floor underneath that loop: the binary that gets installed, the keystore that signs receipts, the SDK that drives the CLI, the CLI itself, the npm path that delivers the binary, and the MCP integration that lets multi-agent sessions exist at all. Every fix in this release closes a finding from the v0.10.0 security and integration review or a bug surfaced during multi-agent dogfooding.

The connective theme: trust starts before the receipt is signed. A receipt is only as trustworthy as the keystore that signed it, the binary that produced it, the SDK that called the binary, and the install path that delivered the binary. v0.10.0 made every receipt URL agent-readable. v0.10.1 makes every step before the receipt — install, key handling, SDK call, CLI invocation — equally honest.

Six landed PRs ship in this release:

  • PR 0 (#68)@treeship/mcp ships a real stdio MCP server alongside the existing client library
  • PR 1 (#69) — keystore permissions enforced at write, sign, and repair
  • PR 2 (#70) — Python SDK correctness pass plus input validation
  • PR 3 (#71) — CLI config/init/json correctness
  • PR 4 (#72) — npm binary integrity via two-trust-root SHA-256 verification
  • PR 5 (#73) — Linux x86_64 (any distro) via static musl binary, with a distro smoke gate that blocks publish on regression

Added (real @treeship/mcp stdio server — PR 0)

  • bridges/mcp/src/server.ts — the npm package now ships a real MCP server (bin: treeship-mcp). Configuring npx -y @treeship/mcp in Claude Code, Codex CLI, Cursor, or Cline now works as documented; v0.10.0's package was a client wrapper class only and silently failed to boot when added as a server.
  • Five tools exposed: treeship_session_status, treeship_session_event, treeship_attest_action, treeship_verify, treeship_session_report. Calls forward to the local treeship CLI with the configured TREESHIP_ACTOR.
  • bridges/mcp/README.md and bridges/mcp/ATTACHING.md distinguish three modes: server mode (this PR), library mode (existing TreeshipMCPClient), and the transparent forwarder model that was previously documented but never built and remains roadmap.

Added (keystore permission hardening — PR 1)

  • Sign-time refusal of insecure private keys. Store::signer(&id) checks the file mode of ~/.treeship/keys/key_<id>.json before decrypting; any group or world bits return KeyError::InsecureKeyPerms { path, mode } with an actionable error.
  • treeship doctor --fix repairs a loose keystore in place: directory to 0700, key files and config.json to 0600. Reports every (path, old_mode, new_mode) triple it changed. Runs first when --fix is set so the rest of the diagnostic checks aren't tripped by the perms we are about to repair.
  • Store::fix_perms() — public API for callers that want the same repair without going through doctor. Returns the change list.
  • Bypass via TREESHIP_ALLOW_INSECURE_KEY_PERMS=1 for sandboxed CI; documented in the error message.

Added (Python SDK correctness + input validation — PR 2)

  • __version__ derived from importlib.metadata.version("treeship-sdk") instead of a hand-edited string. Removes the drift class of bug where pyproject.toml says 0.10.0 but treeship_sdk.__version__ reports 0.9.x.
  • Treeship(cli_path=..., timeout=..., cwd=..., env=...) constructor parameters. Prior versions only accepted bot_mode and silently discarded the resolved binary at call time — _run (a free function) hardcoded "treeship". Fix surfaced a real bug: every SDK user with bot_mode=True was actually still depending on PATH. Now _run_cli_raw / _run_cli_json are methods on Treeship and always call self._binary.
  • wrap() accepts both str and Sequence[str]. str path uses shlex.split for quoted-arg backwards compat; Sequence[str] passes argv exactly. Naive command.split() is gone.
  • session_report() uses --format json (CLI schema treeship/share-result/v1) with a text-format regex parse fallback for older CLIs.
  • Empty artifact_id raises TreeshipError instead of returning the empty string. The old result.get("id") or result.get("artifact_id", "") silently swallowed CLI-shape regressions.
  • _reject_option_like(name, value) rejects values shaped like CLI flags (--format, -x) before subprocess. Without shell expansion this isn't an injection vulnerability per se, but clap would silently parse such a value as a flag and produce a confusing CLI-side parse error far from the SDK call site.
  • Length and range bounds on actor / approver / model / nonce / artifact_id (256 chars), description / summary (4096 chars), tokens_in / tokens_out (0..100M), confidence (0.0..1.0, NaN-safe). Failures raise at the SDK boundary with a clear message.

Added (CLI config / init / json correctness — PR 3)

  • Project-level .treeship/config.json extends: is honored. A stub like {"extends": "/path/to/parent", "project": true} now resolves the parent and inherits ship_id / keys_dir / default_key_id. Prior versions tried to deserialize the stub directly and crashed with missing field ship_id. Cycle detection (canonicalized visited set) catches self-references; depth cap of 5 catches accidental chains.
  • treeship init --force no longer clobbers the global keystore by default. From any cwd that resolves to the global config, --force alone refuses with an actionable error pointing at project-local init, explicit --config <path>, or --global --force for explicit destructive intent. Two earlier dogfooding sessions hit the silent-clobber path; this guard closes it.
  • attest action --format json returns structured success/failure. v0.10.0 emitted {} and exited 1 on errors; the message was dropped entirely, and the @treeship/mcp bridge surfaced every CLI error as a generic isError with no actionable content. Now JSON mode emits {"status": "error", "error": "<full message>", ...} envelopes. Success keeps every existing field plus status: "ok" and message. Backwards compatible.
  • global_config_path() — new helper that returns ONLY ~/.treeship/config.json, never walking up for a project-local match. Used by init's stub writer to prevent the self-referencing extends: bug at the source.

Added (npm binary integrity — PR 4)

  • Two-trust-root SHA-256 verification. The expected hash ships in the npm package as expected-checksum.txt (delivered via npm registry, signed by npm package integrity); the binary ships on GitHub Releases (independent trust root). Each platform's postinstall.js downloads to a .partial file, verifies the SHA-256, atomically renames into place on match, and fails closed on every error path: missing checksum, malformed checksum, hash mismatch, HTTP non-200, redirect chain > 5, network timeout. exit(0)-on-failure is gone.
  • Release pipeline computes SHA256SUMS in the release job, uploads it to the GitHub Release (auditors can cross-check), and surfaces per-platform hashes as job outputs. The publish-npm job has a new "Embed expected SHA-256" step that writes each hash into its matching npm package before npm publish. Refuses to publish if any hash is missing or malformed.
  • Compromise of either trust root alone fails install. A tampered binary on GitHub fails the embedded-hash check; a tampered hash on npm fails because the binary it claims doesn't match what's on GitHub.

Added (Linux musl + distro smoke gate + honest install docs — PR 5)

  • Linux x86_64 binary is now a static musl build. Replaces the v0.10.0 GNU build that required GLIBC 2.39 and silently failed on Debian 12, Ubuntu 22.04, RHEL/Rocky 9, Amazon Linux 2023, and every Alpine release. The musl binary runs everywhere: glibc distros, musl distros, minimal containers (distroless, busybox, scratch).
  • CI static-linkage check. A new Verify static linkage step in the build job aborts the release if file reports the produced binary as dynamically linked — a transitive dep drift could silently produce a "static" binary that runtime-loads glibc and breaks on Alpine. Catches it at release time, not in user issues.
  • Distro smoke gate before publish. A new smoke job runs after build and before any publish step. Pulls the linux-x86_64 artifact, mounts it read-only into debian:12, ubuntu:22.04, ubuntu:24.04, and alpine:3.20 containers, and exercises install / --version / init / attest action / verify against each. release, publish-npm, publish-crates, and publish-pypi all needs: smoke. A smoke failure aborts the entire pipeline before any user-visible artifact (GitHub Release, npm tarball, crate, wheel) is produced. The job graph is now preflight → build → smoke → release → publish-{npm, crates, pypi}.
  • scripts/smoke-platform-release.sh — local smoke harness with the same matrix as the CI gate, runs on the operator's machine via Docker. Useful for pre-tag validation against an existing version or against a locally-built artifact.
  • Honest platform docs. README.md, npm/treeship/README.md, and docs/content/docs/guides/install.mdx now declare a single Linux row: "x86_64 (any distro, glibc or musl) — Supported as of v0.10.1." The v0.9.x lie about a Windows binary "planned for v0.10.0" is gone from every doc that ever made it. Linux ARM64 is honestly marked "Not yet shipped." Windows is "Not supported natively. Use WSL."

Honesty rule preserved

  • The transparent MCP forwarder model is roadmap, not present-tense. PR 0 ships a server-of-Treeship-tools, not a forwarding gateway. Docs were updated to stop promising the forwarder shape.
  • Linux ARM64 is honestly absent. No fake "supported" row in any platform matrix. Users who need it can open an issue.
  • Native Windows is honestly off the roadmap. No "planned for vX.Y" in any doc. The npm preinstall guard prints a real link to file an issue if a Windows user lands on it.

Verification (release-gate)

CheckResult
version-consistency: 21 sites agree at 0.10.1preflight in release.yml
cargo test --workspace270+ tests pass across core / cli / zk / sdk-ts
Python SDK unittest28/28 pass
tests/acceptance/trust-fabric.sh1/1 pass
T1 Python bootstrap✓ resolves CLI without PATH/auth/sudo
T2 treeship setup --yes --no-instrument --format json✓ schema + 5 detected agents + 5 draft cards
T3 treeship session report --share --format json --no-upload✓ all required fields, verification_status: pass
Linux distro smoke (Debian 12 / Ubuntu 22.04 / Ubuntu 24.04 / Alpine 3.20)gates publish in CI on tag push
npm binary integrity: tamper / mismatch / missing checksumfails closed locally; full pipeline gate fires on first 0.10.1 publish
doctor --fix repairs insecure keystore perms✓ end-to-end smoke against tmpdir keystore

Intentionally NOT included

  • No new trust subsystem. No Hub server changes, no AgentGate runtime enforcement, no issuer/registry identity.
  • No Linux ARM64 binary (tracked separately; same musl approach will apply).
  • No native Windows binary (not on the roadmap).
  • No transparent MCP forwarder mode (still roadmap).
  • No Sigstore / cosign signatures of the binary (the two-trust-root SHA-256 model is the v0.10.x answer; Sigstore is future hardening).
  • No report intelligence / receipt redesign work (deferred until the hardening floor is in).

0.10.0 (2026-04-30)

The Agent-Native Receipt Sharing release. Makes Treeship operable by AI agents end-to-end. A human can now say "agent, set up Treeship and send me the receipt" and the agent comes back with receipt_url, raw_json_url, package_download_url, receipt_digest, package_digest, verification_status, and warnings — without asking a clarifying question, without browser auth, without sudo, without modifying PATH. The previous releases made the receipt trustworthy (v0.9.6), the release process trustworthy (v0.9.7), the agent experience legible (v0.9.8), the approval surface complete (v0.9.9), the approval-evidence verifier honest (v0.9.10), and the docs reliable (v0.9.11). v0.10.0 closes the loop: every URL the agent returns leads somewhere a non-browser reader can use.

The connective theme: the receipt is not a webpage; it's a verifiable artifact that happens to render as one. v0.9.x receipts existed but could only be inspected through a JS-required browser visit — curl /receipt/<id> returned literally Loading receipt.... AI agents, link unfurlers, search engines, offline reviewers, and any non-browser tool got nothing. v0.10.0 makes every output channel agent-readable: server-rendered HTML for humans and crawlers, a stable JSON contract for AI agents, a downloadable .tar.gz for offline verifiers, and a CLI bootstrap path so a sandboxed agent can install Treeship without human intervention.

This is the first release where an AI agent in a fresh sandbox — no PATH, no installed CLI, no global state — can run python -m treeship_sdk.bootstrap_cli --json, get a working CLI, run treeship setup --yes --format json, run a session, share the result, and hand a human three URLs that all work. The Kimi dogfood failure that motivated this release ("CLI missing from PATH; install script timed out; agent had to ask human") is closed.

Added (server-rendered receipt page — treeship.dev PR 1)

  • /receipt/<sessionId> is now SSR. curl https://www.treeship.dev/receipt/<id> returns a useful summary in the initial HTML response: session id, name, status, verification (✓ verified / ✗ check), started timestamp, duration, agents (with names), tool count, files changed (written + read), artifact count, handoffs, Merkle root, Approval Authority counts (when present), warning decision cards, and the three agent-readable URLs. Hydrates over the SSR layout into the rich client UI for browser visitors; non-JS readers get the deterministic snapshot.
  • OpenGraph + Twitter card metadata generated from the receipt's facts so link unfurlers (Slack, Discord, Twitter, Mastodon) get useful previews without running JS. Title format: Treeship receipt — <session_name> · <verified|check>.
  • Hub-direct fetch with demo fallback. The page fetches the demo receipt inline for ssn_a1b2c3d4 and proxies to ${HUB_API_URL}/v1/receipt/<id> for real session ids — bypasses the same-origin self-fetch loop that breaks during local dev / build.

Added (stable agent-native JSON contract — treeship.dev PR 2)

  • /api/receipt/<id>/agent returns treeship/receipt-agent/v1: a documented, stable shape designed for AI agents and external orchestration. Sibling of /api/receipt/<id> (raw passthrough, kept untouched). Every URL absolute. Every nullable field stays null (not absent) so consumers can branch on presence without guessing whether a field was renamed.
  • Fields: schema, session_id, receipt_url, raw_json_url, package_download_url, receipt_digest, package_digest, merkle_root, verification ({status, signatures_valid, merkle_root_valid, issues}), session_summary ({name, status, mode, started_at, ended_at, duration_ms, total_agents, handoffs, hosts}), agents (with instance_id, name, role, host, depth, tool_calls, status, model, provider, tokens_in, tokens_out), agent_cards, harnesses, approvals ({grants, uses, checkpoints, *_ids}), replay_checks, decision_cards, files_changed ({written, read, total_*}), tools, warnings, artifact_count.
  • The /agent route projects from the raw hub receipt; reserved fields (agent_cards, harnesses, package_digest) stay null until the hub upgrades to serve them. The shape is stable now; agents code against it.

Added (downloadable .treeship package — treeship.dev PR 3)

  • /receipt/<id>/package returns a .tar.gz with the same <id>.treeship/ directory layout build_package_with_approvals produces in the CLI core. Consumers run curl -L | tar xz; treeship package verify ./<id>.treeship/ and get the full offline verification path.
  • Tarball contents: receipt.json, merkle.json, render.json, proofs/<artifact_id>.proof.json per artifact, approvals/grants/, approvals/uses/, approvals/checkpoints/, approvals/index.json when present.
  • lib/tar.ts is a pure-JS USTAR writer (~80 lines, zero deps). The route gzips via zlib.gzipSync and serves with Content-Type: application/gzip, X-Package-Digest: sha256:..., and platform-appropriate cache headers.

Added (CLI share command — treeship PR 4)

  • treeship session report --share --format json returns the agent-native share shape:
    {
      "schema": "treeship/share-result/v1",
      "session_id": "ssn_...",
      "receipt_url": "...", "raw_json_url": "...", "package_download_url": "...",
      "receipt_digest": "sha256:...", "package_digest": "sha256:...",
      "verification_status": "pass" | "warn" | "fail",
      "warnings": [{ "kind", "headline", "status" }],
      "error": null | "<message>"
    }
  • New flags: --format <text|json>, --no-upload (compute digests + run local verify, return URL fields null — useful for sandboxed CI without hub auth), --share (alias for the agent-native idiom).
  • Local-first fallback — when hub auth is missing in --format json mode, the response carries error: "..." plus the locally-derived digests and verify result. Text mode keeps the original recovery hint pointing at treeship hub attach. AI agents get a structured response either way.
  • package_digest computed as a content-addressed manifest digest: sha256 of sorted <relpath>:<sha256_hex> lines. Stable across builds; doesn't depend on tar/gzip non-determinism.
  • Derives raw_json_url and package_download_url from the hub-issued receipt_url's origin so the three URLs always point at the same site.

Added (Python SDK + CLI bootstrap — treeship PR 5)

  • treeship setup --format json now returns a real shape. Previously treeship setup --yes --format json returned {}; the setup function ignored the global --format flag. The new treeship/setup-result/v1 shape carries schema, ship_id, config_path, detected[...], cards{total, draft, needs_review, active, verified}, instrumented[], smoke{ran, passed, error}, next_steps[], error. Every early-exit path populates the accumulator before emitting JSON.
  • Python Treeship.ensure_cli() + Treeship(bot_mode=True). Resolves a working treeship binary via env / PATH / cache / npm / GitHub Release in that order. Each step sandboxed-safe: no sudo, no network unless steps 4/5 fire, no PATH modification.
    from treeship_sdk import Treeship
    ts = Treeship(bot_mode=True)  # CLI resolved; no human required
  • python -m treeship_sdk.bootstrap_cli --json is the agent-friendly shell entry point. Returns treeship/bootstrap-result/v1 with ok, binary, version, source (env|path|cache|npm-cache|github-release), cache_dir. --no-install disables network fallbacks for agents that should fail loudly.
  • New module structure under packages/sdk-python/treeship_sdk/:
    • bootstrap.py — resolution helpers, BootstrapResult, TreeshipBootstrapError
    • bootstrap_cli.pypython -m entry point
    • client.pyTreeship(bot_mode=True) integration
    • __init__.py — exports

Verification (release-gate T1–T7 smoke)

TestResult
T1 Python bootstrap (CLI resolution without PATH/auth/sudo)
T2 treeship setup --yes --no-instrument --format json✓ schema + 5 detected agents + 5 draft cards
T3 treeship session report --share --format json✓ all 9 required fields, verification_status: pass
T4 curl /receipt/ssn_a1b2c3d4 SSR✓ 84KB initial HTML, all expected fields, Loading receipt... gone
T5 curl /api/receipt/ssn_a1b2c3d4/agenttreeship/receipt-agent/v1 with all 18 required fields
T6 curl /receipt/ssn_a1b2c3d4/package + tar xz + treeship package verify✓ 5 PASS / 0 FAIL / 1 WARN (whitespace-only)
T7 Kimi-style end-to-end flow✓ no browser, no PATH mutation, no interactive prompts

Honesty rule preserved

  • The agent-native JSON contract uses null (not absent) for fields the hub doesn't yet carry (agent_cards, harnesses, package_digest). Consumers know exactly what's missing and what's intentionally not yet provided.
  • Local-first fallback in the share command sets URL fields to null and includes error: "<reason>" rather than fabricating receipt URLs that would fail when followed.
  • The package endpoint synthesizes the .treeship tree from whatever the hub returns; demo fixtures that drift from the current schema produce parse errors at verify time (caught and fixed in treeship.dev follow-up #12).
  • Verification rows never silently downgrade. treeship/share-result/v1 carries the local verify outcome, including warnings, so an AI agent that just shared a receipt can also tell its caller whether the sealed package verifies cleanly.

Intentionally NOT included

  • No new trust subsystem. No Hub server, no AgentGate runtime enforcement, no issuer/registry identity. Those remain the deferred items from v0.9.9.
  • No PATH modification, no sudo, no privilege escalation in the bootstrap path.
  • No treeship-cli on crates.io (still distributed via npm + GitHub Releases — see /guides/install from v0.9.11).
  • No mobile/desktop approval apps.

Migration

Pre-v0.10.0 receipts published to a hub running v0.10.0+ render normally on /receipt/<id> with the SSR pipeline; the agent JSON contract gracefully nulls fields the older receipt didn't carry. Verifiers running v0.10.0 against pre-v0.10.0 packages keep working — the new endpoints are additive. The CLI's session report text mode is unchanged for existing users; --format json is opt-in.

0.9.11 (2026-04-30)

The Docs Reliability + Agent Install Clarity release. No product code. Closes the docs-side findings from a third-party scan (Kimi) of the live docs surface and from the v0.9.6→v0.9.10 cadence's accumulated drift. Four PRs land on top of v0.9.10's trust-fix; this release wraps them into a versioned cutover so installs and the docs site move in lockstep.

The connective theme: make Treeship legible to the next AI agent that has to bootstrap it on a strange machine. Every gap this release closes was something a human or AI would only see at install/inspection time — section roots that 404, CLI commands without reference pages, missing TypeScript SDK page, no single page documenting the registry topology, no post-ship review template. None of these were trust bugs (the v0.9.10 hotfix already closed those); they were UX-of-trust bugs.

Added (docs reliability)

  • Three new section-root redirects + one alias in docs/next.config.mjs/guides → /guides/introduction, /concepts → /concepts/trust-fabric, /integrations → /integrations/claude-code, /hub-api → /api/overview (alias since the api/ section is titled "Hub API" in nav). The five existing redirects (/cli, /sdk, /api, /commerce, /reference) were already in production; these three were the genuine gaps.
  • scripts/check-docs-routes.py + docs-route-health CI gate. Static check, no network. Walks every meta.json under docs/content/docs/ and asserts: every advertised page resolves to a real .mdx file or sub-section, every section root has either an index.mdx or a redirect, every redirect destination resolves to an existing page. Wired into .github/workflows/ci.yml as a sibling to version-consistency. PRs that delete a doc without updating its meta.json — or add a section without a redirect — now fail fast.

Added (CLI reference catch-up)

  • /cli/setuptreeship setup [--yes] [--skip-smoke] [--no-instrument] [--format json]. The orchestrated first-run flow (detect → draft cards → confirm → instrument → smoke). Documents the Active vs Verified status distinction (smoke promotes Draft → Active; only per-harness smokes write Verified).
  • /cli/addtreeship add [agents]... [--all] [--dry-run] [--discover] [--format json]. The focused instrumenter. Documents the --discover read-only mode AI agents use to inspect a machine, with the actual JSON shape the binary emits today.
  • /cli/agentstreeship agents {list, review, approve, remove}. Documents the four-stage status lifecycle (Draft → NeedsReview → Active → Verified) and the deterministic agent_id derivation. Honest callout that --format json returns {} today and points readers at the on-disk ~/.treeship/agents/<id>.json files (which are stable schema-versioned JSON).
  • /cli/harnesstreeship harness {list, inspect, smoke}. Documents the manifest/state split that prevents PotentialCaptures vs VerifiedCaptures confusion, and the privacy posture (record paths, never content).
  • /cli/attest — replaced the v0.9.6 deferred-Hub callout with the actual v0.9.10 surface: every replay row (replay-package-local, replay-local-journal, replay-included-checkpoint, replay-hub-org) and the four bundle-level binding rows (approval-use-record-digest, approval-use-nonce-binding, approval-use-action-binding, approval-use-chain-continuity).
  • /cli/verify — added an Approval Authority replay rows section. What each row proves, the honesty rule ( only when evidence present and verified, only when present and failed, - when absent — never silent pass), and the --strict promotion list.
  • CLI sidebar reordered so the first-run flow comes first: init → setup → add → agents → harness → wrap → attest → verify → ....

Added (SDK + install topology)

  • /sdk/typescript at its canonical URL parallel to /sdk/python. Previous content of /sdk/overview (363 lines of TypeScript SDK reference) renamed without losing material; the new /sdk/overview is a true section landing with cards linking to TypeScript, Python, Verify, and MCP.
  • /guides/install — registry topology page. Documents what's on each registry (npm, PyPI, crates.io, GitHub Releases) and what's intentionally NOT with explicit reasoning. AI agents (Codex, Claude, Kimi) sometimes flag treeship-cli 404 on crates.io as a release failure; the page has the explicit "this is not a release failure" callout so the next scanner that hits it gets the right answer. Also documents the sandbox / agent-friendly install paths (npx, tmpdir + --config).

Added (process)

  • /guides/dogfood-checklist — post-ship review template. Sibling to the release-adversarial discipline at docs/release-adversarial/README.md (which itself landed in v0.9.10). The two cover different failure modes: adversarial review hunts trust bypasses an attacker would exploit; dogfood hunts UX gaps a real user would hit. A release passes both gates before it's done. Saves under docs/dogfood/<version>.md parallel to docs/release-adversarial/<version>.md.
  • docs(cli) JSON-shape honesty fix baked into PR #60. Where the binary returns {} today (treeship setup --format json, treeship agents list --format json), the docs say so and point at the per-component commands that DO emit rich JSON (treeship add --discover --format json, treeship harness list --format json). Truth-in-advertising before the agent-mode shape stabilizes.

Intentionally deferred to a later release

  • Agent-readable receipt page (/receipt/<id> SSR + /receipt/<id>.json + package download). Requires the marketing-site (treeship.dev) repo, which lives separately from this codebase. Tracked for the next product release.
  • Python SDK ensure_cli / bootstrap helper. Substantive product work. Tracked for the next product release.
  • treeship setup --agent --yes --format json rich-shape implementation. Today it returns {}; the v0.9.11 docs flag this honestly. The richer shape lands in the next product release.
  • treeship session report --share --format json returning {receipt_url, raw_json_url, package_url, receipt_digest, verification_status, warnings}. Same release.

The next product release theme will be Agent-Native Bootstrap + Share — the deferred items above. v0.9.11 makes Treeship's existing surface legible; the next release makes a fresh AI agent able to install, run, share, and verify it without asking a human a clarifying question.

Verification

This is a docs-only release. Per the release-adversarial cadence policy at docs/release-adversarial/README.md, no targeted Codex pass is required — adversarial review is reserved for trust-semantics changes. The release machinery's own gates apply:

  • python3 scripts/check-release-versions.py 0.9.11 — 21/21 sites at 0.9.11
  • python3 scripts/check-docs-routes.py — 9 meta.json files checked, all routes resolve (the new gate this release adds)
  • cargo test --workspace — every suite green (251 core unit + 17 binding integration + 7 hub-checkpoint integration + everything else, identical to v0.9.10 since no product code changed)
  • bash tests/acceptance/trust-fabric.sh — smoke pass

0.9.10 (2026-04-30)

The Approval Authority patch release. Closes four trust-bypass paths and three adjacent hardenings that a targeted Codex adversarial review of v0.9.9 found in the new approval-evidence surface. Verifiers running v0.9.9 should upgrade. v0.9.9 packages still verify under v0.9.10, but the binding rows that v0.9.9 silently passed now report honestly: a v0.9.9 package's approval-use-action-binding row will read not asserted by package because v0.9.9 didn't ship the action envelopes the binding check needs. Under --strict, that's a fail — the intended upgrade signal.

The release-adversarial discipline that surfaced these gaps is now durable policy. docs/release-adversarial/README.md writes down the rule: trust-semantics releases run a targeted adversarial review; bump-only releases don't. Findings are saved per-version in docs/release-adversarial/<version>.md. The v0.9.9 file records every blocker with file:line + repro + verification step, and includes the round-2 / round-3 re-check verdicts that gated the v0.9.10 hotfix merge. Running adversarial review on every CHANGELOG bump would be wasted budget; running it on every receipt-format / approval-semantics / replay-behavior change is the discipline that catches the bugs CI doesn't.

What v0.9.10 does NOT add: any new trust subsystem. Hub server, issuer/registry identity, AgentGate runtime enforcement, and mobile/desktop approval apps remain out of scope, carried over from v0.9.9's deferred list. The next product release (Agent-Native Bootstrap + Share + Docs Reliability) will pick up where v0.9.9 left off; v0.9.10 is purely the boring-but-correct patch in front of it.

Fixed (Approval Authority blockers — #57)

  • TOCTOU race in reserve_in_journal bypassed max_usespackages/core/src/journal/mod.rs, packages/cli/src/commands/attest.rs. v0.9.9's reserve_in_journal ran check_replay and list_uses_for_grant outside with_lock, then called append_use which took the lock only for the write. Two parallel treeship attest invocations against a max_uses=1 grant could both pass replay-check pre-lock, then queue serially through the lock, and both write — exceeding max_uses. Closed by a new journal::reserve_use that runs check_replay and use_number derivation inside with_lock and stamps use_number from the count observed at lock-acquire time. The CLI consume path now calls reserve_use; append_use is preserved for tests and trusted callers. Pinned by journal::tests::reserve_use_concurrent_max_uses_1_only_one_succeeds (8 threads contend; exactly one wins; on-disk record count is 1) and reserve_use_retry_after_lock_busy_does_not_bypass_max_uses (idempotency-retry safety).
  • Action↔use binding ignored at verify timepackages/core/src/session/package.rs, packages/cli/src/commands/session.rs. v0.9.9's collect_approval_evidence pulled every use for the grant and verify_package resolved by (grant_id, nonce_digest) only. Worse, the package format never shipped action envelopes, so the verifier could not see meta.approval_use_id even if it had wanted to. Closed by extending ApprovalsBundle with action_envelopes: Vec<(String, Vec<u8>)>, populating it during session close, writing each consuming action envelope to pkg_dir/artifacts/<artifact_id>.json (the directory existed but was empty pre-v0.9.10), and adding a new approval-use-action-binding verify row. The row PASSes only when every consuming action's meta.approval_use_id resolves to a use_id in the bundle AND the action's approval_nonce hashes to that use's nonce_digest. Round 2 added a content-addressing gate: the envelope's artifact_id_from_pae(pae(payload_type, payload)) must equal the filename stem before any field is trusted. Without this gate an attacker controlling the package could write any forged unsigned action JSON to artifacts/<id>.json and the binding row would trust it.
  • nonce_digest in ApprovalUse not cross-checkedpackages/core/src/session/package.rs. v0.9.9's package replay path read each use's nonce_digest verbatim and never compared it to nonce_digest(grant.nonce). An attacker who controlled the package could mutate nonce_digest to claim consumption of any grant whose nonce they hadn't actually used — recompute the use's record_digest and the per-record check still passed. Closed by a new approval-use-nonce-binding verify row: for each use, find the matching grant in bundle.grants, parse its envelope, compute nonce_digest(grant.nonce), compare. Round 2 added the same content-addressing gate to grant envelopes; a forged grant payload with any nonce now fails before the row's signed-nonce extraction runs.
  • Embedded chain not walked in package replaypackages/core/src/session/package.rs. v0.9.9's replay-included-checkpoint and per-use record_digest checks both recomputed individual digests, but neither walked the previous_record_digest chain across embedded records. An attacker could rewrite a chain consistently — recomputing each digest along the way — and every per-record check still passed. Closed by a new approval-use-chain-continuity verify row. Round 2 tightened the algorithm: requires exactly one record with previous_record_digest == "" (one genesis), no two records sharing a non-empty prev (no forks), no cycles detected during the walk from genesis, and every record reachable from the genesis (no disconnected subchains). Pinned by multiple_genesis_records_fail_chain_continuity, forked_chain_two_records_share_prev_fails_chain_continuity, disconnected_subchain_fails_chain_continuity, plus the original dangling_previous_record_digest_fails_chain_continuity.

Fixed (label hygiene — #57)

  • Renamed approval-use-integrityapproval-use-record-digestpackages/core/src/session/package.rs, packages/cli/src/commands/package.rs. The old label suggested the row covered nonce/action binding; it didn't, and Codex's review flagged the over-claim. The strict-mode promotion matcher keeps the old label too for backward compatibility with downstream tooling, but the live emitter uses the new name. Three new verify rows (approval-use-nonce-binding, approval-use-action-binding, approval-use-chain-continuity) carry the responsibilities the old label implied.
  • Strict-mode promotion widened. --strict now promotes every new approval-evidence row from warn → fail alongside the existing replay-* rows.

Added (release-adversarial cadence policy)

  • docs/release-adversarial/README.md — durable policy. Trust-semantics releases (receipt format, approval semantics, replay/journal, package verification, Hub checkpoint verification, MCP/A2A bridges, tool authorization, agent identity, harness coverage, public sharing, installer security) run a targeted Codex adversarial pass before or immediately after release. Bump-only releases don't. Findings save to docs/release-adversarial/<version>.md. Triage is a fixed scale: replay/signature bypass or secret leak → hotfix; docs overclaim → docs patch; missing test → next hardening PR. Codex output is verified against the actual code before being recorded as a confirmed blocker — file-path hallucinations don't become release-blocking findings.
  • docs/release-adversarial/v0.9.9.md — first instance. Records the four blockers, three round-2 sub-blockers, round-3 ship verdict, and the two non-blocking secondaries (duplicate grant_id last-wins, missing 3-cycle and self-loop chain test cases) deferred to the next hardening pass.

Tests

  • journal::tests::reserve_use_first_call_succeeds_and_stamps_use_number
  • journal::tests::reserve_use_max_uses_1_serial_second_call_rejects
  • journal::tests::reserve_use_max_uses_2_two_uses_pass_third_rejects
  • journal::tests::reserve_use_concurrent_max_uses_1_only_one_succeeds
  • journal::tests::reserve_use_retry_after_lock_busy_does_not_bypass_max_uses
  • 17 new integration tests in packages/core/tests/approval_evidence_binding.rs covering action-binding pass/fail/missing-pointer/no-envelopes/forged-envelope, nonce-binding pass/tamper/unknown-grant/forged-grant, chain-continuity pass/dangling/multiple-genesis/fork/disconnected, label rename, action-envelope round trip.
  • All 246+ pre-existing core tests still pass; the 7 hub_checkpoint_verify integration tests still pass; acceptance smoke (tests/acceptance/trust-fabric.sh) still passes.

Known follow-ups (non-blocking, deferred)

  • Duplicate grant_id entries in bundle.grants silently last-wins in the nonce-binding HashMap. Real packages produced by build_package_with_approvals deduplicate by sanitized filename, so this is hard to trigger in production, but a defensive explicit-failure check would harden the row.
  • Test coverage for 3-cycle (a→b→c→a) and self-loop (record_digest == previous_record_digest) chain shapes. The current algorithm should reject both via the visited-set walk; a regression test would pin that explicitly.

Intentionally NOT changed

  • Hub server / issuer registry / AgentGate runtime / mobile-desktop apps remain out of scope (carried over from v0.9.9 deferred list).
  • v0.9.9 packages still verify under v0.9.10. The new binding rows report honestly when the package doesn't ship the evidence; verifiers calling --strict against pre-v0.9.10 packages will fail approval-use-action-binding. That's the intended upgrade signal.

0.9.9 (2026-04-30)

The Approval Authority release. v0.9.6 made the receipt trustworthy. v0.9.7 made the release process trustworthy. v0.9.8 made the agent experience legible. v0.9.9 makes temporary authority itself a first-class trust object: a human approves an agent for a bounded action; the agent consumes that approval at runtime; the receipt records the consumption; offline verifiers can replay the chain on any machine; and where a signed Hub checkpoint is embedded, the same verify run can attest to non-replay across machines. Six PRs (#50–#55) compose into the core trust answer for v0.9.9.

The framing every PR shares: grants define authority, uses prove consumption, the journal enforces replay, packages carry evidence, checkpoints make evidence portable, Hub signatures extend evidence across machines. Each level has its own row in verify output and never silently downgrades. The "honesty rule" baked into the codebase: a row is only when the matching evidence is present and verified, only when present and failed, and - (or absent) only when the evidence isn't in the package. No replay row ever pretends to a stronger guarantee than the embedded evidence supports.

The single most load-bearing decision in v0.9.9 is the split between the consumer side of every Hub-related surface (which ships now) and the Hub server itself (which doesn't). v0.9.9 verifies a Hub-signed JournalCheckpoint and emits replay-hub-org PASS only when the four-gate rule below clears. The thing that signs checkpoints — the Hub server, its issuer registry, its signing key custody story — is out of scope for this release and lives in a separate one. Until a Hub starts signing, the hub-org row reads not checked (no Hub checkpoint in package) on every package, on every machine, and verify will physically refuse to print "global single-use enforced" or "replay-hub-org passed" without real signed evidence.

Added (Approval evidence schemas — #50)

  • treeship/approval-use/v1 — a record that proves a grant was consumed by an action. Carries use_id, use_number, grant_id, nonce_digest, actor, action_artifact_id, subject_ref, max_uses snapshot, package_id, created_at, previous_record_digest, record_digest, optional Ed25519 signature / signing_key_id. The previous_record_digest chain link makes a journal a tamper-evident sequence rather than an unordered set.
  • treeship/approval-revocation/v1 — schema for revoking grants. Wired in the schema layer; the runtime revocation flow stays deferred. Future-compatible without a schema bump.
  • treeship/journal-checkpoint/v1 — Merkle commitment over a contiguous range of journal records. Two kinds with the same shape: LocalJournal (the journal's own compaction primitive; verify only emits replay-included-checkpoint) and HubOrg (signed by an org Hub; verify emits replay-hub-org when the four-gate rule clears).
  • canonical_*_record_digest helpers — every record type computes its digest from a stable JSON form that omits exactly the digest field and the signature. Same discipline as v0.9.6 receipts; now extended to journal records.

Added (Local Approval Use Journal — #51)

  • <config>/journals/approval-use/ — append-only per-workspace journal of ApprovalUse records. Source of truth is the JSON file tree (records/<use_id>.json), with secondary indexes by_grant/<grant_id>.json and by_nonce/<grant_id>/<nonce_digest>.json rebuilt deterministically on startup. Records are truth, indexes are cachejournal::rebuild_indexes reconstructs every index from records alone, so a corrupted index can't degrade the trust answer.
  • Per-grant (grant_id, nonce_digest) uniqueness — the journal enforces single-recorded-use per nonce at write time. A re-issued nonce is the only legitimate way to consume the same grant twice.
  • journal::find_use_for_action(grant_id, nonce_digest, max_uses_hint) — verify-time question ("does the recorded use's use_number stay within bounds?"), distinct from consume-time's "would the next use exceed?". Two different invariants; two different code paths; covered by separate tests.
  • treeship journal list / inspect (read-only) — text + JSON output. No mutating CLI surface in v0.9.9; mutations happen only through the consume path.

Added (consume-before-action + local-journal replay check — #52)

  • Consume-before-action ordering at attest time. When an action carries an approval grant, the attest path appends the ApprovalUse to the journal before running the action. A crash mid-action leaves a recorded use without an artifact (visible at verify time), but cannot leave an artifact without a recorded use — the failure mode the journal is designed to prevent.
  • replay-local-journal row in treeship verify. When the verifier has access to a workspace journal, it asks find_use_for_action per embedded ApprovalUse and reports PASS (use_number ≤ max_uses), FAIL (record exists but exceeds max_uses), or WARN (no journal in this workspace; falls back to package-local). Bare-package verification in someone's inbox doesn't error here.

Added (package export + offline replay verify — #53)

  • approvals/uses/<use_id>.json and approvals/checkpoints/<checkpoint_id>.json in the .treeship package. Every use the package's actions consumed is exported alongside the receipt. Recipients without access to the originating workspace's journal can still verify replay-package-local and replay-included-checkpoint offline.
  • replay-package-local and replay-included-checkpoint rows. Package-local detects duplicate uses inside this package (an always-fails-or-passes invariant; never a warning). Included-checkpoint recomputes each embedded checkpoint's record_digest and rejects any tampered record. Both rows are always available, regardless of whether the verifier has a workspace.
  • session::package::read_approvals_bundle — single entry point that reads approvals/uses/*.json, approvals/checkpoints/*.json, and the receipt's referenced grants, returning a typed bundle every downstream consumer (verify_package, panel, decision cards) reads from. No two readers hand-roll the same parsing.

Added (Approval Authority panel + Decision Cards v0 + first-class docs — #54)

  • Approval Authority panel in treeship package inspect. Renders one block per grant the package consumed: who approved whom for what scope, the embedded grant_id, the subject ref, max_uses and uses recorded, and one row per replay level (package-local, local-journal, included-checkpoint, hub-org). The strongest finding wins per row; never silently downgrades.
  • Decision Cards v0. A "Key decisions" section beneath the panel surfaces the trust calls a reader needs to see: an Approval-scope card (when the grant scope is unbounded), a Replay-warning card (when no verified Hub coverage covers every embedded use), and structural cards for receipt-determinism / event-log anomalies. Every card carries explicit evidence pointers (grant_id, use_id, action_artifact_id, verify check rows) — no LLM, no invented intent; the narrative is generated mechanically.
  • First-class docs. docs/content/docs/concepts/approval-authority.mdx, docs/content/docs/concepts/approval-use-journal.mdx, docs/content/docs/guides/replay-levels.mdx, plus sidebar wiring. The docs spell out the four replay levels, the honesty rule, and what "global single-use" requires (a verified Hub checkpoint, never v0.9.9's local journal alone).

Added (Hub-signed checkpoint consumer-side verifier — #55)

  • CheckpointKind { LocalJournal, HubOrg } discriminator on JournalCheckpoint. #[serde(default)] keeps every pre-#55 record bytes-identical (covered by checkpoint_kind_defaults_to_local_journal).
  • Hub-signature fields on JournalCheckpoint: hub_id, hub_public_key, hub_signature, signed_at, covered_use_ids (+ informational covered_grant_ids). All optional via skip_serializing_if, so a LocalJournal checkpoint serializes byte-identical to #50.
  • canonical_hub_signing_bytes() — stable JSON over every signed field except hub_signature and record_digest. The previous_record_digest is signed (chain-linkage protection).
  • verify_hub_checkpoint_signatureValid | MissingFields(field) | Tampered | NotHubKind. Decodes the embedded public key and signature, checks the Ed25519 signature against the canonical signing bytes, and reports the outcome. Coverage check (does this checkpoint actually cover the package's uses?) is a separate caller-side step.
  • replay-hub-org row in verify. Emits PASS only when all four of: (1) at least one embedded checkpoint declares kind: hub-org, (2) every required Hub field is populated, (3) the Ed25519 signature verifies against the embedded public key, AND (4) every embedded ApprovalUse.use_id is in covered_use_ids. Anything short → warn (default) or fail (--strict). When no Hub-kind checkpoint is embedded, no row is emitted; the panel renders not checked (no Hub checkpoint in package).
  • Per-use panel resolution. The hub-org row resolves per-use: ✓ <detail> (verified + covered for this use), ✗ <reason> (Hub checkpoint present but a gate failed for this use), or not checked (no Hub-kind checkpoint at all). Multiple checkpoints can cover different uses; the panel reports the strongest finding for each.
  • 8 new unit tests + 7 integration tests covering: backward-compat default, kebab-case serialization, signature round-trip with a real Ed25519 keypair, tamper detection (mutating covered_use_ids), wrong-key rejection, malformed-input rejection, end-to-end fixture-built .treeship package with a signed checkpoint embedded.

Changed (trust semantics)

  • replay-hub-org is now a real check, not a placeholder. v0.9.6–v0.9.8 left the row absent or "reserved." v0.9.9 makes it a real verification path with a non-negotiable four-gate rule. The change is consumer-side only: nothing in v0.9.9 generates a Hub-signed checkpoint, so existing packages keep printing not checked. Only packages that have been processed by a Hub (out of scope for this release) will see the row promote.
  • Replay-warning Decision Card retitles to "Replay posture: no verified Hub coverage." Previously fired only when no journal checkpoint was embedded; now fires when any embedded use lacks verified Hub coverage. When every use is covered by a verified Hub checkpoint, the card stays silent — the panel's ✓ hub-org row already tells the story.
  • Approval Use Journal records are append-only. No mutating CLI; no in-place edits. Compaction (when it lands) will replace ranges of records with a single LocalJournal checkpoint; the chain-link previous_record_digest makes the replacement detectable.

Intentionally deferred to a later release

  • The Hub server itself. The thing that signs checkpoints — Hub signing-key custody, the issuer/registry identity surface, the signing API, federated trust roots — is out of scope for v0.9.9 and ships in a separate release. v0.9.9 is the consumer-side verifier in advance of that work.
  • Issuer / registry identity (trusted-key pinning). v0.9.9 verifies signatures against the public key embedded in the checkpoint. A trusted-issuer registry that pins acceptable hub_public_key values is the next layer; until it lands, an attacker who controls a checkpoint can declare any public key, and the signature will verify against it. Coverage and signature checks remain real; what's missing is the "and we trust this Hub" decision.
  • AgentGate runtime enforcement. Treeship today records approval consumption at attest time. Runtime enforcement — refusing to run an action whose grant has been spent or whose scope doesn't match — is the next AgentGate layer and stays out of v0.9.9.
  • Mobile/desktop approval apps. A human-facing surface for granting/revoking approvals out-of-band stays out of v0.9.9.
  • treeship agent invite / treeship join --invite for remote/VPS attach (carried over from v0.9.8).
  • Per-harness verified-captures smokes (carried over from v0.9.8).

The agent-legibility release. v0.9.6 made the receipt trustworthy. v0.9.7 made the release process trustworthy. v0.9.8 makes the agent experience obvious: install Treeship, run one command, and immediately see which agents are on this machine, how Treeship is attached to each, what coverage to expect, and what's actually been proven on this workspace.

The framing the rest of the release builds on: harnesses make agents observable, cards make agents accountable, receipts make their work verifiable. Cards describe who an agent is and what it's allowed to do; harnesses describe how Treeship attaches and observes; the two stay in separate files (.treeship/agents/<id>.json vs .treeship/harnesses/<id>.json) so a future surface with multiple harnesses or a harness that instruments many agents doesn't need a schema change. The most important trust-semantics move in this release is the split of Captures (what a harness could observe if attached and working) into manifest-level PotentialCaptures and state-level VerifiedCaptures (what's actually been proven by a harness-specific smoke on this machine). Setup's generic round-trip smoke promotes harnesses to instrumented, never to verified; the latter is reserved for v0.9.9's per-harness smokes that exercise each capture signal individually. UI code physically cannot show one in the other's slot because they are different types.

The "extend, don't fork" architectural call ran through every PR: each addition reused the existing modules (discovery::discover, cards::list, harnesses::HARNESSES, the existing add instrumenter, the existing source-provenance fields on FileAccess) instead of producing a parallel detector or schema. A new builder running treeship setup and reading a session report sees agents found, cards drafted, harnesses instrumented, files tagged with their capture source -- without Treeship having grown two of anything.

Added (discovery + agent inventory)

  • treeship add --discover -- read-only discovery surface. packages/cli/src/commands/discovery.rs. New module with AgentSurface, ConnectionMode, CoverageLevel, Confidence, DiscoveredAgent. Detects Claude Code, Cursor, Cline, Codex, Hermes, OpenClaw, Ninja Dev (NinjaTech local IDE), generic MCP fallbacks, and shell-wrap custom agents. SuperNinja appears as an always-on low-confidence hint pointing at the (deferred) treeship agent invite flow rather than going silent. Read-only, no config writes, stable JSON shape via --format json so downstream tooling can consume it.
  • Agent Card store at .treeship/agents/<id>.json. packages/cli/src/commands/cards.rs. Per-workspace JSON inventory keyed on a deterministic agent_id = sha256("<surface>|<host>|<workspace>")[..8] so re-running discovery is idempotent. Card lifecycle: DraftNeedsReviewActiveVerified. Card provenance: Discovered | Registered | Manual. Capabilities mirror the v0.9.6 ApprovalScope vocabulary (bounded_tools, escalation_required, forbidden).
  • treeship agents list / review / approve / remove. packages/cli/src/commands/agents.rs. Text + JSON output. Verified is intentionally not exposed as a manual flag; only a smoke session that proves capture writes that status.
  • Certificate-digest drift demotion. cards::upsert compares the incoming card's certificate_digest against the stored card; if they differ and the existing status was Active or Verified, the merged status drops to NeedsReview. Re-registering an agent therefore cannot leave a previously-approved card pointing at a certificate the user never saw.
  • treeship agent register writes a card alongside the .agent package. packages/cli/src/commands/agent.rs. The signed certificate stays the portable artifact; the card is the local trust object that treeship agents lists. Same data, two sites, no schema fork.

Added (setup orchestration)

  • treeship setup [--yes] [--skip-smoke] [--no-instrument]. packages/cli/src/commands/setup.rs. Composition over the existing PR 1 / PR 2 modules: discovery::discovercards::upsert → confirmation prompt → existing add::run instrumenter → smoke session inside an isolated tmpdir → state promotion. Non-interactive without --yes defaults to a safe "no" so CI invocations never touch user configs without permission. If no Treeship config exists, setup points at treeship init rather than auto-initializing -- silently creating a global config when the user might have expected a project-local one would surprise.
  • Setup's smoke session is the v0.9.7 trust-fabric round-trip in a tempdir. Runs initsession startwrapsession closepackage verify against an isolated keystore. Promotes instrumented cards to Active and instrumented harnesses to Instrumented. Never to Verified -- the smoke proves Treeship's pipeline works, not that any specific harness's capture path fired.

Added (Harness Manager)

  • harnesses::HarnessManifest -- declarative profile per surface. packages/cli/src/commands/harnesses.rs. Ten manifests (six installable + four honest non-installable). Each carries harness_id, surface, display_name, connection_modes, coverage, captures: PotentialCaptures, known_gaps, privacy_posture, recommended_backstops, and install: Option<InstallProfile>. The four "no auto-installer" manifests (SuperNinja remote, Ninja Dev IDE, generic-mcp, shell-wrap) describe surfaces Treeship knows about but cannot auto-attach to today; treeship harness inspect is honest about each.
  • HarnessState per-workspace store at .treeship/harnesses/<id>.json. Status lifecycle: DetectedAvailableInstrumentedVerified (with Drifted / Degraded / Disabled reserved for future use). Carries last_smoke_result, last_verified_at, linked_agent_ids, frozen known_gaps, and -- the load-bearing v0.9.8 trust field -- verified_captures. upsert_state merges verified captures monotonically (a smoke that proves files_read does not erase a prior run that proved mcp_call).
  • treeship harness list / inspect / smoke. packages/cli/src/commands/harness.rs. inspect shows two distinct rows -- "potential captures (when attached and working)" and "verified captures (proven by harness-specific smoke)" -- so consumers physically can't conflate the two. JSON keys are separate objects (potential_captures, verified_captures).

Added (integration template profile table)

  • harnesses::HARNESSES table replaces PR 4's branching add.rs install dispatch. What used to be three install functions (install_mcp_config for Claude/Cursor/Cline, install_codex_mcp_config for Codex, install_skill for Hermes/OpenClaw) is now one row per surface with install_method (JsonMcp | TomlMcp | SkillFile), snippet, config_path: fn(&Path) -> PathBuf, and idempotency: fn(&Path) -> bool. Adding a new instrumentable surface is one row in HARNESSES; nothing in add.rs changes. Detection itself moved to discovery::discover everywhere, ending the duplicate detect_agents() that lived in both add.rs and discovery.rs.

Added (session report polish)

  • Files Changed panel with source badges. packages/cli/src/commands/package.rs. treeship package inspect <pkg> renders every read/written file with a badge from the existing FileAccess.source provenance: [hook], [mcp], [git-reconcile], [shell-wrap], [session-event-cli], [daemon-atime], [unknown]. Treeship's own runtime artifacts (.treeship/session.json, .treeship/session.closing, .treeship/sessions/**, .treeship/artifacts/**, .treeship/tmp/**) are filtered out of the list, with the count shown explicitly so a quiet list never masquerades as complete. User-authored trust files (.treeship/config.{yaml,json}, .treeship/policy.yaml, .treeship/agents/**, .treeship/harnesses/**, .treeship/declaration.json) are preserved.
  • Agent Cards panel. Reads cards::list against the workspace card store; renders status, surface, harness id, model, host. No schema fork; same data the standalone treeship agents command shows.
  • Harness Coverage panel. Reads harnesses::list_states joined with HARNESSES; renders status, coverage, two distinct rows for potential vs verified captures (preserving the trust-semantics split), last smoke summary, and the first two known_gaps per harness. Surfaces honest gaps in the report rather than burying them in harness inspect.

Added (project-local config discovery and CLI ergonomics)

  • AgentCard.active_harness_id and DiscoveredAgent::recommended_harness_id(). Each card points at the harness it's attached through; each discovery output advertises the harness Treeship recommends for that surface. JSON output of treeship add --discover --format json includes the recommendation per detection so external orchestration can use it without re-deriving from surface.
  • First-run docs. docs/content/docs/guides/first-run.mdx, docs/content/docs/concepts/agent-cards.mdx, docs/content/docs/concepts/harnesses.mdx, docs/content/docs/guides/coverage-levels.mdx, docs/content/docs/integrations/ninjatech.mdx, plus meta.json updates so the new pages appear in the docs sidebar. The integration page is explicit about Ninja Dev (local, manual register today) versus SuperNinja (remote, basic coverage until v0.9.9 invite/join).

Changed (trust semantics)

  • Setup's smoke does not flip cards or harnesses to Verified. Cards from setup move Draft → Active; harnesses move Detected → Instrumented. last_verified_at stays None. The completion summary spells out: "Each harness is now instrumented. Per-harness capture is NOT yet verified. Run a real session through each harness to populate verified_captures and reach verified." The previous PR 5 first cut promoted everything to Verified after the generic round-trip; that over-claim is fixed before the v0.9.8 release.
  • PotentialCaptures and VerifiedCaptures are different types. Bools per signal on the manifest (PotentialCaptures.files_read: bool); per-signal Option<bool> on state (VerifiedCaptures.files_read: Option<bool>). UI cannot render one in the other's slot. harness inspect text and JSON show the rows as "potential captures (when attached and working)" and "verified captures (proven by harness-specific smoke)".
  • HarnessStatus::Verified is reserved for harness-specific smokes. v0.9.8 ships the type, the lifecycle, and the file format; the smokes that populate VerifiedCaptures per-signal are deferred to v0.9.9 alongside the per-harness capture fixtures.

Intentionally deferred to v0.9.9 / later

  • treeship agent invite / treeship join --invite for remote/VPS/SuperNinja attach. Today the SuperNinja harness is honest about being remote and points users at the future flow.
  • GitHub verification surface (PR Check, Actions artifact, sanitized verification manifest).
  • Per-harness smokes that exercise specific capture signals (the path to actually populating verified_captures).
  • Approval Use Journal, Hub/global replay, issuer/registry identity, full AgentGate runtime enforcement. Those compose into the next major release after v0.9.8.

0.9.7 (2026-04-29)

The hardening release. v0.9.6 shipped the trust-fabric correctness chain -- scoped approvals, capture-normalize-verify, honest replay posture -- but the v0.9.6 cut itself missed PyPI on the first pass: packages/sdk-python/pyproject.toml was 0.9.5 when the v0.9.6 tag fired, so npm and crates published 0.9.6 while PyPI either built the wrong version or skipped publish entirely. A separate batch of internal @treeship/core-wasm pins was also stale -- bridges/mcp and bridges/a2a declared 0.9.4, packages/verify-js declared 0.9.5 -- so users installing those packages at 0.9.6 actually pulled an older verifier than the SDK declared. The post-publish per-package version checks already in release.yml caught neither failure mode because they ran after each publish committed.

v0.9.7 closes that class of bug. A single source-of-truth preflight script now walks every release-version site -- Cargo [package] versions, the workspace-internal treeship-core pin, every published package.json, the npm wrapper's optionalDependencies routing to platform CLIs, every dependencies['@treeship/core-wasm'] pin, pyproject.toml, and treeship_sdk/__version__ -- and either checks them against the explicit tag version (release-time, blocking every publish job) or anchors on packages/core/Cargo.toml and asserts internal consistency on every PR. The PyPI publish step refuses to upload artifacts whose filenames don't carry the expected version, so a stray drift between pyproject and the tag fails the release before twine touches the registry. The release script itself is split into prepare (bump + commit, no tag) and tag (explicit subcommand, mandatory --sha, refuses dirty trees and pre-existing tags, requires typed confirmation) so accidental tagging is structurally impossible.

The release also makes day-to-day operation kinder. Project-local .treeship/config.json discovery means a workspace can keep its own keystore even when the user's global ~/.treeship is broken; treeship doctor now reports which config tier (explicit (--config), env (TREESHIP_CONFIG), project-local, or global) actually fired, with the resolved path, so debugging "wrong keystore" doesn't require strace. A checked-in trust-fabric acceptance smoke runs in CI on every PR, exercising init → session start → wrap → close → package verify against an isolated keystore.

No new trust subsystem in v0.9.7. The Approval Use Journal remains the v0.10 target; agent discovery, Agent Cards, and NinjaTech onboarding are v0.9.8.

Added (release machinery)

  • scripts/check-release-versions.py -- single-source preflight covering 21 version sites. Walks Cargo [package] versions for treeship-core / treeship-cli / treeship-core-wasm, the workspace-internal treeship-core pin in the CLI, every published package.json (@treeship/sdk, @treeship/mcp, @treeship/a2a, @treeship/verify, treeship wrapper, three platform CLIs), every dependencies['@treeship/core-wasm'] pin, the wrapper's optionalDependencies routing, pyproject.toml, and treeship_sdk/__version__. Two modes: explicit-target (<version>) for release time and --consistency (anchors on packages/core/Cargo.toml) for PR time.
  • preflight job in .github/workflows/release.yml. Runs first; every other release job (build, release, publish-npm, publish-crates, publish-pypi) is a transitive needs: of it, so a manifest mismatch blocks the entire pipeline before any registry sees a publish.
  • version-consistency job in .github/workflows/ci.yml. Runs the preflight in --consistency mode on every PR. Catches drift weeks before a release tag would have.
  • tests/acceptance/trust-fabric.sh -- checked-in acceptance smoke + T1-T9 scaffolding. Drives the CLI through a real session round-trip (init, session start, wrap, session close, package verify) inside an isolated tmpdir keystore. Wired into ci.yml after the build step. T1-T9 from the v0.9.6 trust-fabric run remain documented as scaffolds for a follow-up port.

Added (CLI ergonomics)

  • Project-local .treeship/config.json discovery. packages/cli/src/config.rs::resolve_config_path(). Walks up from cwd looking for .treeship/config.json before falling back to ~/.treeship, with one safety rail: a hit at exactly $HOME/.treeship/config.json is not labelled project-local (that path is the global, and labelling it otherwise would mislead users running from $HOME). A corrupt global config no longer blocks a project-local config from working.
  • ConfigSource enum with four tiers and an explicit precedence. Explicit (--config) > Env (TREESHIP_CONFIG) > ProjectLocal (walk-up) > Global (fallback). Ctx carries the resolved source so any command can report it.
  • treeship doctor reports config source and path. New line: config source project-local -- /path/to/.treeship/config.json. v0.9.6 had no signal here; a user with a misbehaving keystore had to grep code to figure out which lookup tier fired.

Changed (release tooling)

  • scripts/release.sh split into prepare and tag subcommands. prepare <version> bumps every version site, runs preflight, commits -- and stops. It contains no git tag, git push --tags, or git push origin v* invocation. tag <version> --sha <sha> [--yes] is the only path that can create a tag, and it requires: an explicit subcommand, a mandatory --sha (no implicit HEAD), a clean working tree, no pre-existing local or remote tag, and either --yes or an interactive type 'tag <version>' to confirm gesture. The legacy scripts/release.sh <version> form is removed -- it errors out with an explanation rather than silently falling through to the old behavior, which previously produced an accidental local tag during v0.9.7 cutover preparation.
  • PyPI publish step refuses wrong-version artifacts. .github/workflows/release.yml. The step now (1) cleans dist/, build/, and *.egg-info/ before python -m build, (2) computes EXPECTED_WHEEL=dist/treeship_sdk-${VERSION}-py3-none-any.whl and EXPECTED_SDIST=dist/treeship_sdk-${VERSION}.tar.gz and refuses to continue if either is missing, (3) refuses to continue if any unexpected artifact is in dist/, (4) runs twine check on the exact expected paths to catch metadata problems server-PyPI would also reject, and (5) calls twine upload --skip-existing "$EXPECTED_WHEEL" "$EXPECTED_SDIST" instead of a blind dist/* glob. With this chain, a pyproject.toml drift can no longer land the wrong version on PyPI.
  • config::default_config_path() preserved as a thin wrapper. Existing callers keep working; the source-aware lookup goes through the new resolve_config_path() which returns (PathBuf, ConfigSource).

Fixed

  • Stale @treeship/core-wasm pins aligned with the current release. bridges/mcp/package.json (was 0.9.4), bridges/a2a/package.json (was 0.9.4), packages/verify-js/package.json (was 0.9.5), and packages/sdk-ts/package.json (was ^0.9.4) now pin exactly to the published @treeship/core-wasm. Surfaced by the new preflight; release.sh prepare now sweeps these forward at every bump.

0.9.6 (2026-04-27)

The trust-fabric release. v0.9.5 shipped receipts with cryptographic integrity but no comparison surface: a session could attest "the agent called Read 14 times" without anyone checking whether Read was even on the agent's authorized tool list, and a receipt's files_written could quietly omit anything that escaped the captured tool channel (a sed -i inside a Bash command, a build output, a manual edit). v0.9.6 closes both holes by building out the full capture-normalize-verify chain: every file the agent touches is captured by at least one of three layers (hook, MCP, or git reconciliation), every tool call is normalized through canonical aliases so cross-verification can compare claimed authorization against actual usage, and the receipt now signals when capture itself was incomplete instead of silently truncating.

It also tightens the approval grant model. v0.9.5 approvals carried only a nonce -- the cryptographic binding was real, but the same nonce could be replayed across unlimited actions and across different actions entirely. An approval to deploy.production could authorize a deploy.staging action; verify still printed single-use enforced because the binding was intact. v0.9.6 adds an ApprovalScope object that signs who (allowed_actors), what (allowed_actions), where (allowed_subjects), and how many times (max_actions) into the grant; verify now checks actor / action / subject statelessly and refuses the actions that don't match. The verify output is rewritten to report only what was actually checked -- three separate lines for binding, scope, and replay posture -- with explicit ⚠ warnings instead of false confidence claims.

The "trust fabric" framing is now load-bearing: an audit reader looking at a v0.9.6 receipt can answer did this agent stay inside its bounds with the same confidence they could already answer was this signature valid.

Added (approval grant model)

  • ApprovalScope carries actor / action / subject allow-lists and max-uses. packages/core/src/statements/mod.rs. ApprovalScope previously held only max_actions, valid_until, and allowed_actions; v0.9.6 adds allowed_actors and allowed_subjects. The grant now answers "who may consume this approval, to do what, against which subject, how many times." is_unscoped() returns true when no axis is populated -- verify uses this to emit a warning instead of a false authorization claim. New fields default-empty and skip_serializing_if-omitted, so a 0.9.5 approval payload deserializes cleanly into a 0.9.6 ApprovalScope (legacy roundtrip test pinned).

  • treeship attest approval flags: --allowed-actor, --allowed-action, --allowed-subject, --max-uses, --unscoped. packages/cli/src/main.rs, packages/cli/src/commands/attest.rs. Each --allowed-* flag is repeatable. --max-uses N is signed into the grant for future ledger enforcement (verify reports replay posture honestly and does not yet claim global single-use). --unscoped is the explicit opt-in for bearer approvals -- without any scope axis AND without --unscoped, the CLI now refuses to mint the approval (defaults to safe).

  • treeship attest action --subject <URI> for symmetric scope binding. Alias for the existing --content-uri. Lets callers naturally write attest approval --allowed-subject env://prod and attest action --subject env://prod and have the verifier match them.

  • packages/core/src/statements/mod.rs::ApprovalScope::is_unscoped(). Public predicate exposed so SDK consumers and the verify pass agree on the same definition of "unscoped."

Changed (verification surface)

  • Verify output rewritten to stop overclaiming. packages/cli/src/commands/verify.rs. The single line ✓ nonce binding approval nonce matched, single-use enforced is replaced with three precisely-scoped lines:
    • ✓ approval binding nonce matched a signed approval (cryptographic only)
    • ✓ approval scope actor / action / subject matched approval scope (when scope present and matched), OR
    • ⚠ approval scope approval is unscoped -- proves binding only, not actor/action/subject authorization (when no scope was signed in)
    • ⚠ replay check package-local only -- no global ledger consulted (always; honest about what stateless verify can and can't do)
  • verify_nonce_bindings enforces allowed_actors and allowed_subjects. Was already enforcing allowed_actions; now covers all three axes plus a separate valid_until on the scope itself. First-violation-wins ordering: actor → action → subject → scope expiry, so a wrong-actor failure isn't masked by a wrong-action one.
  • Package-local replay observation. When two actions in the same verified bundle claim the same approval nonce, the second is rejected with nonce already consumed by <id> in this package (package-local replay). Not a global ledger -- the CHANGELOG and verify text are explicit about this scope -- but catches the obvious in-package replay pattern.
  • treeship attest approval defaults to scoped. Without any --allowed-* / --max-uses and without --unscoped, the command refuses with a message pointing at the right flags. Bearer approvals are now opt-in, not the default.

Added (capture layer)

  • MCP-routed tool calls promote into typed file/process events. packages/core/src/session/. Generic agent.called_tool events emitted by the MCP bridge previously stayed generic -- they appeared in the timeline but never landed in files_read, files_written, or processes on the receipt's side-effects block. New promotion pass at receipt composition inspects each agent.called_tool event's meta.tool_input and synthesizes the matching specialized event (AgentReadFile, AgentWroteFile, AgentExecutedCommand) when a known tool name + path are present. The synthetic event carries meta.source = "mcp" for provenance. Treeship can now serve as a generic MCP-routing fabric and the receipt still answers "what files did this agent touch" correctly. (#11)

  • Git reconciliation as the file-capture backstop. packages/core/src/session/git.rs. Runs at session close: shells out to git diff HEAD --name-status, git diff <since>..HEAD --name-status, and git ls-files --others --exclude-standard, deduplicates against files already captured by hook or MCP, and synthesizes AgentWroteFile events for everything else. The synthetic events are appended to events.jsonl BEFORE the merkle root is sealed, so backstop-discovered changes are cryptographically committed alongside the rest of the session evidence -- not patched into the receipt as out-of-band claims. Closes the long-standing trust gap: an agent that ran sed -i inside a Bash command, a build tool that modified files, or any other untracked side effect would otherwise have vanished from the receipt. Fail-open by design: if the working dir isn't a git repo, the git binary is missing, or any git command errors, returns an empty Vec and the receipt is still produced -- reconciliation is best-effort enhancement, never a gate. (#29, #30, #20, #24, #25)

  • session-event-cli source-of-truth attribution for hook events. Every treeship session event invocation now stamps meta.source = "session-event-cli" on the event it emits. The Claude Code plugin's post-tool-use.sh hook calls this for every tool use Claude makes, so the source label flows from "agent emitted a tool event" all the way through to receipt composition and cross-verification. Without this provenance label, every Claude tool call was indistinguishable from a generic event in the receipt and thus invisible to cross-verification.

Added (cross-verification layer)

  • Cross-verification: receipt vs certificate, with canonical tool aliases. packages/core/src/session/receipt.rs. The receipt's tool_usage block now carries both declared (what the agent's certificate authorized) and actual (what the agent actually called), and the verifier diffs them with TOOL_ALIASES mapping snake_case CLI names (read_file, write_file, bash, web_fetch) to TitleCase Claude tool names (Read, Write, Bash, WebFetch). Without alias normalization, a Claude session would always have actual: ["Read","Write","Bash"] while the certificate declared ["read_file","write_file","bash"], and cross-verification would falsely flag every authorized call as unauthorized. (#31, #32)

  • source_attributes_a_tool() filter so backstop sources don't get counted as direct tool calls. Cross-verification's actual list aggregates AgentReadFile, AgentWroteFile, and AgentExecutedCommand events -- but only when their meta.source is hook, mcp, shell-wrap, session-event-cli, or absent. Events synthesized by git-reconcile or the daemon-atime channel are excluded; they're Treeship's own backstop layers, not the agent calling tools, and counting them as direct tool use would inflate actual against the agent's certificate. (#32)

  • In-band incompleteness signal: proofs.event_log_skipped. packages/core/src/session/receipt.rs. When the event log reader skips a malformed JSON line, the count is now stamped on the sealed receipt as proofs.event_log_skipped: N. A downstream verifier can tell at a glance whether a receipt represents a clean session or one where some events were dropped during parsing. Defaults to 0 and is skip_serializing_if-omitted from canonical JSON, so receipts produced when the event log was clean stay byte-identical to v0.9.5 receipts. (#26)

Added (model + provider attribution)

  • agent.decision event at session start records the model. integrations/claude-code-plugin/scripts/session-start.sh now emits an agent.decision event carrying meta.model so the receipt records what model the agent ran on. Pairs with the CLI surface that plumbs model/provider/token-budget through the treeship session event command. Receipt readers can now answer "which model produced this work" without inferring from event timing. (#28, #6)

Changed (trust gates)

  • Provenance source labels are no longer downgraded to "hook" when unknown. packages/core/src/session/receipt.rs. Side-effect entries in the receipt now preserve the exact meta.source value they were stamped with, instead of falling back to "hook" for any unrecognized label. Caller-asserted provenance is now visible to the audit reader as written. (#20)

  • Git reconcile dedupes against writes only, never reads. packages/cli/src/commands/session.rs. Previous logic suppressed reconciled writes whenever the same path had been read earlier in the session, producing a confidently incomplete audit trail (read + process recorded; the write that the process performed silently dropped). Reads do not change files; only writes belong in the dedup set. Trust-fabric Codex finding #2. (#30)

  • Git reconcile records destination path on rename/copy. packages/core/src/session/git.rs. parse_name_status_line now returns the destination path for R/C codes instead of the source. git mv old new previously surfaced "old" (which no longer exists on disk) instead of "new" (which the agent created). Trust-fabric Codex round-2 finding. (#24)

  • Git reconcile filters Treeship's own runtime artifacts. packages/core/src/session/git.rs. .treeship/sessions/*, .treeship/artifacts/*, .treeship/tmp/*, .treeship/proof_queue/*, .treeship/session.closing, and .treeship/session.json are now excluded from files_written because they're Treeship's own bookkeeping touched by the very session that's closing -- noisy and misleading in a receipt. User-authored files under .treeship/ (config.yaml, declaration.json, agents/*, policy.yaml) are preserved -- those ARE the operator's own changes that an audit reader cares about. (#25)

Changed (security)

  • MCP bridge sanitizer no longer leaks command / cmd into receipts. bridges/mcp/src/client.ts. The __sanitizeToolInput whitelist is now strictly path-only (file_path, path, notebook_path, target_file). The earlier whitelist accepted command and cmd, which would have shipped raw shell-arg secrets (Bearer tokens passed inline to curl, AWS credentials passed inline to aws CLI) into meta.tool_input and ultimately the receipt. Caught by Codex round-2 trust-fabric review. (#19, #27)

Fixed

  • ctx::open no longer overwrites a project-local config.json with a global-extends marker. packages/cli/src/ctx.rs. treeship init previously wrote the marker even when the directory already had a populated config.json from a separately-managed setup, silently breaking that project's keystore reference. (#15)

  • machine_seed is co-located with the keystore for project-local isolation. packages/core/src/keys/. Was previously written to a global path (~/.treeship/machine_seed), which meant two projects on the same host shared a derivation and a keystore-MAC failure in one project could surface as a decryption failure in the other. Now lives in the project's .treeship/machine_seed. (#16)

  • Event log: one malformed line no longer drops the whole receipt. packages/core/src/session/event_log.rs. Previously a single bad JSON line in events.jsonl made read_all() short-circuit and return an empty Vec; the receipt then composed against zero events and looked like an empty session. Now skips the malformed line, increments a skipped counter, and continues. The skipped count surfaces in the sealed receipt via proofs.event_log_skipped (see Added). (#8)

  • Preview UI no longer crashes when tool_usage.declared or tool_usage.actual is absent. docs/components/receipt-preview.tsx. Receipts produced by clients that pre-date the cross-verify block were rendering as a blank panel because the preview component dereferenced both arrays without a guard. (#9)

  • Docs install button no longer advertises the orphaned cargo install treeship-cli. docs/components/install-button.tsx. The crates.io upload was yanked weeks ago in favor of the npm path; the docs hadn't caught up. (#5)

  • CI: cross-SDK matrix no longer wipes /usr/bin from PATH. .github/workflows/ci.yml. Workflow expression ${{ env.PATH }} resolves to empty string at workflow context, so PATH: ./node_modules/.bin:${{ env.PATH }} left PATH as just ./node_modules/.bin:, and /usr/bin/env: 'bash': No such file or directory killed every cross-SDK matrix entry with exit 127. Replaced with the canonical $GITHUB_PATH mechanism. (#33)

  • CI: tests/cross-sdk/ parses as ESM. tests/cross-sdk/package.json. verify-vectors.ts uses import.meta.url and top-level await (added in v0.9.5 for cross-SDK roundtrip Phase B), both ESM-only. Without "type": "module" somewhere up the tree, tsx defaulted to CJS and esbuild rejected the file. Local package.json scopes the ESM treatment to this directory. (#33)

Added (integrations)

  • Codex CLI integration. treeship add codex now detects the Codex CLI and installs an MCP block in ~/.codex/config.toml so Codex sessions flow through the same trust-fabric channels as Claude Code. (#10)

Added (docs)

  • Trust-fabric concept overview. docs/content/docs/concepts/trust-fabric.mdx. Explains the three-axis separation (agent surface / model+provider / tool channel) and the three-layer file capture stack as a single mental model. (#14)

  • Universal MCP attach guide. bridges/mcp/ATTACHING.md. Step-by-step for wiring any MCP-speaking agent runtime (Codex, Cursor, Cline, Continue, custom) through the Treeship MCP bridge so the same trust-fabric behavior applies regardless of which agent surface is in use. (#13)

Notes

  • All 234 unit tests + 10 bridge tests + 8 cross-SDK matrix entries pass on main. Verified after the fix to .github/workflows/ci.yml landed.
  • No keystore format change. v0.9.6 reads and writes the same encrypted entries as v0.9.5; no rotation or rekey required.
  • No SDK API breakage. The cross-verification block is purely additive on the receipt side; existing SDK consumers continue to work without changes. The tool_usage block was already present in v0.9.5 receipts -- v0.9.6 just makes its actual list useful by populating it from the specialized event types and normalizing tool names through aliases.

0.9.5 (2026-04-25)

The performance and key-lifecycle release. Closes the two technical debts called out in the v0.9.4 "Known limitations" -- the O(N) event-log append, and the absence of any rotation primitive in a keystore where every key was meant to live forever. Also adds the first cross-SDK contract tests so TypeScript and Python can no longer drift apart silently, and unbreaks the docs site, which had been 404'ing in production since the 4906398 commit landed an invalid fumadocs root field.

Added

  • Store::rotate(predecessor, grace_period, set_default) -> RotationResult. The first lifecycle surface on the keystore. Mints a freshly generated Ed25519 successor, links the predecessor forward via a new successor_key_id field, and stamps it with valid_until = now + grace_period. Write order is successor-entry first, then stamped predecessor entry, then manifest -- so a partial-failure crash leaves either nothing observable (clean retry) or an orphan successor key file (harmless; not in manifest, retry generates a fresh one). The predecessor remains usable for signing during its grace window so an in-flight session that already started signing under the old key can finish; verifiers that honor valid_until can refuse the predecessor on lifecycle. Refuses to rotate an already-rotated key (caller must rotate the chain head). Backed by Store::successor_chain(id) -> Vec<KeyId> for forward walks and Store::valid_keys_at(at_unix_secs) -> Vec<KeyInfo> for building a verifier's accept-set as of a given time.

  • treeship keys rotate CLI surface. treeship keys rotate [--key id] [--grace-hours 24] [--no-default]. Default 24h grace matches a typical client-cache TTL for fetched public-key bundles. treeship keys list is enriched to show rotation status inline (rotated -> key_xxx, valid until 2026-04-26T...).

  • KeyInfo.valid_until and KeyInfo.successor_key_id. Lifecycle metadata threaded through the public KeyInfo shape and the on-disk encrypted entry. Both Option<String> and Option<KeyId>. Pre-0.9.5 entry files lack these fields entirely; they're #[serde(default)] and skip_serializing_if = "Option::is_none", so legacy files load with None and never-rotated entries don't grow new fields on disk -- a 0.9.5 keystore is on-disk byte-identical to a 0.9.4 keystore until you actually rotate something.

  • Cross-SDK contract suite at tests/cross-sdk/. Generates a scratch corpus of signed artifacts (action, decision, approval, plus one DSSE-tampered variant with a flipped signature byte), invokes the actual SDK public API -- ship().verify.verify(id) and Treeship().verify(id) -- against the same corpus, diffs {outcome, chain} per vector. Any divergence fails CI. The runners point both SDKs at the corpus's scratch keystore via the new TREESHIP_CONFIG env var honored by the CLI binary, so neither SDK needed an API change to participate. The suite caught and forced the fix of two real Python SDK contract bugs on its first green run (see "Fixed: Python SDK"). Local run: ./tests/cross-sdk/run.sh. CI matrix: {ubuntu, macos} x {Node 20, 22} x {Python 3.11, 3.12} -- eight cells so platform-specific divergences (different fetch implementations, different subprocess line endings) surface immediately.

  • TREESHIP_CONFIG env var honored by the CLI. The binary's config-path resolution now consults TREESHIP_CONFIG before falling back to ~/.treeship/config.json. --config still wins over the env var. Designed for SDK consumers and CI runners that need to target an isolated keystore without forcing every SDK to add a per-call config option -- setting one env var redirects every read and write into the scratch directory regardless of which language the caller is using. Empty string is treated as unset to avoid a export TREESHIP_CONFIG= footgun.

Changed

  • Event log append is now O(1) in session length. packages/core/src/session/event_log.rs. The previous implementation re-streamed the entire events.jsonl on every append to derive the next sequence_no. That made each PostToolUse hook O(N) in session length and dominated end-to-end latency on long sessions; a typical Claude Code session of 200-500 events meant each later append re-read 200+ lines under the lock. A new 16-byte events.jsonl.count sidecar ([count: u64 LE, byte_size: u64 LE]) replaces the rescan with two 16-byte file ops in the steady state. The byte_size field is the crash detector: if a writer wrote events.jsonl but crashed before fsyncing the counter (or vice versa), the actual file size and the recorded size disagree -- mismatch, recount once, rewrite. Self-heals on the next append/open without ever assigning a duplicate sequence_no. Counter writes go through write-temp-then-rename so a reader that catches us mid-update sees either the old 16 bytes or the new 16 bytes, never a torn write.

Changed (BREAKING for SDK consumers)

  • TS SDK: s.dock.*s.hub.*; class DockModuleHubModule. Both surfaces previously shelled out to treeship dock push|pull|status, which the CLI removed during the dock→hub rename in the v0.7.x line. The SDK call therefore failed against any current binary -- this is a bug fix as much as a rename. Pure rename, no aliases (we're not yet live with external SDK consumers, so the rename ships clean rather than carrying compat shims forever). s.hub.status() now returns { connected, endpoint?, hubId? } (was { docked, endpoint?, dockId? }); the dock_id field on the wire is still read as a fallback for older Hub responses.

  • Python SDK: Treeship.dock_push()Treeship.hub_push(). Same rename, same underlying bug fix (the old method called the removed treeship dock push subcommand and silently failed). Pure rename, no alias.

Added (testing)

  • Cross-SDK suite Phase B: roundtrip attest+verify. Phase A (the existing vector-parity check) only catches drift in how each SDK interprets the CLI's verify output. Phase B catches the deeper drift: TS attests an artifact, Python verifies it; Python attests an artifact, TS verifies it. All four legs must pass. If TS ever produces an envelope whose digest scheme, payload type, or signature encoding diverges from what Python expects, the suite fails. Two tiny dispatchers (_sdk-helper.mjs and _sdk_helper.py) expose attest-action / verify over argv so the orchestrator can sequence the four legs cleanly.

Fixed

  • Python SDK Treeship.verify() now returns structured failures instead of raising. Previously verify() shelled out via the generic _run helper, which raised TreeshipError on any non-zero CLI exit. But treeship verify exits 1 on a legitimate verify failure -- "the signature didn't check out" is a structured outcome, not a fault. The TypeScript SDK has always handled this correctly: catch the exec error, parse stdout, return outcome=fail. Python now matches: the outcome is in the returned VerifyResult, and TreeshipError is reserved for cases where verification couldn't even be attempted (binary missing, malformed JSON, keystore inaccessible). Caught by the cross-SDK suite on its first green run.

  • Python SDK chain field semantics now match TypeScript. TS's chain reports parsed.passed on outcome=pass and parsed.failed on outcome=fail; the previous Python implementation always reported passed, so a failed verification reported chain=0 while TS reported the failure count. The two now agree on every vector. Caught by the cross-SDK suite -- exactly the kind of silent drift the suite exists to surface.

  • Docs site restored. Commit 4906398 set "root": "overview" in docs/content/docs/cli/meta.json to (purportedly) fix /cli 404'ing. fumadocs-mdx schema actually rejects it (expected boolean, received string), which crashed mdx generation, which made every docs URL return 500. treeship.dev/cli and even treeship.dev/cli/init were 404 in production until this release. Replaces the broken approach with explicit Next.js redirects from /cli, /sdk, /api, /commerce, /reference to their respective first-page targets, plus a permanent /cli/dock -> /cli/hub redirect for the long-pending CLI rename.

  • cli/dock resolved to cli/hub. The CLI command has been treeship hub since v0.7.x; the doc page lived at /cli/dock under the legacy URL with its title set to "hub". Renamed dock.mdx -> hub.mdx, updated the section meta, added the permanent redirect.

Added (docs surface)

  • SEO surfaces. docs/app/robots.ts (with sitemap pointer), docs/app/sitemap.ts (generated from source.getPages() and blogSource.getPages() so all 87 docs + 18 blog URLs appear), and metadata export in docs/app/layout.tsx with metadataBase, OG tags, and Twitter card defaults so social previews stop falling back to bare URLs.

  • llms.txt rewritten. docs/public/llms.txt now uses canonical paths (no /docs/ prefix that depended on the old redirect), correct host (treeship.dev, not the imagined docs.treeship.dev), and lists every current page including /cli/hub, /cli/log, /cli/merkle, /cli/otel, /cli/ui, /cli/install-cmd, /cli/approve, /cli/bundle, /sdk/verify, /sdk/mcp, the full /api/* and /commerce/* sections.

Notes

  • No on-the-wire schema changes. Receipts and certificates produced by 0.9.4 verify identically under 0.9.5; receipts produced by 0.9.5 verify identically under 0.9.4 (the new key-lifecycle fields are scoped to the keystore, not the signed envelope).
  • Workspace crates bumped together per the lockstep convention. Full treeship-core lib suite: 177/177 passing (was 161 in 0.9.4; +5 counter-sidecar tests, +8 key-rotation tests, +3 unrelated). Cross-SDK contract suite: 4/4 vectors agree across both SDKs on this release after the two Python SDK fixes the suite forced. The release went through three review rounds (self-review, then two parallel Codex adversarial passes); each round produced a real fix that's now landed. The Store::rotate cache update was reordered to happen BEFORE the manifest write so a same-process retry sees consistent state on a manifest-write failure, and the cross-SDK runners now also assert expected_chain per vector (not just expected_outcome) so a same-direction regression in both SDKs can no longer silently pass.

Known limitations

  • Verifier-side enforcement of valid_until is NOT in this release. Adding the metadata is half the work; making the receipt-verify path refuse signatures whose key has expired is the other half, and it's a behavior change that would silently invalidate in-flight receipts if shipped in a patch. Slated for v0.10.0 behind an opt-in feature flag, with a migration window measured in releases not days.
  • Compromise-revocation primitive is not in this release. rotate is a graceful primitive (predecessor remains usable through its grace window); it's the wrong primitive for "this key is compromised, distrust it immediately." That needs its own design (revocation list distribution, verifier-side lookup latency, threat model). Tracked separately.
  • Store::rotate and Store::generate are not safe under concurrent invocation. Both write to manifest.json without a flock, so two processes calling rotate at the same instant can produce two successors, only one of which ends up in the manifest. This is the same race that already existed for generate since 0.9.0 -- not a regression introduced by rotate, but worth flagging now that more callers may invoke the keystore concurrently. Workaround for operators running rotation from multiple machines: serialize externally (single CI job, or a lease in your secrets store). Real fix is a flock on manifest.json parallel to the one already in event_log.rs; tracked for the next keystore touch.
  • verifyReceipt(json) parity in the cross-SDK suite is missing. The TS SDK exposes it via WASM; Python doesn't have a JSON-receipt entry point yet. The current suite locks down verify(artifact_id) -- the LCD surface -- and is structured so adding verifyReceipt later is a one-line addition to gen-vectors.sh plus a method call swap in each runner.

0.9.4 (2026-04-21)

Closes the v0.9.3 launch gaps surfaced during live plugin testing: the plugin now has a real install path without waiting for Anthropic marketplace approval, keystore-migration failures produce actionable errors instead of cryptic ones, and the plugin's SessionStart hook no longer silently swallows errors.

Added

  • Zerker Labs Claude Code plugin marketplace. The treeship monorepo now ships .claude-plugin/marketplace.json at the repo root so any user can install the plugin tonight with:

    claude plugin marketplace add zerkerlabs/treeship
    claude plugin install treeship@treeship

    Installs to ~/.claude/plugins/cache/treeship/treeship/<version>/. Every subsequent Claude Code session auto-loads the plugin — SessionStart / PostToolUse / SessionEnd hooks fire, sealed receipts land in .treeship/sessions/<id>.treeship, treeship package verify passes all integrity checks. Independently verified end-to-end on a fresh scratch project. Anthropic's official-marketplace submission remains a separate track.

  • Actionable keystore migration error. When Store::signer() can't MAC-verify a key (typical cause: upgrading from a pre-0.9.x Treeship whose machine-key derivation has since changed), the error now includes a diagnosis and a copy-pasteable recovery path instead of just "MAC verification failed — key file may be corrupt or wrong machine". Detection is best-effort: presence of a legacy .machineseed or machine_seed file inside the keys dir upgrades the diagnosis from "could be many causes" to "this is specifically a version upgrade". The recovery path explicitly notes non-destructive move semantics and that prior sealed .treeship packages remain verifiable (their receipts embed the old public key, so signatures still check out offline).

  • Plugin SessionStart hook surfaces failures to Claude Code. integrations/claude-code-plugin/scripts/session-start.sh previously redirected stderr to /dev/null and silently exited 0 on any session-start error. With a broken keystore, that meant a user got no signal that recording wasn't happening — the worst possible failure mode. The hook now captures stderr, and on failure emits an additionalContext JSON envelope with the full diagnostic (built via python3 so newlines and quotes escape correctly) so Claude sees the recovery commands inline.

Fixed

  • fchmod-on-fd eliminates TOCTOU in lock-file perm re-tightening. The sidecar-lock-file open path (open_lock_file in packages/core/src/session/event_log.rs) previously re-chmodded existing files via set_permissions(path, ...) after an file.metadata() check. That's a TOCTOU: between the metadata read and the path-based chmod, an attacker could swap the inode. Now we call fchmod(fd) on the already-open file descriptor, so the target is pinned to the inode we hold. FFI wrapped locally to avoid adding a full libc dep for one symbol.

  • NFS chmod warning. If fchmod returns non-zero (NFS mount with restricted metadata, some filesystems without full POSIX perm support), we now emit a one-line stderr warning instead of silently ignoring the failure. The lock itself still functions; operators just gain visibility into perms that weren't tightened.

  • Crates verify-script hardening. wait-for-crates-version.sh now queries the sparse index (https://index.crates.io/...) first, falls back to the api/v1 endpoint with a policy-compliant User-Agent, and treats 403 as transient throttle rather than a hard failure. Fixes the ~5 minute verify-timeout false alarm we saw during the v0.9.3 release (the crate itself published successfully; only the verify gate was broken). Also avoids a SIGPIPE interaction where grep -q exiting early under set -o pipefail made successful matches look like network failures.

  • status_check dead _config arg removed. Cosmetic cleanup from the v0.9.3 Codex review punch list. treeship session status --check no longer takes a dead config argument because load_session() reads the project-local session marker from cwd directly.

Notes

  • No schema or API-surface changes. Workspace crates bumped together per the lockstep convention established in v0.9.2.
  • All 161 treeship-core lib tests pass, including the race-safety regression (concurrent_appends_have_unique_sequence_numbers), the lock-perm regression (lock_file_has_owner_only_permissions), and the upgrade-path regression (existing_lock_file_is_re_tightened).

Known limitations

  • Append is still O(N) in the on-disk event count. The counter-sidecar optimization noted as a v0.9.4 target in the v0.9.3 CHANGELOG is deferred again; for typical session lengths (50-500 events) this is negligible, and the current implementation is correct. Scheduled for v0.9.5 alongside the treeship wrap-vs-plugin-hooks architectural discussion.

0.9.3 (2026-04-20)

Trust onboarding for AI agents. The motivating problem: developers attempting to install Treeship would land on treeship.dev/setup (404, missing rewrite) and, even after installing, Claude Code would refuse to attach the MCP server because it had no in-context explanation of what @treeship/mcp captures or where data goes. v0.9.3 fixes both halves.

Added

  • treeship add (any framework) now drops a single ./TREESHIP.md into the project root if one isn't already there. The file is framework-agnostic and answers the trust question for Claude Code, Cursor, Cline, Hermes, OpenClaw, and any future MCP-aware agent in one place: what gets captured (tool name, SHA-256 digest of arguments — not raw args, SHA-256 digest of output — not raw content, exit code, duration, raw error message text on failure), what does NOT get captured (file contents, env values, secrets), when data leaves the machine (only on explicit treeship session report / hub push / auto_push: true), and how to use the wrap/session lifecycle.
  • The TREESHIP.md template ships embedded in the CLI binary via include_str! so the drop works offline and never fetches over the network.
  • Official Claude Code plugin at integrations/claude-code-plugin/. Marketplace-ready (.claude-plugin/plugin.json). Mounts @treeship/mcp via npx -y, wires SessionStart/SessionEnd/PostToolUse hooks for deterministic auto-recording (no model-prompted lifecycle), ships a live monitor for receipt/event counters, and ships three skills (treeship-session, treeship-verify, treeship-report) for the moments that need agent agency. PostToolUse exists because Claude Code's built-in tools (Read, Write, Edit, Bash, Grep, Glob) bypass MCP and would otherwise be missing from the receipt timeline. Hooks fail open: missing CLI or missing .treeship/ makes the plugin a silent no-op. Submit at <https://claude.ai/settings/plugins/submit>.

Changed

  • treeship add no longer touches framework-specific files (CLAUDE.md, .cursorrules, skill files) for trust purposes. Those stay focused on framework-specific instructions; the trust block lives in TREESHIP.md instead. One file, one source of truth, any agent.
  • integrations/claude-code/CLAUDE.md template gains the trust block (what's captured, what isn't, when data leaves) above the existing wrapping rules — so users who copy the template manually also get the trust context, not only those who go through treeship add.
  • bridges/mcp/README.md reframed: the inspect-before-trust path now leads with treeship package verify (offline, WASM, no hub required), with a clear pointer to the bridge source. Removed the previous "Verify our own development" section pending the flagship release receipt.

Fixed (post-Codex-review, two passes)

  • treeship session event is now race-safe across processes. Each invocation acquires an exclusive advisory file lock (fs2::FileExt::try_lock_exclusive in a 500ms bounded retry loop, backed by flock(2) on Unix and LockFileEx on Windows) on a sidecar .lock file before re-deriving sequence_no from the on-disk JSONL line count. Previously, parallel writers (e.g. concurrent PostToolUse hook invocations from a Claude Code plugin) each had their own AtomicU64 initialized from a stale snapshot of the file and would assign duplicate sequence numbers, silently breaking Merkle chain ordering. The retry path is bounded so a wedged or crashed writer cannot freeze hook-driven invocations forever — after 500ms of contention, the append falls through with a stderr warning rather than blocking the agent. New regression test concurrent_appends_have_unique_sequence_numbers spawns 16 racing writers and asserts uniqueness.
  • Sidecar lock file is created mode 0o600 (owner-only) on Unix via OpenOptionsExt::mode. Atomic at file creation so the file can never exist on disk with a more permissive mode. Regression test lock_file_has_owner_only_permissions asserts this.
  • treeship session status --check added: prints nothing, exits 0 if a session is active, exits 1 if not. Shell-script-friendly gate for hooks and monitors. The default treeship session status still always exits 0 (it's a human-facing report), so existing scripts that gated on it are unchanged — but the Claude Code plugin's hooks now use --check so they actually run when a session is active. Without this, the plugin's SessionStart, SessionEnd, and PostToolUse hooks would have no-op'd in every real-world session.
  • Trust documentation rewritten to match the bridge source. The previous TREESHIP.md / CLAUDE.md trust block claimed @treeship/mcp captured "arguments passed to the tool". The bridge actually captures the SHA-256 digest of arguments and output, never raw values. The first-pass fix narrowed that wording but still omitted other emitted fields (action, approval_nonce, server, agent_name, artifact_id, meta.source, etc.). The trust block is now organized by attestation type (intent attestation / result receipt / session event) and enumerates every field the bridge writes, with each field cross-referenced against bridges/mcp/src/client.ts and attest.ts. Conditional fields (e.g. artifact_id is omitted if the receipt write failed; payload.output_digest uses result.content ?? result so the fallback is documented) are flagged as conditional. payload.error_message carries the raw Error.message string on thrown errors — flagged so users treat it like a logged stack trace if their tools can leak in error text.
  • Plugin post-tool-use.sh no longer regex-extracts tool_name from the PostToolUse JSON payload (a greedy sed would pull the wrong value when tool_input itself contained a "tool_name":"foo" substring — confirmed reproducible). It now tries jq, then python3, then node for JSON parsing, falling back to "unknown" only if all three are absent.
  • npm install -g treeship now fails loudly on Windows instead of silently installing a wrapper without a binary. Added npm/treeship/scripts/check-platform.js wired as a preinstall hook, plus an os: ["darwin", "linux"] field in package.json so npm itself rejects the install. Windows users get a clear "use WSL or wait for v0.10" message.
  • Setup script version gate uses strict semver when possible. Primary path: a node-based parser that treats any prerelease of 0.9.3 as < 0.9.3 per semver.org §11.4. Smoke-tested against 0.9.2, 0.9.3-rc.1, 0.9.3, 0.9.3-alpha, 0.9.4, 0.10.0, and 1.0.0. Fallback path (when node isn't on PATH): sort -V with a softer warning that notes prerelease detection is not strict. If treeship --version returns a string we can't parse as semver at all, the gate emits a "could not verify CLI version" warning rather than silently skipping. Upgrade hint also softened: no more rm $(command -v treeship) — points at package-manager reinstall instead.
  • setup.sh adapts the trailing hint based on whether ./TREESHIP.md actually exists after treeship add runs. If the CLI is too old to drop the file, the hint now points at the GitHub raw TREESHIP.md URL instead of a nonexistent local file.

Notes

  • Same safety guards as the rest of treeship add: refuses to write outside a .treeship/-initialized project, refuses through symlinks, never overwrites an existing ./TREESHIP.md.
  • The website's setup.sh one-liner is unchanged in spirit — treeship add --all (which it already calls) now drops TREESHIP.md automatically once v0.9.3 is published. Step 4 from the previous diff (a runtime curl of CLAUDE.md from GitHub raw) is removed; the embedded include_str! template is the source of truth.
  • Website-side fix for the original treeship.dev/setup 404 is in the treeship-website repo (added a Next.js rewrite for /setup/setup.sh mirroring the existing /install rewrite). Independent of this CLI release; deploys with the website.
  • Platform support: macOS and Linux only at v0.9.3. The CLI ships binaries for darwin-arm64, darwin-x64, and linux-x64. The website's setup script is POSIX shell, the plugin's hook scripts are POSIX shell, and treeship add's file-drop semantics rely on POSIX rename overwrite behavior. A native Windows binary, Windows-aware filesystem path, and PowerShell setup script are planned for v0.10.0. Use WSL on Windows today.

Known limitations

  • session event append is O(N) in the on-disk event count. The cross-process safe re-derivation of sequence_no rescans the full events.jsonl file on every append. For a typical session of 50–500 events this is negligible (microseconds). For sessions with 10k+ events it begins to surface as user-visible latency under rapid PostToolUse bursts. Deferred to v0.9.4: replace the rescan with a tiny locked counter sidecar so append becomes O(1). Tracked separately; flagged here so anyone writing high-throughput agent integrations against v0.9.3 can plan around it.

0.9.2 (2026-04-20)

All packages realigned at 0.9.2 after a partial v0.9.1 npm publish. v0.9.1 landed successfully on crates.io, PyPI, and for the treeship wrapper + platform binaries on npm, but the new @treeship/core-wasm and @treeship/verify packages failed to bootstrap (scope permissions for new package names), which cascaded failure to @treeship/sdk, @treeship/mcp, and @treeship/a2a. The release workflow has been hardened to fail loud on publish failures, pre-flight every expected package on the @treeship scope, and verify each package post-publish.

v0.9.2 is the first version where every package lands cleanly on every registry together. Install v0.9.2 everywhere; do not mix with 0.9.1.

Release workflow hardening

  • Every npm publish / cargo publish / twine upload step has continue-on-error removed. A failure in any one now fails the workflow — no more silent partial releases.
  • New pre-flight step on the publish-npm job enumerates every expected package on the @treeship scope and fails fast if any is missing. New scoped packages must be bootstrapped once with a web-auth npm publish from an account that owns the scope; thereafter the workflow's OIDC trusted publisher handles all future versions.
  • New .github/scripts/wait-for-{npm,crates,pypi}-version.sh helpers poll each registry after publish and fail the workflow if the expected version does not appear within 30 seconds. Catches partial propagation, transient registry errors, and accidentally-skipped publish steps.

Contents of this release

All v0.9.1 work below. Semantically identical; the only difference is the version string and that every package actually lands on its registry this time.

Added

  • @treeship/core-wasm npm package. The Rust core compiled to WebAssembly with 10 exported functions (verify_envelope, artifact_id, digest, decode_payload, verify_merkle_proof, verify_zk_proof, version, plus the three new high-level ones below). Bundle size: 167 KB gzipped (target was under 250 KB). First-class published npm package; pinned to exact versions across all dependents to prevent silent drift.
  • @treeship/verify zero-dependency verification package at packages/verify-js/. Install alone, verify receipts and certificates in any runtime with WebAssembly + fetch. Only dependency is @treeship/core-wasm. This is what Witness, dashboards, and third-party consumers install.
  • High-level WASM exports: verify_receipt, verify_certificate, cross_verify. Previously only low-level envelope and Merkle primitives were exposed. Each returns JSON in / JSON out with an error shape on malformed input rather than panicking.
  • Reusable library primitive treeship_core::verify::verify_receipt_json_checks — lifted from the CLI's URL-fetch path so CLI and WASM share one implementation. Same checks (Merkle root recomputation, inclusion proofs, leaf count, timeline ordering, chain linkage) produce the same result across every runtime.
  • treeship_core::agent::verify_certificate — Ed25519 signature check against the certificate's embedded public key. Exposed so the CLI, @treeship/verify, and future WASM consumers share one implementation.
  • Runtime compatibility: Node.js 18+, Deno, browser (bundler), Vercel Edge, Cloudflare Workers, AWS Lambda. Edge runtime deploy harnesses at tests/runtime-acceptance/{vercel-edge,cloudflare-worker,aws-lambda}/ — runnable projects with per-runtime READMEs.
  • New docs: sdk/verify.mdx, guides/edge-runtime.mdx. Runtime compatibility matrices added to @treeship/sdk, @treeship/a2a, and @treeship/mcp READMEs. reference/schema.mdx gains a "Parity between CLI and WASM" section.
  • Command-line build pipeline: packages/core-wasm/build-npm.sh <version> runs wasm-pack build --target bundler, optionally wasm-opt -Oz, then rewrites pkg/package.json with scoped name + license + repo + keywords. Release workflow installs wasm-pack + binaryen and runs this before the other npm publish steps so dependents resolve against the fresh core-wasm.

Changed

  • @treeship/sdk verification path migrated from CLI subprocess to direct WASM calls. ship.verify.verify(id) (legacy artifact-ID form) still subprocesses; new verifyReceipt / verifyCertificate / crossVerify methods run in-process via WASM. Stateful operations (attest, session, dock, agent register) still use subprocess.
  • @treeship/a2a verifyReceipt now performs real cryptographic verification (was previously network-only structural summary). VerifiedReceipt.cryptographicallyVerified surfaces the WASM result; verifyChecks carries the per-step breakdown. Graceful fallback: if @treeship/core-wasm can't load in the runtime, returns the pre-v0.9.1 summary with cryptographicallyVerified: false.
  • @treeship/mcp gains WASM-backed verification helpers alongside its attestation surface. Attest paths remain subprocess-based.
  • scripts/release.sh now pins @treeship/core-wasm to the exact release version across all dependent packages (sdk-ts, a2a, mcp, verify-js) at tag time. No caret ranges for this dependency — drift would break the schema-rules parity guarantee.
  • Workspace Cargo.toml adds [profile.release.package.treeship-core-wasm] with opt-level = "z" and codegen-units = 1. CLI release tuning unchanged.
  • treeship_core hostname dep moved to target.'cfg(not(target_family = "wasm"))'. WASM builds fall back to "host_unknown" in default_host_id; WASM contexts consume receipts rather than author them, so the fallback is benign.

Notes

  • All packages that depend on @treeship/core-wasm pin to exact version 0.9.2 (no caret). release.sh enforces this at tag time.
  • Subprocess fallback was not implemented for WASM. The SDK's verifyReceipt / verifyCertificate / crossVerify functions require a runtime that can load the bundled WebAssembly. Runtimes without WASM support can continue using the legacy verify(id) subprocess path.
  • WASM imports are lazy: the SDK and bridge modules can load in environments where @treeship/core-wasm is not yet resolvable (early-bootstrap CI, non-verification code paths). The first verification call pays the load cost; subsequent calls reuse cached bindings.

Release-window follow-ups

  • Edge runtime acceptance deploys to Vercel Edge, Cloudflare Workers, and AWS Lambda are code-complete in tests/runtime-acceptance/ but the actual deploys + cold-start measurements run out-of-band. Acceptance criteria are documented in each subdirectory's README; rerun to reproduce.
  • Comprehensive Codex adversarial review of the v0.9.x WASM migration surface is planned before v0.10.0 cuts. 174+ unit / integration tests pass workspace-wide, but a formal adversarial pass adds a second set of eyes.

Not in this release (coming in v0.10.0)

  • Approval loop primitives (5 new Hub endpoints + --require-approval flag on treeship wrap + treeship approver CLI)
  • treeship.dev/verify browser drag-and-drop page (unblocked now that v0.9.2 publishes @treeship/core-wasm and @treeship/verify)
  • Command-artifact CLI surfaces to issue KillCommand / TerminateSession / etc. — the schemas exist as of v0.9.0

Rollback

Previous stable is v0.9.0 and remains published on every registry. Do not roll back to v0.9.1 — the npm side of that tag is partial and will leave installs in an inconsistent state. Downgrade straight to v0.9.0:

npm install @treeship/sdk@0.9.0 @treeship/a2a@0.9.0 @treeship/mcp@0.9.0 treeship@0.9.0
cargo install treeship-core@0.9.0
pip install treeship-sdk==0.9.0

v0.9.0 verification uses the CLI subprocess — less portable, but still correct. @treeship/core-wasm and @treeship/verify are new in v0.9.2 and have no v0.9.0 counterpart to roll back to.

0.9.1 (2026-04-18)

Partial publish; superseded by 0.9.2. Do not install 0.9.1 npm packages. This entry remains as a historical record. On npm, only treeship, @treeship/cli-linux-x64, @treeship/cli-darwin-arm64, and @treeship/cli-darwin-x64 reached 0.9.1. @treeship/core-wasm and @treeship/verify never published, which cascaded install failures to @treeship/sdk, @treeship/mcp, and @treeship/a2a. treeship-core (crates.io) and treeship-sdk (PyPI) did reach 0.9.1 cleanly. v0.9.2 realigns everything.

Verification runs anywhere. WASM migration of the core verification surface, published as an npm package, and rewired through the SDK and bridge packages. Second of three planned releases in this window; see v0.9.0 for the schema foundation and v0.10.0 (upcoming) for the approval loop and drag-drop verifier.

Added

  • @treeship/core-wasm npm package. The Rust core compiled to WebAssembly with 10 exported functions (verify_envelope, artifact_id, digest, decode_payload, verify_merkle_proof, verify_zk_proof, version, plus the three new high-level ones below). Bundle size: 167 KB gzipped (target was under 250 KB). First-class published npm package; pinned to exact versions across all dependents to prevent silent drift.
  • @treeship/verify zero-dependency verification package at packages/verify-js/. Install alone, verify receipts and certificates in any runtime with WebAssembly + fetch. Only dependency is @treeship/core-wasm. This is what Witness, dashboards, and third-party consumers install.
  • High-level WASM exports: verify_receipt, verify_certificate, cross_verify (item 1). Previously only low-level envelope and Merkle primitives were exposed. Each returns JSON in / JSON out with an error shape on malformed input rather than panicking.
  • Reusable library primitive treeship_core::verify::verify_receipt_json_checks — lifted from the CLI's URL-fetch path so CLI and WASM share one implementation. Same checks (Merkle root recomputation, inclusion proofs, leaf count, timeline ordering, chain linkage) produce the same result across every runtime.
  • treeship_core::agent::verify_certificate — Ed25519 signature check against the certificate's embedded public key. Exposed so the CLI, @treeship/verify, and future WASM consumers share one implementation.
  • Runtime compatibility: Node.js 18+, Deno, browser (bundler), Vercel Edge, Cloudflare Workers, AWS Lambda. Edge runtime deploy harnesses at tests/runtime-acceptance/{vercel-edge,cloudflare-worker,aws-lambda}/ — runnable projects with per-runtime READMEs.
  • New docs: sdk/verify.mdx, guides/edge-runtime.mdx. Runtime compatibility matrices added to @treeship/sdk, @treeship/a2a, and @treeship/mcp READMEs. reference/schema.mdx gains a "Parity between CLI and WASM" section.
  • Command-line build pipeline: packages/core-wasm/build-npm.sh <version> runs wasm-pack build --target bundler, optionally wasm-opt -Oz, then rewrites pkg/package.json with scoped name + license + repo + keywords. Release workflow installs wasm-pack + binaryen and runs this before the other npm publish steps so dependents resolve against the fresh core-wasm.

Changed

  • @treeship/sdk verification path migrated from CLI subprocess to direct WASM calls. ship.verify.verify(id) (legacy artifact-ID form) still subprocesses; new verifyReceipt / verifyCertificate / crossVerify methods run in-process via WASM. Stateful operations (attest, session, dock, agent register) still use subprocess.
  • @treeship/a2a verifyReceipt now performs real cryptographic verification (was previously network-only structural summary). VerifiedReceipt.cryptographicallyVerified surfaces the WASM result; verifyChecks carries the per-step breakdown. Graceful fallback: if @treeship/core-wasm can't load in the runtime, returns the pre-v0.9.1 summary with cryptographicallyVerified: false.
  • @treeship/mcp gains WASM-backed verification helpers alongside its attestation surface. Attest paths remain subprocess-based.
  • scripts/release.sh now pins @treeship/core-wasm to the exact release version across all dependent packages (sdk-ts, a2a, mcp, verify-js) at tag time. No caret ranges for this dependency — drift would break the schema-rules parity guarantee.
  • Workspace Cargo.toml adds [profile.release.package.treeship-core-wasm] with opt-level = "z" and codegen-units = 1. CLI release tuning unchanged.
  • treeship_core hostname dep moved to target.'cfg(not(target_family = "wasm"))'. WASM builds fall back to "host_unknown" in default_host_id; WASM contexts consume receipts rather than author them, so the fallback is benign.

Notes

  • All packages that now depend on @treeship/core-wasm pin to exact version 0.9.1 (no caret). release.sh enforces this at tag time.
  • Subprocess fallback was not implemented for WASM. The SDK's verifyReceipt / verifyCertificate / crossVerify functions require a runtime that can load the bundled WebAssembly. Runtimes without WASM support can continue using the legacy verify(id) subprocess path.
  • WASM imports are lazy: the SDK and bridge modules can load in environments where @treeship/core-wasm is not yet resolvable (early-bootstrap CI, non-verification code paths). The first verification call pays the load cost; subsequent calls reuse cached bindings.

Release-window follow-ups

  • Edge runtime acceptance deploys to Vercel Edge, Cloudflare Workers, and AWS Lambda are code-complete in tests/runtime-acceptance/ but the actual deploys + cold-start measurements run out-of-band (this session cannot authenticate to any of the three providers). Acceptance criteria are documented in each subdirectory's README; rerun to reproduce.
  • Comprehensive Codex adversarial review of the v0.9.1 WASM migration surface is planned before v0.10.0 cuts. v0.9.0 carried the same note and the same plan holds here: 174+ unit / integration tests pass workspace-wide, but a formal adversarial pass adds a second set of eyes.

Not in this release (coming in v0.10.0)

  • Approval loop primitives (5 new Hub endpoints + --require-approval flag on treeship wrap + treeship approver CLI)
  • treeship.dev/verify browser drag-and-drop page (unblocked now that v0.9.1 publishes @treeship/core-wasm and @treeship/verify)
  • Command-artifact CLI surfaces to issue KillCommand / TerminateSession / etc. — the schemas exist as of v0.9.0

Rollback

Previous stable is v0.9.0 and remains published on every registry. Downgrade:

npm install @treeship/sdk@0.9.0 @treeship/a2a@0.9.0 @treeship/mcp@0.9.0 treeship@0.9.0
cargo install treeship-core@0.9.0
pip install treeship-sdk==0.9.0

v0.9.0 verification uses the CLI subprocess — less portable, but still correct. @treeship/core-wasm and @treeship/verify are new in v0.9.1 and have no v0.9.0 counterpart to roll back to.

0.9.0 (2026-04-18)

Verification UX is now complete and future-proofed. v0.9.0 is the first of three planned releases in this window; see the roadmap at the bottom of this entry for the story.

Added

  • treeship verify <url-or-path-or-artifact-id> accepts three target shapes: HTTPS/HTTP URL fetched as receipt JSON, path to a local .treeship or .agent package directory, or a local artifact ID (the original v0.1 form). The URL and file paths produce the full checkmark-style output specified for the release. (item 1)
  • treeship verify --certificate <path-or-url> cross-verifies a receipt against an Agent Certificate. Pass or fail is a roll-up of three checks: ship IDs match, certificate is valid at verify time, every tool the session called is authorized by the certificate. (item 1 + item 2)
  • New exit codes on verify: 0 success, 1 verification failed, 2 cross-verification failed, 3 network or filesystem error. Documented in docs/cli/verify.mdx. (item 1)
  • Reusable library primitive treeship_core::verify::cross_verify_receipt_and_certificate(receipt, certificate, now_rfc3339) returning CrossVerifyResult with authorized / unauthorized / never-called tool lists, ship-ID status, and certificate validity. Explicit now argument keeps the function deterministic for testing and for future edge-runtime callers. (item 2)
  • schema_version field on Session Receipts and Agent Certificates. New documents emit "1"; documents without the field are treated as "0" (legacy) and verified under existing rules. Optional Option<String> with #[serde(skip_serializing_if = "Option::is_none")] so legacy documents round-trip byte-identical. Full semantics in docs/reference/schema.mdx. (item 3)
  • session.ship_id field on Session Receipts, parsed from the manifest's actor URI when it starts with ship://. Absent on pre-v0.9.0 receipts and on non-ship actors (human://alice, bare agent://). Cross-verification uses it to check receipt and certificate reference the same ship. (item 2)
  • treeship_core::artifacts module with five DSSE-signed command artifact schemas for supervisor → ship control-plane messaging: KillCommand, ApprovalDecision, MandateUpdate, BudgetUpdate, TerminateSession. Plus verify_command(envelope, &authorized_keys) helper. Ship as primitives in v0.9.0; CLI surfaces that issue and consume them ship in v0.10.0. (item 7)
  • treeship_core::agent::verify_certificate validates the embedded Ed25519 signature on an AgentCertificate against its embedded public key. Exposed as a public library API so the CLI, @treeship/verify (v0.9.1), and third parties share one implementation. (item 1)
  • treeship_core::agent::effective_schema_version helper resolves Option<String> to its effective string ("0" default). Use this over manual Option checks so the legacy default flows from one place.
  • New docs: cli/verify.mdx rewrite, concepts/cross-verification.mdx, concepts/command-artifacts.mdx, reference/schema.mdx. concepts/session-receipts.mdx updated to mention the new fields. (item 11)
  • Backwards-compatibility regression suite at packages/core/tests/legacy_receipt_fixtures.rs with synthesized + hand-curated v0.7.2 and v0.8.0 fixtures. Every future release must keep these fixtures verifying cleanly; if the schema changes in a way that breaks them, it must be documented here first. (item 9)

Changed

  • treeship verify dispatcher: URL-shaped and existing-path-shaped targets, or any invocation with --certificate, go through the new external path. Bare artifact IDs (including "last") fall through to the original local-storage verify path unchanged.
  • ArtifactEntry re-exported from treeship_core::session so downstream code can construct receipts without reaching into the package module.
  • Legacy Option<String> defaults on schema_version and session.ship_id are deliberately informational in v0.9.0: schema_version: "1" and "0" both select the same ruleset. Future versions that diverge will bump to "2" and move the field inside the signed payload. See reference/schema.mdx.

Explicitly deferred

Not hidden, not quiet. Each of these was in the original v0.9.0 draft; each got moved because shipping it with a cohesive announcement beats burying it in a release note. Three releases, three stories.

  • v0.9.1 — Runs everywhere. WASM migration of @treeship/sdk and @treeship/a2a from CLI subprocess to direct packages/core-wasm calls, and the new @treeship/verify standalone npm package (zero @treeship/sdk dependency, pure WASM, for Vercel Edge / Cloudflare Workers / AWS Lambda / browser). The @treeship/verify package uses the same cross_verify_receipt_and_certificate implementation that ships in v0.9.0 — no semantic drift.
  • v0.10.0 — Live management primitives. Approval-loop Hub endpoints (5 new routes), --require-approval flag on treeship wrap, treeship approver add / list / remove CLI. treeship.dev/verify browser-based drag-and-drop verifier (uses the WASM bundle from v0.9.1). The command artifact schemas are already in v0.9.0 (ApprovalDecision, etc.) so Witness can start consuming them before v0.10.0 lands.
  • v1.0. API stability guarantee, treeship upgrade self-update, additional platform support and polish.

Follow-ups in this release window

  • Comprehensive Codex adversarial review: v0.9.0 included a scoped review item that was deferred on shipping constraints. The v0.9.0 surface is tested (165+ unit/integration tests across the workspace, including every legacy fixture), but a formal adversarial pass on the URL fetch / certificate cross-verify / command-artifact code paths will be run before v0.9.1 cuts.
  • Page-by-page docs audit for feature status (AUTO / EXPLICIT / NOT YET CAPTURED) is partial. v0.9.0-specific pages are complete and the most-read concept page (session-receipts.mdx) is current; the remaining pages will be audited before v0.10.0.
  • Clean-room VM acceptance tests on macOS arm64 / macOS x64 / Linux x64 run out-of-band.

Rollback

Previous stable is v0.8.0 and remains published on every registry.

npm install -g treeship@0.8.0
npm install @treeship/sdk@0.8.0 @treeship/mcp@0.8.0 @treeship/a2a@0.8.0
cargo install treeship-cli@0.8.0 treeship-core@0.8.0
pip install treeship-sdk==0.8.0

No breaking wire-format changes between v0.8.0 and v0.9.0. A v0.9.0 verifier reads v0.8.0 receipts cleanly (regression suite enforces this). A v0.8.0 verifier reads v0.9.0 receipts cleanly as long as the new optional fields are ignored, which they are by default.

0.8.0 (2026-04-18)

Added

  • treeship add -- auto-detect and instrument installed agent frameworks (Claude Code, Cursor, Cline, Hermes, OpenClaw)
  • treeship quickstart -- guided interactive setup from zero to receipt in under 90 seconds
  • treeship agent register -- Agent Identity Certificate (.agent package with certificate.html)
  • treeship session event -- append structured events to the active session's event log (used by MCP/A2A bridges)
  • treeship session status --watch -- live terminal TUI showing agents, events, security, and verification progress
  • treeship declare -- create .treeship/declaration.json with tool authorization scope
  • TREESHIP_PROVIDER environment variable for provider attribution (anthropic, openrouter, bedrock)
  • Setup one-liner at treeship.dev/setup (installs, initializes, instruments agents)
  • Integration packages for Claude Code, Hermes, OpenClaw in integrations/ with skill files and MCP configs
  • TREESHIP.md universal skill file for any agent that reads markdown instructions
  • Production-quality preview.html: three-panel narrative, trust chain visual, agent cards, timeline grouping, retry detection, approval gates, honest empty states, sidebar IntersectionObserver, print stylesheet, copy buttons
  • Tool authorization in receipts: declared vs actual tool usage, unauthorized calls flagged
  • Self-contained Merkle verification in preview.html via Web Crypto API

Changed

  • treeship init output simplified to Ship ID + Key ID + next step hints
  • treeship wrap without active session shows warning with fix instructions
  • treeship session close auto-opens preview.html on macOS/Linux terminals
  • All error messages now tell the user what command to run to fix the issue
  • Root help text shows quick-start workflow first
  • MCP bridge (@treeship/mcp) now emits session events so tool calls appear in receipt timeline
  • Failed MCP tool calls are now audited (previously vanished from the audit trail)

Removed

  • TREESHIP_COST_USD environment variable and cost_usd field. Cost is a consumer concern (Witness dashboards, billing tools). Receipts store verifiable token usage only.
  • RELEASE_NOTES_NEXT.md

Fixed

  • Device code auth: full 16-char code displayed, hub accepts 8-char prefix for backward compat
  • Terminal escape injection in watch mode (sanitize all event fields)
  • Path traversal in agent register (name sanitized to alphanumeric + dash + underscore)
  • Case-insensitive script tag breakout in preview.html JSON escaping
  • Raw mode guard ensures terminal restoration on all exit paths
  • UTF-8 safe string truncation in TUI
  • Hub: device_code redacted from access logs, format validated before DB lookup
  • Hub: SQLite persistence reads DATABASE_PATH env var (Railway), consistent JSON error responses, session ID length cap, rate limiting

Security

  • 15+ findings from four rounds of Codex adversarial review, all addressed
  • Atomic first-write ownership + write-once receipts on Hub
  • 10 MB body-size limit on receipt upload
  • Honest verification language ("Merkle structure verified", not "Verified")

0.7.2 (2026-04-15)

Session Receipt: production-quality preview.html

  • Self-contained verifier in preview.html: Merkle root recomputation, inclusion proof verification, and timeline ordering checks run client-side via Web Crypto API. Works air-gapped, zero network calls.
  • Production design overhaul: three-panel narrative (planned/done/review), trust chain visual, agent cards with cost bars, command cards with retry detection, timeline grouped by agent, sidebar with IntersectionObserver, print stylesheet, copy buttons, mobile collapse.
  • Honest empty states: grey "not captured" for unmeasured data, green confirmations only for things actually measured.
  • Security hardening: XSS prevention via \u003c escaping, numeric coercion via num() helper, honest "Merkle structure verified" language (not "Verified").

MCP bridge: session event wiring

  • treeship session event CLI command: append structured events to the active session's event log. Used by MCP bridge, A2A bridge, and SDKs.
  • @treeship/mcp now emits agent.called_tool session events after each tool call so MCP tool usage appears in the receipt timeline, agent graph, and side effects.
  • Failed MCP tool calls are now audited (previously vanished from the audit trail).

Agent instrumentation

  • TREESHIP_MODEL, TREESHIP_TOKENS_IN, TREESHIP_TOKENS_OUT, TREESHIP_COST_USD environment variables: set these before treeship wrap to capture model, token counts, and cost in the receipt.
  • treeship declare CLI command: create .treeship/declaration.json with bounded_actions, forbidden, escalation_required. Receipt compares declared vs actual tool usage and flags unauthorized calls.
  • File operation type detection: wrap now distinguishes created, modified, and deleted files.
  • ZK proof detection: zk_proofs_present is set automatically when proof files exist for the session.
  • Approval gates shown in preview.html when approval artifacts are present.

Hub hardening

  • SQLite persistence: reads DATABASE_PATH env var (Railway), persistent default at /var/lib/treeship/hub.db.
  • Consistent JSON error responses across all endpoints.
  • Session ID length cap (128 chars).
  • Rate limiting via chi Throttle middleware.
  • Write-once receipts with RowsAffected check on conditional update.
  • Crash-safe session close with session.closing recovery marker.
  • Case-insensitive log redaction for session query parameters.

0.7.1 (2026-04-09)

Security fixes (from Codex adversarial review)

  • Store full 256-bit SHA-256 Merkle root in receipts instead of truncated 64-bit prefix. Prior receipts should be regenerated.
  • Atomic first-write ownership on PUT /v1/receipt/{session_id}: dock_id is never overwritten on conflict, eliminating the race between two docks.
  • Write-once receipt semantics: once a receipt is uploaded for a session_id, it cannot be replaced (byte-identical replays are accepted for retry safety). The immutable cache header is now honest.
  • 10 MB body-size limit on receipt upload to prevent memory-DoS from authenticated docks.
  • Daemon emits read events even when mtime also advances, preventing touch after a secret read from suppressing the on: access alert.
  • Session close deletes session.json before composing the receipt to prevent late daemon events from landing in the log but not the receipt.
  • treeship session report selects the most recently closed session by session.ended_at inside the receipt, not filesystem mtime.
  • Log redaction matches the session query parameter case-insensitively.

0.7.0 (2026-04-09)

Session Receipts

  • New treeship_core::session module: event model, manifest, context propagation, agent graph, side effects, append-only event log, canonical receipt composer with Merkle root
  • .treeship package format: deterministic receipt.json + merkle.json + render.json + per-artifact inclusion proofs + static preview.html
  • treeship session close now composes a Session Receipt v1 and writes a .treeship package under .treeship/sessions/
  • treeship package inspect and treeship package verify for offline inspection and local verification (no hub required)
  • treeship session report uploads a closed session's receipt to the configured hub and prints the permanent public URL

Hub: public receipt endpoints

  • PUT /v1/receipt/{session_id} (DPoP-authenticated): idempotent upload, rejects cross-dock overwrites, refreshes per-ship agent registry from the receipt's agent graph
  • GET /v1/receipt/{session_id} (public, no auth): returns 200 + raw receipt JSON, 403 "session still open" if the row exists without a receipt, 404 if not found. Permanent URL, immutable cache
  • GET /v1/ship/agents and GET /v1/ship/sessions: per-ship registry endpoints for dashboards and A2A clients
  • New sessions and ship_agents tables with composite keys scoped per dock

Hub: workspace share tokens

  • POST /v1/session (DPoP-authenticated): mints a short-lived opaque token bound to a dock_id at mint time
  • New auth.ResolveReader helper: read endpoints accept either DPoP or ?session=TOKEN, fails closed on expired tokens
  • treeship hub open mints a share token and opens a browser URL that does not require a private key on the client
  • Access logs now redact session query parameters to prevent tokens from landing in stdout

Sensitive file read detection

  • Daemon now tracks both mtime and atime per file; a SnapshotDiff separates writes from reads
  • Sensitive-file pass walks dotfiles at the project root and one level into .aws, .ssh, .gnupg, .docker, .kube
  • When a file matching an on: access rule has its atime advance, the daemon emits an agent.read_file event to the active session's event log with capture_confidence: "inferred" and writes an ALERT line if the rule has alert: true
  • Closes the file-read capture gap that left .env, *.pem, and .ssh/* access invisible in prior releases

A2A Integration

  • New package: @treeship/a2a, framework-agnostic Treeship middleware for A2A (Agent2Agent) servers and clients
  • TreeshipA2AMiddleware with onTaskReceived (awaited intent), onTaskCompleted (chained receipt), onHandoff, and decorateArtifact
  • buildAgentCard, hasTreeshipExtension, getTreeshipExtension, fetchAgentCard for AgentCard discovery + extension publishing
  • verifyReceipt and verifyArtifact for pre-delegation trust checks at line speed
  • Canonical extension URI: treeship.dev/extensions/attestation/v1
  • Zero runtime dependencies; never throws; CLI-missing path prints one actionable warning per process
  • 15 vitest tests covering middleware, AgentCard helpers, CLI-missing handling, and TREESHIP_DISABLE=1 short-circuit
  • Docs: docs/integrations/a2a.mdx (Mintlify) and treeship/docs/content/docs/integrations/a2a.mdx (Fumadocs)
  • Blog post: "A2A Makes Agents Interoperable. Treeship Makes That Interoperability Trustworthy."
  • Release pipeline: bridges/a2a wired into scripts/release.sh and .github/workflows/release.yml

Python SDK

  • Treeship.session_report(session_id=None) returns a SessionReportResult with the permanent receipt URL, agent count, and event count
  • Defaults to the most recently closed session when no session_id is given

0.5.0 (2026-04-04)

Zero-Knowledge Proofs

  • Circom Groth16 proofs: 3 circuits (policy-checker, input-output-binding, prompt-template)
  • Trusted setup complete with Hermez powers-of-tau ceremony
  • Real Groth16 WASM verification via ark-groth16 pairing math
  • Verification keys embedded in WASM binary at compile time
  • treeship prove --circuit, treeship verify-proof, treeship zk-status commands
  • Auto-prove on declaration (when bounded_actions configured)
  • Feature-flagged: --features zk (default build has zero ZK deps)

RISC Zero Chain Proofs

  • Guest program compiled for riscv32im target via rzup
  • Real receipt-based proving and verification
  • Background daemon proof queue with lock file safety
  • Composite checkpoint: Merkle root + ChainProofSummary
  • treeship prove-chain command
  • Bonsai detection via BONSAI_API_KEY (local CPU default)

Trust Model

  • Documented Hermez ceremony trust assumption
  • Bonsai marked as opt-in only (API key = consent)
  • Offline verification documented for all proof types

Release Pipeline

  • npm: Pure OIDC via trusted publisher (no token)
  • crates.io: ZK deps stripped for publish (full ZK via git install)
  • All packages at 0.5.0 across npm, crates.io, PyPI

0.4.0

  • Terminology: dock -> hub, login -> attach, logout -> detach, rm -> kill, workspace -> open
  • Config: docks -> hub_connections, active_dock -> active_hub, dock_id -> hub_id
  • New hub ID prefix: hub_ (backward compat with dck_)
  • serde aliases for backward-compatible config deserialization
  • All docs updated with new terminology
  • New concept pages: ships, hub connections

0.3.1

  • Fix: Remove print statement causing JSONDecodeError in synthetic_media_detector workflow
  • Minor stability improvements

0.3.0

  • Wrap command captures output digest, file changes, and git state
  • Trust templates: 7 official templates (github-contributor, ci-cd, mcp-agent, claude-code, openclaw, hermes, research)
  • Shell hooks for automatic attestation
  • Background daemon for file watching
  • Doctor diagnostic (9 checks)

0.2.1

  • Hotfix for encrypted keystore path resolution on Linux
  • Improved error messages for missing keys

0.1.0 (2026-03-31)

Initial release.

Core

  • DSSE envelope signing with Ed25519 (ed25519-dalek, NCC audited)
  • 6 statement types: action, approval, handoff, endorsement, receipt, decision
  • Encrypted keystore (AES-256-CTR + HMAC, machine-bound)
  • Content-addressed artifact IDs from PAE bytes
  • Rules engine with YAML config and command pattern matching
  • Merkle tree with checkpoints, inclusion proofs, offline verification
  • 120+ tests

CLI

  • 30+ commands: init, wrap, attest, verify, session, approve, hub, merkle, ui, otel
  • Rich wrap receipts: output digest, file changes, git state, auto-chaining
  • Shell hooks for automatic attestation
  • Trust templates (7 official: github-contributor, ci-cd, mcp-agent, claude-code, openclaw, hermes, research)
  • Interactive TUI dashboard (Ratatui)
  • OpenTelemetry export (feature-flagged)
  • Background daemon for file watching
  • Doctor diagnostic (9 checks)

Hub

  • Go HTTP server with 12 API endpoints
  • Device flow authentication with DPoP
  • Artifact push/pull with Rekor anchoring
  • Merkle checkpoint storage and proof serving
  • CORS for treeship.dev

SDKs

  • @treeship/sdk (TypeScript, npm)
  • @treeship/mcp (MCP bridge, npm)
  • treeship-sdk (Python, PyPI)
  • treeship-core, treeship-cli (Rust, crates.io)
  • npm binary wrapper (treeship, platform packages)

Website

  • treeship.dev: landing page, /verify, /merkle, /connect, /hub/activate, /open
  • docs.treeship.dev: 67 pages (Fumadocs), search, VS Code theme

Security

  • PID file locking, file permissions (0600/0700)
  • Command sanitization (redact secrets)
  • Untrusted config detection
  • Shell hook absolute path (prevent PATH hijacking)
  • DPoP (no stored session tokens)

On this page

Unreleased0.20.0 (2026-07-12)AddedChangedDocs0.19.1 (2026-07-11)AddedFixed0.19.0 (2026-07-09)Changed (breaking)SecurityAddedChanged0.18.0 (2026-07-07)AddedFixedFixed0.17.1 (2026-07-06)Fixed0.17.0 (2026-07-06)AddedAddedFixed0.16.0 (2026-07-06)AddedFixed0.15.0 (2026-06-26)AddedFixed0.14.0 (2026-06-25)Added0.13.0 (2026-06-24)Added0.12.0 (2026-06-06)AddedFixed0.11.2 (2026-06-05)Fixed0.11.1 (2026-05-29)AddedFixed0.11.0 (2026-05-28)AddedChanged (breaking)Documentation0.10.4 (2026-05-17)SecurityChanged (breaking)FixedInternalDocumentation0.10.3 (2026-05-17)SecurityAddedChangedChanged (breaking)FixedInternalMigration notes0.10.2 (2026-05-11)Fixed (model+provider attribution on the signed-artifact path — #75)Added (Treeship Agent Skills — #67)Compatibility0.10.1 (2026-05-01)Added (real @treeship/mcp stdio server — PR 0)Added (keystore permission hardening — PR 1)Added (Python SDK correctness + input validation — PR 2)Added (CLI config / init / json correctness — PR 3)Added (npm binary integrity — PR 4)Added (Linux musl + distro smoke gate + honest install docs — PR 5)Honesty rule preservedVerification (release-gate)Intentionally NOT included0.10.0 (2026-04-30)Added (server-rendered receipt page — treeship.dev PR 1)Added (stable agent-native JSON contract — treeship.dev PR 2)Added (downloadable .treeship package — treeship.dev PR 3)Added (CLI share command — treeship PR 4)Added (Python SDK + CLI bootstrap — treeship PR 5)Verification (release-gate T1–T7 smoke)Honesty rule preservedIntentionally NOT includedMigration0.9.11 (2026-04-30)Added (docs reliability)Added (CLI reference catch-up)Added (SDK + install topology)Added (process)Intentionally deferred to a later releaseVerification0.9.10 (2026-04-30)Fixed (Approval Authority blockers — #57)Fixed (label hygiene — #57)Added (release-adversarial cadence policy)TestsKnown follow-ups (non-blocking, deferred)Intentionally NOT changed0.9.9 (2026-04-30)Added (Approval evidence schemas — #50)Added (Local Approval Use Journal — #51)Added (consume-before-action + local-journal replay check — #52)Added (package export + offline replay verify — #53)Added (Approval Authority panel + Decision Cards v0 + first-class docs — #54)Added (Hub-signed checkpoint consumer-side verifier — #55)Changed (trust semantics)Intentionally deferred to a later releaseAdded (discovery + agent inventory)Added (setup orchestration)Added (Harness Manager)Added (integration template profile table)Added (session report polish)Added (project-local config discovery and CLI ergonomics)Changed (trust semantics)Intentionally deferred to v0.9.9 / later0.9.7 (2026-04-29)Added (release machinery)Added (CLI ergonomics)Changed (release tooling)Fixed0.9.6 (2026-04-27)Added (approval grant model)Changed (verification surface)Added (capture layer)Added (cross-verification layer)Added (model + provider attribution)Changed (trust gates)Changed (security)FixedAdded (integrations)Added (docs)Notes0.9.5 (2026-04-25)AddedChangedChanged (BREAKING for SDK consumers)Added (testing)FixedAdded (docs surface)NotesKnown limitations0.9.4 (2026-04-21)AddedFixedNotesKnown limitations0.9.3 (2026-04-20)AddedChangedFixed (post-Codex-review, two passes)NotesKnown limitations0.9.2 (2026-04-20)Release workflow hardeningContents of this releaseAddedChangedNotesRelease-window follow-upsNot in this release (coming in v0.10.0)Rollback0.9.1 (2026-04-18)AddedChangedNotesRelease-window follow-upsNot in this release (coming in v0.10.0)Rollback0.9.0 (2026-04-18)AddedChangedExplicitly deferredFollow-ups in this release windowRollback0.8.0 (2026-04-18)AddedChangedRemovedFixedSecurity0.7.2 (2026-04-15)Session Receipt: production-quality preview.htmlMCP bridge: session event wiringAgent instrumentationHub hardening0.7.1 (2026-04-09)Security fixes (from Codex adversarial review)0.7.0 (2026-04-09)Session ReceiptsHub: public receipt endpointsHub: workspace share tokensSensitive file read detectionA2A IntegrationPython SDK0.5.0 (2026-04-04)Zero-Knowledge ProofsRISC Zero Chain ProofsTrust ModelRelease Pipeline0.4.00.3.10.3.00.2.10.1.0 (2026-03-31)CoreCLIHubSDKsWebsiteSecurity