CHANGELOG.md
Rama main · réplica de lectura del repositorio original.
# 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