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-setupandzk-tls-setupscaffold the SRI-identified building blocks. Thezkfeature 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 <len> <payloadType> <len> <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 jsonpipes the triple straight to a partner. The message is reconstructed from the samepayload_type+payloadthe 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 withtreeship verify).scripts/verify-receipt.py— the reference verifier. Dependency-light, independent Ed25519, offline:treeship receipt export <id> --format json | python3 scripts/verify-receipt.py→VALID. 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-onreceipt_body_bindingscope caveat added in 0.19.0 is informational (every package carries it), but it was flipping every cryptographically-clean session's top-line verdict towarn, makingpassunreachable and breaking any caller that gates onstatus == "pass". It is now surfaced inwarningsfor 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@lateston the runner's Node 24 shipped a build whose bundledsigstoremodule 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 ishttps://treeship.dev/verify/<id>(the hub's ownhub_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
shiptrust-root kind is split intohub_org/cert_issuer/revoker. A single--kind shippin 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 honorsshipany more, andtreeship trust add --kind shipis rejected with guidance to the three new kinds. Existingtrust.jsonfiles still load (the deprecatedshipkind stays parseable but inert); a legacyshippin 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 exportnow emits the new kinds. Migration:treeship trust add <key_id> <pubkey> --kind cert_issuer(orhub_org/revoker).
Security
treeship verifyon 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." (JSONoutcome: "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 JSONoutcomeisstructural-pass(neverpass) withsignatures_verified: false, and an empty receipt is afail. The same fix lands on the WASMverify_receiptsurface.- The hub DPoP signing key is encrypted at rest. It was written to
config.jsonas 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 stolenconfig.jsonis useless on another machine. - The browser/WASM
verify_capabilitynow actually verifies signatures. It reported a card as key-bound / "verified" from the unverifiedsignatures[0].keyidstring (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.
PublishCheckpointstored 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-usesigner → public_keybinding. - 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 exfiltratedkeys/*.jsonwas forgeable by anyone who knew your hostname. It now derives from a 32-byte OS-CSPRNGmachine_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 verifycryptographically checks chain linkage. Every core verifier now usesverify_strict(rejecting signature malleability, small-order keys, and cross-SDK split-view; previously onlypresent.rsdid). And the chain walk now cross-checks each child's signedparentIdagainst the walked parent instead of trusting the unsignedparent_idstorage metadata, exiting non-zero withSIGNED LINKAGE BROKENon 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.toolcan 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 unverifiedsignatures[0].keyid; and provenance grades read off a card that is not key-bound are markedSELF-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 onextraconstraints it cannot evaluate).- The trust-ladder can no longer be laundered. A self-declared
attestation_classon a work-history record was tallied verbatim, so a hand-signedsession.v1withattestation_class: "countersigned"laundered into the highest trust bucket.session.v1can no longer be minted through the generic attest path, the predicate validator now enforcesenum/const, andprofile/historycap 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 closenow stamps areconcile_degradedmarker when the git backstop was disabled mid-session, andpackage verifywarns 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_rngtoOsRng, with sub-128-bit nonces widened to 128. The A2AverifyReceiptno longer returnsshipId/withinDeclaredBoundscomputed from the raw fetched JSON when the receipt did not verify (and no longer defaultswithinDeclaredBoundstotruewhen 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_idis taken from the signature that actually verified (notsignatures.first());format_device_codeno longer panics on a multibyte device code; an unreadable file's digest is recorded asnullrather 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 doespackage verifyimply 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 unverifiedartifact_idas bound. Thezkfeature 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 indexestools_exercisedfrom the signedsession.v1records 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 asunverifiedrather 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 --helpnow 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 --alllists 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 npmpackage-lock.jsonfiles 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 justtreeship-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 areasserteduntil checked. Every number is a deterministic aggregation over the log's firsttree_sizeleaves at a pinned checkpoint whose root travels in the payload;verify-profilerebuilds 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 profilechecked; mismatch is a provable lie, named field by field (nonzero exit, structured JSON).--attestsigns 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-verifiescheckedeven 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 signedsession.v1records — 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 jsonemits 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
statusnow shows WHICH store it resolved. Store discovery walks up from the working directory and silently switches between project-local and global.treeshipconfig — previously visible only indoctor, 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).statusprintsstore: project-local -- <path>in text mode and astoreobject 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/artifactsand allPOST /v1/merkle/*are now capped at 10 MB (the receipts handler always was; the other four buffered unbounded uploads into memory). (2) New indexesartifacts(payload_type, signed_at)andartifacts(dock_id)— the public, unauthenticatedGET /v1/agentsand/v1/agents/logendpoints were full-table scans that decode every receipt row per request and only get slower as the log grows. (3)PRAGMA foreign_keys=ON: everyREFERENCESclause 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 —InsertCheckpointis idempotent on its natural key (signer, root, tree_size) and returns the original row's id, matching the artifact/consistency insert discipline. (5) The publicGET /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 runstreeship verifythemselves). 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.jsonadvertised 0.9.5 — eight releases stale — because it was covered by neitherrelease.shnor 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 preparenow 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.mdno 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 livingvision.md; the report skill'streeship package exportinstruction (a command that does not exist) now describes the real portable-package flow; the Kimi skill'scargo install treeship-cli(an orphaned 0.4.0 stub the rest of the docs explicitly warn against) now saysnpm install -g treeship; and stale@v0.10.0skill-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 publishandmerkle proofcomputed 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 readinclusion INVALIDto every auditor. Both commands now rebuild the tree truncated to exactly the checkpoint'stree_sizeand cross-check the rebuilt root against the checkpoint's before emitting anything (the same rulemerkle publish's consistency proofs andpresentalready applied), andmerkle proofgains 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 reported0/0 inclusion proofs passedas a green check — a check that ran nothing now reports a warning, not a pass. hub attachprobes 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/a2amiddleware invokedattest handoffwith 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 inmeta), 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, andverify-capabilitycomputed 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 (includingverify-presentation) now exit nonzero on a hostile verdict.audit --watchdeliberately 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; previouslyresolve/verify-capabilityreturned a bare success envelope,verify-presentationreturned essentially nothing (its verdict lines went through info, which JSON mode suppresses), andauditstreamed 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 runsverify-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), statusverified (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;presentgives 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-presentationre-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 asresolve --hub), authorized revocations honored, staple checkpoint signature + inclusion re-checked, and freshness reported as an explicit bound —revocation: none included — current as of the staple (9m old)— with--max-staple-age 30s|15m|2h|1dturning 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 reportsUNVERIFIED(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--publishthe fullpublish+merkle checkpoint+merkle publishanchor — into one idempotent pass, and ends by printing the trust bundle: the exacttrust addcommand(s) a counterparty runs (agent_certfor the agent key,hub_checkpointfor the ship key) plus theresolve/auditcommands that verify the agent. One name in, one URI out:onboard deployerandonboard agent://deployerare 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-keynow also mints the protocol-native certificate — a typedagent_cert.v1receipt whose envelope signature by the ship key bindsagent://<name>to its per-agent public key (idempotent per agent+key; the.agentpackage cert remains the human-portable rendering).treeship publishpushes the cert chain with the resolvable set, the Hub serves it verbatim in a newcertsfield, andresolve --hubwalks the chain when the leaf key is not directly pinned: cert envelope verified against a pinnedShiproot (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 readverified (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_idhashed(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 whenfable-scout's registration destroyedfable-code's. The name is now part of the id hash, andregistered_key_for_actorpicks 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.
InsertArtifactwas a bareINSERT, so re-publishing an agent's resolvable set (exactly what an idempotentonboard --publishon every agent boot does) hit theartifact_idprimary 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 nowON 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 pinnableed25519:<base64url>form together with the exacttreeship trust addcommand(s) the counterparty runs:--kind agent_certfor a per-agent key (--agent agent://deployer),--kind ship+--kind hub_checkpointfor the ship key — a remote verifier needs both to getverifiedon resolve andanchored & verifiedon 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 jsonfor scripts.session.v1— every closed session becomes a typed, signed work-history record (work-history slice 1).treeship session closenow mints asession.v1receipt into the agent's chain alongside the sealed package: headline, outcome, duration,tools_exercised(computed from captured tool usage, never hand-written), counts, and areceipt_digestbinding 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 (anchoredis a later, derivable state). The record is validated against the registeredsession.v1predicate 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
resolveno 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 underhub_checkpointin your trust roots — but those demand opposite reactions (distrust the hub vs pin a root), and reporting the second as "INVALID" is a mislabel.resolvenow distinguishes them: an unpinned signer reportscheckpoint signer not in your trust rootswith the ready-to-runtrust add … --kind hub_checkpointcommand (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 reportsINVALID.- 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 renameskern.hostname(network name collisions, DHCP), after which the keystore failed with a misleadingMAC verification failed — wrong machineand 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-idon Linux,IOPlatformSerialNumberon 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 underscutil LocalHostNamevariants, which typically retain the name the store was written under afterkern.hostnamedrifts 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_matchesonly expanded a trailing*, but the patternsattest card --from-harnesscaptures from a Claude Codesettings.jsoncarry 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), soBash(git:*)matchesBash(git:status)and still rejectsBash(gh:pr); the falsifiable cross-check keeps flagging genuinely off-card actions (payments.charge). Trailing-glob (family.*), bare-*, and exact matching are unchanged. Sharedtreeship_core::capabilityfix, 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/mcpbridge provisions a per-agent signing key for its actor (agent register --own-key --quiet), so theattest action --actor <agent>calls it already makes sign with that agent's own key. Receipts now verify asactor proof: proven (key-bound)instead ofasserted, with no change to the attestation path, per-actor signing already did the work once the key exists. Best-effort and idempotent: iftreeshipis 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-keyis now idempotent, and a new--quietflag skips the on-disk.agentpackage. 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--quietproduces the card + key + pin without dropping a.agentdirectory into the working directory. Both exist so programmatic callers (the MCP/A2A bridges, which register on every startup) can provision identity safely and quietly; interactiveregisteris unchanged.treeship attest card --tools-json <path>— operator-declared capability sets (protocol-integration slice 2). The runtime companion to--from-harness: where--from-harnesscaptures tools observed in a config (gradedcaptured),--tools-jsonrecords an operator's explicit declaration from a JSON file (a["tool", ...]array, or{ "tools": [...] }). Each entry is stampeddeclaredincapability_provenancewith the file as itssource, 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 strongercapturedgrade. This is the deliberate slice-2 choice: the@treeship/mcpbridge 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 publishedAgentCard.skillstoagent_card.v1capabilities, stamped with a newdiscoveredprovenance grade (read from the agent's own descriptor) and the AgentCard'surlassource.discoveredis a real grade, distinct fromcaptured(harness) anddeclared(operator), weaker than receipt-exercised, andverify-capability/resolvecount it in its own bucket so A2A skills are never mislabeled as operator-declared. Protocol-levelcapabilities(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/a2amiddleware 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 asproven (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 auditnow 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: aconsistency:line reports monotonic growth, and audit warns on a forking or regressing hub, two different roots at the sametree_size(equivocation), or atree_sizethat 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 auditnow 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 publishcomputes 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 viaGET /v1/merkle/consistency.auditfetches the chain and re-verifies every link offline against the shippedverify_consistencyprimitive, 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 reportsappend-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 misleadingMAC verification failed — wrong machineerror on a perfectly valid keystore.Store::opennow 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)andverify_consistency(version, old_size, old_root, new_size, new_root, proof): prove (and verify offline) that a later tree extends an earlier one, the firstmleaves 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 againstcompute_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 committedevidence_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 latestcommitted_anchor(the card'sevidence_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 authenticatedhub pushpath, so anyone canresolve --hubit 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/agentsnow includes the current card's Merkle inclusion proof when it has been anchored, andtreeship resolve --hubre-verifies it offline (the checkpoint signature against your trust roots + inclusion in the signed root), reportingtransparency: anchored & verified (checkpoint #N)ornot 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). Seeagent-resolverspec. -
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). Reportssignature: verified | UNVERIFIED,key-bound, revocation status, and the card's captured-capability mix. Theexercisedgrade 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-capabilitynow grades each declared capability by its provenance —captured(read off the card's optionalcapability_provenanceblock, set from harness config at mint time),exercised(backed by captured action receipts, computed from the cross-check), ordeclared-only(asserted, never wired or used) — and reports the breakdown.treeship resolvesurfaces the same breakdown, so a resolved agent's capability mix travels with it. A new optionalcapability_provenancefield onagent_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. Seecapability-provenancespec. -
treeship attest card --from-harness <path>. Builds a card's capability set from the agent's real harness config (a Claude Codesettings.json'spermissions.allowlist) instead of a hand-typed--toolsstring, and stamps each entrycapturedwith itssource. Deterministic capture at the source: the capability set comes from observed config, not the operator or the model.--toolsentries (if also given) are merged asdeclared.
0.13.0 (2026-06-24)
Added
- Predicate registry: typed, schema-validated receipt payloads. Registered
kindsuffixes 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. Seereference/predicates. - Agent capability cards (
agent_card.v1).treeship attest cardmints 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 itskeyidis the envelope signer pinned underAgentCert; otherwise it is reportedself-asserted. The cross-check reports in-scope / out-of-scope actions (exact orfamily.*glob) plus an optionalevidence_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-keymints a dedicated per-agent key, certifies it (the ship signs as issuer), and pins it underAgentCert.treeship attest action/attest card/attest decision/attest handoffthen sign with the agent's own key when the actor has one, so theactoris cryptographically provable rather than an asserted label.treeship verifyreportsactor 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-capabilityhonors it only when its signer is authorized (the card's own key, or aShiptrust root) and then reportsstatus: 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 typedverifyCapability()in@treeship/verifylet 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 sharedtreeship_core::capabilitymodule 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. Seeagent-resolverspec. (#141)
0.12.0 (2026-06-06)
Added
treeship attest receipt --payload-fileand--payload-digestfor external proof receipts. Providers can now sign a receipt over a JSON payload read from a file (mutually exclusive with--payload) and record apayload_digestfor large or out-of-band payloads.treeship attest actiongains--output-digest, folded into the action'smeta. The flags are provider-neutral:--systemand--kindaccept any value. New docs describe the provider contract atintegrations/memory-proofs(usable by ZMem, Mem0, Zep, Letta, a SQLite store, or an in-house layer) andconcepts/multi-agent-sessions. ZMem is one worked example, not built-in behavior.- Explicit Hub device-flow states.
GET /v1/dock/authorizedandPOST /v1/dock/authorizenow return a stablestatefield coveringinvalid,expired,pending,approved,attached, andalready_attached, plus provider-neutralnext_stepson attach.treeship hub attachreports distinct poll errors (404 / 410 / 409) and concrete next steps (status,attest receipt,hub push,verify). Newpackages/hub/internal/dock/dock_test.gocovers 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/authorizedafter the CLI finalized received a 404 that rendered as "device_code not found" even though attach had succeeded. Finalization now atomically claims the challenge bydock_id(preserving the single-use guarantee) and retains the row behind a bounded grace window, so the activation page reads the terminalattachedstate. DPoP and Hub auth invariants are unchanged. An idempotentALTER TABLEmigration adds thedock_idcolumn to existing Hub databases. (#119)
0.11.2 (2026-06-05)
Fixed
- Receipt preview no longer hangs on "Verifying receipt...".
render_preview_htmlfilled 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 localtreeship dashboardSession 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, andrender_preview_htmlfills only the first (data-slot) occurrence. Covered by a regression assertion insession::packagetests. (#118)
0.11.1 (2026-05-29)
Added
treeship session abandonfor 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 startnow refuses to start when the git toplevel is the user's home directory unless--allow-dangerous-rootis passed.
Fixed
- Bounded close-time git reconciliation.
treeship session closeno 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 reportnow distinguishes an incomplete.treeshippackage missingreceipt.jsonfrom "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/v1andtreeship/session-participant/v1-- both underpackages/core/src/statements/. Invitations carrysession_ref,issuer(host pubkey),invitee_restriction(one ofPubkey { fingerprint },Cert { issuer_pubkey, allowed_subjects }, orOpen),granted_capabilities.action_types,expires_at,max_uses(always 1 in Phase 1), and a randomnonce. 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 intosha256:<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_enveloperejectsMissingHostCountersign,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 ininvitation.rs+ 6 insession_participant.rspin 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 jsonreturns{ invitation_id, bootstrap_blob, session_ref, expires_at }). Restriction selection is explicit: exactly one of--invitee-cert,--invitee-pubkey, or--openis 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 againstSessionHosttrust roots, verifies the envelope signature, checks expiry + restriction, writes the invitation's nonce into the Approval Use Journal withmax_uses=1(so a second join attempt fails with the journal's existingMaxUsesExceededpath), 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 inpackages/cli/tests/invitation_cli.rsexercise the mint -> join -> countersign roundtrip, the no-session-context error path, and the pending-countersign on-disk shape. SessionHosttrust root kind added toTrustRootKindinpackages/core/src/trust/mod.rs. The invitation issuer pubkey embedded in everyInvitationStatementmust be pinned under this kind beforetreeship session joinhonors 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 --yesregisters a new session host. Separate fromShipso 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.treeshipsession 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)
TrustRootKindenum gained theSessionHostvariant. Exhaustivematcharms onTrustRootKindin third-party code MUST add the new arm. The on-disk JSON wire format gains"session_host"as a permittedkindvalue (treeship/trust_roots.jsonv1 schema). No existing roots are migrated; operators add session-host pins explicitly viatreeship trust add. Thetreeship trust add --kindclap value parser was widened to includesession_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.mdis the single source of truth for code-quality expectations across human and AI contributors;CONTRIBUTING.md,CLAUDE.md,AGENTS.md, and thetreeship-devcontributor skill (bothskills/treeship-devandplugins/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: silentunwrap_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.ymlis 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 atscripts/check-feature-inventory.pywalks every entry and warns when a listed CLI command isn't inpackages/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 Accessgh apirecipes specific to Perplexity's tool environment) and the Kimi-formatintegrations/kimi-code-plugin/treeship-user/skill bundle (SKILL.md+ executableattest_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.mdindex. 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 tointegrations/agent-skills/README.md(install commands) andintegrations/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 publishestreeship-core+treeship-cli+treeship-core-wasm(not justtreeship-core), and pinned trust roots have been required for hub-checkpoint and agent-certificate verification since v0.10.3.skills/treeship/SKILL.mdandintegrations/hermes/README.mdupdated 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.jsonfor public discovery. Thedocs/content/docs/integrations/install.mdxpage is generated from this source viadocs/scripts/sync-agents-matrix.mjsso the install matrix never drifts from the source of truth. Closes the brokenintegrations/agents.jsoncross-reference from the v0.10.4 skills README PR (#106). Pairs with the discovery direction recommended indocs/specs/workflow-declarations.md(separate PR). - Integration parity test harness.
integrations/tests/parity.shruns the claude-code-plugin and kimi-code-plugin hook scripts against shared fixtures and asserts both produce equivalent Treeship CLI invocations. Fixtures inintegrations/tests/fixtures/(hook input JSON) and expectations inintegrations/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_toolvsagent.called_tool). - Kimi plugin scripts and config template tracked.
integrations/kimi-code-plugin/scripts/{pre,post}-tool-use.shandsession-{start,end}.sh, plusconfig.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()incanonical_hub_signing_bytes,approval_use_record_digest,approval_revocation_record_digest, andjournal_checkpoint_record_digestwould silently sign or verify againstVec<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). Ed25519Signernow zeroizes its secret key on Drop. The signer wraps an ed25519-dalekSigningKey; 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 implementsZeroize+Drop+ZeroizeOnDrop; the innerSigningKeyalso implementsZeroizeOnDropvia ed25519-dalek'szeroizefeature (newly enabled), so secrets are wiped at both layers.Ed25519Signer::secret_bytesnow returnsZeroizing<[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 usesOsRngdirectly. The few remainingthread_rngsites 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 viacheck_key_file_perms(path)to verify mode bits, then a second time viaload_entry(id)/fs::read(path)to pull the encrypted scalar. An attacker with write access to~/.treeship/keys/could swap<id>.jsonbetween 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 newread_entry_with_perm_checkhelper opens the file exactly once, runsFile::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 tosession/event_log.rs:351-354in v0.10.3 (audit lane F). TheTREESHIP_ALLOW_INSECURE_KEY_PERMS=1bypass 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.jsonis 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 viacheck_open_trust_file_perms, and reads the JSON from the same fd. Same bypass semantics as the keystore; the one-timeWARN_INSECURE_PERMS_ONCEstderr 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}"wherezk_proof_digestis the hex-encoded SHA-256 of the sorted-key canonical JSON of theChainProofSummary(empty string forNone). v3 closes a second-tier downgrade where an attacker could take a legitimately v2-signed checkpoint, mutatealgorithmor attach/swap/stripzk_proofon the wire, and have verification still pass. Thecanonical_versionbyte is itself bound into v3 to prevent a downgrade-by-relabel attack: flippingcanonical_version: 3 → 2on 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)] = 2on the newcanonical_versionfield). 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/verifyis a thin WASM shim overcore-wasmand inherits the change automatically; no consumer action needed there. Programs usingtreeship 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_signingsignature changed to takecanonical_version,algorithm: Option<&str>, andzk_proof: Option<&ChainProofSummary>arguments. The fn ispub(crate)so external callers should not be affected; the WASM shim and CLI verify paths go throughCheckpoint::verifywhich handles dispatch internally.- New
canonical_version: u8field onCheckpointwith#[serde(default)] = 2so v0.10.3-era checkpoints (which never carried this field) deserialize as v2 and continue to verify. - The
algorithmfield onCheckpointis now informationally redundant withmerkle_versionbut 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 proveandtreeship verify --external(prove.rs:127,verify_external.rs:224). The twounreachable!()calls were reachable via clap-parsed input; replaced with explicitErrreturns 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 anapi.on()registration method; the plugin now registers under BOTH the snake_case + camelCase names AND via bothregisterHook+api.on(), so events are captured regardless of which hook surface OpenClaw uses at runtime. (2) Event types:before_tool_callnow emitsagent.called_toolwithphase=intentmetadata (not the unsupportedagent.requested_tool); the bash handler emitsagent.called_toolwithcommandmetadata (not the unsupportedagent.ran_shell); the network handler emitsagent.connected_networkwith--destination. (3) Type safety: dynamic hook registration sites useas anycasts for TypeScript without runtime impact. End-to-end verified on a real OpenClaw session: paired intent + result events captured correctly forread,exec, and network calls.
Internal
- Seven new tests in
merkle::checkpoint::canonical_v3_testspin 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, andfresh_checkpoint_is_v3. update_checkpoint_with_proofin the daemon is now broken-by-design under v3 because it mutates a signed checkpoint after the fact to attach azk_proofsummary. 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. Thezkfeature 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.tomlpins channel1.94.1(current stable, matching the movingstablebranch CI'sdtolnay/rust-toolchain@29eef33resolves to as of 2026-05-17); rustup will auto-install the channel plusclippyandrustfmtcomponents on firstcargoinvocation..rustfmt.tomlpinsedition = "2021"andmax_width = 100without forcing a workspace-wide reformat.clippy.tomlpinsmsrv = "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.mdplusplugins/treeship-dev/.codex-plugin/plugin.jsongive 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 theskills/treeshipagent skill from v0.10.2 (#67); same shape, different audience (contributor vs. end-user).docs/content/docs/integrations/codex.mdxis the docs page;integrations/codex/README.mdcovers 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, andpackages/zk-risc0follow independent release cadences from the main Treeship monorepo (each ispublish = falseand not a hard dependency of the CLI release path). The version-consistency preflight (scripts/check-release-versions.py) intentionally does not cover them.CONTRIBUTING.mdnow 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 betweenpackages/core(0.10.3) andpackages/vi(0.6.0) /packages/zk-*(0.5.0). No genuine drift found; the stalepackages/zk-circom/package.jsonat 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_keybyte was reused once per plaintext byte modulo 32, so the construction was not a real stream cipher). Replaced with the RustCryptoaes-gcm0.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. Thetreeship-visibling 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::Zeroizingso 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'senc_priv_keyciphertext into entry B's JSON envelope and silently un-bindKeyInfo.public_keyfrom 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_600also nowfsyncs 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::verifypreviously returnedOkon envelopes whose signature list was empty because the verification loop simply did not run.bundle::importwrote every envelope to storage without verifying signatures. After this release, both paths reject before any state mutation. The fakechain_linkage = passrow that advertised a check the verifier never actually performed is gone, with a regression test pinning it down.bundle::importnow uses a verifier built from the local keystore and filters byalgorithm == "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.bootstrapnow 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.partialpath, verifies SHA-256, fails closed on any mismatch (nochmod, 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::appendpreviously fell through a lock-acquisition timeout and wrote events without holding the lock, collidingsequence_novalues under PostToolUse hook concurrency. The fix uses blockinglock_exclusive()(callers are fire-and-forgetlet _ = 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 betweenlock_exclusiveandunlock. 8-thread × 25-append concurrency test assertssequence_nois exactly0..200with no duplicates or gaps.
Added
treeship trustsubcommand for managing pinned trust roots (list,add,remove).~/.treeship/trust_roots.jsonconfig file (mode0o600, JSON schema version 1) listing trusted issuers for checkpoint, ship (hub-org), and agent-certificate verification. Path override viaTREESHIP_TRUST_ROOTS.treeship_core::trustmodule withTrustRootStore,TrustRoot,TrustRootKind,TrustRootError.treeship trust addandtreeship trust removenow require interactive y/N confirmation or an explicit--yesflag. Both print the affected key's 16-hex-character SHA-256 fingerprint before the change. Non-interactive stdin without--yesrefuses; 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_ROOTSis set (path override) orTREESHIP_ALLOW_INSECURE_KEY_PERMS=1is honored (perms bypass). Deduplicated per-process viastd::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 (
0x00byte prefix on leaves,0x01byte prefix on internal nodes). Receipts in v0.10.3+ include amerkle_version: 2field on themerklesection, on each inclusion proof, and on each checkpoint. v0.10.2 and earlier receipts continue to verify (dual-verify) because the field deserializes as1(legacy hashing) when absent. New receipts always emit2. v1 verification removed in v0.13.0 (~3 minor releases out). Closes the second-preimage attack where a 64-byte artifact ID whosesha256happened 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). NewHubCheckpointVerification::UntrustedIssuervariant covers the unpinned-issuer case.verify_certificate(&cert)→verify_certificate(&cert, trust: &TrustRootStore). NewCertificateVerifyError::UntrustedIssuer { key_id }andCertificateVerifyError::NoTrustConfiguredvariants.treeship_core::session::verify_package(path)now auto-loads~/.treeship/trust_roots.json; tests that want an explicit store can call the newverify_package_with_trust(path, &trust). If the trust file is malformed or has overly-open permissions the function emits atrust-rootFail check row instead of silently falling back to an empty store.- The
replay-hub-orgcheck row now emitsFailby default (notWarn) when the hub signature is untrusted, tampered, or has the wrong kind. Previously these wereWarnand--strictpromoted them toFail, 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--strictstill promotes them. - WASM exports
verify_certificate,cross_verify, andverify_merkle_prooftake an additionaltrust_roots_json: &strargument (empty string = fail closed).@treeship/verifyexposes this via an optionaltrustRootsparameter onverifyCertificateandcrossVerify. - 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 bindingmerkle_versioninto 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 haveverify_proofdispatch 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/verifyis a thin WASM shim overcore-wasmand inherits this change automatically; no consumer action needed there. Programs usingtreeship verify,treeship merkle verify, the Hub verifier, the Python/TS SDKs, or the in-browser receipt preview also inherit automatically. MerkleTree::verify_proofsignature changed. It now takesexpected_version: u8as its first argument (the trusted version, sourced from a signature-verified checkpoint or parent receipt section) and rejects ifproof.merkle_version != expected_version. The old form that read the version straight off the proof blob is gone —proof.merkle_versionis attacker-controllable, so dispatching on it was the downgrade primitive.MerkleTree::with_versionnow returnsResult<Self, MerkleError>and rejects anything outside{1, 2}. Receipts declaring an unknownmerkle_versionsurface as a hard fail in bothverify_receipt_json_checksandverify_packageinstead of silently being treated as v1.
Fixed
- CLI JSON output for
hubandsessionsubcommands (audit lane G, #84).treeship hub ls,hub status,session, andsession logpreviously emitted nothing on--format jsonbecause they routed throughprinter.info(...)which is a no-op in JSON mode. SDK consumers callingJSON.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 acrossci.ymlandrelease.ymlnow reference 40-char SHAs with the version tag in a comment. Workflow-levelcontents: writeremoved; scoped to thereleasejob only.id-token: writescoped per-job.wasm-packinstall switched fromcurl | shtocargo install wasm-pack --version 0.14.0 --lockedin both workflows. - TS SDK real CLI round-trip test (audit lane D, #85).
packages/sdk-ts/test/roundtrip.test.tsexercisesship().attest()against a real builttreeshipbinary, 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.pywalks bothmetadata.versionandplugins[name=treeship].versionin.claude-plugin/marketplace.json, andscripts/release.sh preparebumps them. Closes the drift class that left v0.10.2 advertising 0.9.5 in the plugin marketplace. Stale "177 tests" mentions inCONTRIBUTING.md,AGENTS.md, and.github/PULL_REQUEST_TEMPLATE.mdaligned to current count;SECURITY.mdsupported-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 underdocs/security/.SECURITY.mdupdated 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.jsonfor the matchingkind. Operators with existing hub artifacts must extract the embeddedsigner/hub_public_keyvalue from one of their own trusted artifacts and add it viatreeship trust add <key_id> <pubkey> --kind <hub_checkpoint|ship|agent_cert>before this version's verifier will accept their existing receipts. Atreeship 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 thehub_public_key(for hub-org checkpoints),public_key(for Merkle checkpoints), orsignature.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 decisioninto 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)
DecisionStatementgains optionalprovider. Receipts produced viatreeship attest decisionnow carryproviderin 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 --providernow accepted. Previously rejected by clap (--provideronly existed ontreeship session event). The documented Kimi-attribution invocation —--model kimi-k2 --provider moonshot— actually works as documented.attest decisionemitsSessionEvent::AgentDecisioninto the active session timeline, populatingmodel,provider,tokens_in,tokens_out,summary,confidencefrom the same CLI args. Without this, the receipt'sagent_graphcarriedmodel: null, provider: nulleven 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_roundtripsanddecision_statement_legacy_payload_without_provider_decodespin 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 attreeship.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
DecisionStatementartifacts continue to verify because the newproviderfield 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/mcpships 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). Configuringnpx -y @treeship/mcpin 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 localtreeshipCLI with the configuredTREESHIP_ACTOR. bridges/mcp/README.mdandbridges/mcp/ATTACHING.mddistinguish three modes: server mode (this PR), library mode (existingTreeshipMCPClient), 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>.jsonbefore decrypting; any group or world bits returnKeyError::InsecureKeyPerms { path, mode }with an actionable error. treeship doctor --fixrepairs 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--fixis 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 throughdoctor. Returns the change list.- Bypass via
TREESHIP_ALLOW_INSECURE_KEY_PERMS=1for sandboxed CI; documented in the error message.
Added (Python SDK correctness + input validation — PR 2)
__version__derived fromimportlib.metadata.version("treeship-sdk")instead of a hand-edited string. Removes the drift class of bug wherepyproject.tomlsays 0.10.0 buttreeship_sdk.__version__reports 0.9.x.Treeship(cli_path=..., timeout=..., cwd=..., env=...)constructor parameters. Prior versions only acceptedbot_modeand silently discarded the resolved binary at call time —_run(a free function) hardcoded"treeship". Fix surfaced a real bug: every SDK user withbot_mode=Truewas actually still depending on PATH. Now_run_cli_raw/_run_cli_jsonare methods onTreeshipand always callself._binary.wrap()accepts bothstrandSequence[str].strpath usesshlex.splitfor quoted-arg backwards compat;Sequence[str]passes argv exactly. Naivecommand.split()is gone.session_report()uses--format json(CLI schematreeship/share-result/v1) with a text-format regex parse fallback for older CLIs.- Empty
artifact_idraisesTreeshipErrorinstead of returning the empty string. The oldresult.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, butclapwould 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.jsonextends:is honored. A stub like{"extends": "/path/to/parent", "project": true}now resolves the parent and inheritsship_id/keys_dir/default_key_id. Prior versions tried to deserialize the stub directly and crashed withmissing field ship_id. Cycle detection (canonicalized visited set) catches self-references; depth cap of 5 catches accidental chains. treeship init --forceno longer clobbers the global keystore by default. From any cwd that resolves to the global config,--forcealone refuses with an actionable error pointing at project-local init, explicit--config <path>, or--global --forcefor explicit destructive intent. Two earlier dogfooding sessions hit the silent-clobber path; this guard closes it.attest action --format jsonreturns structured success/failure. v0.10.0 emitted{}and exited 1 on errors; the message was dropped entirely, and the@treeship/mcpbridge surfaced every CLI error as a genericisErrorwith no actionable content. Now JSON mode emits{"status": "error", "error": "<full message>", ...}envelopes. Success keeps every existing field plusstatus: "ok"andmessage. 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-referencingextends: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'spostinstall.jsdownloads to a.partialfile, 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
SHA256SUMSin thereleasejob, uploads it to the GitHub Release (auditors can cross-check), and surfaces per-platform hashes as job outputs. Thepublish-npmjob has a new "Embed expected SHA-256" step that writes each hash into its matching npm package beforenpm 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 linkagestep in the build job aborts the release iffilereports 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
smokejob runs afterbuildand before any publish step. Pulls thelinux-x86_64artifact, mounts it read-only intodebian:12,ubuntu:22.04,ubuntu:24.04, andalpine:3.20containers, and exercises install /--version/init/attest action/verifyagainst each.release,publish-npm,publish-crates, andpublish-pypiallneeds: 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 nowpreflight → 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, anddocs/content/docs/guides/install.mdxnow 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)
| Check | Result |
|---|---|
| version-consistency: 21 sites agree at 0.10.1 | preflight in release.yml |
| cargo test --workspace | 270+ tests pass across core / cli / zk / sdk-ts |
| Python SDK unittest | 28/28 pass |
tests/acceptance/trust-fabric.sh | 1/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 checksum | fails 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_a1b2c3d4and 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>/agentreturnstreeship/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 staysnull(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(withinstance_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
/agentroute 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>/packagereturns a.tar.gzwith the same<id>.treeship/directory layoutbuild_package_with_approvalsproduces in the CLI core. Consumers runcurl -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.jsonper artifact,approvals/grants/,approvals/uses/,approvals/checkpoints/,approvals/index.jsonwhen present. lib/tar.tsis a pure-JS USTAR writer (~80 lines, zero deps). The route gzips viazlib.gzipSyncand serves withContent-Type: application/gzip,X-Package-Digest: sha256:..., and platform-appropriate cache headers.
Added (CLI share command — treeship PR 4)
treeship session report --share --format jsonreturns 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 jsonmode, the response carrieserror: "..."plus the locally-derived digests and verify result. Text mode keeps the original recovery hint pointing attreeship hub attach. AI agents get a structured response either way. package_digestcomputed 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_urlandpackage_download_urlfrom the hub-issuedreceipt_url's origin so the three URLs always point at the same site.
Added (Python SDK + CLI bootstrap — treeship PR 5)
treeship setup --format jsonnow returns a real shape. Previouslytreeship setup --yes --format jsonreturned{}; the setup function ignored the global--formatflag. The newtreeship/setup-result/v1shape carriesschema,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 workingtreeshipbinary via env / PATH / cache / npm / GitHub Release in that order. Each step sandboxed-safe: nosudo, no network unless steps 4/5 fire, noPATHmodification.from treeship_sdk import Treeship ts = Treeship(bot_mode=True) # CLI resolved; no human required python -m treeship_sdk.bootstrap_cli --jsonis the agent-friendly shell entry point. Returnstreeship/bootstrap-result/v1withok,binary,version,source(env|path|cache|npm-cache|github-release),cache_dir.--no-installdisables network fallbacks for agents that should fail loudly.- New module structure under
packages/sdk-python/treeship_sdk/:bootstrap.py— resolution helpers,BootstrapResult,TreeshipBootstrapErrorbootstrap_cli.py—python -mentry pointclient.py—Treeship(bot_mode=True)integration__init__.py— exports
Verification (release-gate T1–T7 smoke)
| Test | Result |
|---|---|
| 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/agent | ✓ treeship/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
.treeshiptree from whatever the hub returns; demo fixtures that drift from the current schema produce parse errors at verify time (caught and fixed intreeship.devfollow-up #12). - Verification rows never silently downgrade.
treeship/share-result/v1carries 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-clion crates.io (still distributed via npm + GitHub Releases — see/guides/installfrom 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-healthCI gate. Static check, no network. Walks everymeta.jsonunderdocs/content/docs/and asserts: every advertised page resolves to a real.mdxfile or sub-section, every section root has either anindex.mdxor a redirect, every redirect destination resolves to an existing page. Wired into.github/workflows/ci.ymlas a sibling toversion-consistency. PRs that delete a doc without updating itsmeta.json— or add a section without a redirect — now fail fast.
Added (CLI reference catch-up)
/cli/setup—treeship setup [--yes] [--skip-smoke] [--no-instrument] [--format json]. The orchestrated first-run flow (detect → draft cards → confirm → instrument → smoke). Documents theActivevsVerifiedstatus distinction (smoke promotes Draft → Active; only per-harness smokes write Verified)./cli/add—treeship add [agents]... [--all] [--dry-run] [--discover] [--format json]. The focused instrumenter. Documents the--discoverread-only mode AI agents use to inspect a machine, with the actual JSON shape the binary emits today./cli/agents—treeship agents {list, review, approve, remove}. Documents the four-stage status lifecycle (Draft → NeedsReview → Active → Verified) and the deterministicagent_idderivation. Honest callout that--format jsonreturns{}today and points readers at the on-disk~/.treeship/agents/<id>.jsonfiles (which are stable schema-versioned JSON)./cli/harness—treeship harness {list, inspect, smoke}. Documents the manifest/state split that preventsPotentialCapturesvsVerifiedCapturesconfusion, 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--strictpromotion list.- CLI sidebar reordered so the first-run flow comes first:
init → setup → add → agents → harness → wrap → attest → verify → ....
Added (SDK + install topology)
/sdk/typescriptat 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/overviewis 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 flagtreeship-cli404 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 atdocs/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 underdocs/dogfood/<version>.mdparallel todocs/release-adversarial/<version>.md.docs(cli) JSON-shape honesty fixbaked 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 jsonrich-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 jsonreturning{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_journalbypassedmax_uses—packages/core/src/journal/mod.rs,packages/cli/src/commands/attest.rs. v0.9.9'sreserve_in_journalrancheck_replayandlist_uses_for_grantoutsidewith_lock, then calledappend_usewhich took the lock only for the write. Two paralleltreeship attestinvocations against amax_uses=1grant could both pass replay-check pre-lock, then queue serially through the lock, and both write — exceedingmax_uses. Closed by a newjournal::reserve_usethat runscheck_replayanduse_numberderivation insidewith_lockand stampsuse_numberfrom the count observed at lock-acquire time. The CLI consume path now callsreserve_use;append_useis preserved for tests and trusted callers. Pinned byjournal::tests::reserve_use_concurrent_max_uses_1_only_one_succeeds(8 threads contend; exactly one wins; on-disk record count is 1) andreserve_use_retry_after_lock_busy_does_not_bypass_max_uses(idempotency-retry safety). - Action↔use binding ignored at verify time —
packages/core/src/session/package.rs,packages/cli/src/commands/session.rs. v0.9.9'scollect_approval_evidencepulled every use for the grant andverify_packageresolved by(grant_id, nonce_digest)only. Worse, the package format never shipped action envelopes, so the verifier could not seemeta.approval_use_ideven if it had wanted to. Closed by extendingApprovalsBundlewithaction_envelopes: Vec<(String, Vec<u8>)>, populating it during session close, writing each consuming action envelope topkg_dir/artifacts/<artifact_id>.json(the directory existed but was empty pre-v0.9.10), and adding a newapproval-use-action-bindingverify row. The row PASSes only when every consuming action'smeta.approval_use_idresolves to a use_id in the bundle AND the action'sapproval_noncehashes to that use'snonce_digest. Round 2 added a content-addressing gate: the envelope'sartifact_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 toartifacts/<id>.jsonand the binding row would trust it. nonce_digestin ApprovalUse not cross-checked —packages/core/src/session/package.rs. v0.9.9's package replay path read each use'snonce_digestverbatim and never compared it tononce_digest(grant.nonce). An attacker who controlled the package could mutatenonce_digestto claim consumption of any grant whose nonce they hadn't actually used — recompute the use'srecord_digestand the per-record check still passed. Closed by a newapproval-use-nonce-bindingverify row: for each use, find the matching grant inbundle.grants, parse its envelope, computenonce_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 replay —
packages/core/src/session/package.rs. v0.9.9'sreplay-included-checkpointand per-userecord_digestchecks both recomputed individual digests, but neither walked theprevious_record_digestchain 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 newapproval-use-chain-continuityverify row. Round 2 tightened the algorithm: requires exactly one record withprevious_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 bymultiple_genesis_records_fail_chain_continuity,forked_chain_two_records_share_prev_fails_chain_continuity,disconnected_subchain_fails_chain_continuity, plus the originaldangling_previous_record_digest_fails_chain_continuity.
Fixed (label hygiene — #57)
- Renamed
approval-use-integrity→approval-use-record-digest—packages/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.
--strictnow promotes every new approval-evidence row from warn → fail alongside the existingreplay-*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 todocs/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_numberjournal::tests::reserve_use_max_uses_1_serial_second_call_rejectsjournal::tests::reserve_use_max_uses_2_two_uses_pass_third_rejectsjournal::tests::reserve_use_concurrent_max_uses_1_only_one_succeedsjournal::tests::reserve_use_retry_after_lock_busy_does_not_bypass_max_uses- 17 new integration tests in
packages/core/tests/approval_evidence_binding.rscovering 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_verifyintegration tests still pass; acceptance smoke (tests/acceptance/trust-fabric.sh) still passes.
Known follow-ups (non-blocking, deferred)
- Duplicate
grant_identries inbundle.grantssilently last-wins in the nonce-binding HashMap. Real packages produced bybuild_package_with_approvalsdeduplicate 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
--strictagainst pre-v0.9.10 packages will failapproval-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. Carriesuse_id,use_number,grant_id,nonce_digest,actor,action_artifact_id,subject_ref,max_usessnapshot,package_id,created_at,previous_record_digest,record_digest, optional Ed25519signature/signing_key_id. Theprevious_record_digestchain 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 emitsreplay-included-checkpoint) andHubOrg(signed by an org Hub; verify emitsreplay-hub-orgwhen the four-gate rule clears).canonical_*_record_digesthelpers — 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 ofApprovalUserecords. Source of truth is the JSON file tree (records/<use_id>.json), with secondary indexesby_grant/<grant_id>.jsonandby_nonce/<grant_id>/<nonce_digest>.jsonrebuilt deterministically on startup. Records are truth, indexes are cache —journal::rebuild_indexesreconstructs 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'suse_numberstay 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
ApprovalUseto 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-journalrow intreeship verify. When the verifier has access to a workspace journal, it asksfind_use_for_actionper embeddedApprovalUseand reportsPASS(use_number ≤ max_uses),FAIL(record exists but exceedsmax_uses), orWARN(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>.jsonandapprovals/checkpoints/<checkpoint_id>.jsonin the.treeshippackage. Every use the package's actions consumed is exported alongside the receipt. Recipients without access to the originating workspace's journal can still verifyreplay-package-localandreplay-included-checkpointoffline.replay-package-localandreplay-included-checkpointrows. Package-local detects duplicate uses inside this package (an always-fails-or-passes invariant; never a warning). Included-checkpoint recomputes each embedded checkpoint'srecord_digestand 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 readsapprovals/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_usesand 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 onJournalCheckpoint.#[serde(default)]keeps every pre-#55 record bytes-identical (covered bycheckpoint_kind_defaults_to_local_journal).- Hub-signature fields on
JournalCheckpoint:hub_id,hub_public_key,hub_signature,signed_at,covered_use_ids(+ informationalcovered_grant_ids). All optional viaskip_serializing_if, so aLocalJournalcheckpoint serializes byte-identical to #50. canonical_hub_signing_bytes()— stable JSON over every signed field excepthub_signatureandrecord_digest. Theprevious_record_digestis signed (chain-linkage protection).verify_hub_checkpoint_signature→Valid | 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-orgrow inverify. EmitsPASSonly when all four of: (1) at least one embedded checkpoint declareskind: hub-org, (2) every required Hub field is populated, (3) the Ed25519 signature verifies against the embedded public key, AND (4) every embeddedApprovalUse.use_idis incovered_use_ids. Anything short → warn (default) or fail (--strict). When no Hub-kind checkpoint is embedded, no row is emitted; the panel rendersnot checked (no Hub checkpoint in package).- Per-use panel resolution. The
hub-orgrow resolves per-use:✓ <detail>(verified + covered for this use),✗ <reason>(Hub checkpoint present but a gate failed for this use), ornot 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.treeshippackage with a signed checkpoint embedded.
Changed (trust semantics)
replay-hub-orgis 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 printingnot 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-orgrow 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
LocalJournalcheckpoint; the chain-linkprevious_record_digestmakes 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_keyvalues 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 --invitefor 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 withAgentSurface,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 inviteflow rather than going silent. Read-only, no config writes, stable JSON shape via--format jsonso 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 deterministicagent_id = sha256("<surface>|<host>|<workspace>")[..8]so re-running discovery is idempotent. Card lifecycle:Draft→NeedsReview→Active→Verified. 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.Verifiedis intentionally not exposed as a manual flag; only a smoke session that proves capture writes that status.- Certificate-digest drift demotion.
cards::upsertcompares the incoming card'scertificate_digestagainst the stored card; if they differ and the existing status wasActiveorVerified, the merged status drops toNeedsReview. Re-registering an agent therefore cannot leave a previously-approved card pointing at a certificate the user never saw. treeship agent registerwrites a card alongside the.agentpackage.packages/cli/src/commands/agent.rs. The signed certificate stays the portable artifact; the card is the local trust object thattreeship agentslists. 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::discover→cards::upsert→ confirmation prompt → existingadd::runinstrumenter → smoke session inside an isolated tmpdir → state promotion. Non-interactive without--yesdefaults to a safe "no" so CI invocations never touch user configs without permission. If no Treeship config exists, setup points attreeship initrather 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
init→session start→wrap→session close→package verifyagainst an isolated keystore. Promotes instrumented cards toActiveand instrumented harnesses toInstrumented. Never toVerified-- 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 carriesharness_id,surface,display_name,connection_modes,coverage,captures: PotentialCaptures,known_gaps,privacy_posture,recommended_backstops, andinstall: 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 inspectis honest about each.HarnessStateper-workspace store at.treeship/harnesses/<id>.json. Status lifecycle:Detected→Available→Instrumented→Verified(withDrifted/Degraded/Disabledreserved for future use). Carrieslast_smoke_result,last_verified_at,linked_agent_ids, frozenknown_gaps, and -- the load-bearing v0.9.8 trust field --verified_captures.upsert_statemerges verified captures monotonically (a smoke that provesfiles_readdoes not erase a prior run that provedmcp_call).treeship harness list / inspect / smoke.packages/cli/src/commands/harness.rs.inspectshows 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::HARNESSEStable replaces PR 4's branchingadd.rsinstall dispatch. What used to be three install functions (install_mcp_configfor Claude/Cursor/Cline,install_codex_mcp_configfor Codex,install_skillfor Hermes/OpenClaw) is now one row per surface withinstall_method(JsonMcp|TomlMcp|SkillFile),snippet,config_path: fn(&Path) -> PathBuf, andidempotency: fn(&Path) -> bool. Adding a new instrumentable surface is one row inHARNESSES; nothing inadd.rschanges. Detection itself moved todiscovery::discovereverywhere, ending the duplicatedetect_agents()that lived in bothadd.rsanddiscovery.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 existingFileAccess.sourceprovenance:[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::listagainst the workspace card store; renders status, surface, harness id, model, host. No schema fork; same data the standalonetreeship agentscommand shows. - Harness Coverage panel. Reads
harnesses::list_statesjoined withHARNESSES; renders status, coverage, two distinct rows for potential vs verified captures (preserving the trust-semantics split), last smoke summary, and the first twoknown_gapsper harness. Surfaces honest gaps in the report rather than burying them inharness inspect.
Added (project-local config discovery and CLI ergonomics)
AgentCard.active_harness_idandDiscoveredAgent::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 oftreeship add --discover --format jsonincludes 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, plusmeta.jsonupdates 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 moveDetected → Instrumented.last_verified_atstaysNone. The completion summary spells out: "Each harness is nowinstrumented. Per-harness capture is NOT yet verified. Run a real session through each harness to populate verified_captures and reachverified." 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. PotentialCapturesandVerifiedCapturesare different types. Bools per signal on the manifest (PotentialCaptures.files_read: bool); per-signalOption<bool>on state (VerifiedCaptures.files_read: Option<bool>). UI cannot render one in the other's slot.harness inspecttext and JSON show the rows as "potential captures (when attached and working)" and "verified captures (proven by harness-specific smoke)".HarnessStatus::Verifiedis reserved for harness-specific smokes. v0.9.8 ships the type, the lifecycle, and the file format; the smokes that populateVerifiedCapturesper-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 --invitefor 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 fortreeship-core/treeship-cli/treeship-core-wasm, the workspace-internaltreeship-corepin in the CLI, every publishedpackage.json(@treeship/sdk,@treeship/mcp,@treeship/a2a,@treeship/verify,treeshipwrapper, three platform CLIs), everydependencies['@treeship/core-wasm']pin, the wrapper'soptionalDependenciesrouting,pyproject.toml, andtreeship_sdk/__version__. Two modes: explicit-target (<version>) for release time and--consistency(anchors onpackages/core/Cargo.toml) for PR time.preflightjob in.github/workflows/release.yml. Runs first; every other release job (build,release,publish-npm,publish-crates,publish-pypi) is a transitiveneeds:of it, so a manifest mismatch blocks the entire pipeline before any registry sees a publish.version-consistencyjob in.github/workflows/ci.yml. Runs the preflight in--consistencymode 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 intoci.ymlafter 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.jsondiscovery.packages/cli/src/config.rs::resolve_config_path(). Walks up from cwd looking for.treeship/config.jsonbefore falling back to~/.treeship, with one safety rail: a hit at exactly$HOME/.treeship/config.jsonis 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. ConfigSourceenum with four tiers and an explicit precedence.Explicit (--config)>Env (TREESHIP_CONFIG)>ProjectLocal(walk-up) >Global(fallback).Ctxcarries the resolved source so any command can report it.treeship doctorreports 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.shsplit intoprepareandtagsubcommands.prepare <version>bumps every version site, runs preflight, commits -- and stops. It contains nogit tag,git push --tags, orgit 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--yesor an interactivetype 'tag <version>' to confirmgesture. The legacyscripts/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) cleansdist/,build/, and*.egg-info/beforepython -m build, (2) computesEXPECTED_WHEEL=dist/treeship_sdk-${VERSION}-py3-none-any.whlandEXPECTED_SDIST=dist/treeship_sdk-${VERSION}.tar.gzand refuses to continue if either is missing, (3) refuses to continue if any unexpected artifact is indist/, (4) runstwine checkon the exact expected paths to catch metadata problems server-PyPI would also reject, and (5) callstwine upload --skip-existing "$EXPECTED_WHEEL" "$EXPECTED_SDIST"instead of a blinddist/*glob. With this chain, apyproject.tomldrift 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 newresolve_config_path()which returns(PathBuf, ConfigSource).
Fixed
- Stale
@treeship/core-wasmpins 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), andpackages/sdk-ts/package.json(was^0.9.4) now pin exactly to the published@treeship/core-wasm. Surfaced by the new preflight;release.sh preparenow 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)
-
ApprovalScopecarries actor / action / subject allow-lists and max-uses.packages/core/src/statements/mod.rs.ApprovalScopepreviously held onlymax_actions,valid_until, andallowed_actions; v0.9.6 addsallowed_actorsandallowed_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 andskip_serializing_if-omitted, so a 0.9.5 approval payload deserializes cleanly into a 0.9.6ApprovalScope(legacy roundtrip test pinned). -
treeship attest approvalflags:--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 Nis signed into the grant for future ledger enforcement (verify reports replay posture honestly and does not yet claim global single-use).--unscopedis 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 writeattest approval --allowed-subject env://prodandattest action --subject env://prodand 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 enforcedis 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_bindingsenforcesallowed_actorsandallowed_subjects. Was already enforcingallowed_actions; now covers all three axes plus a separatevalid_untilon 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 approvaldefaults to scoped. Without any--allowed-*/--max-usesand 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/. Genericagent.called_toolevents emitted by the MCP bridge previously stayed generic -- they appeared in the timeline but never landed infiles_read,files_written, orprocesseson the receipt's side-effects block. New promotion pass at receipt composition inspects eachagent.called_toolevent'smeta.tool_inputand synthesizes the matching specialized event (AgentReadFile,AgentWroteFile,AgentExecutedCommand) when a known tool name + path are present. The synthetic event carriesmeta.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 togit diff HEAD --name-status,git diff <since>..HEAD --name-status, andgit ls-files --others --exclude-standard, deduplicates against files already captured by hook or MCP, and synthesizesAgentWroteFileevents for everything else. The synthetic events are appended toevents.jsonlBEFORE 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 ransed -iinside 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-clisource-of-truth attribution for hook events. Everytreeship session eventinvocation now stampsmeta.source = "session-event-cli"on the event it emits. The Claude Code plugin'spost-tool-use.shhook 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'stool_usageblock now carries bothdeclared(what the agent's certificate authorized) andactual(what the agent actually called), and the verifier diffs them withTOOL_ALIASESmapping 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 haveactual: ["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'sactuallist aggregatesAgentReadFile,AgentWroteFile, andAgentExecutedCommandevents -- but only when theirmeta.sourceishook,mcp,shell-wrap,session-event-cli, or absent. Events synthesized bygit-reconcileor thedaemon-atimechannel are excluded; they're Treeship's own backstop layers, not the agent calling tools, and counting them as direct tool use would inflateactualagainst 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 asproofs.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 to0and isskip_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.decisionevent at session start records the model.integrations/claude-code-plugin/scripts/session-start.shnow emits anagent.decisionevent carryingmeta.modelso the receipt records what model the agent ran on. Pairs with the CLI surface that plumbs model/provider/token-budget through thetreeship session eventcommand. 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 exactmeta.sourcevalue 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_linenow returns the destination path forR/Ccodes instead of the source.git mv old newpreviously 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.jsonare now excluded fromfiles_writtenbecause 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/cmdinto receipts.bridges/mcp/src/client.ts. The__sanitizeToolInputwhitelist is now strictly path-only (file_path,path,notebook_path,target_file). The earlier whitelist acceptedcommandandcmd, which would have shipped raw shell-arg secrets (Bearer tokens passed inline to curl, AWS credentials passed inline to aws CLI) intometa.tool_inputand ultimately the receipt. Caught by Codex round-2 trust-fabric review. (#19, #27)
Fixed
-
ctx::openno longer overwrites a project-localconfig.jsonwith a global-extends marker.packages/cli/src/ctx.rs.treeship initpreviously wrote the marker even when the directory already had a populatedconfig.jsonfrom a separately-managed setup, silently breaking that project's keystore reference. (#15) -
machine_seedis 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 inevents.jsonlmaderead_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 askippedcounter, and continues. The skipped count surfaces in the sealed receipt viaproofs.event_log_skipped(see Added). (#8) -
Preview UI no longer crashes when
tool_usage.declaredortool_usage.actualis 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. Thecrates.ioupload 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/binfrom PATH..github/workflows/ci.yml. Workflow expression${{ env.PATH }}resolves to empty string at workflow context, soPATH: ./node_modules/.bin:${{ env.PATH }}left PATH as just./node_modules/.bin:, and/usr/bin/env: 'bash': No such file or directorykilled every cross-SDK matrix entry with exit 127. Replaced with the canonical$GITHUB_PATHmechanism. (#33) -
CI:
tests/cross-sdk/parses as ESM.tests/cross-sdk/package.json.verify-vectors.tsusesimport.meta.urland 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 codexnow detects the Codex CLI and installs an MCP block in~/.codex/config.tomlso 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.ymllanded. - 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_usageblock was already present in v0.9.5 receipts -- v0.9.6 just makes itsactuallist 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 newsuccessor_key_idfield, and stamps it withvalid_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 honorvalid_untilcan refuse the predecessor on lifecycle. Refuses to rotate an already-rotated key (caller must rotate the chain head). Backed byStore::successor_chain(id) -> Vec<KeyId>for forward walks andStore::valid_keys_at(at_unix_secs) -> Vec<KeyInfo>for building a verifier's accept-set as of a given time. -
treeship keys rotateCLI 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 listis enriched to show rotation status inline (rotated -> key_xxx, valid until 2026-04-26T...). -
KeyInfo.valid_untilandKeyInfo.successor_key_id. Lifecycle metadata threaded through the publicKeyInfoshape and the on-disk encrypted entry. BothOption<String>andOption<KeyId>. Pre-0.9.5 entry files lack these fields entirely; they're#[serde(default)]andskip_serializing_if = "Option::is_none", so legacy files load withNoneand 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)andTreeship().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 newTREESHIP_CONFIGenv 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 (differentfetchimplementations, different subprocess line endings) surface immediately. -
TREESHIP_CONFIGenv var honored by the CLI. The binary's config-path resolution now consultsTREESHIP_CONFIGbefore falling back to~/.treeship/config.json.--configstill 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 aexport 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 entireevents.jsonlon every append to derive the nextsequence_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-byteevents.jsonl.countsidecar ([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 wroteevents.jsonlbut 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.*; classDockModule→HubModule. Both surfaces previously shelled out totreeship 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? }); thedock_idfield 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 removedtreeship dock pushsubcommand 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.mjsand_sdk_helper.py) exposeattest-action/verifyover argv so the orchestrator can sequence the four legs cleanly.
Fixed
-
Python SDK
Treeship.verify()now returns structured failures instead of raising. Previouslyverify()shelled out via the generic_runhelper, which raisedTreeshipErroron any non-zero CLI exit. Buttreeship verifyexits 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, returnoutcome=fail. Python now matches: the outcome is in the returnedVerifyResult, andTreeshipErroris 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
chainfield semantics now match TypeScript. TS'schainreportsparsed.passedonoutcome=passandparsed.failedonoutcome=fail; the previous Python implementation always reportedpassed, so a failed verification reportedchain=0while 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"indocs/content/docs/cli/meta.jsonto (purportedly) fix/cli404'ing. fumadocs-mdx schema actually rejects it (expected boolean, received string), which crashed mdx generation, which made every docs URL return 500.treeship.dev/cliand eventreeship.dev/cli/initwere 404 in production until this release. Replaces the broken approach with explicit Next.js redirects from/cli,/sdk,/api,/commerce,/referenceto their respective first-page targets, plus a permanent/cli/dock -> /cli/hubredirect for the long-pending CLI rename. -
cli/dockresolved tocli/hub. The CLI command has beentreeship hubsince v0.7.x; the doc page lived at/cli/dockunder the legacy URL with its title set to "hub". Renameddock.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 fromsource.getPages()andblogSource.getPages()so all 87 docs + 18 blog URLs appear), and metadata export indocs/app/layout.tsxwithmetadataBase, OG tags, and Twitter card defaults so social previews stop falling back to bare URLs. -
llms.txtrewritten.docs/public/llms.txtnow uses canonical paths (no/docs/prefix that depended on the old redirect), correct host (treeship.dev, not the imagineddocs.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-corelib 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. TheStore::rotatecache 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 assertexpected_chainper vector (not justexpected_outcome) so a same-direction regression in both SDKs can no longer silently pass.
Known limitations
- Verifier-side enforcement of
valid_untilis 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.
rotateis 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::rotateandStore::generateare not safe under concurrent invocation. Both write tomanifest.jsonwithout a flock, so two processes callingrotateat the same instant can produce two successors, only one of which ends up in the manifest. This is the same race that already existed forgeneratesince 0.9.0 -- not a regression introduced byrotate, 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 onmanifest.jsonparallel to the one already inevent_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 downverify(artifact_id)-- the LCD surface -- and is structured so addingverifyReceiptlater is a one-line addition togen-vectors.shplus 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.jsonat the repo root so any user can install the plugin tonight with:claude plugin marketplace add zerkerlabs/treeship claude plugin install treeship@treeshipInstalls 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 verifypasses 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.machineseedormachine_seedfile 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.treeshippackages 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.shpreviously redirected stderr to/dev/nulland 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 anadditionalContextJSON 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_fileinpackages/core/src/session/event_log.rs) previously re-chmodded existing files viaset_permissions(path, ...)after anfile.metadata()check. That's a TOCTOU: between the metadata read and the path-based chmod, an attacker could swap the inode. Now we callfchmod(fd)on the already-open file descriptor, so the target is pinned to the inode we hold. FFI wrapped locally to avoid adding a fulllibcdep for one symbol. -
NFS chmod warning. If
fchmodreturns 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.shnow 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 wheregrep -qexiting early underset -o pipefailmade successful matches look like network failures. -
status_checkdead_configarg removed. Cosmetic cleanup from the v0.9.3 Codex review punch list.treeship session status --checkno longer takes a dead config argument becauseload_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-corelib 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.mdinto 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 explicittreeship 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/mcpvianpx -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 addno 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.mdtemplate 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 throughtreeship add.bridges/mcp/README.mdreframed: the inspect-before-trust path now leads withtreeship 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 eventis now race-safe across processes. Each invocation acquires an exclusive advisory file lock (fs2::FileExt::try_lock_exclusivein a 500ms bounded retry loop, backed byflock(2)on Unix andLockFileExon Windows) on a sidecar.lockfile before re-derivingsequence_nofrom the on-disk JSONL line count. Previously, parallel writers (e.g. concurrent PostToolUse hook invocations from a Claude Code plugin) each had their ownAtomicU64initialized 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 testconcurrent_appends_have_unique_sequence_numbersspawns 16 racing writers and asserts uniqueness.- Sidecar lock file is created mode
0o600(owner-only) on Unix viaOpenOptionsExt::mode. Atomic at file creation so the file can never exist on disk with a more permissive mode. Regression testlock_file_has_owner_only_permissionsasserts this. treeship session status --checkadded: prints nothing, exits0if a session is active, exits1if not. Shell-script-friendly gate for hooks and monitors. The defaulttreeship session statusstill always exits0(it's a human-facing report), so existing scripts that gated on it are unchanged — but the Claude Code plugin's hooks now use--checkso they actually run when a session is active. Without this, the plugin'sSessionStart,SessionEnd, andPostToolUsehooks would have no-op'd in every real-world session.- Trust documentation rewritten to match the bridge source. The previous
TREESHIP.md/CLAUDE.mdtrust block claimed@treeship/mcpcaptured "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 againstbridges/mcp/src/client.tsandattest.ts. Conditional fields (e.g.artifact_idis omitted if the receipt write failed;payload.output_digestusesresult.content ?? resultso the fallback is documented) are flagged as conditional.payload.error_messagecarries the rawError.messagestring 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.shno longer regex-extractstool_namefrom the PostToolUse JSON payload (a greedysedwould pull the wrong value whentool_inputitself contained a"tool_name":"foo"substring — confirmed reproducible). It now triesjq, thenpython3, thennodefor JSON parsing, falling back to"unknown"only if all three are absent. npm install -g treeshipnow fails loudly on Windows instead of silently installing a wrapper without a binary. Addednpm/treeship/scripts/check-platform.jswired as apreinstallhook, plus anos: ["darwin", "linux"]field inpackage.jsonso 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.3as< 0.9.3per semver.org §11.4. Smoke-tested against0.9.2,0.9.3-rc.1,0.9.3,0.9.3-alpha,0.9.4,0.10.0, and1.0.0. Fallback path (whennodeisn't on PATH):sort -Vwith a softer warning that notes prerelease detection is not strict. Iftreeship --versionreturns 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 morerm $(command -v treeship)— points at package-manager reinstall instead. setup.shadapts the trailing hint based on whether./TREESHIP.mdactually exists aftertreeship addruns. If the CLI is too old to drop the file, the hint now points at the GitHub rawTREESHIP.mdURL 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.shone-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 runtimecurlof CLAUDE.md from GitHub raw) is removed; the embeddedinclude_str!template is the source of truth. - Website-side fix for the original
treeship.dev/setup404 is in thetreeship-websiterepo (added a Next.js rewrite for/setup→/setup.shmirroring the existing/installrewrite). 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, andlinux-x64. The website's setup script is POSIX shell, the plugin's hook scripts are POSIX shell, andtreeship add's file-drop semantics rely on POSIXrenameoverwrite 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 eventappend is O(N) in the on-disk event count. The cross-process safe re-derivation ofsequence_norescans the fullevents.jsonlfile 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 uploadstep hascontinue-on-errorremoved. A failure in any one now fails the workflow — no more silent partial releases. - New pre-flight step on the
publish-npmjob enumerates every expected package on the@treeshipscope and fails fast if any is missing. New scoped packages must be bootstrapped once with a web-authnpm publishfrom 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.shhelpers 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-wasmnpm 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/verifyzero-dependency verification package atpackages/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/mcpREADMEs.reference/schema.mdxgains a "Parity between CLI and WASM" section. - Command-line build pipeline:
packages/core-wasm/build-npm.sh <version>runswasm-pack build --target bundler, optionallywasm-opt -Oz, then rewritespkg/package.jsonwith 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/sdkverification path migrated from CLI subprocess to direct WASM calls.ship.verify.verify(id)(legacy artifact-ID form) still subprocesses; newverifyReceipt/verifyCertificate/crossVerifymethods run in-process via WASM. Stateful operations (attest, session, dock, agent register) still use subprocess.@treeship/a2averifyReceiptnow performs real cryptographic verification (was previously network-only structural summary).VerifiedReceipt.cryptographicallyVerifiedsurfaces the WASM result;verifyCheckscarries the per-step breakdown. Graceful fallback: if@treeship/core-wasmcan't load in the runtime, returns the pre-v0.9.1 summary withcryptographicallyVerified: false.@treeship/mcpgains WASM-backed verification helpers alongside its attestation surface. Attest paths remain subprocess-based.scripts/release.shnow pins@treeship/core-wasmto 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.tomladds[profile.release.package.treeship-core-wasm]withopt-level = "z"andcodegen-units = 1. CLI release tuning unchanged. treeship_corehostnamedep moved totarget.'cfg(not(target_family = "wasm"))'. WASM builds fall back to"host_unknown"indefault_host_id; WASM contexts consume receipts rather than author them, so the fallback is benign.
Notes
- All packages that depend on
@treeship/core-wasmpin to exact version0.9.2(no caret).release.shenforces this at tag time. - Subprocess fallback was not implemented for WASM. The SDK's
verifyReceipt/verifyCertificate/crossVerifyfunctions require a runtime that can load the bundled WebAssembly. Runtimes without WASM support can continue using the legacyverify(id)subprocess path. - WASM imports are lazy: the SDK and bridge modules can load in environments where
@treeship/core-wasmis 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-approvalflag ontreeship wrap+treeship approverCLI) treeship.dev/verifybrowser drag-and-drop page (unblocked now that v0.9.2 publishes@treeship/core-wasmand@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.0v0.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-x64reached 0.9.1.@treeship/core-wasmand@treeship/verifynever published, which cascaded install failures to@treeship/sdk,@treeship/mcp, and@treeship/a2a.treeship-core(crates.io) andtreeship-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-wasmnpm 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/verifyzero-dependency verification package atpackages/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/mcpREADMEs.reference/schema.mdxgains a "Parity between CLI and WASM" section. - Command-line build pipeline:
packages/core-wasm/build-npm.sh <version>runswasm-pack build --target bundler, optionallywasm-opt -Oz, then rewritespkg/package.jsonwith 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/sdkverification path migrated from CLI subprocess to direct WASM calls.ship.verify.verify(id)(legacy artifact-ID form) still subprocesses; newverifyReceipt/verifyCertificate/crossVerifymethods run in-process via WASM. Stateful operations (attest, session, dock, agent register) still use subprocess.@treeship/a2averifyReceiptnow performs real cryptographic verification (was previously network-only structural summary).VerifiedReceipt.cryptographicallyVerifiedsurfaces the WASM result;verifyCheckscarries the per-step breakdown. Graceful fallback: if@treeship/core-wasmcan't load in the runtime, returns the pre-v0.9.1 summary withcryptographicallyVerified: false.@treeship/mcpgains WASM-backed verification helpers alongside its attestation surface. Attest paths remain subprocess-based.scripts/release.shnow pins@treeship/core-wasmto 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.tomladds[profile.release.package.treeship-core-wasm]withopt-level = "z"andcodegen-units = 1. CLI release tuning unchanged. treeship_corehostnamedep moved totarget.'cfg(not(target_family = "wasm"))'. WASM builds fall back to"host_unknown"indefault_host_id; WASM contexts consume receipts rather than author them, so the fallback is benign.
Notes
- All packages that now depend on
@treeship/core-wasmpin to exact version0.9.1(no caret).release.shenforces this at tag time. - Subprocess fallback was not implemented for WASM. The SDK's
verifyReceipt/verifyCertificate/crossVerifyfunctions require a runtime that can load the bundled WebAssembly. Runtimes without WASM support can continue using the legacyverify(id)subprocess path. - WASM imports are lazy: the SDK and bridge modules can load in environments where
@treeship/core-wasmis 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-approvalflag ontreeship wrap+treeship approverCLI) treeship.dev/verifybrowser drag-and-drop page (unblocked now that v0.9.1 publishes@treeship/core-wasmand@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.0v0.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.treeshipor.agentpackage 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:0success,1verification failed,2cross-verification failed,3network or filesystem error. Documented indocs/cli/verify.mdx. (item 1) - Reusable library primitive
treeship_core::verify::cross_verify_receipt_and_certificate(receipt, certificate, now_rfc3339)returningCrossVerifyResultwith authorized / unauthorized / never-called tool lists, ship-ID status, and certificate validity. Explicitnowargument keeps the function deterministic for testing and for future edge-runtime callers. (item 2) schema_versionfield on Session Receipts and Agent Certificates. New documents emit"1"; documents without the field are treated as"0"(legacy) and verified under existing rules. OptionalOption<String>with#[serde(skip_serializing_if = "Option::is_none")]so legacy documents round-trip byte-identical. Full semantics indocs/reference/schema.mdx. (item 3)session.ship_idfield on Session Receipts, parsed from the manifest'sactorURI when it starts withship://. Absent on pre-v0.9.0 receipts and on non-ship actors (human://alice, bareagent://). Cross-verification uses it to check receipt and certificate reference the same ship. (item 2)treeship_core::artifactsmodule with five DSSE-signed command artifact schemas for supervisor → ship control-plane messaging:KillCommand,ApprovalDecision,MandateUpdate,BudgetUpdate,TerminateSession. Plusverify_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_certificatevalidates the embedded Ed25519 signature on anAgentCertificateagainst 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_versionhelper resolvesOption<String>to its effective string ("0"default). Use this over manualOptionchecks so the legacy default flows from one place.- New docs:
cli/verify.mdxrewrite,concepts/cross-verification.mdx,concepts/command-artifacts.mdx,reference/schema.mdx.concepts/session-receipts.mdxupdated to mention the new fields. (item 11) - Backwards-compatibility regression suite at
packages/core/tests/legacy_receipt_fixtures.rswith 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 verifydispatcher: 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.ArtifactEntryre-exported fromtreeship_core::sessionso downstream code can construct receipts without reaching into the package module.- Legacy
Option<String>defaults onschema_versionandsession.ship_idare 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. Seereference/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/sdkand@treeship/a2afrom CLI subprocess to directpackages/core-wasmcalls, and the new@treeship/verifystandalone npm package (zero@treeship/sdkdependency, pure WASM, for Vercel Edge / Cloudflare Workers / AWS Lambda / browser). The@treeship/verifypackage uses the samecross_verify_receipt_and_certificateimplementation that ships in v0.9.0 — no semantic drift. - v0.10.0 — Live management primitives. Approval-loop Hub endpoints (5 new routes),
--require-approvalflag ontreeship wrap,treeship approver add / list / removeCLI.treeship.dev/verifybrowser-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 upgradeself-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.0No 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 secondstreeship 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 progresstreeship declare-- create .treeship/declaration.json with tool authorization scopeTREESHIP_PROVIDERenvironment 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 initoutput simplified to Ship ID + Key ID + next step hintstreeship wrapwithout active session shows warning with fix instructionstreeship session closeauto-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_USDenvironment 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 eventCLI command: append structured events to the active session's event log. Used by MCP bridge, A2A bridge, and SDKs.@treeship/mcpnow emitsagent.called_toolsession 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_USDenvironment variables: set these beforetreeship wrapto capture model, token counts, and cost in the receipt.treeship declareCLI command: create.treeship/declaration.jsonwithbounded_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_presentis 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_PATHenv 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.closingrecovery 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
immutablecache 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
touchafter a secret read from suppressing theon: accessalert. - Session close deletes
session.jsonbefore composing the receipt to prevent late daemon events from landing in the log but not the receipt. treeship session reportselects the most recently closed session bysession.ended_atinside the receipt, not filesystem mtime.- Log redaction matches the
sessionquery parameter case-insensitively.
0.7.0 (2026-04-09)
Session Receipts
- New
treeship_core::sessionmodule: event model, manifest, context propagation, agent graph, side effects, append-only event log, canonical receipt composer with Merkle root .treeshippackage format: deterministicreceipt.json+merkle.json+render.json+ per-artifact inclusion proofs + staticpreview.htmltreeship session closenow composes a Session Receipt v1 and writes a.treeshippackage under.treeship/sessions/treeship package inspectandtreeship package verifyfor offline inspection and local verification (no hub required)treeship session reportuploads 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 graphGET /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 cacheGET /v1/ship/agentsandGET /v1/ship/sessions: per-ship registry endpoints for dashboards and A2A clients- New
sessionsandship_agentstables 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.ResolveReaderhelper: read endpoints accept either DPoP or?session=TOKEN, fails closed on expired tokens treeship hub openmints a share token and opens a browser URL that does not require a private key on the client- Access logs now redact
sessionquery parameters to prevent tokens from landing in stdout
Sensitive file read detection
- Daemon now tracks both mtime and atime per file; a
SnapshotDiffseparates 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: accessrule has its atime advance, the daemon emits anagent.read_fileevent to the active session's event log withcapture_confidence: "inferred"and writes an ALERT line if the rule hasalert: 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 TreeshipA2AMiddlewarewithonTaskReceived(awaited intent),onTaskCompleted(chained receipt),onHandoff, anddecorateArtifactbuildAgentCard,hasTreeshipExtension,getTreeshipExtension,fetchAgentCardfor AgentCard discovery + extension publishingverifyReceiptandverifyArtifactfor 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=1short-circuit - Docs:
docs/integrations/a2a.mdx(Mintlify) andtreeship/docs/content/docs/integrations/a2a.mdx(Fumadocs) - Blog post: "A2A Makes Agents Interoperable. Treeship Makes That Interoperability Trustworthy."
- Release pipeline:
bridges/a2awired intoscripts/release.shand.github/workflows/release.yml
Python SDK
Treeship.session_report(session_id=None)returns aSessionReportResultwith the permanent receipt URL, agent count, and event count- Defaults to the most recently closed session when no
session_idis 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-statuscommands- Auto-prove on declaration (when
bounded_actionsconfigured) - 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-chaincommand- 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)