SIGN IN SIGN UP

chore(release): prepare DomainKit (#121)

## Summary

All bumped packages.

| Package | From | To |
| --- | --- | --- |
| `@domainkit/capsuledb` | `0.12.2` | `0.13.0` |
| `@domainkit/react` | `0.12.2` | `0.13.0` |
| `domainkit` | `0.12.2` | `0.13.0` |

## Changelogs
### `domainkit-callback-identity-binding.md`

<details>
<summary>Show Bumped Packages (1)</summary>

| Package | Bump |
| --- | --- |
| `npm:domainkit` | minor |

</details>

#### Security: the provider callback binds to the flow that started it

`GET /callback/:provider` now establishes whose connection it is
finishing from the continuation
DomainKit recorded, and refuses a callback presented by anyone other
than the session that started
it.

Before, the callback resolved `Identity.principal` first and then read
the continuation through the
owner-scoped `Storage.continuations.get`, which checks the owner and
nothing else. Within one
organization, any administrator whose browser followed the provider's
redirect completed the
connection, and the resulting authorization was recorded as theirs. That
is latent in every released
`0.12.x`. There is no known external consumer and no advisory or CVE is
being filed, but a host that
sees new refusals after upgrading is seeing this check.

Now the handler reads the flow's header before it resolves identity, and
the resolved principal must
match the recorded `ownerId` **and** `actorId`, with the recorded
provider matching the route. A
callback that names no flow, one that has expired, one presented at
another provider's route, and
one presented by a session that did not start it are all the same
`InvalidInput` refusal with the
same text, and the host authenticates before any of them, so nothing
distinguishes "no such flow"
from "not your flow". The refusal leaves the flow unspent, so the person
who started it can still
finish it.
#### `Identity.principal` takes an optional second argument

`Server.IdentityContext` carries the endpoint being served and the
continuation header, and arrives
on the callback alone. A host that takes one argument keeps compiling
and keeps being checked; a
host whose session can hold several tenants reads
`context.continuation.ownerId` to land the
callback on the right one. The doctrine is unchanged: the request still
does not name its own owner,
DomainKit's durable record does, and the `state` only points at it.
#### `Storage.continuations.header` is a new required method

`header(id)` returns a flow's `ownerId`, `actorId`, `provider`, and
`expiresAt` and takes no
`Principal` — the one read in the interface that is not tenant-scoped,
and the reason for the minor
bump. It never returns `payload`, which holds the PKCE `codeVerifier`,
and it enforces the TTL
exactly as `get` does. It is a read, not a spend, so a leaked `state`
cannot burn the flow it names.
`get` and `consume` keep their owner filters, so a host that resolves a
wrong principal still fails
closed. Implemented in `Storage.layerMemory` and `@domainkit/capsuledb`;
a host on
`Storage.layerFromAsync` implements it too, and
`Testing.conformance.storage` covers it.

## Publish

The following packages will be published if merged:

| Package | Version | Registry |
| --- | --- | --- |
| `@domainkit/capsuledb` | `0.13.0` | `npm` |
| `@domainkit/react` | `0.13.0` | `npm` |
| `domainkit` | `0.13.0` | `npm` |

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
G
github-actions[bot] committed
62f1e7cd21434ed14c183453f261b0eb53d1f656
Parent: f10e1cc
Committed by GitHub <noreply@github.com> on 9/16/2026, 2:31:30 PM