# Changelog All notable changes to Cairn are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project aims to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html) once it reaches 1.0. ## [Unreleased] ### Added - **The lexical search index foundation** (SPEC-0022 / ADR-0028, #306). Searching a Bin meant listing titles. This lands the half of full-text search that has to exist before any query can: a `search_documents` table holding each live artifact's weighted tsvector (title, tags, body, comments) plus a `search_queue` the write paths add to *after* their commit, so an artifact is saved whether or not the indexer is reachable, and a bounded worker pool turns the queue into documents with `FOR UPDATE SKIP LOCKED`, so several cairnd processes can share one queue without doing the same work twice. Expired and deleted artifacts stop matching the instant they expire, and their documents go with them. Operators rebuild the index with `cairn search reindex [--owner ]`. Querying is deliberately not in this story: `cairn search `, the REST endpoint and the MCP tool come next, on top of this. - **Artifact relations, the surfaces** (SPEC-0024 slice 2, #456). The relations core landed without anything a user could touch; this makes it usable. MCP `artifact_create` and `bundle_create` gain `relations` (`[{"kind":"reply_to","id":"…"}]`) and `traceparent` arguments; the CLI gains `--reply-to`, `--derived-from` and `--follows` (repeatable, like `--tag`) and `--traceparent`, defaulting to the `TRACEPARENT` environment so an instrumented run threads its trace into every artifact it publishes. The web viewer now renders an "In reply to / Derived from / Follows" header above the body and a "Replies and derivatives" list below it; a target the reader cannot open shows neither its id nor its title anywhere, and every title renders escaped. A traced create shows its trace id in the viewer's Details. - **`cairn read` and `cairn comment`** (SPEC-0008). The CLI can now open an artifact and annotate one, which is what made the MCP server the only way to do either. `cairn read >` prints a body, or a bundle's file list when no member is named, and `--json` emits the metadata envelope with the body inlined up to 1 MiB plus a truncation flag. `cairn comment` posts a comment, taking the body from an argument, `--file`, or stdin, with `--parent` to reply. Reading needs no credential — an artifact's link is the capability — and both commands resolve ids through the one shared handle parser, so no surface has its own idea of what an id is. `GET /v1/artifacts/{id}` now lists a bundle's members, which is how a reader learns the name `/members/` needs. ### Changed - **A comment body that names a file is read** (ADR-0035, harness#1048). Every other positional argument in this CLI that can name a file opens it; `cairn comment` alone stored its argument verbatim. An unattended run that wrote its report to a scratch file and passed the path therefore posted the path, and whoever judged the handoff could not tell a finished build from a refusal — once, that funded a duplicate dispatch. A single-line argument spelling an absolute path, or one starting `./`, `../` or `~/`, that names an existing regular file is now read, and stderr says which file. Anything else is still posted as text, and an argument that looked like a path but opens on nothing is posted with a warning, so a report is never lost. `--file` is unchanged. ### Fixed - **GitHub login reaches the container** (cairn#452). The `compose.yaml` in the self-hosting guide forwarded the OIDC settings and static tokens but not `CAIRN_GITHUB_CLIENT_ID` / `CAIRN_GITHUB_CLIENT_SECRET`, so a self-hoster who set them in `.env`, exactly as `.env.example` invites, got a server that never offered GitHub. Both are now passed through with empty defaults, and a test keeps the guide's compose file and `.env.example` in step for every variable that decides whether a caller can sign in at all — including the development seams that must stay *out* of the production path. ### Security - **Users and identities** (SPEC-0023, #326). Sign-ins now resolve to user rows keyed on the provider's `(issuer, subject)`. A new identity joins an existing user only through a verified email equal (case-insensitively) to that user's primary email; the OIDC callback ignores an email whose `email_verified` claim is not true, closing a takeover where an IdP-typed email became the owner (audit A5). GitHub identities key on the numeric account id. The dev password login is disabled whenever OIDC or GitHub is configured, and answers `404` (A6). Anonymous and other readers see the creator's display handle, not their email (A18). - **Explicit ownership** (SPEC-0023 REQ "Owner Model", #327). Every owner and author is a user id: artifacts carry `owner_user_id` (or, once teams exist, `owner_team_id`, never both) and `created_by_user_id`; comments, reactions, personal access tokens, OAuth grants, MCP sessions and web sessions carry `user_id`. The free-form `owner_id` / `actor_id` strings are gone, and every ownership check compares user ids. Wire fields named `actor_id` keep their name and are rendered from the user row: its verified email, else the name it was known by. A sign-in whose **verified** email (see `CAIRN_OIDC_TRUST_EMAIL`) equals a pre-upgrade owner string claims that owner's artifacts, whether or not it is the identity's first; an unverified one never does. An OIDC identity that a pre-upgrade session shows acting as an owner string is linked to that owner by the migration, so it lands on its own Bin at its next sign-in even without a verified email. That evidence is not used when two or more OIDC identities acted as the same email, or for GitHub sessions (keyed then on the renameable login). The dev logins resolve their actor to a user the same way, so they keep the Bin they had. Reactions and comments stay owned per actor kind: the reaction key becomes `(artifact_id, anchor_type, anchor_key, emoji, user_id, actor_kind)`, and `actor_kind` and `on_behalf_of` keep their stored values, so an agent's reaction and its human's stay two rows and per-kind tallies are unchanged. - **Operator profile** (SPEC-0023 REQ "Operator and User Profiles" and "Operator Surfaces Bound Tenant Data and Never Read It", #330). The operator is named by `CAIRN_OPERATORS` (comma-separated `|`) or `CAIRN_OPERATOR_GROUP` (an OIDC group); with neither set there is no operator and every operator route answers `404`. The operator gets a console at `/operator` and `GET /v1/operator/directory` (users with artifact counts and bytes, never content) and can suspend a user with `POST /v1/operator/suspensions`. Suspension offboards in one transaction: sessions, personal access tokens, OAuth grants with their access and refresh tokens, and unredeemed authorization codes stop authenticating on the next request, and a suspended user cannot sign in. Each action writes an `operator_audit` row, with a required reason, in the same transaction; the affected user reads it in Settings. Operator routes need a browser session, and operator status never widens what the operator may read (audit A12, A13). - **Static API tokens act only as an operator's user** (SPEC-0023 REQ "Static API Tokens Act as an Operator's User", #333, audit A2). `CAIRN_API_TOKENS` entries are now `secret:[:agent|:human]`, where `` is an `|` or a verified email that must resolve at boot to an existing user who is listed in `CAIRN_OPERATORS`. A token acts as that user and no other, defaults to agent scopes (no `sharing:manage`, no delete), and is listed on its user's Settings page as operator-provisioned. Before this a token acted as whatever string the operator typed, with human scopes by default, so the operator could impersonate anyone. A token therefore no longer keeps the Bin its old `secret:actor` entry had: see the upgrade note on orphaned legacy Bins. - **Owned outbound subscriptions** (SPEC-0023 REQ "Owned Outbound Subscriptions", "Events Go Only to the Artifact's Workspace" and "Subscription Target Safety", #185, audit A4). Outbound events now go only to `outbound_subscriptions` owned by the artifact's owner, managed in Settings → Outbound subscriptions or over `/v1/subscriptions` by a human principal (agent tokens and personal access tokens are refused). Before this, every user's `artifact.created` event, with its title, link and creator, went to the operator's instance-wide targets. Each subscription has its own signing secret, either supplied by its creator (at least 32 bytes, such as a Switchboard `cairn` webhook's `signing_secret`) or minted by Cairn, shown once and stored encrypted under the new `CAIRN_ENCRYPTION_KEY`. Filters on event type, share type and tag; health with auto-disable after 20 consecutive failed deliveries; ceilings of 5 per user and 10 per team (`CAIRN_SUBSCRIPTIONS_PER_USER` / `_PER_TEAM`). Targets must be `https` and resolve to public addresses, re-checked on every dial so a rebinding name is never dialled; redirects are failed deliveries. `CAIRN_OUTBOUND_ALLOW_HTTP=true` admits `http://` targets and logs a WARN at startup. A subscription receives every event kind about its owner's artifacts that its event-type filter admits (no filter admits all): `artifact.created`, and the `comment.created`, `reaction.*` and `run.closed` events below. Once the delivery queue is more than half full, events other than `artifact.created` are dropped and counted first (SPEC-0016 EV-7, EV-8). - **Existing users land on their account when they sign in** (SPEC-0023). Matching by verified email is no longer one-shot: an identity whose user has no verified email yet is matched again on every sign-in that carries one, so a first sign-in that arrived unverified no longer strands the owner's Bin. ### Added - **`CAIRN_OIDC_TRUST_EMAIL`** (default `false`). Declares the OIDC provider authoritative for emails, so its `email` claim counts as verified even when `email_verified` is absent or `false` (Pocket ID sends `false` unless `EMAILS_VERIFIED` is set). Set it only for a provider you run whose users cannot set their own email; on one where they can, it lets anyone sign in as the owner of any email they type. GitHub is unaffected: it always uses the primary verified email. ### Removed - **`CAIRN_OUTBOUND_WEBHOOK_URLS` and `CAIRN_OUTBOUND_WEBHOOK_SECRET`** (#185). Cairn no longer reads either variable: owned subscriptions are the only outbound delivery path. There is no deprecation window and no import. ### Upgrade notes - **The instance-wide outbound targets are gone.** A deployment with `CAIRN_OUTBOUND_WEBHOOK_URLS` still set starts normally and delivers nothing to those URLs. To keep an existing receiver, such as your Switchboard handoff webhook: set `CAIRN_ENCRYPTION_KEY` (`openssl rand -base64 32`; back it up with the database), restart, sign in, and add a subscription in Settings → Outbound subscriptions with the receiver's URL, pasting the value you had in `CAIRN_OUTBOUND_WEBHOOK_SECRET` as its signing secret. The receiver needs no change. It then gets your artifacts' events only; other users add their own. Remove both old variables from your environment and compose file. Migration `0035_outbound_subscriptions` adds one empty table. - **Legacy `CAIRN_API_TOKENS` entries now fail boot.** Every free-form `secret:actor[:role]` entry is refused, with no grace period, and `cairn serve` exits with `CAIRN_API_TOKENS entry N: legacy secret:actor entries are no longer accepted; …`, naming the entry's position and the new form, never the secret. Before upgrading, rewrite each entry as `secret:[:agent|:human]` naming an operator (Settings → Account shows your `|`), make sure that operator has signed in once and is in `CAIRN_OPERATORS`, and move every other consumer to a personal access token. Remove the variable entirely if nothing needs it. Tokens now default to the **agent** role: add `:human` where a script changes sharing, expiry or deletes. - **A legacy token actor's Bin is orphaned unless you move it.** Migration `0023` turned each old token actor into a user known by that string, and until now the token reached that user. After upgrading no credential acts as it: a token names an operator's own user, and a sign-in claims a legacy user only by a verified email equal to its name, so a non-email actor such as `ci-bot` is never claimed. Its Bin (pins, permanent artifacts, anything not yet expired) stays in the database with nobody able to manage or delete it. Nothing is deleted, so you can fix this any time after `0023` has run (the queries need its columns), but expiring artifacts are still reaped on schedule meanwhile. List what such users own: ```sql SELECT u.actor_key, a.public_id, a.title, a.expires_at FROM artifacts a JOIN users u ON u.id = a.owner_user_id WHERE u.primary_email IS NULL AND u.actor_key IS NOT NULL AND NOT EXISTS (SELECT 1 FROM user_identities i WHERE i.user_id = u.id) ORDER BY u.actor_key, a.created_at; ``` To keep one, move its artifacts to the operator's user, with the old actor and the operator's verified email filled in. If the email matches no user the update fails on `artifacts_one_owner` and changes nothing: ```sql BEGIN; UPDATE artifacts SET owner_user_id = (SELECT id FROM users WHERE primary_email = 'you@example.com' AND email_verified) WHERE owner_user_id = (SELECT id FROM users WHERE actor_key = 'ci-bot' AND primary_email IS NULL); COMMIT; ``` This changes ownership only: `created_by_user_id`, comments and reactions keep crediting the old actor. Never delete the legacy user, since deleting a user deletes the artifacts it owns. - **Take a database backup first.** Migration `0017_users` adds the `users` and `user_identities` tables and `sessions.user_id`. - **OIDC must send `email_verified: true`, or set `CAIRN_OIDC_TRUST_EMAIL`.** A sign-in whose email is not verified is keyed on its subject and does not reach artifacts owned by that email until a later sign-in carries a verified one. Confirm your provider marks the operator's email verified, or, for a provider you run whose users cannot change their email (Pocket ID with LDAP sync), set `CAIRN_OIDC_TRUST_EMAIL=true` before the first sign-in after the upgrade. - **`CAIRN_DEV_INSECURE_BEARER_AUTH` with an `https` `CAIRN_BASE_URL` now fails boot** (A21). It was never safe there. - **Back up the database before upgrading past `0023_owner_columns`.** It resolves every owner and actor string to a user and drops the strings in one transaction; there is no in-database way back, so rollback is a restore from that backup. Before dropping anything it compares every owner's Bin; if any would change, the whole migration rolls back, the old schema is left intact, and `cairn serve` refuses to start with `ownership backfill would change the Bin of owner ` naming the first mismatch (or `would merge the reactions of owner ` when two owner strings that become one user reacted alike). Report that error rather than editing rows by hand. It runs after `0022_annotation_actor_kind`. - Migration `0024_operator_profile` adds the `operator_audit` table, a constant-default `sessions.operator_group` column and an index on `sessions.user_id`. No data is rewritten. The operator profile is off until you set `CAIRN_OPERATORS` or `CAIRN_OPERATOR_GROUP`. ### Changed - **Outbound webhooks**: the `artifact.created` body appends two keys to `data`: `actor_kind` (`human` or `agent`) and `auth` (`session`, `oauth`, `pat` or `api_token`), both derived from the creator's credential. No other byte changes. The encoder now handles every SPEC-0016 event kind, delivered only to owned subscriptions (above). (ADR-0022, SPEC-0016, #305) - **Traces announce themselves.** Opening a run, or uploading one whole, now emits `artifact.created` with `share_type: "trajectory"`, like every other artifact, so subscriptions start receiving trace creations. A consumer that routes on `share_type` or tags needs no change; one that assumed every event was a single-body artifact or a bundle should ignore `trajectory`. Closing a run (`POST /v1/runs/{id}/close`), and a batch run born closed, also emit `run.closed` with its status, span count, start, end and duration; like the other new kinds it goes to owned subscriptions only. (ADR-0022, SPEC-0016 EV-2, EV-3, SPEC-0023, #313) - **Annotations announce themselves.** A new comment emits `comment.created`, a new reaction `reaction.added` (a duplicate click emits nothing), and each removed reaction `reaction.removed`, whichever surface made the change. Reaction events carry `approval_class` (the emoji is in the approval class) and `approval` (in the class **and** stored by a browser session). An agent's 👍 is stored and announced, but never as an approval, and no request field can change that. The class is set by the new `CAIRN_APPROVAL_REACTIONS` (comma-separated, default 👍 ✅ ✔️; skin tones and VS-16 are ignored when matching), and a malformed entry fails startup. Like `run.closed`, these kinds go to owned subscriptions only. (ADR-0022, SPEC-0016 EV-2, EV-5, #311) - **Reactions and comments are owned per actor kind.** Each row stores the server-derived `actor_kind` (`human` for a browser session, `agent` for every bearer credential), and reaction idempotency is keyed per `(actor_id, actor_kind)`. An agent reacting with your credentials no longer shares, occupies or can withdraw your own reaction: un-react, delete-by-id (403 on the other kind's row) and comment edit/delete all match the kind. Reactions gain `on_behalf_of`, set exactly as on comments (the REST body field, or the MCP client's name). Reaction and comment responses carry `actor_kind`; reaction tallies add `human_count` and `agent_count`, and `reacted` now means "a row you, as this kind, can remove". Rows written before the upgrade read back `actor_kind: ""`, are never counted as human, and only their own actor removes them. Migration 0022 builds the new unique index concurrently and runs outside a transaction; it is safe to rerun. `GET /v1/artifacts/{id}/reactions?include=reactors` also returns the rows behind each tally, each with `actor_id`, `actor_kind` and `on_behalf_of`. (ADR-0022, SPEC-0016 EV-6, #159) **Upgrade note: a binary rollback breaks reactions.** Migration 0022 drops the old five-column reaction key, and a binary from before this change upserts on exactly that key. Once 0022 has applied, an older cairnd returns 500 on every reaction write (Postgres 42P10, no matching unique constraint) until you roll forward again; the same holds for an old process still serving while the new one migrates. The old key cannot be recreated once a human and an agent row share an actor. Roll forward, not back. ## [0.1.1] - Unreleased Changes since `v0.1.0`, staged for the next patch release. ### Added - **GitHub OAuth login** — a provider interface for web authentication with a GitHub provider as the first implementation, and login-method provenance recorded on sessions. (ADR-0019, SPEC-0013, #259) ### Fixed - CI checks out the PR head SHA rather than the branch ref, so required checks gate the exact commit under review. (#245) - CI's integration job probes Postgres over TCP, so the tests stop racing the database's first-boot initialisation. (#353) - Self-hosting guide: corrected env secrets and the `CAIRN_API_TOKENS` compose passthrough. (#239) - Docs build: unlinked two repository-host references that broke the build, and renumbered the embedded-docs spec off the SPEC-0013 collision. (#258, #257) ### Added (records) - ADR-0019 + SPEC-0013 — GitHub login for the web app shell; ADR-0020 + SPEC-0015 — single-binary runtime with embedded docs; ADR-0021 + SPEC-0014 — Prometheus metrics led by storage and expiry. (#251, #254, #255, #257) - The Operation Stumply design records, accepted: ADR-0022 + SPEC-0016 (annotation and trace lifecycle events), ADR-0023 + SPEC-0017 (secret redaction at ingest), ADR-0024 (enrollment modes; GitHub sign-in fails closed), ADR-0025 + SPEC-0019 (actionable validation errors), ADR-0026 + SPEC-0020 (opt-in permanent retention), ADR-0027 + SPEC-0021 (structured receipts), ADR-0028 + SPEC-0022 (search and export by tag), ADR-0029 + SPEC-0023 (teams and tenancy). Design only; none of it is implemented in this release. (#360) ### Removed - The repo-root production deploy path: `DEPLOY.md`, `docker-compose.prod.yml` and `deploy/Caddyfile`. The prod compose never forwarded `CAIRN_API_TOKENS`, `CAIRN_OIDC_*` or `CAIRN_GITHUB_*`, so it rejected every authenticated caller. The self-hosting guide is now the one documented deploy path, and `.env.example` lists the sign-in variables. (#362) ## [0.1.0] - 2026-09-12 First release after the module paths moved to their public homes, and the first cut under the MIT license. The headline: Cairn talks to the rest of the machine — outbound webhooks on artifact creation, client-asserted tags for handoff routing, and a wave of A2UI work that renders traces, bundles, and markdown straight into an agent's UI. ### Added - **Outbound webhooks on artifact creation** (SPEC-0012) — configured destinations receive a signed `artifact.created` event when a share lands, the seam Switchboard's todo routing is built on. (#175) - **Client-asserted artifact tags for handoff routing** (ADR-0018) — creators can tag artifacts at create time so downstream routers can lane them. (#181) - **A2UI everywhere over MCP** — `cairn://artifact/{id}/a2ui` for single-body artifacts, trace and bundle views as A2UI resources, a widened and colorized flame graph, bundle members served as their own resources with `open_member` navigation via `a2ui_action` (and `a2ui_error` for render failures), and guidance for agents to prefer `/a2ui` surfaces. (#91, #92, #93, #94, #98, #102, #107) - **Click-to-copy** for markdown code blocks and the whole document. (#134) - `--help` and usage errors styled with fang in the Cairn palette. (#132) - Cross-platform `cairn` CLI binaries built and attached to releases, with the release destinations split: binaries on Gitea, container image on GitHub. (#60, and the release-pipeline commits through `v0.1.0`) - Homebrew tap publishing for the CLI. (release-pipeline commits) ### Changed - Module paths moved to their public homes (`github.com/stump-wtf/…`), and the Settings template updated to match. (81454e2, a5e7eb9) - Cairn is now MIT-licensed. (#206) - Traces, not trajectories — naming settled in ADR-0016 Phase 1. (#86) - Release archives ship only the binary, and the shipped CLI is gated against containing any server code. (215e99f, 5c3ffb6) - The four racing CI workflows retired in favor of one composed pipeline. (#29) ### Fixed - **Artifact creation 500ed on every call when webhooks were disabled** — `cairnd` now degrades gracefully instead. (#203) - The CLI's default server resolves; dead install links dropped. (#217) - MCP: boolean JSON schemas no longer void the tool list (#45); span output is plain text (#44); string-encoded spans unwrap via middleware with end-to-end regression tests (#108); `bundle_create` publishes concrete array types so the tool is callable (#116); the `run_capture` prompt no longer names a nonexistent task argument (#115); A2UI resources honor the `?w=` width hint (#102). - Trajectory viewer: unbounded zoom and toggling pickers (#55), pan tracks the centre row with gaps collapsing for real (#57), the category bar is a composition not a wall-clock fraction (#58), pan reaches the run's ends and the timeline box scrubs (#59), bullet reactions on their own line with prompt spans as markers (#48). - The bin's type menu stays reachable while filtering, and overlapping tabs became visibility + type lenses. (020245c, f383ad1) - A loading indicator for bundle member navigation. (7eaf852) - Nil-guarded storeless handlers so port reuse cannot panic `httpapi`. (0c8007f) ### Security - `golang.org/x/net` bumped for CVE-2026-46600. (#165) ### Dependencies - Go 1.27, chi v5.3.2, Lip Gloss v2.0.6, MCP Go SDK v1.7.0, and the usual Renovate churn. (#124, #126, #135, #166, #167, #168, #169) ### Docs - Getting-started guides for hosted Cairn users, aligned Switchboard sections, and links to Switchboard's new first-webhook and handoff recipe pages. (#186, #187, #188) - `cairn.sh` was never acquired — the record and the site no longer imply it might be. (#219) - Deployment docs corrected to the files a self-hoster actually reads. (#227) - The mobile drawer behind the glass navbar unbroken. (#225) - Three ways a shell check reports the wrong answer, recorded. (#233) ## [0.0.4] - 2026-07-15 ### Fixed - **MCP clients using a personal access token got `401` at `initialize`** — the MCP bearer verifier only accepted OAuth access tokens (`cairn_at_`), so a `cairn_pat_` token (the credential Settings creates *for agents*, which connect over MCP) was rejected. `/mcp` now accepts personal access tokens with the same scope enforcement as OAuth; set a client's `CAIRN_TOKEN` to a `cairn_pat_…` value and connect, no OAuth browser flow needed. (#99, closes upstream #51) ## [0.0.3] - 2026-07-14 The complete build: every share type, the `cairn` CLI, a Settings surface with agent tokens and MCP session tracking, the webhook inspector, owner policy, and retention. Every SPEC (0001–0009) is now implemented. ### Added - **Code viewer** — server-side syntax highlighting (chroma, embedded/CSP-safe), line numbers, a jump-to-symbol outline, and `code_line`/`code_range`/ `text_selection` line-level comments and reactions. (#68) - **Image viewer** — display with a bespoke SVG **pin overlay**: drop a pin to anchor a region comment (normalized `0..1` coords that survive scaling) plus a react-below affordance; degrades gracefully if the pin script fails. (#71) - **Webhook inspector** (`HK`) — the requestbin share type: an endpoint model with ring-buffer retention (#83), a hardened open ingress at `/h/{id}` (inert capture, header redaction, body caps, per-endpoint rate limiting) (#84), live SSE + read-only MCP fan-out (#85), and a live inspector viewer with reactions-only annotation (#86). - **Settings page** (`/settings`, absorbs `/connect`) — create/list/revoke **personal access tokens** for agents (secret shown once), the MCP connection info restyled, a CLI section, and account/sign-out. (#74, #75) - **MCP agent sessions** — each MCP connection is recorded (client name/version, activity counters); a Settings "Agent sessions" panel shows which agent is doing what, with revoke-to-disconnect. (#76) - **MCP create tools** — `run_create`, `run_append_spans`, and `bundle_create`, so agents can create trajectory runs and bundles over MCP (not just read them), through the same core services as `/v1`. (#65) - **Owner policy** — TTL countdown, extend/shorten expiry, and **id rotation** (old link 404s, annotations follow the new id); owner + `sharing:manage` only. (#94) - **Retention reaper** — a background worker that deletes past-TTL artifacts and reference-counts + garbage-collects orphaned content-addressed blobs (a dedup-shared blob survives until its last referrer expires), with a staging-prefix object-storage backstop. (#93) - **The `cairn` CLI** (v0.0.3, issues #20–#22): a single static Go binary and pure `/v1` REST client (ADR-0003). - `cat file | cairn` / `cairn file` — pipe or path ingest, with a spinner, extension/content media-type detection, best-effort clipboard copy (OSC52/pbcopy/xclip/xsel/wl-copy fallback chain), and a decorated `✓ pushed` / link / expiry+access summary on an interactive terminal (bare link only when piped). (#22) - `cairn add f1 f2 …` — one atomic bundle via multipart `POST /v1/artifacts`, with a bounded concurrent worker pool preparing files and a Bubble Tea + Lip Gloss per-file progress display (done/in-flight/ queued rows) on an interactive terminal. (#22) - `--ttl` / `--title` flags; the server now accepts an optional, capped `X-Cairn-Ttl-Seconds` request on create (SPEC-0008 `--ttl`) alongside the existing `--title`. (#22) - `cairn login` / `logout` / `whoami` against the ADR-0004 bearer-token seam, with secure OS-keyring-or-`0600`-file credential storage. (#20, #21) ### Fixed - **MCP clients could not connect** — the OAuth server now accepts the MCP server's canonical `/mcp` URI as the RFC 8707 resource indicator (and serves the RFC 9728 path-specific metadata), so clients like Crush no longer get `invalid_target`. (#62) - **OAuth consent Approve did nothing in Safari** — the consent page's CSP `form-action` now includes the client's validated redirect origin, so the approval redirect to a CLI/localhost callback is allowed. (#63, #64) - **Reactions now render on click** — the reaction pill + count appears immediately (a duplicate picker click-handler was removed), across trajectory spans, markdown blocks, and bundle members. (#66, #72) ## [0.0.2] - 2026-07-12 The agent-usable milestone: Cairn now speaks MCP, logs humans in passwordlessly via Pocket ID, and renders Markdown, bundles, and richer trajectory views. ### Added - **MCP server surface** (`POST/GET /mcp`) — read, create, comment, and react tools plus a trajectory-stream resource, in-process over the core services. OAuth-only, per-tool scope enforcement, provenance stamped `via MCP`. (#44) - **OAuth 2.1 authorization server** — authorization-code grant with mandatory S256 PKCE, RFC 8414 discovery, RFC 7591 dynamic client registration, RFC 7009 revocation, exactly three scopes (`artifacts:read`, `artifacts:write`, `annotations:write`). (#42, #43) - **Native OIDC login** — Cairn is a Pocket ID relying party; humans sign in passwordlessly (`/auth/login` → code+PKCE → `/auth/callback`). oauth2-proxy is no longer required in front of the app. (ADR-0013, #56) - **OAuth consent UI** — three-scope approval screen with WCAG landmarks. (#45) - **Markdown viewer** — sanitized render, table of contents, block and text-selection annotation anchors. (#18) - **Bundle viewer** — file rail, per-file Markdown panes, per-file comments. (#19) - **Share dialog** — header Share button with link/MCP copy and policy summary. (#46) - **Home is the Bin** — `/` shows the caller's artifact listing when signed in and an on-brand landing when logged out; a `/connect` view documents MCP setup. (#57) - **Inline span comments** — the trajectory viewer's per-span comment affordance opens a focus-trapped composer at the clicked line. (#58) ### Changed - Web shell hardened against the Alpine CSP build: all directive expressions use bare identifiers (call syntax silently no-ops under the CSP build). A regression test guards it. (#41) - Assorted web polish: htmx indicator CSP noise removed, panel class leak fixed, live-view refinements. (#47) ### Security - Real bearer authentication with audience-bound short access tokens and rotating refresh (reuse detection revokes the family); tokens hashed at rest. (#14, #43) - An OIDC web session grants nothing on `/v1` or `/mcp` — those remain OAuth/bearer-only (regression-tested). (#56) ## [0.0.1] - 2026-07-12 The MVP: trajectory shares end to end on a composable share-type SDK. ### Added - **Composable share-type SDK** — a registry with optional capability interfaces (URL prefix, body viewer, metadata panel, locator schema, route mounter); behavior is data, not `switch` statements. (#3) - **Trajectory shares** — an OpenTelemetry-inspired span/run model with content-addressed span-output spill, batch and incremental ingestion (`/v1/runs*`), and live SSE span streaming with replay-then-tail resume. (#7, #8, #9) - **Trajectory viewer** — bespoke SVG span waterfall, activity stream, cross-highlight, RUN panel, and keyboard/focus accessibility. (#15) - **Annotations** — polymorphic anchors with canonical keys, idempotent reactions, threaded comments, and separate count rollups. (#6) - **Web app shell** — unified header/body/panel chrome, registry-resolved body slot, the Bin listing, generic-file viewer, CSRF protection. (#10, #12) - **Minimal web session auth and login.** (#11) [Unreleased]: https://github.com/stump-wtf/cairn/compare/v0.1.0...HEAD [0.1.0]: https://github.com/stump-wtf/cairn/compare/v0.0.4...v0.1.0 [0.0.4]: https://github.com/stump-wtf/cairn/compare/v0.0.3...v0.0.4 [0.0.3]: https://github.com/stump-wtf/cairn/compare/v0.0.2...v0.0.3 [0.0.2]: https://github.com/stump-wtf/cairn/compare/v0.0.1...v0.0.2 [0.0.1]: https://github.com/stump-wtf/cairn/releases/tag/v0.0.1