releaseStreamGrants
releaseStreamGrants(
options,claims,token,now):Promise<{released:number; } | {refused:string; }>
Defined in: packages/server/src/circuits/subscribe.ts:704
Give back the engine claims a token’s grants acquired — the close half of subscribe.
Why this route exists. A native POST /shapes with a definition the engine already holds does
not create a second shape: it JOINS the existing one, under a claim of its own. Live claims are
load-bearing rather than bookkeeping — one live claim blocks both dormancy and eviction, precisely
because native durable-streams reads bypass the engine entirely, so a claim is the only thing that
tells it a reader it cannot see still exists. Without a release, a shape waits out its whole lease
window before it can go dormant, every time.
The token IS the request, exactly as on the re-mint path: its signature proves this control
plane minted these grants, so nothing has to be stored server-side to know what was acquired.
Verified with allowExpired, and that is not a weakening — a session releases at CLOSE, which is
routinely past a 5-minute TTL, and expiry bounds how long a grant keeps working, not what it
proves was issued. A release grants its bearer nothing; the only thing it can do is give away
claims that this token’s own subject holds.
ONE release per GRANT, deliberately not deduplicated by shapeId. Two grants naming one shape
are two joins — the engine deduplicated identical definitions and counted both (ADR-0055 decision
6) — so collapsing them here would return one of two claims and leak the other permanently. Each
grant releases its OWN claim (DELETE /shapes/{id}?subscription=<claim>), which is what makes one
release distinguishable from the other at all.
Idempotent and retry-safe (fork ADR-0008). A claim is named, so releasing it twice is releasing
it once: the second DELETE is a no-op 200 and cannot touch another subscriber’s claim. That is
a change in kind from the anonymous refcount decrement this route was first written against, and it
collapses the old asymmetry — a client, a proxy or an operator may repeat a release freely. A
release that never arrives at all is not a permanent leak either: the claim is a LEASE, renewed on
the token re-mint, so a session that crashed or was unloaded has its claims reclaimed by the engine
within leaseSeconds. What this route buys is promptness, not correctness.
Refusals are refusals, not no-ops: a bad signature or a mismatched subject answers an error rather
than { released: 0 }, because a token presented by anyone but its own subject is not a release,
it is an attempt to drop the victim’s claims.
Parameters
Section titled “Parameters”options
Section titled “options”engine
Section titled “engine”{ createShape: Promise<CircuitsShapeHandle>; releaseShape: Promise<void>; replicationState: Promise<CircuitsReplicationState>; }
engine.createShape
Section titled “engine.createShape”engine.releaseShape
Section titled “engine.releaseShape”engine.replicationState
Section titled “engine.replicationState”CryptoKey
claims
Section titled “claims”{[key: string]: unknown; app_metadata?: {[key: string]: unknown; roles?: string[]; }; sub?: string; } | null
string
number
Returns
Section titled “Returns”Promise<{ released: number; } | { refused: string; }>