Treeship
About

Changelog

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

The latest Treeship releases are documented here.

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

Unreleased

0.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 verify grades it. treeship attest handoff --verified <presentation> --challenge <nonce> re-runs the core presentation verifier and, only on a clean live verdict, signs a custody block: 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 --from another; --verified without --challenge is a parser error. --custody-reason same_computer records the shared-keystore case as what it is.

    verify grades the block itself rather than echoing it: custody: live only when the block carries the digest and the nonce a live check produces; a live without them, an unknown grade, and no block at all all print as asserted with the reason. --format json carries the same grade as checks[].custody.live. Canonical bytes of every existing handoff are unchanged.

  • --close-loop <session_id> binds sealed-session evidence to a handoff. The session's receipt.json digest 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 admitTask passes, @treeship/a2a mints the receiver-signed custody: live handoff and returns it as handoffId; the opt-out path never gets one. handoff envelopes are validated on parse. treeship_attest_handoff in @treeship/mcp takes verified + 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-handshake skill 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 handoff records --artifacts[0] as the storage parent, but handoff/v1 has no parentId, so verify reported "claims parent (none)" and chain_linkage_ok: false on correctly-signed evidence. The signed artifacts list is the edge, the same shape as receipt.v1's subject.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 signed session.start that binds the run, and the Merkle evidence that the declaration pre-existed it -- existed separately, and nothing composed them. verify_workflow_run does, and it is now the only place the pre-existence grade is decided.

    This closes a hole rather than adding a feature. evaluate_workflow_conformance reads pre_existence.grade off its input, and its doc comment made not-lying an obligation on callers. An observation set that claimed checked with placeholder checkpoint ids got a checked report. The composed path discards whatever the input claims and replaces it with what the supplied evidence proves, so a run with no --proof reports asserted no matter what its file says. Omitting the proof is allowed and costs the run its checked grade; 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.

    --strict exits 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 json output; one never summarizes another.

  • Observation sets can be derived from verified actions instead of typed by hand. treeship workflow verify --run read a file somebody wrote, which meant the attribution rule -- which declared node an action belongs to -- lived outside the trust boundary entirely. derive_observed_run builds 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_tools covers the signed action label. Exactly one admitting node is the only case that earns checked, 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 at captured, 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_tools covers 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's budget.max_actions, which counts unique signed-action references, so duplicates are refused during derivation -- validate_run only dedupes within a single attempt.

    Derivation leaves pre_existence asserted. verify_workflow_run remains the only place that grades ordering.

  • The recovered workflow conformance verifier. workflow_conformance.rs, its golden fixtures, and the workflow.v1 predicate 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-wasm was rewritten by whatever wasm-opt the build machine happened to have. wasm-pack runs wasm-opt on the package it just built whenever binaryen is on PATH. The release workflow installed binaryen from apt, which on Ubuntu noble is 108-1 -- a 2022 build predating the bulk-memory-opt and call-indirect-overlong features the module declares. It lowered the externref table to a non-growable one, so require() threw WebAssembly.Table.grow(): failed to grow table by 4 on first use. A machine with no wasm-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's wasm-opt pass is now disabled for core-wasm, so the published nodejs artifact is wasm-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-pack was already pinned with cargo install --locked specifically 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.toml pins wasm32-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/verify threw 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_externrefs was bound to the funcref table, which wasm-pack emits with min == max and 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.sh now 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.sh packs a local build, and the post-publish smoke installs the published CLI — a Rust binary that never touches the wasm. So a broken @treeship/verify shipped twice under green checks. The release now installs @treeship/verify from the registry after publishing and calls a verify function, because the wasm loads lazily and an import proves nothing. (#340)
  • release.sh prepare could not update the lockfiles it exists to update, and npm ci then failed on main. --package-lock-only resolves against the registry, so it failed on the unpublished version being released. Prepare now writes the declared range offline; refresh-lockfiles fills in resolved entries after publish. The sync check also compared only the declared range, not the installed entry npm ci actually 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 view is 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 --config a user was told to move their global keystore while the broken one stayed in place. The command also did not work — moving keys aside leaves config.json, so init refused as "already initialized" while attest said to run init. And it promised existing receipts stay verifiable; they report unknown key until 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_approval in the Python SDK could not mint an approval. It forwarded none of the scoping arguments the CLI requires and passed expires_in where the CLI takes expires_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 json printed 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 1h signed a timestamp already in the past. --expires takes 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 init told 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 --help promised a verdict the command does not produce. It documented promoting cards to Verified; the code deliberately sets Active. (#330)
  • release.sh prepare could not update the npm lockfiles it exists to update. npm install --package-lock-only resolves 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 and release.sh refresh-lockfiles fills 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-wasm threw 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/verify and every SDK method that calls it. build-npm.sh ran only wasm-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 an exports map routing Node to a nodejs build. (#320)
  • treeship session event --format json exited 0 and printed nothing. It built the response and passed it to printer.info, which returns early in JSON mode. Every SDK wrapper reading event_id got undefined from an empty document, with a success exit code. (#311)
  • treeship present panicked on a fresh onboard. range end index N out of range — checkpoints load from ~/.treeship/merkle regardless of --config while artifacts come from the scoped store, so the two could describe different histories. Now a typed error naming both sizes. (#313)
  • grant show and grant list put 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)
  • profile and history reported 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 revoke mints a signed grant_revocation.v1; verify honors 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 carries dock_id and rekor_index so a client can check inclusion itself; and grant show --check-log walks 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-arm runner so a broken target blocks merge rather than failing on release day. (#318)
  • verify gates on authority and reports what was granted but never used, so authority_ok: true can no longer mean "checked nothing". (#297)
  • ship.session.event() in the TypeScript SDK. (#203, contributed by @piyushpathakqa)

Changed

  • Hub /v1/stats labels agent counts as claims. They are derived from AgentGraph nodes 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_total is 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_authority on a room is recorded and not enforced. verify does not read it, so a receipt naming host-only and 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.v1 landing mid-chain reported chain SIGNED LINKAGE BROKEN — possible tampering against correctly-signed evidence. mint_session_record writes the parent as subject.artifactId; the linkage check looked only for a top-level parentId. 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_dir were always absolute, so mv ~/.treeship ~/.treeship.bak produced 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 events ticks 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 over treeship session; a room is a session whose participant set evolves via invitations. invitation_authority is carried and signed but not yet enforced — conformance checking is the follow-up.
  • room rides inside the signed receipt. RoomInfo on SessionManifest and mirrored into the DSSE-signed session.v1, so invitation_authority is attested rather than living only in an editable local file.
  • The room roster is derived, not stored. room participants walks two-signature participant artifacts instead of reading a list from session.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-response proves the joining agent controls its key now, not whenever join ran. Omitting both countersigns exactly as before.
  • treeship session mint-challenge. 128 bits from OsRng, 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.
  • Custody on the session receipt. Records when a service signed on an actor's behalf rather than the actor signing for itself. Deliberately a separate axis from attestation_class, which grades how evidence was captured; this grades who held the key. Absent means self-custody, so existing receipts are byte-identical.
  • execution_identity on wrapped commands. Resolved executable path, sha256, argv, cwd, uid/gid. git is git only until PATH says 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.json records 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. AgentGraph depth calculation recursively followed untrusted parent_agent_instance_id links and only cached a node after visiting its parent. A self-parent or A -> B -> A cycle therefore recursed until the process crashed while composing the receipt. Depth calculation is now iterative, malformed cycles are recorded in invalid_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_tools can no longer deserialize as an omitted default.

Added

  • Pure workflow-conformance reducer. treeship-core now validates the minimal workflow.v1 graph and compares it with already-verified node observations. It reports path deviations, missing terminals, actor/tool authority deviations, bounded-loop breaches, and checked | captured | asserted provenance as separate axes. Undeclared graph cycles and empty evidence fail closed. Seven golden reports pin the contract. workflow.v1 is now a registered predicate whose nested graph runs the same full validator before the generic attest receipt path signs it. Real checkpoint ordering can be checked with verify_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 signed session.start root action. The manifest and composed session receipt mirror the reference, while verify_first_run_workflow_binding independently 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.
  • RoomInfo on SessionManifest. Optional room field (room_id, host_pubkey, invitation_authority, workflow_ref, checkpoint_cadence, participants) following the schema proposed in docs/specs/agent-invitations-rooms.md Phase 2. Purely additive — absent on ordinary sessions and on manifests written before this field existed. No CLI surface yet (treeship room create/status/participants is 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 create is sugar over session start that also populates RoomInfo (fresh room_id, host pubkey, --invitation-authority host-only|delegated|open, --delegate, --workflow-ref, --checkpoint-every); room status shows session status plus room fields and errors clearly on a plain (non-room) session; room participants lists the room's finalized (two-signature) joins by reading each participant artifact. treeship session invite/join/countersign are unchanged except that countersign now appends the finalized participant id to room.participants when the active session is a room, which is the only way room participants has real data to show. invitation_authority remains informational-only in this PR — nothing gates any authority decision on it yet. There is no room close: plain treeship session close already 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.md referenced 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, and delegation_depth is 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 show re-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 (--grant or --grant-file), an optional effect block (--effect-confidence, --finality, --readback, --context-snapshot, --resolution-deadline, --on-deadline), and runtime identity. Ancestors are walked from parent_grant_id and carried inline, so a delegated action still verifies offline with no fetch.

    A root grant leaves mandate.chain empty 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_claimed is the honest answer.

Fixed

  • treeship-core reaches crates.io again. It did not publish in v0.22.0 while npm and PyPI did — packages/core/src/session/package.rs embedded a webfont from design/fonts/, outside the crate root, and cargo publish builds 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 own assets/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.py flags a published crate embedding anything from outside its root, and CI now runs cargo package -p treeship-core, the same verify step publish does.

Notes

  • Revocation is still unchecked, so the authority axis reports unverified rather than pass and 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 of treeship/action/v2 receipts. Pass --grant <id> (workspace store) or --grant-file <path>, optionally bind effect (--effect-confidence, --finality, --readback, …) and runtime identity (--provider, --model, …). treeship verify then 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_id is now content-derived (grn_ + hex(sha256(canonical))[..16]), mirroring artifact_id, so an id is a fact rather than a claim. Grants carry a signed parent_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.chain carries ancestors inline so a delegated action still verifies offline, and resolve_grant_chain derives 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 does verify_grant_chain judge 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 reports holds / widened / unresolvable / not_claimed. Both appear in --format json as per-check authority and delegation_chain objects, plus a top-level authority_ok a CI gate can test in one field. Without a revocation resolver wired the authority verdict is unverified, never pass: 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 to EffectConfidence. 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_effect caps an unbacked Finalized exactly as it already caps an unbacked Verified — those are the only two claims that assert something definite, so the only two an actor can inflate. NotAttempted with a bound input_hash is the "no authority moved" receipt, making a timeout an explicit signed negative instead of silence.
  • Resolution deadlines. Resolution { deadline, on_deadline } and check_resolution bound 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. Indefinite is reported as its own outcome rather than folded into "resolved", because unresolved-with-no-deadline is the failure shape, not the safe default. check_resolution takes now_unix explicitly; a verifier that reached for the system clock would give different answers on replay.

Fixed

  • ChainResolveError and GrantChainError have real Display impls. The CLI was formatting them with {:?}, so operators read AncestorMissing { 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_seed falls back to ~/.treeship/machine_seed first — which exists on any machine that has run treeship init. The test therefore failed for every developer who had actually used the tool while passing on clean CI.
  • docs-drift is green again. Two stacked failures: the feature inventory pointed at packages/core/src/verify.rs, gone since the module became a directory, and docs/specs/receipt-system.md had no row in the specs index. The first masked the second.

Notes

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

Changed

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

Docs

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

Older releases

Read the complete release history in the repository.