Ctrl K
ComunidadGuías…

CHANGELOG.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# 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 <id>]`. Querying is
  deliberately not in this story: `cairn search <query>`, 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 <id|mcp://cairn/<id>>` 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/<name>` 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 `<issuer>|<subject>`) 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:<user>[:agent|:human]`, where `<user>` is an
  `<issuer>|<subject>` 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:<user>[:agent|:human]` naming an operator (Settings → Account shows
  your `<issuer>|<subject>`), 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 <owner>` naming the first mismatch (or `would merge the
  reactions of owner <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