Reference
Changelog — @bubblio/server
Reconstructed from the release history (#52); kept honest and current from 0.12.0 on. Versions below 0.9.0 predate this file — their story lives in the README's feature sections and the repository history.
0.13.0 — 2026-08-16
SDK papercuts from the stylica integration audit (#49). Additive — no breaking changes.
- First-request door sync as a config line:
sync: { door, callbackUrl, connectPath?, payments? }oncreateBubblioToolRoutewires the pattern every platform hand-rolled — build-safe (never at module scope;next buildevaluates route modules), once per process, in-flight-deduped, retry-on-failure throttled toretrySeconds(default 60), never blocking a callback.door.sync()itself is unchanged for explicit control. - `maxActiveKeysPerUser` on
createAgentKeyRoute(andmintAgentKey) — a per-user active-key quota enforced by Bubblio inside the mint path (it ownsbl_agent_keys; a platform-side check-then-act can always lose the race, and stylica's did). Racing mints fail closed — a spurious at-cap 409 a retry resolves, never an overshoot — provided every mint for that user carries the cap:createAgentKeyRoutestamps it on every mint (grant path included), but an uncapped mint from another code path commits outside the enforcement and is never rolled back. At cap: calm 409 withcode: 'key_cap_reached'; the phone-connect grant path mints through the same wall, so it is capped identically.BubblioApiErrornow carries the API's machine-readablecode, andcreateAgentKeyRouterelays it to your UI. - API-key rotation no longer silently breaks managed-key identity:
previousBubblioApiKeysoncreateBubblioToolRouteis the N/N-1 verify window — ctx tokens signed under the old key keep resolving their user while pre-rotation keys are live. Local and verification-only (the old key is never sent anywhere; it may already be revoked).verifyUserContext/userContextFromRequestnow acceptstring | string[]. Full runbook in the README ("Rotating your Bubblio API key"). Deferred, by name: a server-assisted re-mint of stored ctx (PATCH /v1/agent-keys/:id { ctx }) — until it exists, a window shorter than the ctx lifetime means pre-rotation users re-mint through your route once the window closes. - Docs, corrected per the audit: the
meta.idempotencyKeydocs now say plainly that the hosted callback fires at most once per quote (no retry loop; duplicate confirms replay the receipt without reaching your endpoint) — handler-side replay caches are defense-in-depth for your own step-2 retries, not the primary double-execution defense; the primary double-spend vector is re-quoting, guarded platform-side by a per-identity unique claim. Teaching surfaces (README, docs site,examples/nextjs-door) now use thesyncoption instead of the hand-rolledsyncOnce(), and door-only install docs no longer imply@bubblio/widget(the door never needs it). - OpenAPI spec for the door HTTP surface —
docs/agent-door.openapi.yamlin the repo: discovery, per-door + hub MCP endpoints, the approvals API, agent keys + connect grant, the signed push endpoints, the actions sync, and the outbound callback contract as an OpenAPI webhook. Derived from the routes; when code and spec disagree, code wins.
Release order for this wave (load-bearing, not a nicety): publish @bubblio/server 0.13.0 and deploy the API before (or with) the dashboard deploy. The dashboard's docs teach sync: (silently ignored by an installed 0.12.0 in plain JS — no error anywhere), and /agents + llms.txt advertise GET /agent/feed.json, which 404s until the API is live. Publishing/deploying in that order closes the window where public teaching surfaces describe features nothing serves.
0.12.0 — 2026-08-15
The money lane: paid actions on your own Stripe. Additive — no breaking changes.
- `price` on declared actions (
price: '$0.40'or{ amount_cents, currency }, usd-only for now). Synced as shape; every discovery surface (manifest, llms.txt,list_actions,about) shows it, and each quote pins it — the charged amount always derives from the pin, never from anything a caller sent. - Charge-wrap:
payments: { stripeSecretKey }oncreateBubblioToolRoutecharges on your own Stripe account, then runs your handler, and auto-refunds on throw. The quote id is the Stripe idempotency key (redelivered callbacks replay the PaymentIntent instead of charging twice); refunds run underquoteId + ':refund'. The payment token is transit-only — never stored, logged, echoed, or handed to your handler (meta.paymentis the settled summary). - Payment rails profile:
payments: { rails, stripePublishableKey }ondoor.syncdeclarespayment_linkand/orstripe_spt. Publishable key (pk_…) only —sk_/rk_values are refused by name, before anything is stored. - `completeBubblioCheckout(session, opts)` — call from your Stripe
checkout.session.completedwebhook to settle a payment-link quote; non-Bubblio sessions are ignored (safe on a shared webhook), and a payment Bubblio refuses to settle is auto-refunded (quoteId + ':refund:' + sessionId). `resumePaidQuote` is the primitive underneath. - `pushDoorResult` now carries the payment summary with a late result, so an upgraded receipt still shows the charge. A connection that dies mid-charge retries once under the same idempotency key, then reports "outcome unknown" — never a false "nothing was charged".
- Declaring a
priceagainst an API without migration 0036 refuses the whole sync (fail-closed: serving a paid action unpriced is a money bug, not a degraded mode) — the SDK throwsBubblioApiError; the previously-synced registry keeps serving.
0.11.0 — 2026-08-15
Phone connect and approval hints. Additive — no breaking changes.
- Phone connect:
connectPathondoor.syncdeclares your login page (path-only — it resolves againstcallbackUrl's origin and may never point off your site).createAgentKeyRoutebecomes grant-aware:POST { grant: 'g_…' }mints thebak_key and forwards it into Bubblio's connect grant, answering{ ok, connected, id }— never the raw key. On a forward failure the just-minted key is revoked (best-effort) and the error is reported; no credential ends up owned by nobody. Requires API migrations 0033–0035 — against an older API, sync warns and the forward answers 404/409. - `approvalSuggestion` (
'auto' | 'mandate' | 'always') on declared actions — a hint for the owner's per-action approval control, exactly likepolicySuggestion: stored on first sync, shown in the dashboard, never applied by a deploy. - Docs: the
'mandate'control's live evaluation (standing mandates under per-action caps and a daily budget) documented as shipped.
0.10.0 — 2026-08-15
Identity moves out of the URL; late results get a push channel.
- `body.ctx`: on Agent Door managed-key confirms the user-context JWT now arrives inside the HMAC-signed callback body instead of only as
?ctx=on the URL (where access logs, proxies, error trackers, andRefererheaders see a 90-day act-as-user credential).createBubblioToolRouteanduserContextFromRequest(new third argument: the verified body) read the body first and fall back to the query param — both directions of version skew keep resolvinguser. A platform-side deprecation window keeps echoing?ctx=for SDKs ≤0.9.1. - `pushDoorResult`: actions that outlive the ≤20s confirm window can deliver their late result to the quote (V2-signed, direction reversed), upgrading an
unreachablereceipt tookinstead of inviting a re-quote.createBubblioToolRoutepushes automatically when a handler finishes after the window. - Doctrine, written down: spend mandates never ride in ctx — the ctx token carries identity only. If a key path becomes spend-linked, mint its ctx with a short
expiresInSeconds. - Callback bodies are extensible: fields arrive additively (
agentin 0.8,ctxandcallbackTimeoutSecondshere) — never validate them with a closed schema.
0.9.1 — 2026-08-13
Docs and package metadata only — no code changes.
- README reframed for npm (chat-first, hosted API URLs, support via email).
repositoryfield dropped (private repo),bugs→ email.
0.9.0 — 2026-08-09
Security release: the legacy signature is refused by default.
- `verifyBubblioWebhook` and `createBubblioToolRoute` require the replay-protected pair
X-Bubblio-Signature-V2+X-Bubblio-Timestamp; a request carrying only the body-onlyX-Bubblio-Signatureis answered 401 (that signature never expires, so a captured callback could replay forever). Escape hatch while you fix a stale platform or a header-stripping proxy:allowLegacySignature: true— temporary by design. - `onFailure` callback on verification:
missing_signature,legacy_signature_rejected,stale_timestamp,bad_signature,invalid_json— the 401 body names the reason, but the platform's log doesn't, so put it in your own. - `meta.idempotencyKey` on Agent Door confirms (the quote id) when the platform sends it — treat
undefinedas "no dedupe anchor available", not "first delivery".