@robota-sdk/agent-transport-http 3.0.0-beta.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,62 @@
1
+ /**
2
+ * RUNTIME-38 — who currently holds the one turn a session can run.
3
+ *
4
+ * Split out of `routes.ts`, which had grown past its size ceiling: routing is one job and deciding
5
+ * whether a second turn may start on a session is another, and the second is the only part of that
6
+ * file with state of its own.
7
+ *
8
+ * PER SESSION, not one flag for the router. `sessionFactory` resolves a session per request (the
9
+ * documented multi-tenant shape), so a single flag made one tenant's turn refuse every other
10
+ * tenant's — a busy neighbour is not a reason to refuse you, and that is a worse defect than the
11
+ * race it closed.
12
+ */
13
+
14
+ import type { IHttpTransportSession } from './http-session.js';
15
+
16
+ export interface ITurnClaims {
17
+ /** The key this session is claimed under, or `undefined` when it will not name itself. */
18
+ keyFor(session: IHttpTransportSession): string | undefined;
19
+ isHeld(key: string): boolean;
20
+ hold(key: string): void;
21
+ release(key: string): void;
22
+ }
23
+
24
+ /**
25
+ * A claim registry keyed by the session's declared ID, not by object identity.
26
+ *
27
+ * Keying on the OBJECT makes identity-stability a requirement of every caller that nothing can
28
+ * check: a `sessionFactory` returning a fresh wrapper per call for the same logical session — a
29
+ * proxy, an adapter, a spread copy — defeats the guard in silence, and every request looks
30
+ * unclaimed. `getSession(): { getSessionId(): string }` already names the session, so asking for the
31
+ * id turns an unenforceable requirement into no requirement at all.
32
+ *
33
+ * A claim is released in the caller's `finally`, so this does not grow with the number of sessions
34
+ * the host has ever served — which is what weak keying would otherwise buy.
35
+ */
36
+ export function createTurnClaims(): ITurnClaims {
37
+ const held = new Set<string>();
38
+
39
+ return {
40
+ keyFor(session) {
41
+ try {
42
+ // No `?.`: `getSession(): { getSessionId(): string }` is non-nullable, and optional chaining
43
+ // here would suggest the contract allows an absent session when it does not. A session that
44
+ // breaks the contract anyway is caught below.
45
+ const id = session.getSession().getSessionId();
46
+ return typeof id === 'string' && id !== '' ? id : undefined;
47
+ } catch {
48
+ // allow-fallback: an unnameable session must not be guessed at
49
+ // A session that throws while naming itself has not told us who it is, and guessing would
50
+ // claim the wrong turn. The CALLER refuses on `undefined`, so nothing is swallowed here.
51
+ //
52
+ // `undefined` rather than a placeholder — a shared fallback key would collide every
53
+ // unnameable session into one claim and refuse them all as each other's neighbours. There is
54
+ // no `isExecuting()` fallback on the submit path, and an earlier comment said there was.
55
+ return undefined;
56
+ }
57
+ },
58
+ isHeld: (key) => held.has(key),
59
+ hold: (key) => void held.add(key),
60
+ release: (key) => void held.delete(key),
61
+ };
62
+ }