Changelog
Recent Treeship releases, newest first. The complete history remains in the repository changelog.
The latest Treeship releases are documented here.
The format follows Keep a Changelog; versioning follows Semantic Versioning. The canonical source and complete release history live in CHANGELOG.md at the repo root. Edit that file, not this page.
Unreleased
0.27.0 (2026-09-02)
Upgrade if you use @treeship/a2a or @treeship/mcp. Foreign work is
now refused until the other agent proves live key control, and the receipt
records that it did. Integrations that pass fromAgent to onTaskReceived
without calling admitTask first will throw; that is the fail-closed
default this release exists to make.
Added
-
A handoff records how custody was established, and
verifygrades it.treeship attest handoff --verified <presentation> --challenge <nonce>re-runs the core presentation verifier and, only on a clean live verdict, signs acustodyblock: the sha256 of the exact presentation bytes, the nonce this ship minted, the card id, the verifier, and the time. A failing presentation refuses to mint rather than downgrading quietly; a presentation for one agent cannot certify a handoff--fromanother;--verifiedwithout--challengeis a parser error.--custody-reason same_computerrecords the shared-keystore case as what it is.verifygrades the block itself rather than echoing it:custody: liveonly when the block carries the digest and the nonce a live check produces; alivewithout them, an unknown grade, and no block at all all print asassertedwith the reason.--format jsoncarries the same grade aschecks[].custody.live. Canonical bytes of every existing handoff are unchanged. -
--close-loop <session_id>binds sealed-session evidence to a handoff. The session'sreceipt.jsondigest is signed into the statement; a session the machine cannot find is refused. Evidence never upgrades custody. -
The A2A gate records what it verified. After
admitTaskpasses,@treeship/a2amints the receiver-signedcustody: livehandoff and returns it ashandoffId; the opt-out path never gets one.handoffenvelopes are validated on parse.treeship_attest_handoffin@treeship/mcptakesverified+challenge. -
Every harness skill teaches the gate. The universal skill, the Hermes, OpenClaw, Kimi and Perplexity skills, the Claude Code / Codex / Cursor integration notes, a new
treeship-handshakeskill in the Claude Code plugin, and the Grok Bot skill all carry the same four steps: mint the nonce, verify the presentation, refuse on any failure, record the verify.
Fixed
- Every handoff the CLI can mint failed chain linkage.
attest handoffrecords--artifacts[0]as the storage parent, but handoff/v1 has noparentId, soverifyreported "claims parent (none)" andchain_linkage_ok: falseon correctly-signed evidence. The signedartifactslist is the edge, the same shape as receipt.v1'ssubject.artifactId; the walk now reads it.
Blocked
- Grok Bot approvals (slice 3b of the A2A spec): the decision is visible only in Grok's app UI, so the packaged skill states the boundary instead of recording an approval nobody observed.
0.26.0 (2026-09-01)
Added
-
treeship workflow verify: one fail-closed path from a signed declaration to a conformance report. The three verification pieces -- declaration signature and validity, the signedsession.startthat binds the run, and the Merkle evidence that the declaration pre-existed it -- existed separately, and nothing composed them.verify_workflow_rundoes, and it is now the only place the pre-existence grade is decided.This closes a hole rather than adding a feature.
evaluate_workflow_conformancereadspre_existence.gradeoff its input, and its doc comment made not-lying an obligation on callers. An observation set that claimedcheckedwith placeholder checkpoint ids got acheckedreport. The composed path discards whatever the input claims and replaces it with what the supplied evidence proves, so a run with no--proofreportsassertedno matter what its file says. Omitting the proof is allowed and costs the run itscheckedgrade; it is not an error, because an unproven ordering claim is a weaker report rather than a malformed one.The workflow reference and the first-run id are both read from artifacts whose signatures were just checked, never from caller-supplied strings.
--strictexits non-zero on any deviation, gap, or exceeded limit. It is off by default: the spec is explicit that there is no single workflow score, so the pass/fail policy belongs to the caller and the substrate reports the file. Path, authority, and loop findings stay on separate axes in both the text and--format jsonoutput; one never summarizes another. -
Observation sets can be derived from verified actions instead of typed by hand.
treeship workflow verify --runread a file somebody wrote, which meant the attribution rule -- which declared node an action belongs to -- lived outside the trust boundary entirely.derive_observed_runbuilds the observation set from verified actions, so that rule is now code with tests against it.Attribution is by admissibility, never by label. A node admits an action when its executor matches the signed actor (or a capability the verified mandate carried) and its
allowed_toolscovers the signed action label. Exactly one admitting node is the only case that earnschecked, and a recorded node label is not consulted there at all: a runtime cannot relabel work the declaration already places. A label may only pick within an already admissible set, which caps that attempt atcaptured, and a label naming a node that cannot admit the action is reported rather than used as a tiebreak.Two ways this could have reported a clean run for a dirty one, both now regression tests. Dropping an action that no node's
allowed_toolscovers would have hidden an out-of-scope tool, so such an action stays attributed by executor match and the authority axis still reports it. Accepting a repeated action reference would have let one action pay twice against a loop'sbudget.max_actions, which counts unique signed-action references, so duplicates are refused during derivation --validate_runonly dedupes within a single attempt.Derivation leaves
pre_existenceasserted.verify_workflow_runremains the only place that grades ordering. -
The recovered workflow conformance verifier.
workflow_conformance.rs, its golden fixtures, and theworkflow.v1predicate schema had existed only in a local stash since 2026-08-18. They are on main.
0.25.3 (2026-08-31)
Upgrade if you use @treeship/verify, @treeship/sdk, or any npm package
that verifies in Node. 0.25.2 never reached npm: the gate added in 0.25.2
caught that the artifact still did not load and refused to publish it, so npm
remained on the broken 0.25.1 while crates.io and PyPI moved to 0.25.2. This
release fixes the underlying cause and brings every registry back in line.
Fixed
-
@treeship/core-wasmwas rewritten by whateverwasm-optthe build machine happened to have.wasm-packrunswasm-opton the package it just built whenever binaryen is onPATH. The release workflow installed binaryen from apt, which on Ubuntu noble is108-1-- a 2022 build predating thebulk-memory-optandcall-indirect-overlongfeatures the module declares. It lowered the externref table to a non-growable one, sorequire()threwWebAssembly.Table.grow(): failed to grow table by 4on first use. A machine with nowasm-opt, or a current one, built the same commit correctly, which is why 0.25.0 and 0.25.1 both shipped broken.wasm-pack'swasm-optpass is now disabled forcore-wasm, so the published nodejs artifact iswasm-bindgen's output and nothing else.
Changed
- The release and CI workflows install a checksummed binaryen 132 tarball
instead of an unpinned apt package.
wasm-packwas already pinned withcargo install --lockedspecifically so the release job would not run whatever a CDN served at job runtime; the tool that rewrites the artifact is now pinned to the same standard. - CI installs binaryen at all, which it previously did not. The two pipelines built differently, so every gate ran against an artifact that was never the one published.
rust-toolchain.tomlpinswasm32-unknown-unknown. This did not cause the failure above, but it was the other unpinned input feeding the same artifact.
0.25.2 (2026-08-30)
Upgrade if you use @treeship/verify, @treeship/sdk, or any npm package
that verifies in Node. Those packages could not verify anything at all in
0.25.1 or 0.25.0.
Fixed
@treeship/verifythrew on first use in Node.WebAssembly.Table.grow(): failed to grow table by 4, from a clean install, on Node 20.19.5, 22.17.1, 22.20.0, 22.23.1 and 23.2.0. This is the failure 0.25.0 was released to fix. The structural half of that fix worked —require()resolves to the nodejs build rather than a bundler-only one — but the binary it resolved to was miscompiled: the export named__wbindgen_externrefswas bound to the funcref table, which wasm-pack emits withmin == maxand therefore cannot grow. Same rustc, same wasm-bindgen, same wasm-pack as a local build that works; the difference is the host platform, which made it invisible to anyone building on a Mac and fatal to everything published from Linux CI.build-npm.shnow loads its own nodejs output and calls into it before packaging, so a build that does not work cannot be packaged. (#341)- Nothing tested the published packages. Every check ran against an
artifact CI had just built:
check-verify-js-loads.shpacks a local build, and the post-publish smoke installs the published CLI — a Rust binary that never touches the wasm. So a broken@treeship/verifyshipped twice under green checks. The release now installs@treeship/verifyfrom the registry after publishing and calls a verify function, because the wasm loads lazily and an import proves nothing. (#340) release.sh preparecould not update the lockfiles it exists to update, andnpm cithen failed on main.--package-lock-onlyresolves against the registry, so it failed on the unpublished version being released. Prepare now writes the declared range offline;refresh-lockfilesfills in resolved entries after publish. The sync check also compared only the declared range, not the installed entrynpm ciactually rejects on, so it reported green on a tree where every JS job failed. (#337)- The npm preflight checked that packages exist, not that CI can publish
them.
npm viewis an unauthenticated read; npm returns 404 rather than 403 for an unauthorized write. A package bootstrapped by hand but never given a trusted publisher passed the check and then failed mid-publish, leaving 0.25.1 split across three registries. Preflight now checks provenance, which is the observable trace of a successful CI publish. (#335) - Keystore recovery advice named the wrong keystore. The path came from
$HOME/.treeship, but that is the store root only in the default layout, so under--configa user was told to move their global keystore while the broken one stayed in place. The command also did not work — moving keys aside leavesconfig.json, soinitrefused as "already initialized" whileattestsaid to runinit. And it promised existing receipts stay verifiable; they reportunknown keyuntil the old keystore is restored. All three corrected, and every command in the message is now executed verbatim in testing. (#338)
0.25.1 (2026-08-21)
Upgrade if you use the Python SDK or treeship wrap --format json. Two
SDK entry points could not work at all — not misconfigured, structurally
unable to succeed — which is the same shape as the bug that forced 0.25.0.
Fixed
attest_approvalin the Python SDK could not mint an approval. It forwarded none of the scoping arguments the CLI requires and passedexpires_inwhere the CLI takesexpires_at, so every call that reached the subprocess was rejected. The scoping arguments (allowed_actions,allowed_actors,allowed_subjects,max_uses,unscoped) are now forwarded, and an unscoped approval must say so explicitly rather than becoming one by omission. (#331)treeship wrap --format jsonprinted nothing parseable. It emitted the result through a path that returns early in JSON mode, and interleaved the wrapped command's own stdout into the document, so callers got either an empty string or invalid JSON. The result is now emitted as JSON and the child process's stdout is routed to stderr, leaving stdout a single document. (#332)treeship attest --expires 1hsigned a timestamp already in the past.--expirestakes an absolute RFC 3339 time; a duration parsed to epoch zero and was signed into the artifact, producing an approval born expired with a valid signature. Durations are now refused with a message naming the expected form. (#328)treeship inittold a fresh directory it was "already initialized." The guard tested the resolved config path, which falls back to the global config, so any directory on a machine with a global Treeship config was reported as already set up. It now distinguishes a local workspace from the global fallback. (#333)treeship setup --helppromised a verdict the command does not produce. It documented promoting cards toVerified; the code deliberately setsActive. (#330)release.sh preparecould not update the npm lockfiles it exists to update.npm install --package-lock-onlyresolves against the registry, so it failed on the version being released, which is not published yet — the step added to prevent v0.25.0's out-of-sync lockfiles could never run during a release. Prepare now writes the declared range offline andrelease.sh refresh-lockfilesfills in resolved entries after publish. The sync check also covered only four hardcoded packages; it now discovers all ten, which surfaced three runtime-acceptance lockfiles five releases behind.
Documentation
- Documentation QA pass across 127 pages, plus eight gates that keep the docs and the CLI from drifting apart: internal links, API routes, verify rows, event types, predicates, command names, flags, and option tables. Four of the fixes above were found by those gates. (#321)
0.25.0 (2026-08-19)
Upgrade if you use any npm package. @treeship/core-wasm@0.24.0 cannot be
loaded in Node at all, and npm does not allow republishing a version — a new
release is the only way to fix it.
Fixed
@treeship/core-wasmthrew on import in Node.WebAssembly.Table.grow(): failed to grow table by 4, reproduced from a clean install on Node 22.20.0 and 22.23.1. The module never finished loading, so every export was unreachable — taking down@treeship/verifyand every SDK method that calls it.build-npm.shran onlywasm-pack --target bundler, which emits an import that needs a bundler to resolve. It survived a release because the one consumer anyone exercised was the website, which vendors and bundles the wasm. Now dual-target with anexportsmap routing Node to a nodejs build. (#320)treeship session event --format jsonexited 0 and printed nothing. It built the response and passed it toprinter.info, which returns early in JSON mode. Every SDK wrapper readingevent_idgotundefinedfrom an empty document, with a success exit code. (#311)treeship presentpanicked on a fresh onboard.range end index N out of range— checkpoints load from~/.treeship/merkleregardless of--configwhile artifacts come from the scoped store, so the two could describe different histories. Now a typed error naming both sizes. (#313)grant showandgrant listput a clean checkmark on revoked grants. A signature stays valid forever; revocation is what makes a grant stop counting. Both commands reported the signature and neither consulted the revocation receipt the CLI had just written. (#314)profileandhistoryreported 0 where the answer was "unknown". An empty result is not a zero. (#302)- The MCP bridge recorded raw error text into signed receipts. Everything beside it was a digest; an error message routinely contains the thing that failed. Now digested, with raw text behind an explicit opt-in. (#309)
Added
- Revocation works end to end.
treeship grant revokemints a signedgrant_revocation.v1;verifyhonors it locally and from a hub's published list; the hub publishes real revocations over an indexed lookup and states in the response that absence is not evidence; each entry carriesdock_idandrekor_indexso a client can check inclusion itself; andgrant show --check-logwalks the dock's consistency chain and re-verifies every link with RFC 6962. Unknown stays a third state throughout — never a quiet pass. (#303, #305, #306, #314, #315, #316, #317) - Linux arm64 binaries, built and executed on a native
ubuntu-24.04-armrunner so a broken target blocks merge rather than failing on release day. (#318) verifygates on authority and reports what was granted but never used, soauthority_ok: truecan no longer mean "checked nothing". (#297)ship.session.event()in the TypeScript SDK. (#203, contributed by @piyushpathakqa)
Changed
- Hub
/v1/statslabels agent counts as claims. They are derived fromAgentGraphnodes in uploaded receipts, which the hub does not cryptographically verify. Artifact, dock and session counts are facts about the hub's own state and are unaffected.claimed_totalis the honest name; the old keys remain for one release. (#308) - The hub indexes on fields derived from the envelope, never accepted from the caller — so a resolver filters on content rather than an uploader's claim about itself. (#307)
Known limitations
invitation_authorityon a room is recorded and not enforced.verifydoes not read it, so a receipt naminghost-onlyand an invite minted by a non-host both verify clean. Fail-safe today because nothing trusts the field, but do not treat it as access control.- A receipt fetched by URL is verified structurally only — Merkle root, inclusion proofs, leaf count, timeline order. Signatures and issuer are not checked from that source; use the local artifact form for those.
0.24.0 (2026-08-10)
Trusted Rooms, end to end — plus three fixes worth upgrading for.
Fixed
- Legitimate receipts no longer report tampering. Every
receipt.v1landing mid-chain reportedchain SIGNED LINKAGE BROKEN — possible tamperingagainst correctly-signed evidence.mint_session_recordwrites the parent assubject.artifactId; the linkage check looked only for a top-levelparentId. Measured on a real store: 4 of 4 receipts affected, 9 of 9 actions fine. A false tampering alarm is worse than a noisy check — it teaches operators to ignore the one signal the product exists to give. - Keystores are portable.
storage_dir/keys_dirwere always absolute, somv ~/.treeship ~/.treeship.bakproduced a "backup" whose config still pointed at whatever replaced it. Relative paths now resolve against the config's own directory. Absolute paths are untouched. - The Claude Code plugin monitor stopped shouting. It emitted a line
whenever any counter changed, and
eventsticks on essentially every tool call — one working session produced well over a hundred notifications. Now reports state transitions only: two lines for a full session.
Added
treeship room create/status/participants. Sugar overtreeship session; a room is a session whose participant set evolves via invitations.invitation_authorityis carried and signed but not yet enforced — conformance checking is the follow-up.roomrides inside the signed receipt.RoomInfoonSessionManifestand mirrored into the DSSE-signedsession.v1, soinvitation_authorityis attested rather than living only in an editable local file.- The room roster is derived, not stored.
room participantswalks two-signature participant artifacts instead of reading a list fromsession.json— which anyone with write access could edit, and which was silently incomplete whenever the best-effort append was skipped. - Liveness challenge on countersign. Opt-in
--challenge/--challenge-responseproves the joining agent controls its key now, not wheneverjoinran. Omitting both countersigns exactly as before. treeship session mint-challenge. 128 bits fromOsRng, because the challenge flag shipped without anything to produce a value and the help text suggested a six-character example. Weak nonces are now refused at both ends: a guessable nonce can be pre-signed, and the proof means nothing.Custodyon the session receipt. Records when a service signed on an actor's behalf rather than the actor signing for itself. Deliberately a separate axis fromattestation_class, which grades how evidence was captured; this grades who held the key. Absent means self-custody, so existing receipts are byte-identical.execution_identityon wrapped commands. Resolved executable path, sha256, argv, cwd, uid/gid.gitisgitonly untilPATHsays otherwise; two receipts with identical argv and different digests are two different events. Environment names only, never values.- A public Go client for the Hub (
pkg/dpop,pkg/hubclient). DPoP proof signing existed only in Rust, so a Go service had no path to the API. Tested against the real server verifier, not a stub. - The capability index now covers Hub endpoints and core types. CI fails
when a live endpoint belongs to no feature entry, and a generated
capability-map.jsonrecords every reachable surface — 34 CLI commands, 22 routes, 109 wire types.
Documentation
- New concept pages: what a receipt proves (and does not), logs vs receipts, effect receipts, secrets and redaction, authority delta, wrapping real commands. The redaction page documents, with a measured table, which secret shapes the scrubber catches and which it misses.
Unreleased
Fixed
- Cyclic agent-parent events no longer overflow the stack during session close.
AgentGraphdepth calculation recursively followed untrustedparent_agent_instance_idlinks and only cached a node after visiting its parent. A self-parent orA -> B -> Acycle therefore recursed until the process crashed while composing the receipt. Depth calculation is now iterative, malformed cycles are recorded ininvalid_parent_cycles, and cyclic nodes retain depth 0 instead of being presented with a fabricated hierarchy. - Workflow conformance now fails closed at five trust boundaries. Session
close re-verifies the signed root instead of signing a mutable manifest's
substituted workflow reference; pre-existence proofs require one checkpoint
signing identity rather than composing unrelated trusted logs; loop action
budgets count verified action artifacts instead of tool labels; every
undeclared observed node produces a deviation, including a one-node run; and
schema-required
allowed_toolscan no longer deserialize as an omitted default.
Added
- Pure workflow-conformance reducer.
treeship-corenow validates the minimalworkflow.v1graph and compares it with already-verified node observations. It reports path deviations, missing terminals, actor/tool authority deviations, bounded-loop breaches, andchecked | captured | assertedprovenance as separate axes. Undeclared graph cycles and empty evidence fail closed. Seven golden reports pin the contract.workflow.v1is now a registered predicate whose nested graph runs the same full validator before the genericattest receiptpath signs it. Real checkpoint ordering can be checked withverify_workflow_pre_existence: trusted signatures, both inclusion proofs, leaf position, and consistency must all pass.treeship session start --workflow-ref art_...now validates a locally signed declaration before writing any run state and binds the artifact ID inside the signedsession.startroot action. The manifest and composed session receipt mirror the reference, whileverify_first_run_workflow_bindingindependently checks the trusted root signature, content-derived run ID, action type, and exact declaration reference. Automatic checkpoint composition, external workflow-authority trust, and CLI conformance verification remain follow-ups. RoomInfoonSessionManifest. Optionalroomfield (room_id,host_pubkey,invitation_authority,workflow_ref,checkpoint_cadence,participants) following the schema proposed indocs/specs/agent-invitations-rooms.mdPhase 2. Purely additive — absent on ordinary sessions and on manifests written before this field existed. No CLI surface yet (treeship room create/status/participantsis the follow-up); this lands the data model first so the wire format is settled.treeship room create/status/participants. The CLI surface promised above:room createis sugar oversession startthat also populatesRoomInfo(freshroom_id, host pubkey,--invitation-authority host-only|delegated|open,--delegate,--workflow-ref,--checkpoint-every);room statusshows session status plus room fields and errors clearly on a plain (non-room) session;room participantslists the room's finalized (two-signature) joins by reading each participant artifact.treeship session invite/join/countersignare unchanged except thatcountersignnow appends the finalized participant id toroom.participantswhen the active session is a room, which is the only wayroom participantshas real data to show.invitation_authorityremains informational-only in this PR — nothing gates any authority decision on it yet. There is noroom close: plaintreeship session closealready works on a room since a room is just a session.
Documentation
- Workflow conformance now has an executable design contract. Added the
missing
docs/specs/workflow-declarations.mdreferenced by commitments, rooms, vision, and the v0.11 changelog. Seven golden reports pin valid runs, undeclared edges, missing terminals, loop-cap breaches, asserted edge evidence, declaration pre-existence, and out-of-scope tools before broader CLI wiring.
0.23.0 (2026-08-05)
The emission release. v0.21 and v0.22 built a verifier that could check mandates, delegation chains, effect finality, and resolution deadlines — and none of it was reachable, because nothing could mint a grant or emit an action/v2 receipt. Both halves now exist, so the loop closes:
treeship grant issue --scope 'payments.*' --audience acme --expiry 2027-12-31T23:59:59Z
treeship grant issue --parent grn_… --scope payments.charge --audience acme --expiry 2027-06-30T00:00:00Z
treeship attest action --v2 --actor agent://worker --action payments.charge --grant grn_… \
--effect-confidence not_verified --finality initiated
treeship verify last --format json"authority": { "outcome": "unverified", "reasons": ["revocation could not be checked: …"] },
"delegation_chain": { "hops": 2, "outcome": "holds" },
"resolution": { "outcome": "indefinite" },
"effect": { "effective_finality": "initiated" }Added
-
treeship grant issue/list/show. Mint a signed capability grant, or delegate a narrower one with--parent. Ids are content-derived (grn_+hex(sha256(canonical))[..16]), so a parent pointer is a hash commitment to one specific grant rather than a reference to a name anyone could claim. Grants are workspace-scoped, resolved from the active config the same way the approval-use journal is.Attenuation is enforced at mint, not only at verify: issuing refuses a child that widens scope, outlives its parent, changes audience, or exceeds
max_delegation, anddelegation_depthis derived from the parent rather than accepted from the caller. A verifier catching an invalid chain later is the backstop; refusing to create one is the fix, because an invalid chain that exists is one somebody will eventually be asked to trust. Also refuses an expiry already in the past — a grant that can never verify is never minted deliberately.grant showre-derives the id and re-checks the signature off disk rather than trusting the file, and will open an unsound grant in order to report it as unsound. -
treeship attest action --v2. Emits a real action/v2 receipt carrying the mandate from a grant (--grantor--grant-file), an optional effect block (--effect-confidence,--finality,--readback,--context-snapshot,--resolution-deadline,--on-deadline), and runtime identity. Ancestors are walked fromparent_grant_idand carried inline, so a delegated action still verifies offline with no fetch.A root grant leaves
mandate.chainempty rather than carrying its lone leaf. A one-element chain has no adjacent pair to check, so reporting it as "attenuation holds" would name a check that never ran;not_claimedis the honest answer.
Fixed
-
treeship-corereaches crates.io again. It did not publish in v0.22.0 while npm and PyPI did —packages/core/src/session/package.rsembedded a webfont fromdesign/fonts/, outside the crate root, andcargo publishbuilds from a tarball containing only files under that root. The include landed after v0.21.0, which is why the previous release was clean. Each crate now vendors the font under its ownassets/fonts/.Two guards, because the version preflight could never have caught this — every version was correct, the crate simply could not be built:
scripts/check-vendored-fonts.pyflags a published crate embedding anything from outside its root, and CI now runscargo package -p treeship-core, the same verify step publish does.
Notes
- Revocation is still unchecked, so the authority axis reports
unverifiedrather thanpassand names the layer it could not check. The hub endpoint remains an unsigned, hardcoded-empty stub; reading it would convert an honest "I don't know" into a false "not revoked" for every grant ever issued. - Capability grants ship as beta, not stable, for that reason.
Added
treeship attest action --v2— CLI emission oftreeship/action/v2receipts. Pass--grant <id>(workspace store) or--grant-file <path>, optionally bind effect (--effect-confidence,--finality,--readback, …) and runtime identity (--provider,--model, …).treeship verifythen surfaces authority / effect / runtime on a receipt the CLI itself produced — the half that was verification-only through 0.22.
0.22.0 (2026-08-05)
The delegation-and-completion release. v0.21 answered what was authorized and what happened. This one answers the two questions sitting behind those: was the authority legitimately delegated, and did the change actually land?
CLI emission of action/v2 receipts still does not exist. Everything below is
verification-side and reachable today only from treeship-core and the SDKs; a
CLI user cannot yet produce a receipt that exercises it. That is the whole of
0.23.
Added
- Grant chain resolution.
grant_idis now content-derived (grn_+hex(sha256(canonical))[..16]), mirroringartifact_id, so an id is a fact rather than a claim. Grants carry a signedparent_grant_id, making a parent pointer a hash commitment to one specific grant instead of a reference to a name anyone could also assert.Mandate.chaincarries ancestors inline so a delegated action still verifies offline, andresolve_grant_chainderives the order from those signed links rather than trusting the order the carrier supplied — reordering becomes a no-op, truncation surfaces as a missing ancestor, and splicing surfaces as an unreachable extra. Only then doesverify_grant_chainjudge attenuation, on a chain whose shape is already proven. - Authority and delegation on
treeship verify. A new authority line reports whether an action was in scope, in window, and not revoked, and a chain line reportsholds/widened/unresolvable/not_claimed. Both appear in--format jsonas per-checkauthorityanddelegation_chainobjects, plus a top-levelauthority_oka CI gate can test in one field. Without a revocation resolver wired the authority verdict isunverified, neverpass: claiming a grant is live because nobody looked is the false pass this verifier exists to refuse. EffectFinality— lifecycle, separate from evidence.NotAttempted / Initiated / Finalized / Failed / Indeterminate, orthogonal toEffectConfidence. Collapsing the two lets a receipt be accurate in every field and false as a composite: a write accepted, acknowledged, assigned an id, served back on read, and never committed.verify_effectcaps an unbackedFinalizedexactly as it already caps an unbackedVerified— those are the only two claims that assert something definite, so the only two an actor can inflate.NotAttemptedwith a boundinput_hashis the "no authority moved" receipt, making a timeout an explicit signed negative instead of silence.- Resolution deadlines.
Resolution { deadline, on_deadline }andcheck_resolutionbound an unresolved effect. An effect that never resolves emits nothing forever, which is worse than a timeout — a timeout at least produces a countable transition.Indefiniteis reported as its own outcome rather than folded into "resolved", because unresolved-with-no-deadline is the failure shape, not the safe default.check_resolutiontakesnow_unixexplicitly; a verifier that reached for the system clock would give different answers on replay.
Fixed
ChainResolveErrorandGrantChainErrorhave realDisplayimpls. The CLI was formatting them with{:?}, so operators readAncestorMissing { parent_grant_id: "grn_..." }— an internal representation, not a diagnosis.- The AUD-19 seed test is hermetic. It relied on first-open minting a
co-located seed, but
read_or_create_machine_seedfalls back to~/.treeship/machine_seedfirst — which exists on any machine that has runtreeship init. The test therefore failed for every developer who had actually used the tool while passing on clean CI. docs-driftis green again. Two stacked failures: the feature inventory pointed atpackages/core/src/verify.rs, gone since the module became a directory, anddocs/specs/receipt-system.mdhad no row in the specs index. The first masked the second.
Notes
- Both new
Effectfields are optional and skipped when absent, so existing v2 receipts keep byte-identical canonical bytes. - Known wart, deliberately left:
EffectConfidenceserializessnake_case, so a receipt carriesnot_verifiedwhile--format jsonreportsnot-verified. Changing it breaks a field emitted since v0.21 and is a decision to make on purpose, not as a side effect of adding a neighbouring one. Documented oneffect_label. - Revocation remains unresolvable by design. The hub endpoint is an unsigned,
hardcoded-empty stub; wiring a resolver to it would convert an honest
Unverifiedinto a falseNotRevokedfor every grant ever issued.
0.21.0 (2026-07-21)
The accountability release. Treeship starts answering not just "was this signed?" but "what was it authorized to do, what did it actually touch, and how confident are we the effect really happened?", with the verdict separating operational confidence from cryptographic validity.
Added
- action/v2 receipt format (verification path). An additive superset of
action.v1: the signed payload now carries a per-hopmandate(the authority an action ran under, grantor, scope, audience, expiry, delegation depth, revocation) and aneffectblock (what the action touched, input/output/ readback hashes, bytes moved, cost, side effects, context snapshot). Fully optional and backward-compatible; existing v1 artifact IDs are unaffected. This release ships the format, the core types, and verification; CLI emission of v2 receipts is a follow-up. effect_confidence, the honest effect verdict ("the ack is not the act"). An actor declares how confident it is that an action's real-world effect happened (verified/partial/ambiguous/unknown/not_verified).verify_effectreconciles that claim against independent, actor-unmintable evidence (an externalreadback, or a witness a trusted authority vouches for): aVerifiedclaim with no such evidence is downgraded, never taken on faith, and the verdict separates operational confidence from cryptographic validity. Honest lesser claims pass through unchanged, the verifier blocks inflation, it does not erase an actor's own hedging.- Runtime identity on action/v2. Optional
provider/model/tool_schema_hash/system_prompt_hash, bound into the signed statement, so a verifier holding a pinned expectation can detect a swapped model, an altered tool set, or a changed system prompt after the fact. - Witness map on effects. Independent corroborating observers per effect. A
witness lifts the confidence ceiling only once a
WitnessAuthorityconfirms its signature against a trusted, non-actor key observing the same post-state; the default authority trusts nothing, so a bundled witness never inflates a verdict on its own. - Unified verifier in the browser and SDKs. Resolution and presentation
verification moved into
treeship-coreand exposed via WASM + the TS/Python SDKs, so browser and edge verification run the same logic as the CLI. - Memory-provenance binding spine. Quarantine-gated approvals (a high-privilege
action's grant is withheld if a memory-provenance check comes back dirty, and the
denial is itself signed), and signed
blocked.v1refusal artifacts that make negative space, what an agent was stopped from doing, and why, first-class evidence.
Changed
treeship trust add --kindaccepts the v0.19 split kindscert_issuer,hub_org, andrevokeralongside the existing ones, closing the pin-exchange gap where a counterparty following the documented flow hit a CLI error.treeship verifysurfaces the action/v2 effect verdict, aruntime:line and aneffect:line on the timeline, and the same verdict in--json, kept clearly distinct from the signature check.
Docs
- Full docs truth-sync. Every documented CLI command, Hub API route, and SDK contract regenerated from code and rewritten to match what v0.20+ actually does, plus CI drift gates (generated command matrix, feature inventory, docs routes) that fail on any code-vs-docs divergence, so a docs lie now requires deliberately overriding a failing check rather than simply forgetting.
Older releases
Read the complete release history in the repository.