# Changelog

## Unreleased

## 0.12.0 (2026-06-06)

### Added

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

### Fixed

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

## 0.11.2 (2026-06-05)

### Fixed

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

## 0.11.1 (2026-05-29)

### Added

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

### Fixed

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

## 0.11.0 (2026-05-28)

### Added

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

### Changed (breaking)

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

### Documentation

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

## 0.10.4 (2026-05-17)

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

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

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

### Security

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

### Changed (breaking)

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

### Fixed

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

### Internal

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

### Documentation

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

## 0.10.3 (2026-05-17)

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

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

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

### Security

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

### Added

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

### Changed

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

### Changed (breaking)

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

### Fixed

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

### Internal

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

### Migration notes

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

## 0.10.2 (2026-05-11)

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

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

Two landed PRs ship in this release:

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

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

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

### Added (Treeship Agent Skills — #67)

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

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

### Compatibility

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

## 0.10.1 (2026-05-01)

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

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

Six landed PRs ship in this release:

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

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

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

### Added (keystore permission hardening — PR 1)

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

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

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

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

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

### Added (npm binary integrity — PR 4)

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

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

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

### Honesty rule preserved

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

### Verification (release-gate)

| 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_a1b2c3d4` and proxies to `${HUB_API_URL}/v1/receipt/<id>` for real session ids — bypasses the same-origin self-fetch loop that breaks during local dev / build.

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

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

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

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

### Added (CLI share command — `treeship` PR 4)

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

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

- **`treeship setup --format json`** now returns a real shape. Previously `treeship setup --yes --format json` returned `{}`; the setup function ignored the global `--format` flag. The new `treeship/setup-result/v1` shape carries `schema`, `ship_id`, `config_path`, `detected[...]`, `cards{total, draft, needs_review, active, verified}`, `instrumented[]`, `smoke{ran, passed, error}`, `next_steps[]`, `error`. Every early-exit path populates the accumulator before emitting JSON.
- **Python `Treeship.ensure_cli()` + `Treeship(bot_mode=True)`.** Resolves a working `treeship` binary via env / PATH / cache / npm / GitHub Release in that order. Each step sandboxed-safe: no `sudo`, no network unless steps 4/5 fire, no `PATH` modification.
  ```python
  from treeship_sdk import Treeship
  ts = Treeship(bot_mode=True)  # CLI resolved; no human required
  ```
- **`python -m treeship_sdk.bootstrap_cli --json`** is the agent-friendly shell entry point. Returns `treeship/bootstrap-result/v1` with `ok`, `binary`, `version`, `source` (`env|path|cache|npm-cache|github-release`), `cache_dir`. `--no-install` disables network fallbacks for agents that should fail loudly.
- New module structure under `packages/sdk-python/treeship_sdk/`:
  - `bootstrap.py` — resolution helpers, `BootstrapResult`, `TreeshipBootstrapError`
  - `bootstrap_cli.py` — `python -m` entry point
  - `client.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 `.treeship` tree from whatever the hub returns; demo fixtures that drift from the current schema produce parse errors at verify time (caught and fixed in `treeship.dev` follow-up #12).
- Verification rows never silently downgrade. `treeship/share-result/v1` carries the local verify outcome, including warnings, so an AI agent that just shared a receipt can also tell its caller whether the sealed package verifies cleanly.

### Intentionally NOT included

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

### Migration

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

## 0.9.11 (2026-04-30)

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

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

### Added (docs reliability)

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

### Added (CLI reference catch-up)

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

### Added (SDK + install topology)

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

### Added (process)

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

### Intentionally deferred to a later release

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

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

### Verification

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

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

## 0.9.10 (2026-04-30)

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

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

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

### Fixed (Approval Authority blockers — #57)

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

### Fixed (label hygiene — #57)

- **Renamed `approval-use-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.** `--strict` now promotes every new approval-evidence row from warn → fail alongside the existing `replay-*` rows.

### Added (release-adversarial cadence policy)

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

### Tests

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

### Known follow-ups (non-blocking, deferred)

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

### Intentionally NOT changed

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

## 0.9.9 (2026-04-30)

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

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

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

### Added (Approval evidence schemas — #50)

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

### Added (Local Approval Use Journal — #51)

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

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

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

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

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

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

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

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

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

### Changed (trust semantics)

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

### Intentionally deferred to a later release

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



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

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

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

### Added (discovery + agent inventory)

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

### Added (setup orchestration)

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

### Added (Harness Manager)

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

### Added (integration template profile table)

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

### Added (session report polish)

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

### Added (project-local config discovery and CLI ergonomics)

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

### Changed (trust semantics)

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

### Intentionally deferred to v0.9.9 / later

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

## 0.9.7 (2026-04-29)

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

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

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

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

### Added (release machinery)

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

### Added (CLI ergonomics)

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

### Changed (release tooling)

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

### Fixed

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

## 0.9.6 (2026-04-27)

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

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

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

### Added (approval grant model)

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

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

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

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

### Changed (verification surface)

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

### Added (capture layer)

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

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

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

### Added (cross-verification layer)

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

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

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

### Added (model + provider attribution)

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

### Changed (trust gates)

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

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

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

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

### Changed (security)

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

### Fixed

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

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

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

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

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

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

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

### Added (integrations)

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

### Added (docs)

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

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

### Notes

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

## 0.9.5 (2026-04-25)

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

### Added

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

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

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

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

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

### Changed

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

### Changed (BREAKING for SDK consumers)

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

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

### Added (testing)

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

### Fixed

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

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

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

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

### Added (docs surface)

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

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

### Notes

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

### Known limitations

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

## 0.9.4 (2026-04-21)

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

### Added

- **Zerker Labs Claude Code plugin marketplace.** The treeship monorepo now ships `.claude-plugin/marketplace.json` at the repo root so any user can install the plugin tonight with:
  ```
  claude plugin marketplace add zerkerlabs/treeship
  claude plugin install treeship@treeship
  ```
  Installs to `~/.claude/plugins/cache/treeship/treeship/<version>/`. Every subsequent Claude Code session auto-loads the plugin — SessionStart / PostToolUse / SessionEnd hooks fire, sealed receipts land in `.treeship/sessions/<id>.treeship`, `treeship package verify` passes all integrity checks. Independently verified end-to-end on a fresh scratch project. Anthropic's official-marketplace submission remains a separate track.

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

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

### Fixed

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

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

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

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

### Notes

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

### Known limitations

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

## 0.9.3 (2026-04-20)

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

### Added

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

### Changed

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

### Fixed (post-Codex-review, two passes)

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

### Notes

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

### Known limitations

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

## 0.9.2 (2026-04-20)

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

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

### Release workflow hardening

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

### Contents of this release

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

#### Added

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

#### Changed

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

#### Notes

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

#### Release-window follow-ups

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

#### Not in this release (coming in v0.10.0)

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

#### Rollback

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

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

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

## 0.9.1 (2026-04-18)

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

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

### Added

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

### Changed

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

### Notes

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

### Release-window follow-ups

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

### Not in this release (coming in v0.10.0)

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

### Rollback

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

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

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

## 0.9.0 (2026-04-18)

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

### Added

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

### Changed

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

### Explicitly deferred

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

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

### Follow-ups in this release window

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

### Rollback

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

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

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

## 0.8.0 (2026-04-18)

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

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

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

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

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

## 0.7.2 (2026-04-15)

### Session Receipt: production-quality preview.html

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

### MCP bridge: session event wiring

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

### Agent instrumentation

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

### Hub hardening

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

## 0.7.1 (2026-04-09)

### Security fixes (from Codex adversarial review)

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

## 0.7.0 (2026-04-09)

### Session Receipts

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

### Hub: public receipt endpoints

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

### Hub: workspace share tokens

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

### Sensitive file read detection

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

### A2A Integration

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

### Python SDK

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

## 0.5.0 (2026-04-04)

### Zero-Knowledge Proofs

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

### RISC Zero Chain Proofs

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

### Trust Model

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

### Release Pipeline

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

## 0.4.0

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

## 0.3.1

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

## 0.3.0

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

## 0.2.1

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

## 0.1.0 (2026-03-31)

Initial release.

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

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

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

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

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

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