@forwardimpact/libbridge 0.1.12 → 0.1.13
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.
- package/package.json +1 -1
- package/src/callback-registry.js +19 -0
- package/src/dispatcher.js +1 -1
- package/src/inbox-handler.js +15 -9
- package/src/server.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forwardimpact/libbridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "Threaded-channel bridge primitives — relay messages between human channels (GitHub Discussions, Microsoft Teams) and the Kata agent team.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bridge",
|
package/src/callback-registry.js
CHANGED
|
@@ -91,6 +91,25 @@ export class CallbackRegistry {
|
|
|
91
91
|
return { ...entry };
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Return the bound `tenant_id` for any active token whose correlationId
|
|
96
|
+
* matches; null if no active token binds the correlation. The inbox
|
|
97
|
+
* route uses this to verify a path-supplied tenant against the binding
|
|
98
|
+
* the dispatcher recorded. Single-pass scan of the entries map; the
|
|
99
|
+
* registry is one entry per in-flight dispatch per bridge process.
|
|
100
|
+
* @param {string} correlationId
|
|
101
|
+
* @returns {string | null}
|
|
102
|
+
*/
|
|
103
|
+
tenantOf(correlationId) {
|
|
104
|
+
if (typeof correlationId !== "string" || !correlationId) return null;
|
|
105
|
+
for (const entry of this.#entries.values()) {
|
|
106
|
+
if (entry.correlationId === correlationId) {
|
|
107
|
+
return entry.meta.tenant_id;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
94
113
|
/**
|
|
95
114
|
* Drop entries older than ttlMs. Caller drives the clock so tests stay
|
|
96
115
|
* deterministic.
|
package/src/dispatcher.js
CHANGED
|
@@ -113,7 +113,7 @@ export class Dispatcher {
|
|
|
113
113
|
ctx.pending_callbacks[token] = correlationId;
|
|
114
114
|
ctx.active_requester = requester;
|
|
115
115
|
const callbackUrl = `${this.#callbackBaseUrl}/api/callback/${tenant_id}/${token}`;
|
|
116
|
-
const inboxUrl = `${this.#callbackBaseUrl}/api/inbox/${
|
|
116
|
+
const inboxUrl = `${this.#callbackBaseUrl}/api/inbox/${tenant_id}/${correlationId}`;
|
|
117
117
|
|
|
118
118
|
// The workflow_dispatch targets the resolved tenant's repository when the
|
|
119
119
|
// resolver supplies one (multi-tenant); otherwise the static configured
|
package/src/inbox-handler.js
CHANGED
|
@@ -4,9 +4,16 @@ import { bridge } from "@forwardimpact/libtype";
|
|
|
4
4
|
* Long-poll handler for the per-correlation inbox. The run's InboxPoller
|
|
5
5
|
* fetches injected messages via this endpoint.
|
|
6
6
|
*
|
|
7
|
+
* The handler verifies the path `tenant_id` against the tenant bound to
|
|
8
|
+
* the `correlationId` in `callbacks` (the `CallbackRegistry`) before
|
|
9
|
+
* entering the poll loop. Unknown or mismatched correlations return
|
|
10
|
+
* `404 {error: "Unknown correlation"}` — the same shape the sister
|
|
11
|
+
* callback route emits for an unknown token (`callback-handler.js:100`).
|
|
12
|
+
*
|
|
7
13
|
* @param {object} deps
|
|
8
14
|
* @param {object} deps.client - Bridge gRPC client with DrainInbox
|
|
9
15
|
* @param {object} deps.logger
|
|
16
|
+
* @param {import("./callback-registry.js").CallbackRegistry} deps.callbacks
|
|
10
17
|
* @param {number} [deps.pollTimeoutMs] - Max wait before returning empty (default 30s)
|
|
11
18
|
* @param {number} [deps.pollIntervalMs] - Poll interval (default 1s)
|
|
12
19
|
* @param {import("@forwardimpact/libutil/runtime").Runtime["clock"]} [deps.clock]
|
|
@@ -15,23 +22,22 @@ import { bridge } from "@forwardimpact/libtype";
|
|
|
15
22
|
export function createInboxHandler({
|
|
16
23
|
client,
|
|
17
24
|
logger,
|
|
25
|
+
callbacks,
|
|
18
26
|
pollTimeoutMs = 30_000,
|
|
19
27
|
pollIntervalMs = 1_000,
|
|
20
28
|
clock,
|
|
21
29
|
}) {
|
|
22
30
|
if (!clock) throw new Error("clock is required");
|
|
31
|
+
if (!callbacks) throw new Error("callbacks is required");
|
|
23
32
|
return async (c) => {
|
|
33
|
+
const tenant_id = c.req.param("tenant_id");
|
|
24
34
|
const correlationId = c.req.param("correlationId");
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
// a caller error: fail fast rather than leaking a cross-tenant queue.
|
|
30
|
-
const tenant_id = c.req.query("tenant_id");
|
|
31
|
-
if (!tenant_id) {
|
|
32
|
-
logger.error?.("inbox", "missing tenant_id");
|
|
33
|
-
return c.json({ error: "tenant_id required" }, 400);
|
|
35
|
+
const bound = callbacks.tenantOf(correlationId);
|
|
36
|
+
if (!bound || bound !== tenant_id) {
|
|
37
|
+
logger.debug?.("inbox", "unknown correlation");
|
|
38
|
+
return c.json({ error: "Unknown correlation" }, 404);
|
|
34
39
|
}
|
|
40
|
+
const sinceSeq = parseInt(c.req.query("since") ?? "0", 10);
|
|
35
41
|
const deadline = clock.now() + pollTimeoutMs;
|
|
36
42
|
|
|
37
43
|
while (clock.now() < deadline) {
|
package/src/server.js
CHANGED